(五)洞悉linux下的Netfilter&iptables:如何理解鏈接跟蹤機制?【上】

如何理解Netfilter中的鏈接跟蹤機制?linux

    本篇我打算以一個問句開頭,由於在知識探索的道路上只有多問而後充分調動起思考的機器才能讓本身走得更遠。鏈接跟蹤定義很簡單:用來記錄和跟蹤鏈接的狀態。數組

問:爲何又須要鏈接跟蹤功能呢?服務器

答:由於它是狀態防火牆NAT的實現基礎。網絡

OK,算是明白了。Neftiler爲了實現基於數據鏈接狀態偵測的狀態防火牆功能和NAT地址轉換功能纔開發出了鏈接跟蹤這套機制。那就意思是說:若是編譯內核時開啓了鏈接跟蹤選項,那麼Linux系統就會爲它收到的每一個數據包維持一個鏈接狀態用於記錄這條數據鏈接的狀態。接下來咱們就來研究一下Netfilter的鏈接跟蹤的設計思想和實現方式。數據結構

    以前有一副圖,咱們能夠很明確的看到:用於實現鏈接跟蹤入口 hook 函數以較高的優先級分別被註冊到了 netfitler NF_IP_PRE_ROUTING NF_IP_LOCAL_OUT 兩個 hook 點上;用於實現鏈接跟蹤出口 hook 函數以很是低的優先級分別被註冊到了 netfilter NF_IP_LOCAL_IN NF_IP_POST_ROUTING 兩個 hook 點上。

其實PRE_ROUTINGLOCAL_OUT點能夠看做是整個netfilter的入口,而POST_ROUTINGLOCAL_IN能夠看做是其出口。在只考慮鏈接跟蹤的狀況下,一個數據包無外乎有如下三種流程能夠走:框架

1、發送給本機的數據包函數

流程:PRE_ROUTING----LOCAL_IN---本地進程佈局

2、須要本機轉發的數據包this

流程:PRE_ROUTING---FORWARD---POST_ROUTING---外出spa

3、從本機發出的數據包

流程:LOCAL_OUT----POST_ROUTING---外出


咱們都知道在INET層用於表示數據包的結構是大名鼎鼎的sk_buff{}(後面簡稱skb),若是你不幸的沒據說過這個東東,那麼我強烈的建議你先補一下網絡協議棧的基礎知識再繼續閱讀這篇文章。在skb中有個成員指針nfct,類型是struct nf_conntrack{},該結構定義在include/linux/skbuff.h文件中。該結構記錄了鏈接記錄被公開應用的計數,也方便其餘地方對鏈接跟蹤的引用。鏈接跟蹤在實際應用中通常都經過強制類型轉換將nfct轉換成指向ip_conntrack{}類型(定義在include/linux/netfilter_ipv4/ip_conntrack.h)來獲取一個數據包所屬鏈接跟蹤的狀態信息的。即:Neftilter框架用ip_conntrack{}來記錄一個數據包與其鏈接的狀態關係

同時在include/linux/netfilter_ipv4/ip_conntrack.h文件中還提供了一個很是有用的接口:struct ip_conntrack *ip_conntrack_get(skb, ctinfo)用於獲取一個skbnfct指針,從而得知該數據包的鏈接狀態和該鏈接狀態的相關信息ctinfo。從鏈接跟蹤的角度來看,這個ctinfo表示了每一個數據包的幾種鏈接狀態:

l  IP_CT_ESTABLISHED

Packet是一個已建鏈接的一部分,在其初始方向。

l  IP_CT_RELATED

Packet屬於一個已建鏈接的相關鏈接,在其初始方向。

l  IP_CT_NEW

Packet試圖創建新的鏈接

l  IP_CT_ESTABLISHED+IP_CT_IS_REPLY

Packet是一個已建鏈接的一部分,在其響應方向。

l  IP_CT_RELATED+IP_CT_IS_REPLY

Packet屬於一個已建鏈接的相關鏈接,在其響應方向。

    在鏈接跟蹤內部,收到的每一個skb首先被轉換成一個ip_conntrack_tuple{}結構,也就是說ip_conntrack_tuple{}結構纔是鏈接跟蹤系統所「認識」的數據包。那麼skbip_conntrack_tuple{}結構之間是如何轉換的呢?這個問題沒有一個統一的答案,與具體的協議息息相關。例如,對於TCP/UDP協議,根據「源、目的IP+源、目的端口」再加序列號就能夠惟一的標識一個數據包了;對於ICMP協議,根據「源、目的IP+類型+代號」再加序列號才能夠惟一肯定一個ICMP報文等等。對於諸如像FTP這種應用層的「活動」協議來講狀況就更復雜了。本文不試圖去分析某種具體協議的鏈接跟蹤實現,而是探究鏈接跟蹤的設計原理和其工做流程,使你們掌握鏈接跟蹤的精髓。由於如今Linux內核更新的太快的都到3.4.x,變化之大啊。就算是2.6.222.6.21在鏈接跟蹤這塊仍是有些區別呢。一旦你們理解了鏈接跟蹤的設計思想,掌握了其神韻,它再怎麼也萬變不離其宗,再看具體的代碼實現時就不會犯迷糊了。俗話說「授人一魚,不如授人一漁」,咱們教給你們的是方法。有了方法再加上本身的勤學苦練,那就成了技能,最後可使得你們在爲本身的協議開發鏈接跟蹤功能時內心有數。這也是我寫這個系列博文的初衷和目的。與君共勉。

