認(rèn)真聊一次Iptables和Netfilter,簡(jiǎn)單過(guò)下Istio Route
大家好,我是二哥。
上一篇文章本意是給大家一個(gè)新的視角來(lái)研究 istio route 的細(xì)節(jié)。不過(guò)后臺(tái)不少同學(xué)私信我說(shuō),一直沒(méi)有辦法理解 iptables ,也就不想細(xì)看那篇文章了。二哥一看就慌了,為了讓大家能安心地研究那篇文章,我就先來(lái)聊聊 iptables ,準(zhǔn)確地說(shuō)我們需要聊的是 netfilter 。
理解 iptables 其實(shí)不難,難的是看懂 iptables 是如何配合協(xié)議棧處理流量的。本篇除了聊 iptables 之外,更重要的是二哥會(huì)帶大家一探協(xié)議棧和 iptables 密切配合過(guò)程。最后我以 istio route 為例來(lái)看看它是如何利用 iptables 將網(wǎng)絡(luò)包透明地劫持到了 Envoy 的 Outbound hanlder 15001 端口。
本文會(huì)混用流量、數(shù)據(jù)包、網(wǎng)絡(luò)包這些名詞,準(zhǔn)確地說(shuō),它們指代的是內(nèi)核數(shù)據(jù)結(jié)構(gòu) skb (sk_buffer)。
這是一篇長(zhǎng)文,誠(chéng)意滿滿,干貨滿滿,掉發(fā)也滿滿。如果你已經(jīng)對(duì) iptables/netfilter 很熟悉了,那可以跳過(guò)前兩部分。
在開(kāi)始之前,我們先區(qū)分兩個(gè)概念:
- netfilter:內(nèi)核中對(duì)數(shù)據(jù)包進(jìn)行控制、修改和過(guò)濾(manipulation and filtering)的框架 。一個(gè)著名的實(shí)現(xiàn)是內(nèi)核模塊 ip_tables 。
- iptables:客戶端命令行,用于操作(CRUD)各種規(guī)則來(lái)干預(yù)內(nèi)核協(xié)議棧的行為。
大家日常工作中,碰到直接上手操作 netfilter 的機(jī)會(huì)越來(lái)越少了。但這不表示 netfilter 不重要。實(shí)際上 netfilter 是 K8s 網(wǎng)絡(luò)的基礎(chǔ),即使在kubernetes v1.8 中引入了 ipvs 模式,ipvs 的著力點(diǎn)也是 netfilter 。不信你看下面的一段規(guī)則。ipvs 介入工作的前提是它得作為規(guī)則的一部分,讓 netfilter 框架在合適的時(shí)機(jī)點(diǎn)運(yùn)行。
肯定有另一部分同學(xué)有疑惑:既然平時(shí)都不怎么用它了,為什么還要學(xué)習(xí) netfilter 呢?
不知道大家有沒(méi)有另外一個(gè)疑惑:既然整個(gè)小學(xué)都不可能用到微積分,為什么小學(xué)的數(shù)學(xué)教師需要學(xué)高等數(shù)學(xué)呢?這其實(shí)涉及到處理問(wèn)題時(shí)的一個(gè)角度問(wèn)題:如果解決一個(gè)問(wèn)題只需要 3 分功力,你最好得具有 6 成內(nèi)功。只有這樣你才能俯視而不是仰視或者平視它,唯有俯視方得從容。
1、只看 iptables
文首提到 iptables 是客戶端命令,用于操作各種規(guī)則來(lái)干預(yù)內(nèi)核協(xié)議棧的行為。那它具體是如何使用的呢?
先來(lái)看命令長(zhǎng)什么樣子的:
再來(lái)看一個(gè)使用示例:
(1)四表五鏈
圖 1:四表五鏈(橫鏈豎表,橫為鏈,豎為表) 圖片來(lái)源:公眾號(hào)“開(kāi)發(fā)內(nèi)功修煉”
圖中橫向的四個(gè)框表示四表,對(duì)應(yīng)于 iptables 命令里面的 -t <table-name> 。如果不指定,那么默認(rèn)的 table 是 filter 。其實(shí)還有一個(gè) security 表,用于在數(shù)據(jù)包上應(yīng)用 SELinux ,這張表并不常用,故本篇我們略過(guò)。
圖中第 1 列表示的五個(gè)框叫做五鏈,對(duì)應(yīng)于命令里面的 <chain-name> 。每個(gè)鏈像竹簽一樣串著不少肉串。這些肉串叫規(guī)則,它們的種類不同,且由不同的表提供。比如 mangle 表可能提供的是羊肉串,而 nat 表提供的是牛肉串,filter 表提供的是雞肉串。
這四個(gè)表的具體作用我就不細(xì)講了,大家可以到網(wǎng)上搜索出更詳細(xì)的答案。但下面這兩點(diǎn)是重點(diǎn)(重點(diǎn),重點(diǎn),重點(diǎn)),你一定要記得。
- 這五個(gè)預(yù)置的鏈直接源自于 Netfilter 的鉤子,它們與四張規(guī)則表的關(guān)系是固定的。用戶即不能增加自定義表也不能修改圖 1 中已有的表與鏈的關(guān)系,但可以增加自定義的鏈(見(jiàn)下文)。
新增的自定義鏈與 Netfilter 的鉤子沒(méi)有天然的對(duì)應(yīng)關(guān)系。自定義鏈不會(huì)被自動(dòng)觸發(fā),只有顯式地使用 JUMP 行為(見(jiàn)后文),才能從默認(rèn)的五條鏈中跳轉(zhuǎn)過(guò)去執(zhí)行它們。 - 每個(gè)命名空間都是有自己獨(dú)立的 iptables 規(guī)則,這當(dāng)然也包括四表五鏈。
表、鏈和規(guī)則之間的關(guān)系,一句話總結(jié)就是:規(guī)則是執(zhí)行的最小單元,鏈決定了規(guī)則被執(zhí)行的時(shí)機(jī),而表則限定了規(guī)則的類別。鏈的執(zhí)行時(shí)機(jī)詳見(jiàn)后文。
(2) command
命令中的 command 是啥?它是一些由大寫(xiě)字母表示的動(dòng)作。見(jiàn)圖 2 所示。比如 -A 用于將一個(gè)新規(guī)則插入到鏈上,嗯,就是把肉串插到竹簽上。每一次用 -A 這樣的 command 調(diào)用 iptables ,都會(huì)在對(duì)應(yīng)的鏈和表所形成的宮格里面插入一個(gè)新的規(guī)則。
圖 2:iptables command 列表
(3)自定義鏈
我們可以用 -N 來(lái)創(chuàng)建一個(gè)新鏈。如果不用 -t 來(lái)指定 table 的話,新建的 chain 默認(rèn)使用 filter table 。熟悉自定義 chain 的創(chuàng)建過(guò)程非常重要,因?yàn)楹笪奈覀円治龅?istio route 就自建了不少鏈。
二哥再?gòu)?qiáng)調(diào)一遍:自定義鏈不能被 netfilter 自動(dòng)執(zhí)行,只有從五大入口鏈那里通過(guò) -j target 才能跳轉(zhuǎn)到自定義鏈(例見(jiàn)后文)。
下面的例子里,自定義了一個(gè) chain LANCE-OUTPUT ,可以看到它被放到了 table filter 里面。
然后用 -A 來(lái)追加一個(gè)規(guī)則到這個(gè)自定義鏈里面。
(4)parameter / option
光有 command 還不行,它太粗獷了,得細(xì)膩、得精準(zhǔn)控制。這就需要通過(guò) parameter 來(lái)實(shí)現(xiàn)。<parameter-1> <option-1> 里面填什么呢?看你喜歡,你有若干個(gè)選擇,比如文首示例里面的 -s 1.2.3.4 和 -p tcp --dport 22 。有一些 parameter 還提供了額外的以 -- 開(kāi)頭的 額外 match option ,比如對(duì) -p tcp ,你可以添加 --dport 22 這樣的額外 match option ,用以更精準(zhǔn)地控制要命中的規(guī)則。除了 tcp 外你還有 -p udp -p icmp 可供選擇。
下面是可供使用的 parameter 列表。
圖 3:iptables parameter 列表
(5)額外的 match option
這就結(jié)束了嗎?不,還有大招沒(méi)有放。我們來(lái)看下面這個(gè)例子。例子很平淡,重點(diǎn)看 -m 。-m comment 表示這個(gè)規(guī)則需要加載 comment 模塊,從字面意思你大概能猜得出來(lái)它可以干啥。對(duì),就是給這條規(guī)則加點(diǎn)注釋。通過(guò) --comment xxx 這個(gè) option ,你可以添加最多 265 個(gè)字符的注釋,前文在介紹用 -A 命令追加規(guī)則到自定義鏈時(shí),從 iptables -L -t filter 的輸出里面你可以體驗(yàn)到這些注釋的作用。
通過(guò) -m 我們可以調(diào)用包括 set / ipvs 在內(nèi)的各種擴(kuò)展模塊。有多少模塊可以選擇呢?多到?jīng)]朋友,不信你到這個(gè)鏈接里面去看:https://ipset.netfilter.org/iptables-extensions.man.html#lbAD 。我估計(jì)應(yīng)該有 60 個(gè)左右。
(6)跳轉(zhuǎn)到特點(diǎn)的目標(biāo) -j
我們?cè)O(shè)置的規(guī)則匹配上數(shù)據(jù)包后,總得干點(diǎn)啥是吧,不然不是白廢老大勁了么。當(dāng)然, -j 不是必填項(xiàng),但你非得說(shuō)我就不想讓這個(gè)規(guī)則干具體的事情,也行!
我們可以給 -j 指定像 ACCEPT / DROP / QUEUE / RETURN 這樣的 netfilter 自帶的標(biāo)準(zhǔn) target ,也可以給它指定我們自定義的鏈,除此之外還有若干個(gè)像 SNAT / REDIRECT / SET 這樣的擴(kuò)展 target 可供我們使用。
比如下面這個(gè)例子中,就通過(guò) -j KUBE-SERVICES 跳轉(zhuǎn)到自定義鏈 KUBE-SERVICES 去了。
流量通過(guò) -j 跳轉(zhuǎn)到指定 target 之后會(huì)發(fā)生什么?這取決于 target 會(huì)對(duì)流量做啥:
- 比如對(duì)于 DROP target ,你也能猜出結(jié)局是什么:不但流量會(huì)丟棄了,它更加不會(huì)到達(dá)傳輸層(見(jiàn)后文)。
- 而對(duì)于 KUBE-SERVICES 這樣的 target,netfilter 會(huì)去執(zhí)行這個(gè)鏈所定義的各種規(guī)則。
還記得前文我們說(shuō)到的那默認(rèn)的五條鏈嗎?它們既是默認(rèn)的五條鏈更是 netfilter 施展拳腳的入口。從這些入口進(jìn)去,netfilter 可能會(huì)調(diào)用到若干個(gè)自定義鏈以及串在鏈上的多種多樣的規(guī)則。假如所有的規(guī)則都不會(huì)下流量下死手,那么這些規(guī)則執(zhí)行完后,就又回到入口處,也就是這五個(gè)默認(rèn)的鏈。
2、不能單看 iptables
其實(shí)讀懂和理解 iptables 規(guī)則并不難,難的是理解 netfilter 是如何和 TCP/IP 協(xié)議棧緊密集成和協(xié)作以控制流量的行為的。你們見(jiàn)過(guò)機(jī)場(chǎng)行李托運(yùn)輸送系統(tǒng)嗎?我們?cè)谥禉C(jī)口托運(yùn)的行李會(huì)穿過(guò)行李分揀大廳的各條分叉,兜兜轉(zhuǎn)轉(zhuǎn)才來(lái)到飛機(jī)貨艙里面。
無(wú)論是入口流量還是本地進(jìn)程產(chǎn)生的出口流量都如同我們?cè)谥禉C(jī)口托運(yùn)的行李,而 netfilter 和 TCP/IP 協(xié)議棧則扮演了那個(gè)行李分揀系統(tǒng)。
圖 4:四表五鏈與協(xié)議棧集成細(xì)節(jié) 圖片來(lái)源:公眾號(hào)“開(kāi)發(fā)內(nèi)功修煉”
既然說(shuō)到 netfilter 和 TCP/IP 協(xié)議棧則的緊密合作,那我們先看看協(xié)議棧部分。
圖中 ip_rcv() 是流量進(jìn)入 IP 層的入口,ip_forward() 是轉(zhuǎn)發(fā)流量的入口,而流量通過(guò) ip_output() 離開(kāi) IP 層。當(dāng) IP 層決定要把流量送往傳輸層的時(shí)候,它通過(guò) ip_local_deliver() 來(lái)完成,相對(duì)應(yīng)地,本地進(jìn)程想要把數(shù)據(jù)發(fā)送出去,需要借助 __ip_local_out() 。注意所有這些函數(shù)都在 IP 層。
協(xié)議棧在執(zhí)行這些不同的入口函數(shù)時(shí),會(huì)有選擇地查看四表五鏈里面的鏈和相應(yīng)的規(guī)則并執(zhí)行這些規(guī)則。而規(guī)則里面所定義的 target 也反過(guò)來(lái)影響協(xié)議棧下一步的行為。
(1)過(guò)客和山海
從圖 4 我們可以看得出來(lái),四表五鏈以及路由選擇其實(shí)是協(xié)議棧留出來(lái)給大家自由發(fā)揮的空間和口子。我們以圖中標(biāo)號(hào) ④ 這一步的 __ip_local_out() 為例,看看內(nèi)核是如何與這些開(kāi)口打交道的:
可以看到在這個(gè)函數(shù)的最后一步,協(xié)議棧就開(kāi)始通過(guò) nf_hook() 去遍歷 OUTPUT 鏈里面的規(guī)則了。這也是為什么我們說(shuō) OUTPUT 鏈?zhǔn)俏彐溨坏脑颉?/span>
nf_hook() 在遍歷完 OUTPUT 鏈之后,就調(diào)用 dst_output() 來(lái)送別網(wǎng)絡(luò)包。而網(wǎng)絡(luò)包從此則需獨(dú)自一人進(jìn)入下一段旅程,過(guò)一會(huì)兒它將會(huì)遇到 ip_output() ,從那里離開(kāi) IP 層。
我們也可以看得出來(lái)對(duì)于發(fā)送流程, OUTPUT 鏈只是一個(gè)過(guò)客,網(wǎng)絡(luò)包在這一站稍作停留后還是要繼續(xù)奔赴山海,在后面的旅途中它會(huì)碰到協(xié)議棧其它代碼和其它鏈,比如在 ip_output() 里面,它會(huì)遇到 POSTROUTING 鏈。
(2)PREROUTING 鏈
讓我們把圖 4 仔細(xì)看一遍。
對(duì)于 ① ,當(dāng)流量從外部進(jìn)入網(wǎng)卡,ip_rcv() 負(fù)責(zé)將其接入 IP 層,PREROUTING 鏈先于路由選擇介入對(duì)流量的處理流程。比如下面的例子里,每一個(gè)原本想訪問(wèn) 8022 端口的流量的 dest IP 和 dest Port 全部都被改成 127.0.0.1:22 。
dest IP 和 dest Port 全部都改成 127.0.0.1:22 ,你很容易就猜到:在接下來(lái)的路由選擇這一步,協(xié)議棧會(huì)把修改過(guò)之后的流量通過(guò) ② 送往本地進(jìn)程。
如果 dest IP 和 dest Port 被改成了 39.156.66.10:443 呢?流量不會(huì)被送往本機(jī),而是通過(guò) ③ 被 forward 離開(kāi)本機(jī)。當(dāng)然前提是本機(jī) forward 功能已經(jīng)開(kāi)啟了。
(3)INPUT 鏈
我們剛才說(shuō)流量最終是通過(guò) ip_local_deliver() 離開(kāi) IP 層并進(jìn)入傳輸層的。不過(guò)在這之前,還有一個(gè) INPUT 鏈等著它。流量能否被傳輸層處理還得看 INPUT 鏈?zhǔn)欠裨试S。
比如對(duì)于下面這條規(guī)則,它存放在 INPUT 鏈的 filter 表里面,當(dāng)發(fā)現(xiàn)流量是 tcp 協(xié)議,且訪問(wèn)的是本機(jī) 22 端口,就把流量丟棄掉,說(shuō)白話就是不允許任何人通過(guò) ssh 訪問(wèn)本機(jī)。于是對(duì)于任何進(jìn)來(lái)的流量,② 這條路就算走到頭了。ip_local_deliver() 不會(huì)被執(zhí)行,sshd 也就沒(méi)有機(jī)會(huì)收到這個(gè)請(qǐng)求。
又或者如果一切都很正常,不出意外的話,位于傳輸層的函數(shù) tcp_v4_rcv() 會(huì)接收到這個(gè)可能已經(jīng)被修改過(guò)之后的流量,從此流量開(kāi)始了它在傳輸層的旅程。
(4)FORWARD 鏈
當(dāng)路由選擇決定要把流量 forward 后,會(huì)調(diào)用 ip_forward() 開(kāi)始后續(xù)的 forward 處理流程,這個(gè)流程如 ③ 所示。如果你喜歡,你可以在 FORWARD 鏈中加入你喜歡的規(guī)則來(lái)控制流量從命運(yùn)。比如下面的例子:
(5)OUTPUT 鏈
① ② ③ 這三條數(shù)據(jù)流里面涉及到的三個(gè)鏈都是因網(wǎng)卡接收到了外部的流量引起的,它們都是被動(dòng)被執(zhí)行。而 OUTPUT 鏈則是因?yàn)楸镜爻绦蛳蛑鲃?dòng)發(fā)送流量而觸發(fā)執(zhí)行流程。
當(dāng)網(wǎng)絡(luò)包從傳輸層通過(guò) tcp_write_xmit() 來(lái)到 IP 層時(shí),首先迎接它的還是路由選擇。這一步會(huì)產(chǎn)生兩個(gè)重要的決定:
- next-hop 是誰(shuí),也即由誰(shuí)來(lái)接收網(wǎng)絡(luò)包
- 從本機(jī)哪個(gè) interface 離開(kāi)
你可能會(huì)困惑,不是由應(yīng)用程序?qū)懞玫?dest IP 來(lái)接收網(wǎng)絡(luò)包嗎?沒(méi)錯(cuò),不過(guò)那是最終接收者,在這中間還會(huì)有若干個(gè)設(shè)備會(huì)經(jīng)手并傳遞這個(gè)網(wǎng)絡(luò)包。這就好像你從南京快遞一個(gè) iPhone 給遠(yuǎn)在北京的女友。當(dāng)然最終是你的女友負(fù)責(zé)接收、拆開(kāi)這個(gè)包裹,但在她拿到包裹之前,有非常多的快遞站中轉(zhuǎn)站、快遞小哥也會(huì)觸碰到它。這里所說(shuō)的 next-hop 就是負(fù)責(zé)收取快遞的第一個(gè)人。
如果這臺(tái)設(shè)備有多個(gè)網(wǎng)卡的話,得選擇其中一個(gè)網(wǎng)卡來(lái)將網(wǎng)絡(luò)包傳送出去。
ip_output() 負(fù)責(zé)將網(wǎng)絡(luò)包送離 IP 層,但且慢,看到 ④ 那里的 OUTPUT 鏈了嗎?是的,這次輪到它大顯身手了。我們可以在這里對(duì)包做一次 SNAT ,使得它離開(kāi)本機(jī)的時(shí)候,源地址使用本機(jī)的 IP 地址。關(guān)于 OUTPUT 鏈具體的例子我們留到最后聊 istio route 的時(shí)候再細(xì)說(shuō)。
3、通過(guò) loopback 通信
問(wèn)大家一個(gè)問(wèn)題:現(xiàn)有下面這兩個(gè)網(wǎng)絡(luò)通信場(chǎng)景:
場(chǎng)景一:本機(jī)同一個(gè) network namespace 下面的兩個(gè)進(jìn)程之間通過(guò) loopback(后文簡(jiǎn)稱 lo) 設(shè)備進(jìn)行網(wǎng)絡(luò)通信,如圖 5 所示。
場(chǎng)景二:一個(gè)局域網(wǎng)內(nèi),連接在同一個(gè)交換機(jī)上的兩臺(tái)主機(jī)上的兩個(gè)進(jìn)程相互進(jìn)行網(wǎng)絡(luò)通信,如圖 6 所示。
這兩個(gè)場(chǎng)景下,除去鏈路層設(shè)備的不同所帶來(lái)的二層收發(fā)數(shù)據(jù)的區(qū)別外,內(nèi)核協(xié)議棧對(duì)數(shù)據(jù)包的處理過(guò)程有本質(zhì)的不同嗎?從圖中你也可以看出來(lái),無(wú)論是圖 5 還是圖 6,數(shù)據(jù)均需要走完如下的過(guò)程:
發(fā)端應(yīng)用層 -> 發(fā)端傳輸層 -> 發(fā)端網(wǎng)絡(luò)層 -> 發(fā)端鏈路層 ->(物理層數(shù)據(jù)收發(fā))-> 收端鏈路層 -> 收端網(wǎng)絡(luò)層 -> 收端傳輸層 -> 收端應(yīng)用層
對(duì)于圖 6,上述這個(gè)過(guò)程大家應(yīng)該沒(méi)有任何異議。重點(diǎn)是對(duì)于圖 5 所示的場(chǎng)景:即便通過(guò) loopback 設(shè)備通信,網(wǎng)絡(luò)包還是要兩次完整地穿越協(xié)議棧。注意我這里的用詞:完整地。理解這點(diǎn)非常重要,因?yàn)楹竺嬉谩?/span>
圖 5:通信場(chǎng)景一:兩個(gè)進(jìn)程相互之間通過(guò) loopback 設(shè)備通信
圖 6:通信場(chǎng)景二:LAN 通信
4、簡(jiǎn)單過(guò)下 istio route
二哥需要強(qiáng)調(diào)一個(gè)重點(diǎn):圖 7 所示的包括四表五鏈、conntrack 表、供路由選擇的路由表、接口 eth0 和 loopback 在內(nèi)的信息都是 network namespace 的一部分。對(duì)于一個(gè)進(jìn)程來(lái)說(shuō),這些要素其實(shí)就構(gòu)成了它發(fā)起和響應(yīng)網(wǎng)絡(luò)請(qǐng)求的基本環(huán)境。在正常情況下,一個(gè) Pod 里面所有的 container 都共享一個(gè) network ns,也就共享著這個(gè)基本環(huán)境。
一個(gè) network ns 如同一座圍城,圍住了所有的數(shù)據(jù)。
我們都知道 Linux 支持多個(gè) network namespace,這也就意味著類似這樣的基本環(huán)境會(huì)有若干份。當(dāng)然,在每個(gè)基本環(huán)境里面,像四表五鏈、路由表之類的數(shù)據(jù)各有千秋。
我們可以將 TCP/IP 協(xié)議??闯墒浅绦虻拇a部分,而將上述的基本環(huán)境看成是程序的數(shù)據(jù)部分。很顯然 TCP/IP 棧應(yīng)該是被這個(gè) OS 上所有人共享的,無(wú)論是進(jìn)程還是容器,甚至是基于 qemu-kvm 的虛擬機(jī)都共享著宿主機(jī)的協(xié)議棧,但 network ns 所圍起來(lái)的數(shù)據(jù)卻是各個(gè) network ns 獨(dú)享的。
圖 7:Envoy 劫持網(wǎng)絡(luò)流量全景圖
下面我們將以 App container 想要訪問(wèn) www.baidu.com 443 端口為例來(lái)帶大家過(guò)一下 istio route 。
這個(gè)過(guò)程在圖 7 上來(lái)看,就是始于 App container internal logic 的,標(biāo)號(hào)為 ⑨ ~ ? ~ ? 的數(shù)據(jù)流。我們沿著箭頭的方向會(huì)發(fā)現(xiàn)網(wǎng)絡(luò)包被透明地劫持到了 Envoy 的 Outbound hanlder 15001 端口。我們重點(diǎn)分析 Envoy 是如何通過(guò) iptables 來(lái)做到這一點(diǎn)的。
(1)相關(guān) iptables
首先我們來(lái)看下與這個(gè)流程相關(guān)的 iptables 。為節(jié)省篇幅,突出重點(diǎn),二哥省去了其余的部分,只保留了 OUTPUT 鏈及其會(huì)調(diào)用到的自定義鏈。
首先我們看到這段輸出是用命令 iptables -t nat -L -v 得到的。你看到 -t nat 了嗎?這表示這些規(guī)則全部都存放在 nat 表里面。我相信大家對(duì) NAT 有所耳聞,看到它也就大概猜得出來(lái)幾分:既然這些規(guī)則是與 NAT 表相關(guān)的,那么它們干的事情也就涉及到修改 IP 地址或端口這樣的操作。
我把這些規(guī)則之間的關(guān)系用圖 8 表示出來(lái)了。我們來(lái)看看它們是如何協(xié)作的。
圖 8:istio route 自定義鏈 ISTIO_OUTPUT 細(xì)節(jié)
(2)協(xié)作細(xì)節(jié)
首先當(dāng) App cotainer 訪問(wèn) baidu.com 時(shí),請(qǐng)求從傳輸層出來(lái)后,首先需要經(jīng)過(guò)一次路由。這個(gè)時(shí)候協(xié)議棧也僅僅知道這個(gè)包的目的 IP(39.156.66.10) 和 目的端口(443),還不知道它的二層信息是什么。為什么呢?得經(jīng)過(guò)路由后,才能知道包需要從本地哪個(gè)接口離開(kāi),以及誰(shuí)是 next-hop ,也只有當(dāng)知曉了這些信息后,才能填充二層頭的 src MAC 和 dest MAC。因?yàn)?IP 地址是與接口綁在一起的,所以從哪里接口離開(kāi)也就決定了 src IP 是什么。
路由選擇細(xì)節(jié)就不細(xì)講了。我們先把它看作一個(gè)黑盒子,經(jīng)過(guò)它之后,協(xié)議棧做了一個(gè)決定:去 39.156.66.10 的話,得從接口 eth0 離開(kāi)。再?gòu)?qiáng)調(diào)一次,eth0 位于 App cotainer 所在的 network namespace 里面。
按照前文所述的協(xié)議棧和 netfilter 配合流程,我們現(xiàn)在知道路由選擇后,緊接著需要執(zhí)行 OUTPUT 鏈里面的規(guī)則。
? OUTPUT 鏈?zhǔn)俏宕笕肟阪溨?,可在這里,它啥都沒(méi)干,直接把活外包給自定義鏈 ISTIO_OUTPUT 了。我們可以看到鏈 ISTIO_OUTPUT 上掛了 9 個(gè)規(guī)則。
圖 9:ISTIO_OUTPUT 鏈上的 9 個(gè)規(guī)則
規(guī)則 1,2,3,5,6 都不滿足條件,因?yàn)槲覀冃枰獜慕涌?eth0 離開(kāi),而不是 lo ,當(dāng)然這兩個(gè)接口都屬于這個(gè) Pod 所使用的 network ns。
規(guī)則 4,7 對(duì)目的地的 owner UID 和 GID 做了限制,不符合我們的場(chǎng)景。
規(guī)則 8 的目的地是 localhost,而我們想要去 39.156.66.10。很抱歉,完美錯(cuò)過(guò)。
最后就剩規(guī)則 9 了。這個(gè)規(guī)則非常粗礦,啥都行,碰到這個(gè)它,大家就只能全部乖乖地跳轉(zhuǎn)到自定義鏈 ISTIO_REDIRECT 了。
? 這一步表示了這樣的跳轉(zhuǎn)過(guò)程。
? 自定義鏈 ISTIO_REDIRECT 也是人狠話不多。只要傳輸層是 TCP 的流量,全部統(tǒng)一 REDIRECT 到 127.0.0.1:10051 。
我們來(lái)看看 target REDIRECT 會(huì)對(duì)流量干啥事。
說(shuō)白了,它會(huì)把 dest IP 和 dest Port 改成 127.0.0.1:15001 。這好理解,畢竟只有這樣,在圖 8 ? 處路由的時(shí)候, IP 層才會(huì)把從 App container 出來(lái)的流量路由至 listen 在 15001 的 Outbound hanlder 去處理。
這一步做完后,從 ? 開(kāi)始的 OUTPUT 鏈遍歷執(zhí)行過(guò)程就結(jié)束了。那結(jié)束之后下一步協(xié)議棧要干什么呢?
跟著 ? ? ? ? ? 走一遍,你會(huì)知道全部的答案。不過(guò)注意看 ? 那里所標(biāo)示的目的 IP 和目的端口,它們已經(jīng)被改掉了。既然目的 IP 已經(jīng)被改成了 127.0.0.1 了,那在 ? ? 那里發(fā)生的就是前文所述的通過(guò) loopback 通信所涉及的技術(shù)細(xì)節(jié)了。