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