如何理解Netfilter中的連接跟蹤機(jī)制? 本篇我打算以一個問句開頭,因為在知識探索的道路上只有多問然后充分調(diào)動起思考的機(jī)器才能讓自己走得更遠(yuǎn)。連接跟蹤定義很簡單:用來記錄和跟蹤連接的狀態(tài)。 問:為什么又需要連接跟蹤功能呢? 答:因為它是狀態(tài)防火墻和NAT的實現(xiàn)基礎(chǔ)。 OK,算是明白了。Neftiler為了實現(xiàn)基于數(shù)據(jù)連接狀態(tài)偵測的狀態(tài)防火墻功能和NAT地址轉(zhuǎn)換功能才開發(fā)出了連接跟蹤這套機(jī)制。那就意思是說:如果編譯內(nèi)核時開啟了連接跟蹤選項,那么Linux系統(tǒng)就會為它收到的每個數(shù)據(jù)包維持一個連接狀態(tài)用于記錄這條數(shù)據(jù)連接的狀態(tài)。接下來我們就來研究一下Netfilter的連接跟蹤的設(shè)計思想和實現(xiàn)方式。 之前有一副圖,我們可以很明確的看到:用于實現(xiàn)連接跟蹤入口的hook函數(shù)以較高的優(yōu)先級分別被注冊到了netfitler的NF_IP_PRE_ROUTING和NF_IP_LOCAL_OUT兩個hook點(diǎn)上;用于實現(xiàn)連接跟蹤出口的hook函數(shù)以非常低的優(yōu)先級分別被注冊到了netfilter的NF_IP_LOCAL_IN和NF_IP_POST_ROUTING兩個hook點(diǎn)上。其實PRE_ROUTING和LOCAL_OUT點(diǎn)可以看作是整個netfilter的入口,而POST_ROUTING和LOCAL_IN可以看作是其出口。在只考慮連接跟蹤的情況下,一個數(shù)據(jù)包無外乎有以下三種流程可以走: 一、發(fā)送給本機(jī)的數(shù)據(jù)包
流程:PRE_ROUTING----LOCAL_IN---本地進(jìn)程 二、需要本機(jī)轉(zhuǎn)發(fā)的數(shù)據(jù)包 流程:PRE_ROUTING---FORWARD---POST_ROUTING---外出 三、從本機(jī)發(fā)出的數(shù)據(jù)包 流程:LOCAL_OUT----POST_ROUTING---外出
我們都知道在INET層用于表示數(shù)據(jù)包的結(jié)構(gòu)是大名鼎鼎的sk_buff{}(后面簡稱skb),如果你不幸的沒聽說過這個東東,那么我強(qiáng)烈的建議你先補(bǔ)一下網(wǎng)絡(luò)協(xié)議棧的基礎(chǔ)知識再繼續(xù)閱讀這篇文章。在skb中有個成員指針nfct,類型是struct nf_conntrack{},該結(jié)構(gòu)定義在include/linux/skbuff.h文件中。該結(jié)構(gòu)記錄了連接記錄被公開應(yīng)用的計數(shù),也方便其他地方對連接跟蹤的引用。連接跟蹤在實際應(yīng)用中一般都通過強(qiáng)制類型轉(zhuǎn)換將nfct轉(zhuǎn)換成指向ip_conntrack{}類型(定義在include/linux/netfilter_ipv4/ip_conntrack.h里)來獲取一個數(shù)據(jù)包所屬連接跟蹤的狀態(tài)信息的。即:Neftilter框架用ip_conntrack{}來記錄一個數(shù)據(jù)包與其連接的狀態(tài)關(guān)系。 同時在include/linux/netfilter_ipv4/ip_conntrack.h文件中還提供了一個非常有用的接口:struct ip_conntrack *ip_conntrack_get(skb, ctinfo)用于獲取一個skb的nfct指針,從而得知該數(shù)據(jù)包的連接狀態(tài)和該連接狀態(tài)的相關(guān)信息ctinfo。從連接跟蹤的角度來看,這個ctinfo表示了每個數(shù)據(jù)包的幾種連接狀態(tài): l IP_CT_ESTABLISHED Packet是一個已建連接的一部分,在其初始方向。 l IP_CT_RELATED Packet屬于一個已建連接的相關(guān)連接,在其初始方向。 l IP_CT_NEW Packet試圖建立新的連接 l IP_CT_ESTABLISHED+IP_CT_IS_REPLY Packet是一個已建連接的一部分,在其響應(yīng)方向。 l IP_CT_RELATED+IP_CT_IS_REPLY Packet屬于一個已建連接的相關(guān)連接,在其響應(yīng)方向。 在連接跟蹤內(nèi)部,收到的每個skb首先被轉(zhuǎn)換成一個ip_conntrack_tuple{}結(jié)構(gòu),也就是說ip_conntrack_tuple{}結(jié)構(gòu)才是連接跟蹤系統(tǒng)所“認(rèn)識”的數(shù)據(jù)包。那么skb和ip_conntrack_tuple{}結(jié)構(gòu)之間是如何轉(zhuǎn)換的呢?這個問題沒有一個統(tǒng)一的答案,與具體的協(xié)議息息相關(guān)。例如,對于TCP/UDP協(xié)議,根據(jù)“源、目的IP+源、目的端口”再加序列號就可以唯一的標(biāo)識一個數(shù)據(jù)包了;對于ICMP協(xié)議,根據(jù)“源、目的IP+類型+代號”再加序列號才可以唯一確定一個ICMP報文等等。對于諸如像FTP這種應(yīng)用層的“活動”協(xié)議來說情況就更復(fù)雜了。本文不試圖去分析某種具體協(xié)議的連接跟蹤實現(xiàn),而是探究連接跟蹤的設(shè)計原理和其工作流程,使大家掌握連接跟蹤的精髓。因為現(xiàn)在Linux內(nèi)核更新的太快的都到3.4.x,變化之大啊。就算是2.6.22和2.6.21在連接跟蹤這塊還是有些區(qū)別呢。一旦大家理解了連接跟蹤的設(shè)計思想,掌握了其神韻,它再怎么也萬變不離其宗,再看具體的代碼實現(xiàn)時就不會犯迷糊了。俗話說“授人一魚,不如授人一漁”,我們教給大家的是方法。有了方法再加上自己的勤學(xué)苦練,那就成了技能,最后可以使得大家在為自己的協(xié)議開發(fā)連接跟蹤功能時心里有數(shù)。這也是我寫這個系列博文的初衷和目的。與君共勉。 在開始分析連接跟蹤之前,我們還是站在統(tǒng)帥的角度來俯視一下整個連接跟蹤的布局。這里我先用比較粗略的精簡流程圖為大家做個展示,目的是方便大家理解,好入門。當(dāng)然,我的理解可能還有不太準(zhǔn)確的地方,還請大牛們幫小弟指正。 我還是重申一下:連接跟蹤分入口和出口兩個點(diǎn)。謹(jǐn)記:入口時創(chuàng)建連接跟蹤記錄,出口時將該記錄加入到連接跟蹤表中。我們分別來看看。
入口:
整個入口的流程簡述如下:對于每個到來的skb,連接跟蹤都將其轉(zhuǎn)換成一個tuple結(jié)構(gòu),然后用該tuple去查連接跟蹤表。如果該類型的數(shù)據(jù)包沒有被跟蹤過,將為其在連接跟蹤的hash表里建立一個連接記錄項,對于已經(jīng)跟蹤過了的數(shù)據(jù)包則不用此操作。緊接著,調(diào)用該報文所屬協(xié)議的連接跟蹤模塊的所提供的packet()回調(diào)函數(shù),最后根據(jù)狀態(tài)改變連接跟蹤記錄的狀態(tài)。 出口:
整個出口的流程簡述如下:對于每個即將離開Netfilter框架的數(shù)據(jù)包,如果用于處理該協(xié)議類型報文的連接跟蹤模塊提供了helper函數(shù),那么該數(shù)據(jù)包首先會被helper函數(shù)處理,然后才去判斷,如果該報文已經(jīng)被跟蹤過了,那么其所屬連接的狀態(tài),決定該包是該被丟棄、或是返回協(xié)議棧繼續(xù)傳輸,又或者將其加入到連接跟蹤表中。
連接跟蹤的協(xié)議管理: 我們前面曾說過,不同協(xié)議其連接跟蹤的實現(xiàn)是不相同的。每種協(xié)議如果要開發(fā)自己的連接跟蹤模塊,那么它首先必須實例化一個ip_conntrack_protocol{}結(jié)構(gòu)體類型的變量,對其進(jìn)行必要的填充,然后調(diào)用ip_conntrack_protocol_register()函數(shù)將該結(jié)構(gòu)進(jìn)行注冊,其實就是根據(jù)協(xié)議類型將其設(shè)置到全局?jǐn)?shù)組ip_ct_protos[]中的相應(yīng)位置上。 ip_ct_protos變量里保存連接跟蹤系統(tǒng)當(dāng)前可以處理的所有協(xié)議,協(xié)議號作為數(shù)組唯一的下標(biāo),如下圖所示。 結(jié)構(gòu)體ip_conntrack_protocol{}中的每個成員,內(nèi)核源碼已經(jīng)做了很詳細(xì)的注釋了,這里我就不一一解釋了,在實際開發(fā)過程中我們用到了哪些函數(shù)再具體分析。
連接跟蹤的輔助模塊: Netfilter的連接跟蹤為我們提供了一個非常有用的功能模塊:helper。該模塊可以使我們以很小的代價來完成對連接跟蹤功能的擴(kuò)展。這種應(yīng)用場景需求一般是,當(dāng)一個數(shù)據(jù)包即將離開Netfilter框架之前,我們可以對數(shù)據(jù)包再做一些最后的處理。從前面的圖我們也可以看出來,helper模塊以較低優(yōu)先級被注冊到了Netfilter的LOCAL_OUT和POST_ROUTING兩個hook點(diǎn)上。 每一個輔助模塊都是一個ip_conntrack_helper{}結(jié)構(gòu)體類型的對象。也就是說,如果你所開發(fā)的協(xié)議需要連接跟蹤輔助模塊來完成一些工作的話,那么你必須也去實例化一個ip_conntrack_helper{}對象,對其進(jìn)行填充,最后調(diào)用ip_conntrack_helper_register{}函數(shù)將你的輔助模塊注冊到全局變量helpers里,該結(jié)構(gòu)是個雙向鏈表,里面保存了當(dāng)前已經(jīng)注冊到連接跟蹤系統(tǒng)里的所有協(xié)議的輔助模塊。 全局helpers變量的定義和初始化在net/netfilter/nf_conntrack_helper.c文件中完成的。 最后,我們的helpers變量所表示的雙向鏈表一般都是像下圖所示的這樣子: 由此我們基本上就可以知道,注冊在Netfilter框架里LOCAL_OUT和POST_ROUTING兩個hook點(diǎn)上ip_conntrack_help()回調(diào)函數(shù)所做的事情基本也就很清晰了:那就是通過依次遍歷helpers鏈表,然后調(diào)用每個ip_conntrack_helper{}對象的help()函數(shù)。
期望連接: Netfilter的連接跟蹤為支持諸如FTP這樣的“活動”連接提供了一個叫做“期望連接”的機(jī)制。我們都知道FTP協(xié)議服務(wù)端用21端口做命令傳輸通道,主動模式下服務(wù)器用20端口做數(shù)據(jù)傳輸通道;被動模式下服務(wù)器隨機(jī)開一個高于1024的端口,然后客戶端來連接這個端口開始數(shù)據(jù)傳輸。也就是說無論主、被動,都需要兩條連接:命令通道的連接和數(shù)據(jù)通道的連接。連接跟蹤在處理這種應(yīng)用場景時提出了一個“期望連接”的概念,即一條數(shù)據(jù)連接和另外一條數(shù)據(jù)連接是相關(guān)的,然后對于這種有“相關(guān)性”的連接給出自己的解決方案。我們說過,本文不打算分析某種具體協(xié)議連接跟蹤的實現(xiàn)。接下來我們就來談?wù)勂谕B接。 每條期望連接都用一個ip_conntrack_expect{}結(jié)構(gòu)體類型的對象來表示,所有的期望連接存儲在由全局變量ip_conntrack_expect_list所指向的雙向鏈表中,該鏈表的結(jié)構(gòu)一般如下: 結(jié)構(gòu)體ip_conntrack_expect{}中的成員及其意義在內(nèi)核源碼中也做了充分的注釋,這里我就不逐一介紹了,等到需要的時候再詳細(xì)探討。
連接跟蹤表: 說了半天終于到我們連接跟蹤表拋頭露面的時候了。連接跟蹤表是一個用于記錄所有數(shù)據(jù)包連接信息的hash散列表,其實連接跟蹤表就是一個以數(shù)據(jù)包的hash值組成的一個雙向循環(huán)鏈表數(shù)組,每條鏈表中的每個節(jié)點(diǎn)都是ip_conntrack_tuple_hash{}類型的一個對象。連接跟蹤表是由一個全局的雙向鏈表指針變量ip_conntrack_hash[]來表示。為了使我們更容易理解ip_conntrack_hash[]這個雙向循環(huán)鏈表的數(shù)組,我們將前面提到的幾個重要的目前還未介紹的結(jié)構(gòu)ip_conntrack_tuple{}、ip_conntrack{}和ip_conntrack_tuple_hash{}分別介紹一下。
我們可以看到ip_conntrack_tuple_hash{}僅僅是對ip_conntrack_tuple{}的封裝而已,將其組織成了一個雙向鏈表結(jié)構(gòu)。因此,在理解層面上我們可以認(rèn)為它們是同一個東西。 在分析ip_conntrack{}結(jié)構(gòu)時,我們將前面所有和其相關(guān)的數(shù)據(jù)結(jié)構(gòu)都列出來,方便大家對其理解和記憶。
該圖可是說是連接跟蹤部分的數(shù)據(jù)核心,接下來我們來詳細(xì)說說ip_conntrack{}結(jié)構(gòu)中相關(guān)成員的意義。 l ct_general:該結(jié)構(gòu)記錄了連接記錄被公開應(yīng)用的計數(shù),也方便其他地方對連接跟蹤的引用。 l status:數(shù)據(jù)包連接的狀態(tài),是一個比特位圖。 l timeout:不同協(xié)議的每條連接都有默認(rèn)超時時間,如果在超過了該時間且沒有屬于某條連接的數(shù)據(jù)包來刷新該連接跟蹤記錄,那么會調(diào)用這種協(xié)議類型提供的超時函數(shù)。 l counters:該成員只有在編譯內(nèi)核時打開了CONFIG_IP_NF_CT_ACCT開完才會存在,代表某條連接所記錄的字節(jié)數(shù)和包數(shù)。 l master:該成員指向另外一個ip_conntrack{}。一般用于期望連接場景。即如果當(dāng)前連接是另外某條連接的期望連接的話,那么該成員就指向那條我們所屬的主連接。 l helper:如果某種協(xié)議提供了擴(kuò)展模塊,就通過該成員來調(diào)用擴(kuò)展模塊的功能函數(shù)。 l proto:該結(jié)構(gòu)是ip_conntrack_proto{}類型,和我們前面曾介紹過的用于存儲不同協(xié)議連接跟蹤的ip_conntrack_protocol{}結(jié)構(gòu)不要混淆了。前者是個枚舉類型,后者是個結(jié)構(gòu)體類型。這里的proto表示不同協(xié)議為了實現(xiàn)其自身的連接跟蹤功能而需要的一些額外參數(shù)信息。目前這個枚舉類型如下: 如果將來你的協(xié)議在實現(xiàn)連接跟蹤時也需要一些額外數(shù)據(jù),那么可以對該結(jié)構(gòu)進(jìn)行擴(kuò)充。 l help:該成員代表不同的應(yīng)用為了實現(xiàn)其自身的連接跟蹤功能而需要的一些額外參數(shù)信息,也是個枚舉類型的ip_conntrack_help{}結(jié)構(gòu),和我們前面剛介紹過的結(jié)構(gòu)體類型ip_conntrack_helpers{}容易混淆。ip_conntrack_proto{}是為協(xié)議層需要而存在的,而ip_conntrack_help{}是為應(yīng)用層需要而存在。 l tuplehash:該結(jié)構(gòu)是個ip_conntrack_tuple_hash{}類型的數(shù)組,大小為2。tuplehash[0]表示一條數(shù)據(jù)流“初始”方向上的連接情況,tuplehash[1]表示該數(shù)據(jù)流“應(yīng)答”方向的響應(yīng)情況,見上圖所示。 到目前為止,我們已經(jīng)了解了連接跟蹤設(shè)計思想和其工作機(jī)制:連接跟蹤是Netfilter提供的一套基礎(chǔ)框架,不同的協(xié)議可以根據(jù)其自身協(xié)議的特殊性在連接跟蹤機(jī)制的指導(dǎo)和約束下來開發(fā)本協(xié)議的連接跟蹤功能,最后將其交給連接跟蹤機(jī)制來統(tǒng)一管理。 |
|