在開始分析鏈接跟蹤以前,咱們仍是站在統帥的角度來俯視一下整個鏈接跟蹤的佈局。這裏我先用比較粗略的精簡流程圖爲你們作個展現,目的是方便你們理解,好入門。固然,個人理解可能還有不太準確的地方,還請大牛們幫小弟指正。

    我仍是重申一下:鏈接跟蹤分入口和出口兩個點。謹記:入口時建立鏈接跟蹤記錄,出口時將該記錄加入到鏈接跟蹤表中。咱們分別來看看。

入口:

整個入口的流程簡述以下:對於每一個到來的skb,鏈接跟蹤都將其轉換成一個tuple結構,而後用該tuple去查鏈接跟蹤表。若是該類型的數據包沒有被跟蹤過,將爲其在鏈接跟蹤的hash表裏創建一個鏈接記錄項,對於已經跟蹤過了的數據包則不用此操做。緊接着,調用該報文所屬協議的鏈接跟蹤模塊的所提供的packet()回調函數,最後根據狀態改變鏈接跟蹤記錄的狀態。

出口:

整個出口的流程簡述以下:對於每一個即將離開Netfilter框架的數據包,若是用於處理該協議類型報文的鏈接跟蹤模塊提供了helper函數,那麼該數據包首先會被helper函數處理,而後纔去判斷,若是該報文已經被跟蹤過了,那麼其所屬鏈接的狀態,決定該包是該被丟棄、或是返回協議棧繼續傳輸,又或者將其加入到鏈接跟蹤表中。


鏈接跟蹤的協議管理:

    咱們前面曾說過,不一樣協議其鏈接跟蹤的實現是不相同的。每種協議若是要開發本身的鏈接跟蹤模塊,那麼它首先必須實例化一個ip_conntrack_protocol{}結構體類型的變量,對其進行必要的填充,而後調用ip_conntrack_protocol_register()函數將該結構進行註冊,其實就是根據協議類型將其設置到全局數組ip_ct_protos[]中的相應位置上。

     ip_ct_protos變量裏保存鏈接跟蹤系統當前能夠處理的全部協議,協議號做爲數組惟一的下標,以下圖所示。

    結構體ip_conntrack_protocol{}中的每一個成員,內核源碼已經作了很詳細的註釋了,這裏我就不一一解釋了,在實際開發過程當中咱們用到了哪些函數再具體分析。


  鏈接跟蹤的輔助模塊:

    Netfilter的鏈接跟蹤爲咱們提供了一個很是有用的功能模塊:helper。該模塊可使咱們以很小的代價來完成對鏈接跟蹤功能的擴展。這種應用場景需求通常是,當一個數據包即將離開Netfilter框架以前,咱們能夠對數據包再作一些最後的處理。從前面的圖咱們也能夠看出來,helper模塊以較低優先級被註冊到了NetfilterLOCAL_OUTPOST_ROUTING兩個hook點上。

每個輔助模塊都是一個ip_conntrack_helper{}結構體類型的對象。也就是說,若是你所開發的協議須要鏈接跟蹤輔助模塊來完成一些工做的話,那麼你必須也去實例化一個ip_conntrack_helper{}對象,對其進行填充,最後調用ip_conntrack_helper_register{}函數將你的輔助模塊註冊到全局變量helpers裏,該結構是個雙向鏈表,裏面保存了當前已經註冊到鏈接跟蹤系統裏的全部協議的輔助模塊。

全局helpers變量的定義和初始化在net/netfilter/nf_conntrack_helper.c文件中完成的。

最後,咱們的helpers變量所表示的雙向鏈表通常都是像下圖所示的這樣子:

由此咱們基本上就能夠知道,註冊在Netfilter框架裏LOCAL_OUTPOST_ROUTING兩個hook點上ip_conntrack_help()回調函數所作的事情基本也就很清晰了:那就是經過依次遍歷helpers鏈表,而後調用每一個ip_conntrack_helper{}對象的help()函數。


