Linux連接跟蹤C(jī)onntrack:原理、應(yīng)用與內(nèi)核實(shí)現(xiàn)
從我們?nèi)粘J褂玫氖謾C(jī) APP,到企業(yè)復(fù)雜的網(wǎng)絡(luò)架構(gòu),連接跟蹤(conntrack)都在背后默默發(fā)揮著關(guān)鍵作用。它就像是網(wǎng)絡(luò)世界的 “大管家”,幫助眾多網(wǎng)絡(luò)應(yīng)用維持著高效、穩(wěn)定的運(yùn)行。以 Kubernetes Service 為例,在容器編排的復(fù)雜環(huán)境中,Kubernetes 需要將外部的網(wǎng)絡(luò)請(qǐng)求準(zhǔn)確無(wú)誤地轉(zhuǎn)發(fā)到對(duì)應(yīng)的容器服務(wù)上。這一過(guò)程中,連接跟蹤就像是導(dǎo)航儀,它記錄著每個(gè)服務(wù)的連接狀態(tài),確保請(qǐng)求能夠被正確路由,讓各個(gè)容器服務(wù)之間的通信有條不紊地進(jìn)行 。
再看 Docker network,當(dāng)我們?cè)谌萜髦羞\(yùn)行各種應(yīng)用時(shí),容器之間以及容器與外部網(wǎng)絡(luò)之間的通信管理十分重要。連接跟蹤能夠精準(zhǔn)地識(shí)別和跟蹤這些連接,讓容器里的應(yīng)用如同在一個(gè)有序的網(wǎng)絡(luò)社區(qū)中,彼此可以順暢交流,互不干擾。還有 iptables 主機(jī)防火墻,它守護(hù)著主機(jī)網(wǎng)絡(luò)的安全。
連接跟蹤則為它提供了關(guān)鍵信息,幫助防火墻判斷哪些連接是合法的,哪些可能存在風(fēng)險(xiǎn),從而對(duì)網(wǎng)絡(luò)流量進(jìn)行有效的過(guò)濾和控制,保障主機(jī)的網(wǎng)絡(luò)安全。可以說(shuō),連接跟蹤就像網(wǎng)絡(luò)世界的基石,雖然平時(shí)不太容易被我們察覺(jué),但一旦缺失,許多網(wǎng)絡(luò)應(yīng)用都將陷入混亂。接下來(lái),讓我們深入了解連接跟蹤的原理、應(yīng)用以及它在 Linux 內(nèi)核中的實(shí)現(xiàn)機(jī)制,揭開(kāi)它神秘的面紗。
Part1.連接跟蹤(Conntrack)概述
顧名思義,連接跟蹤是保存連接狀態(tài)的一種機(jī)制。為什么要保存連接狀態(tài)呢? 舉個(gè)例子,當(dāng)你通過(guò)瀏覽器訪問(wèn)一個(gè)網(wǎng)站(連接網(wǎng)站的80端口)時(shí),預(yù)期會(huì)收到服務(wù)器發(fā)送的源端口為80的報(bào)文回應(yīng),防火墻自然應(yīng)該放行這些回應(yīng)報(bào)文。那是不是所有源端口為80端口的報(bào)文都應(yīng)該放行呢?顯然不是,我們只應(yīng)該放行源IP為服務(wù)器地址,源端口為80的報(bào)文,而應(yīng)該阻止源地址不符的報(bào)文,即使它的源端口也是80。總結(jié)一下這種情況就是,我們只應(yīng)該讓主動(dòng)發(fā)起的連接產(chǎn)生的雙向報(bào)文通過(guò)。
圖片
另一個(gè)例子是NAT。我們可以使用iptables配置nat表進(jìn)行地址或者端口轉(zhuǎn)換的規(guī)則。如果每一個(gè)報(bào)文都去查詢(xún)規(guī)則,這樣效率太低了,因?yàn)橥粋€(gè)連接的轉(zhuǎn)換方式是不變的!連接跟蹤提供了一種緩存解決方案:當(dāng)一條連接的第一個(gè)數(shù)據(jù)包通過(guò)時(shí)查詢(xún)nat表時(shí),連接跟蹤將轉(zhuǎn)換方法保存下來(lái),后續(xù)的報(bào)文只需要根據(jù)連接跟蹤里保存的轉(zhuǎn)換方法就可以了。
1.1連接跟蹤發(fā)生在哪里?
連接跟蹤需要拿到報(bào)文的第一手資料,因此它們的入口是以高優(yōu)先級(jí)存在于LOCAL_OUT(本機(jī)發(fā)送)和PRE_ROUTING(報(bào)文接收)這兩個(gè)鏈。
既然有入口,自然就有出口。連接跟蹤采用的方案是在入口記錄,在出口確認(rèn)(confirm)。以IPv4為例:
圖片
當(dāng)連接的第一個(gè)skb通過(guò)入口時(shí),連接跟蹤會(huì)將連接跟蹤信息保存在skb->nfctinfo,而在出口處,連接跟蹤會(huì)從skb上取下連接跟蹤信息,保存在自己的hash表中。當(dāng)然,如果這個(gè)數(shù)據(jù)包在中途其他HOOK點(diǎn)被丟棄了,也就不存在最后的confirm過(guò)程了。
1.2連接跟蹤信息是什么?
連接跟蹤信息會(huì)在入口處進(jìn)行計(jì)算,保存在skb上,信息具體包括tuple信息(地址、端口、協(xié)議號(hào)等)、擴(kuò)展信息以及各協(xié)議的私有信息。
圖片
- tuple信息包括發(fā)送和接收兩個(gè)方向,對(duì)TCP和UDP來(lái)說(shuō),是IP加Port;對(duì)ICMP來(lái)說(shuō)是IP加Type和Code,等等;
- 擴(kuò)展信息比較復(fù)雜,本文暫時(shí)略過(guò);
- 各協(xié)議的私有信息,比如對(duì)TCP就是序號(hào)、重傳次數(shù)、縮放因子等。
報(bào)文的連接跟蹤狀態(tài)
途徑Netfilter框架的每一個(gè)報(bào)文總是會(huì)在入口處(PRE ROUTING或者LOCAL OUT)被賦予一個(gè)連接跟蹤狀態(tài)。這個(gè)狀態(tài)存儲(chǔ)在skb->nfctinfo,有以下常見(jiàn)的取值:
- IP_CT_ESTABLISHED:這是一個(gè)屬于已經(jīng)建立連接的報(bào)文,Netfilter目擊過(guò)兩個(gè)方向都互通過(guò)報(bào)文了
- IP_CT_RELATED:這個(gè)狀態(tài)的報(bào)文所處的連接與另一個(gè)IP_CT_ESTABLISHED狀態(tài)的連接是有聯(lián)系的。比如典型的ftp,ftp-data的連接就是ftp-control派生出來(lái)的,它就是RELATED狀態(tài)
- IP_CT_NEW:這是連接的第一個(gè)包,常見(jiàn)的就是TCP中的SYN包,UDP、ICMP中第一個(gè)包,
- IP_CT_ESTABLISHED + IP_CT_IS_REPLY:與IP_CT_ESTABLISHED類(lèi)似,但是是在回復(fù)方向
- IP_CT_RELATED + IP_CT_IS_REPLY:與IP_CT_RELATED類(lèi)似,但是是在回復(fù)方向
Part2.連接跟蹤(Conntrack)原理剖析
2.1 底層工作機(jī)制
連接跟蹤的底層工作機(jī)制,就像是一場(chǎng)精心編排的 “數(shù)據(jù)包處理交響樂(lè)”,每一個(gè)環(huán)節(jié)都緊密相扣,有條不紊。
當(dāng)數(shù)據(jù)包進(jìn)入網(wǎng)絡(luò)節(jié)點(diǎn)時(shí),連接跟蹤系統(tǒng)就像一個(gè)敏銳的 “攔截者”,迅速對(duì)其進(jìn)行攔截。它會(huì)仔細(xì)分析數(shù)據(jù)包的頭部信息,從中提取出關(guān)鍵的五元組信息,即源 IP 地址、目的 IP 地址、源端口、目的端口和協(xié)議類(lèi)型。這些信息就像是數(shù)據(jù)包的 “身份密碼”,連接跟蹤系統(tǒng)憑借它們來(lái)判斷這個(gè)數(shù)據(jù)包是否屬于一個(gè)已有的連接,或者是否需要建立一個(gè)新的連接。
一旦識(shí)別出數(shù)據(jù)包的 “身份”,連接跟蹤系統(tǒng)就會(huì)開(kāi)始建立連接追蹤記錄。如果這個(gè)數(shù)據(jù)包是一個(gè)新連接的開(kāi)始,比如 TCP 協(xié)議中的 SYN 包,連接跟蹤系統(tǒng)會(huì)在連接跟蹤表中創(chuàng)建一個(gè)新的記錄項(xiàng)。這個(gè)記錄項(xiàng)就像是一個(gè)新的 “檔案”,記錄著這個(gè)連接的初始狀態(tài)和相關(guān)信息。在這個(gè) “檔案” 里,會(huì)包含連接的五元組信息、創(chuàng)建時(shí)間、當(dāng)前狀態(tài)等內(nèi)容,為后續(xù)對(duì)這個(gè)連接的跟蹤和管理提供了基礎(chǔ) 。
隨著數(shù)據(jù)的傳輸,連接跟蹤系統(tǒng)會(huì)持續(xù)關(guān)注連接的狀態(tài)變化。當(dāng)接收到屬于已有連接的數(shù)據(jù)包時(shí),它會(huì)像一個(gè)嚴(yán)謹(jǐn)?shù)?“檔案管理員”,及時(shí)更新連接記錄中的各種統(tǒng)計(jì)信息,比如收發(fā)包數(shù)、字節(jié)數(shù)等。同時(shí),它還會(huì)根據(jù)數(shù)據(jù)包的類(lèi)型和內(nèi)容,更新連接的狀態(tài)。例如,在 TCP 連接中,當(dāng)接收到 SYN + ACK 包時(shí),連接狀態(tài)就會(huì)從 “SYN_SENT” 更新為 “ESTABLISHED”,標(biāo)志著連接已經(jīng)成功建立 。
當(dāng)連接結(jié)束時(shí),無(wú)論是正常的結(jié)束,還是因?yàn)槌瑫r(shí)、錯(cuò)誤等原因?qū)е碌漠惓=Y(jié)束,連接跟蹤系統(tǒng)都會(huì)及時(shí)刪除連接記錄。它會(huì)從連接跟蹤表中移除對(duì)應(yīng)的記錄項(xiàng),釋放相關(guān)的系統(tǒng)資源,就像清理不再使用的 “檔案” 一樣,確保連接跟蹤表的高效運(yùn)行,為新的連接記錄騰出空間 。
2.2與 Netfilter 的淵源
在 Linux 內(nèi)核的網(wǎng)絡(luò)世界里,Netfilter 就像是一個(gè)強(qiáng)大的 “交通樞紐”,而連接跟蹤則是這個(gè) “交通樞紐” 中不可或缺的一部分。Netfilter 是 Linux 內(nèi)核中一個(gè)對(duì)數(shù)據(jù)包進(jìn)行控制、修改和過(guò)濾的框架,它在內(nèi)核協(xié)議棧中精心設(shè)置了若干 hook 點(diǎn),這些 hook 點(diǎn)就像是交通要道上的 “檢查站”,所有流經(jīng)的數(shù)據(jù)包都必須在這里接受檢查和處理 。
連接跟蹤正是借助 Netfilter 的 hook 點(diǎn)來(lái)實(shí)現(xiàn)其功能的。當(dāng)數(shù)據(jù)包到達(dá) Netfilter 設(shè)置的 hook 點(diǎn)時(shí),連接跟蹤模塊就會(huì)被觸發(fā)。它會(huì)對(duì)數(shù)據(jù)包進(jìn)行分析和處理,提取出連接相關(guān)的信息,并將這些信息記錄到連接跟蹤表中。在 NF_INET_PRE_ROUTING 這個(gè) hook 點(diǎn),連接跟蹤模塊會(huì)對(duì)進(jìn)入系統(tǒng)的數(shù)據(jù)包進(jìn)行檢查,判斷是否需要建立新的連接跟蹤記錄;而在 NF_INET_POST_ROUTING 這個(gè) hook 點(diǎn),它會(huì)對(duì)離開(kāi)系統(tǒng)的數(shù)據(jù)包進(jìn)行處理,更新連接的狀態(tài)信息 。
不過(guò),隨著技術(shù)的不斷發(fā)展,云原生網(wǎng)絡(luò)方案 Cilium 帶來(lái)了一種全新的思路。Cilium 在 1.7.4 + 版本中,基于 BPF hook 實(shí)現(xiàn)了一套獨(dú)立的連接跟蹤和 NAT 機(jī)制。BPF hook 就像是 Cilium 自己搭建的 “檢查站”,它能夠?qū)崿F(xiàn)與 Netfilter 中 hook 機(jī)制類(lèi)似的數(shù)據(jù)包攔截功能 。
在這個(gè)基礎(chǔ)上,Cilium 構(gòu)建了一套全新的連接跟蹤和 NAT 系統(tǒng),這使得它即便在卸載 Netfilter 的情況下,也能正常支持 Kubernetes 的 ClusterIP、NodePort、ExternalIPs 和 LoadBalancer 等功能 。由于這套連接跟蹤機(jī)制是獨(dú)立于 Netfilter 的,它的連接跟蹤和 NAT 信息不會(huì)存儲(chǔ)在內(nèi)核中 Netfilter 的連接跟蹤表和 NAT 表中,而是有自己獨(dú)立的存儲(chǔ)和管理方式,這也為網(wǎng)絡(luò)管理帶來(lái)了更多的靈活性和創(chuàng)新性 。
Part3.連接跟蹤(Conntrack)的多元應(yīng)用
3.1 在 NAT 中的關(guān)鍵角色
NAT,全稱(chēng) Network Address Translation,即網(wǎng)絡(luò)地址轉(zhuǎn)換,它就像是網(wǎng)絡(luò)世界里的 “地址翻譯官”。在 IPv4 地址資源日益緊張的今天,NAT 技術(shù)應(yīng)運(yùn)而生,它允許一個(gè)機(jī)構(gòu)或網(wǎng)絡(luò)以一個(gè)公用 IP 地址出現(xiàn)在 Internet 上 。簡(jiǎn)單來(lái)說(shuō),它能夠把內(nèi)部私有網(wǎng)絡(luò)地址翻譯成合法網(wǎng)絡(luò) IP 地址 。
在一個(gè)企業(yè)內(nèi)部網(wǎng)絡(luò)中,眾多員工的電腦都分配有私有 IP 地址,如 192.168.1.x 段的地址。當(dāng)這些電腦需要訪問(wèn)互聯(lián)網(wǎng)時(shí),NAT 設(shè)備就會(huì)發(fā)揮作用。它會(huì)將數(shù)據(jù)包中的源 IP 地址(私有 IP)轉(zhuǎn)換為一個(gè)合法的公網(wǎng) IP 地址,然后將數(shù)據(jù)包發(fā)送出去。當(dāng)外部服務(wù)器返回響應(yīng)數(shù)據(jù)包時(shí),NAT 設(shè)備又能根據(jù)之前的轉(zhuǎn)換記錄,把目的 IP 地址(公網(wǎng) IP)轉(zhuǎn)換回對(duì)應(yīng)的私有 IP 地址,確保數(shù)據(jù)包能準(zhǔn)確無(wú)誤地回到員工的電腦上 。
在這個(gè)過(guò)程中,連接跟蹤起著至關(guān)重要的輔助作用。當(dāng)NAT設(shè)備進(jìn)行地址轉(zhuǎn)換時(shí),連接跟蹤系統(tǒng)會(huì)記錄下每個(gè)連接的相關(guān)信息,包括源IP、目的 IP、源端口、目的端口以及協(xié)議類(lèi)型等 。這些信息就像是一個(gè)個(gè) “連接檔案”,當(dāng)后續(xù)的數(shù)據(jù)包到來(lái)時(shí),NAT設(shè)備可以根據(jù)連接跟蹤表中的記錄,快速準(zhǔn)確地進(jìn)行地址轉(zhuǎn)換,確保通信的順暢 。而且,對(duì)于那些經(jīng)過(guò)NAT轉(zhuǎn)換的連接,連接跟蹤系統(tǒng)能夠識(shí)別出回復(fù)報(bào)文,自動(dòng)完成反向轉(zhuǎn)換,無(wú)需額外添加規(guī)則,大大提高了網(wǎng)絡(luò)通信的效率 。
3.2 助力狀態(tài)包過(guò)濾
狀態(tài)包過(guò)濾,是一種比傳統(tǒng)包過(guò)濾更為智能和高效的網(wǎng)絡(luò)安全技術(shù) 。傳統(tǒng)包過(guò)濾只基于每個(gè)數(shù)據(jù)包的源和目的地址以及端口號(hào)進(jìn)行檢查,對(duì)每個(gè)數(shù)據(jù)包獨(dú)立處理,就像是一個(gè)只看表面信息的 “檢查員” 。而狀態(tài)包過(guò)濾則像是一個(gè)經(jīng)驗(yàn)豐富的 “偵探”,它會(huì)根據(jù)網(wǎng)絡(luò)連接的狀態(tài),對(duì)數(shù)據(jù)包進(jìn)行過(guò)濾和管理 。
狀態(tài)包過(guò)濾器能夠檢測(cè)網(wǎng)絡(luò)連接的狀態(tài),比如TCP連接的三次握手過(guò)程,它都了如指掌 。當(dāng)一個(gè)TCP SYN包到達(dá)時(shí),狀態(tài)包過(guò)濾器會(huì)知道這是一個(gè)新連接的開(kāi)始;當(dāng)后續(xù)的SYN + ACK 包和 ACK包到來(lái)時(shí),它能根據(jù)之前的記錄,判斷這些數(shù)據(jù)包是否屬于同一個(gè)連接 。通過(guò)這種方式,狀態(tài)包過(guò)濾器可以判斷是否允許特定的數(shù)據(jù)包通過(guò)網(wǎng)絡(luò),從而有效地防范會(huì)話劫持、拒絕服務(wù)攻擊等網(wǎng)絡(luò)安全威脅 。
連接跟蹤為狀態(tài)包過(guò)濾提供了關(guān)鍵的連接狀態(tài)信息。連接跟蹤系統(tǒng)會(huì)維護(hù)一個(gè)連接狀態(tài)表,記錄著每個(gè)連接的詳細(xì)信息,包括連接的建立、數(shù)據(jù)傳輸以及關(guān)閉等各個(gè)階段 。狀態(tài)包過(guò)濾器在處理數(shù)據(jù)包時(shí),會(huì)參考連接跟蹤表中的信息,判斷該數(shù)據(jù)包是否屬于一個(gè)已建立的合法連接 。如果是,就允許數(shù)據(jù)包通過(guò);如果不是,就會(huì)根據(jù)安全策略進(jìn)行處理,比如丟棄數(shù)據(jù)包 。在一個(gè)企業(yè)網(wǎng)絡(luò)中,連接跟蹤表記錄著內(nèi)部員工與外部服務(wù)器建立的 HTTP 連接信息。當(dāng)外部服務(wù)器返回的 HTTP 響應(yīng)數(shù)據(jù)包到達(dá)時(shí),狀態(tài)包過(guò)濾器會(huì)根據(jù)連接跟蹤表,確認(rèn)該數(shù)據(jù)包屬于合法連接,從而允許其進(jìn)入企業(yè)內(nèi)部網(wǎng)絡(luò) 。
3.3 應(yīng)用層網(wǎng)關(guān)擴(kuò)展
在網(wǎng)絡(luò)協(xié)議的世界里,大多數(shù)協(xié)議在 IP 層和傳輸層頭部進(jìn)行轉(zhuǎn)換處理就可以正常工作,但有些應(yīng)用層協(xié)議比較特殊,它們?cè)趨f(xié)議數(shù)據(jù)報(bào)文中包含了地址信息 。為了讓這些應(yīng)用也能順利完成 NAT 轉(zhuǎn)換,就需要連接跟蹤通過(guò)擴(kuò)展組件來(lái)跟蹤應(yīng)用層協(xié)議,這就是應(yīng)用層網(wǎng)關(guān)(ALG)技術(shù) 。
以 FTP 協(xié)議為例,它在數(shù)據(jù)傳輸過(guò)程中會(huì)包含地址和端口信息。當(dāng)一個(gè) FTP 客戶(hù)端連接到 FTP 服務(wù)器時(shí),首先會(huì)建立控制連接,用于傳輸命令和響應(yīng) 。在這個(gè)過(guò)程中,連接跟蹤系統(tǒng)會(huì)記錄下控制連接的相關(guān)信息 。當(dāng)客戶(hù)端需要傳輸數(shù)據(jù)時(shí),會(huì)通過(guò) PORT 或 PASV 命令來(lái)建立數(shù)據(jù)連接 。這些命令中包含了客戶(hù)端或服務(wù)器的數(shù)據(jù)傳輸?shù)刂泛投丝谛畔?。
連接跟蹤的擴(kuò)展組件會(huì)對(duì)這些信息進(jìn)行分析和處理,確保在 NAT 環(huán)境下,數(shù)據(jù)連接能夠正確建立 。如果客戶(hù)端使用 PORT 命令,連接跟蹤組件會(huì)根據(jù) NAT 轉(zhuǎn)換規(guī)則,修改命令中的地址信息,使其與轉(zhuǎn)換后的公網(wǎng)地址一致 。這樣,F(xiàn)TP 服務(wù)器就能正確地與客戶(hù)端建立數(shù)據(jù)連接,實(shí)現(xiàn)文件的傳輸 。
Part4.內(nèi)核中連接跟蹤的實(shí)現(xiàn)
4.1 模塊初始化流程
在 Linux 內(nèi)核中,連接跟蹤的模塊初始化是一個(gè)嚴(yán)謹(jǐn)且關(guān)鍵的過(guò)程,就像是搭建一座大廈前的奠基工作,為后續(xù)的連接跟蹤功能奠定了堅(jiān)實(shí)的基礎(chǔ)。這一過(guò)程主要由 nf_conntrack_init_init_net () 函數(shù)和 nf_conntrack_init_net () 函數(shù)協(xié)同完成 。
圖片
這張圖乍一看比較亂,但是這張圖非常好的說(shuō)明了數(shù)據(jù)流在內(nèi)核中的過(guò)程;在圖中的灰色框標(biāo)記了conntrack的位置,這個(gè)是連接跟蹤的起始點(diǎn),其實(shí)在netfilter的所有HOOK點(diǎn)都可以更改流(flow)跟蹤的信息。
nf_conntrack_init_init_net () 函數(shù)首先會(huì)根據(jù)系統(tǒng)內(nèi)存的大小來(lái)確定連接跟蹤表的關(guān)鍵參數(shù)。它會(huì)給全局變量 nf_conntrack_htable_size 賦值,這個(gè)值指定了存放連接跟蹤條目的哈希表的大小 。同時(shí),它還會(huì)計(jì)算出系統(tǒng)可以創(chuàng)建的連接跟蹤條目的最大數(shù)量,并賦值給 nf_conntrack_max 。這兩個(gè)參數(shù)就像是連接跟蹤表的 “容量規(guī)劃師”,合理地規(guī)劃了連接跟蹤表的存儲(chǔ)能力 。接著,該函數(shù)會(huì)為 nf_conn 結(jié)構(gòu)申請(qǐng) slab cache,這個(gè)緩存就像是一個(gè)專(zhuān)門(mén)為 nf_conn 結(jié)構(gòu)打造的 “倉(cāng)庫(kù)”,用于高效地存儲(chǔ)和管理 nf_conn 結(jié)構(gòu) 。
每個(gè)連接的狀態(tài)都由一個(gè) nf_conn 結(jié)構(gòu)體實(shí)例來(lái)描述,每個(gè)連接又分為 original 和 reply 兩個(gè)方向,每個(gè)方向都用一個(gè)元組(tuple)表示,tuple 中包含了這個(gè)方向上數(shù)據(jù)包的關(guān)鍵信息,如源 IP、目的 IP、源 port、目的 port 等 。隨后,它會(huì)給全局的 nf_ct_l3protos [] 數(shù)組賦上默認(rèn)值,這個(gè)數(shù)組中的每個(gè)元素都被賦值為 nf_conntrack_l3proto_generic,這是一個(gè)不區(qū)分 L3 協(xié)議的處理函數(shù),后續(xù)的初始化會(huì)根據(jù)不同的 L3 協(xié)議為其賦上相應(yīng)的值 。最后,它還會(huì)給全局的 hash 表 nf_ct_helper_hash 分配一個(gè)頁(yè)大小的空間,這個(gè) hash 表用于存放 helper 類(lèi)型的 conntrack extension 。
nf_conntrack_init_net () 函數(shù)則主要負(fù)責(zé)初始化 net->ct 成員 。net 是本地 CPU 的網(wǎng)絡(luò)命名空間,在單 CPU 系統(tǒng)中就是全局變量 init_net 。它會(huì)將 net->ct.count 計(jì)數(shù)器設(shè)置為 0,就像是給連接跟蹤的 “計(jì)數(shù)器” 清零,準(zhǔn)備開(kāi)始記錄連接數(shù)量 。然后初始化 unconfirmed 鏈表和 dying 鏈表,這兩個(gè)鏈表就像是連接跟蹤的 “臨時(shí)存放區(qū)”,分別用于存放未確認(rèn)的連接和即將死亡的連接 。
接著,它會(huì)給 net->ct.stat 分配空間并清零,net->ct.stat 用于統(tǒng)計(jì)連接跟蹤的相關(guān)數(shù)據(jù),就像是一個(gè) “數(shù)據(jù)統(tǒng)計(jì)員” 。之后,它會(huì)初始化 conntrack hash table,根據(jù)之前計(jì)算好的 nf_conntrack_htable_size 來(lái)分配相應(yīng)的內(nèi)存空間 。最后,它還會(huì)初始化 net->ct.expect_hash 及緩存,并在 /proc 中創(chuàng)建相應(yīng)文件,完成一系列的初始化工作 。
4.2 連接跟蹤表的數(shù)據(jù)結(jié)構(gòu)
連接跟蹤表的數(shù)據(jù)結(jié)構(gòu)是連接跟蹤功能實(shí)現(xiàn)的核心部分,它就像是一個(gè)精心設(shè)計(jì)的 “信息倉(cāng)庫(kù)”,高效地存儲(chǔ)和管理著網(wǎng)絡(luò)連接的各種信息 。在這個(gè) “倉(cāng)庫(kù)” 中,nf_conn 結(jié)構(gòu)體是描述連接狀態(tài)的關(guān)鍵 “單元” 。每個(gè) nf_conn 結(jié)構(gòu)體實(shí)例都對(duì)應(yīng)著一個(gè)網(wǎng)絡(luò)連接,它包含了連接的各種詳細(xì)信息,如連接的兩個(gè)方向(original 和 reply)的元組信息,這些元組信息就像是連接的 “身份標(biāo)識(shí)”,包含了源 IP、目的 IP、源端口、目的端口和協(xié)議類(lèi)型等關(guān)鍵內(nèi)容 。
同時(shí),nf_conn 結(jié)構(gòu)體還記錄了連接的狀態(tài),比如是新建連接(NEW)、已建立連接(ESTABLISHED)還是其他狀態(tài),這些狀態(tài)信息就像是連接的 “狀態(tài)標(biāo)簽”,方便系統(tǒng)對(duì)連接進(jìn)行管理和處理 。此外,它還包含了一些與連接相關(guān)的統(tǒng)計(jì)信息,如收發(fā)包數(shù)、字節(jié)數(shù)等,這些統(tǒng)計(jì)信息就像是連接的 “運(yùn)行數(shù)據(jù)記錄”,有助于系統(tǒng)了解連接的運(yùn)行情況 。
連接跟蹤表采用哈希表(hash table)來(lái)實(shí)現(xiàn),這是一種高效的數(shù)據(jù)結(jié)構(gòu),能夠快速地定位和查找連接信息 。哈希表就像是一個(gè)大型的 “索引庫(kù)”,每個(gè)連接的元組信息通過(guò)特定的哈希函數(shù)計(jì)算出一個(gè)哈希值,這個(gè)哈希值就像是連接在 “索引庫(kù)” 中的 “索引編號(hào)”,通過(guò)這個(gè)編號(hào)可以快速地找到對(duì)應(yīng)的連接記錄 。
在哈希表中,每個(gè)哈希桶(bucket)是一個(gè)鏈表,當(dāng)多個(gè)連接的哈希值相同時(shí),這些連接的記錄就會(huì)以鏈表的形式存儲(chǔ)在同一個(gè)哈希桶中 。這種設(shè)計(jì)既利用了哈希表的快速查找特性,又解決了哈希沖突的問(wèn)題 。在一個(gè)高并發(fā)的網(wǎng)絡(luò)環(huán)境中,可能會(huì)有大量的連接同時(shí)存在,哈希表的這種設(shè)計(jì)能夠保證系統(tǒng)在處理這些連接時(shí),依然能夠快速地進(jìn)行查找和匹配操作,大大提高了連接跟蹤的效率 。
(1)重要結(jié)構(gòu)體
- struct nf_hook_ops {}: 在HOOK點(diǎn)上注冊(cè)的連接跟蹤信息,通過(guò)nf_register_hooks()注冊(cè)
- struct nf_conntrack_tuple {}: 連接跟蹤的基本元素,表示特定方向的流。
- struct nf_conn {}:連接跟蹤條目,定義一個(gè) flow。
在nf_conn中有重要成員:如ct_general、status、master、tuplehash、timeout等。
struct nf_conn {
struct nf_conntrack ct_general;
spinlock_t lock;
u16 cpu;
/* XXX should I move this to the tail ? - Y.K */
/* These are my tuples; original and reply */
struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
/* Have we seen traffic both ways yet? (bitset) */
unsigned long status;
/* Timer function; drops refcnt when it goes off. */
struct timer_list timeout;
possible_net_t ct_net;
/* all members below initialized via memset */
u8 __nfct_init_offset[0];
/* If we were expected by an expectation, this will be it */
struct nf_conn *master;
#if defined(CONFIG_NF_CONNTRACK_MARK)
u_int32_t mark;
#endif
#ifdef CONFIG_NF_CONNTRACK_SECMARK
u_int32_t secmark;
#endif
/* Extensions */
struct nf_ct_ext *ext;
/* Storage reserved for other modules, must be the last member */
union nf_conntrack_proto proto;
};(2)重要函數(shù)
- hash_conntrack_raw():根據(jù) tuple 計(jì)算出一個(gè) 32 位的哈希值(hash key)。
- nf_conntrack_in():連接跟蹤模塊的核心,包進(jìn)入連接跟蹤的地方。在此函數(shù)中包含下邊的步驟:resolve_normal_ct() -> nf_ct_timeout_lookup()在resolve_normal_ct() 中會(huì)計(jì)算元組的散列值,進(jìn)行匹配,沒(méi)有就創(chuàng)建nf_conntrack_tuple_hash,將其加入未確認(rèn)tuplehash列表中,已經(jīng)創(chuàng)建則判斷狀態(tài)是否超時(shí)。
- nf_conntrack_confirm():確認(rèn)前面通過(guò) nf_conntrack_in() 創(chuàng)建的新連接(是否被丟棄),將元組從未確認(rèn)tuplehash列表中刪除。
- nf_ct_get(skb, ctinfo):獲取連接跟蹤數(shù)據(jù),沒(méi)有建立返回null
具體細(xì)節(jié)可以在內(nèi)核代碼中查看代碼路徑官方網(wǎng)站:https://elixir.bootlin.com/linux/v4.4.155/source/net/netfilter
4.3 連接查找與匹配策略
當(dāng)一個(gè)數(shù)據(jù)包到達(dá)時(shí),連接查找的流程就像是在一個(gè)龐大的圖書(shū)館中查找一本特定的書(shū)籍 。首先,系統(tǒng)會(huì)從數(shù)據(jù)包中提取出關(guān)鍵的元組信息,這些元組信息就像是書(shū)籍的 “關(guān)鍵詞” 。然后,根據(jù)這些元組信息,通過(guò)哈希函數(shù)計(jì)算出一個(gè)哈希值,這個(gè)哈希值就像是圖書(shū)館中的 “書(shū)架編號(hào)” 。系統(tǒng)會(huì)根據(jù)這個(gè)哈希值快速定位到哈希表中的相應(yīng)哈希桶,就像是找到了對(duì)應(yīng)的書(shū)架 。
如果哈希桶中只有一個(gè)連接記錄,那么就可以直接找到對(duì)應(yīng)的連接,這就像是在書(shū)架上只有一本書(shū)時(shí),直接就能拿到想要的書(shū) 。但如果哈希桶中存在多個(gè)連接記錄(即發(fā)生了哈希沖突),系統(tǒng)就會(huì)沿著鏈表依次查找,對(duì)比每個(gè)連接記錄的元組信息與數(shù)據(jù)包的元組信息是否匹配,這就像是在書(shū)架上有很多本書(shū)時(shí),需要逐一查看書(shū)籍的內(nèi)容來(lái)找到目標(biāo)書(shū)籍 。
匹配策略的實(shí)現(xiàn)主要基于數(shù)據(jù)包的元組信息 。系統(tǒng)會(huì)嚴(yán)格對(duì)比數(shù)據(jù)包的源 IP、目的 IP、源端口、目的端口和協(xié)議類(lèi)型等元組信息與連接跟蹤表中記錄的元組信息 。只有當(dāng)這些信息完全一致時(shí),才認(rèn)為找到了匹配的連接 。在一個(gè)企業(yè)網(wǎng)絡(luò)中,當(dāng)內(nèi)部員工的電腦向外部服務(wù)器發(fā)送數(shù)據(jù)包時(shí),系統(tǒng)會(huì)根據(jù)數(shù)據(jù)包的元組信息在連接跟蹤表中進(jìn)行查找和匹配 。如果找到了匹配的連接,就說(shuō)明這是一個(gè)已建立連接的后續(xù)數(shù)據(jù)包,系統(tǒng)會(huì)根據(jù)連接的狀態(tài)進(jìn)行相應(yīng)的處理,比如更新連接的統(tǒng)計(jì)信息等 。如果沒(méi)有找到匹配的連接,就說(shuō)明這可能是一個(gè)新的連接請(qǐng)求,系統(tǒng)會(huì)按照新連接的處理流程進(jìn)行處理,比如創(chuàng)建新的連接跟蹤記錄 。
4.4 連接生命周期管理
連接的生命周期管理就像是一場(chǎng)精心安排的 “旅程”,涵蓋了連接的創(chuàng)建、確認(rèn)、更新和刪除等多個(gè)重要階段,每個(gè)階段都有其特定的內(nèi)核處理邏輯和狀態(tài)轉(zhuǎn)換 。
當(dāng)一個(gè)新的連接請(qǐng)求到來(lái)時(shí),比如 TCP 協(xié)議中的 SYN 包到達(dá),內(nèi)核會(huì)創(chuàng)建一個(gè)新的連接跟蹤記錄 。內(nèi)核會(huì)從數(shù)據(jù)包中提取出五元組信息,然后為這個(gè)連接分配一個(gè) nf_conn 結(jié)構(gòu)體實(shí)例 。在這個(gè)結(jié)構(gòu)體中,會(huì)初始化連接的兩個(gè)方向的元組信息,將連接狀態(tài)設(shè)置為初始狀態(tài),比如 TCP 連接的 SYN_SENT 狀態(tài) 。同時(shí),還會(huì)將這個(gè)連接的原始方向的 tuple_hash 添加到 unconfirmed 鏈表中,等待進(jìn)一步的確認(rèn) 。
當(dāng)連接收到對(duì)方的確認(rèn)信息時(shí),比如 TCP 協(xié)議中的 SYN + ACK 包到達(dá),內(nèi)核會(huì)對(duì)連接進(jìn)行確認(rèn)操作 。它會(huì)將連接從 unconfirmed 鏈表中移除,并將其添加到連接跟蹤表的哈希表中,標(biāo)志著連接已被確認(rèn) 。同時(shí),會(huì)更新連接的狀態(tài),將 TCP 連接的狀態(tài)更新為 ESTABLISHED,表明連接已成功建立 。
在連接的數(shù)據(jù)傳輸過(guò)程中,內(nèi)核會(huì)持續(xù)關(guān)注連接的狀態(tài)變化,并及時(shí)更新連接的相關(guān)信息 。當(dāng)接收到屬于已有連接的數(shù)據(jù)包時(shí),內(nèi)核會(huì)更新連接記錄中的收發(fā)包數(shù)、字節(jié)數(shù)等統(tǒng)計(jì)信息 。如果連接的狀態(tài)發(fā)生了變化,比如 TCP 連接進(jìn)入了 FIN_WAIT_1 狀態(tài),內(nèi)核也會(huì)及時(shí)更新連接的狀態(tài)信息 。
當(dāng)連接結(jié)束時(shí),無(wú)論是正常結(jié)束還是異常結(jié)束,內(nèi)核都會(huì)執(zhí)行刪除操作 。對(duì)于正常結(jié)束的連接,比如 TCP 連接完成了四次揮手過(guò)程,內(nèi)核會(huì)將連接從連接跟蹤表的哈希表中移除,并釋放 nf_conn 結(jié)構(gòu)體實(shí)例所占用的內(nèi)存空間 。對(duì)于異常結(jié)束的連接,比如連接超時(shí),內(nèi)核也會(huì)同樣進(jìn)行刪除操作,確保連接跟蹤表的高效運(yùn)行,為新的連接記錄騰出空間 。 。




