指望鏈接:

    Netfilter的鏈接跟蹤爲支持諸如FTP這樣的「活動」鏈接提供了一個叫作「指望鏈接」的機制。咱們都知道FTP協議服務端用21端口作命令傳輸通道,主動模式下服務器用20端口作數據傳輸通道;被動模式下服務器隨機開一個高於1024的端口,而後客戶端來鏈接這個端口開始數據傳輸。也就是說不管主、被動,都須要兩條鏈接:命令通道的鏈接和數據通道的鏈接。鏈接跟蹤在處理這種應用場景時提出了一個「指望鏈接」的概念,即一條數據鏈接和另一條數據鏈接是相關的,而後對於這種有「相關性」的鏈接給出本身的解決方案。咱們說過,本文不打算分析某種具體協議鏈接跟蹤的實現。接下來咱們就來談談指望鏈接。

    每條指望鏈接都用一個ip_conntrack_expect{}結構體類型的對象來表示,全部的指望鏈接存儲在由全局變量ip_conntrack_expect_list所指向的雙向鏈表中,該鏈表的結構通常以下:

         結構體ip_conntrack_expect{}中的成員及其意義在內核源碼中也作了充分的註釋,這裏我就不逐一介紹了,等到須要的時候再詳細探討。


鏈接跟蹤表:

    說了半天終於到咱們鏈接跟蹤表拋頭露面的時候了。鏈接跟蹤表是一個用於記錄全部數據包鏈接信息的hash散列表,其實鏈接跟蹤表就是一個以數據包的hash值組成的一個雙向循環鏈表數組,每條鏈表中的每一個節點都是ip_conntrack_tuple_hash{}類型的一個對象。鏈接跟蹤表是由一個全局的雙向鏈表指針變量ip_conntrack_hash[]來表示。爲了使咱們更容易理解ip_conntrack_hash[]這個雙向循環鏈表的數組,咱們將前面提到的幾個重要的目前還未介紹的結構ip_conntrack_tuple{}ip_conntrack{}ip_conntrack_tuple_hash{}分別介紹一下。

    咱們能夠看到ip_conntrack_tuple_hash{}僅僅是對ip_conntrack_tuple{}的封裝而已,將其組織成了一個雙向鏈表結構。所以,在理解層面上咱們能夠認爲它們是同一個東西。

在分析ip_conntrack{}結構時,咱們將前面全部和其相關的數據結構都列出來,方便你們對其理解和記憶。

該圖但是說是鏈接跟蹤部分的數據核心,接下來咱們來詳細說說ip_conntrack{}結構中相關成員的意義。

l  ct_general:該結構記錄了鏈接記錄被公開應用的計數,也方便其餘地方對鏈接跟蹤的引用。

l  status:數據包鏈接的狀態,是一個比特位圖。

l  timeout:不一樣協議的每條鏈接都有默認超時時間,若是在超過了該時間且沒有屬於某條鏈接的數據包來刷新該鏈接跟蹤記錄,那麼會調用這種協議類型提供的超時函數。

l  counters:該成員只有在編譯內核時打開了CONFIG_IP_NF_CT_ACCT開完纔會存在,表明某條鏈接所記錄的字節數和包數。

l  master:該成員指向另一個ip_conntrack{}。通常用於指望鏈接場景。即若是當前鏈接是另外某條鏈接的指望鏈接的話,那麼該成員就指向那條咱們所屬的主鏈接。

l  helper:若是某種協議提供了擴展模塊,就經過該成員來調用擴展模塊的功能函數。

l  proto:該結構是ip_conntrack_proto{}類型,和咱們前面曾介紹過的用於存儲不一樣協議鏈接跟蹤的ip_conntrack_protocol{}結構不要混淆了。前者是個枚舉類型,後者是個結構體類型。這裏的proto表示不一樣協議爲了實現其自身的鏈接跟蹤功能而須要的一些額外參數信息。目前這個枚舉類型以下:

   若是未來你的協議在實現鏈接跟蹤時也須要一些額外數據,那麼能夠對該結構進行擴充。

l  help:該成員表明不一樣的應用爲了實現其自身的鏈接跟蹤功能而須要的一些額外參數信息,也是個枚舉類型的ip_conntrack_help{}結構,和咱們前面剛介紹過的結構體類型ip_conntrack_helpers{}容易混淆。ip_conntrack_proto{}是爲協議層須要而存在的,而ip_conntrack_help{}是爲應用層須要而存在

l  tuplehash:該結構是個ip_conntrack_tuple_hash{}類型的數組,大小爲2tuplehash[0]表示一條數據流「初始」方向上的鏈接狀況,tuplehash[1]表示該數據流「應答」方向的響應狀況,見上圖所示。

    到目前爲止,咱們已經瞭解了鏈接跟蹤設計思想和其工做機制:鏈接跟蹤是Netfilter提供的一套基礎框架,不一樣的協議能夠根據其自身協議的特殊性在鏈接跟蹤機制的指導和約束下來開發本協議的鏈接跟蹤功能,最後將其交給鏈接跟蹤機制來統一管理。

    未完,待續

相關文章
相關標籤/搜索