1. 程式人生 > >(六)洞悉linux下的Netfilter&iptables:如何理解連線跟蹤機制?【中】

(六)洞悉linux下的Netfilter&iptables:如何理解連線跟蹤機制?【中】

如果找到了該tuple所屬於的tuple_hash連結串列,則返回該連結串列的地址;如果沒找到,表明該型別的資料包沒有被跟蹤,那麼我們首先必須建立一個ip_conntrack{}結構的例項,即建立一個連線記錄項。

然後,計算tuple的應答repl_tuple,對這個ip_conntrack{}物件做一番必要的初始化後,其中還包括,將我們計算出來的tuple和其反向tuple的地址賦給連線跟蹤ip_conntrack裡的tuplehash[IP_CT_DIR_ORIGINAL]和tuplehash[IP_CT_DIR_REPLY]。

最後,把ip_conntrack->tuplehash[IP_CT_DIR_ORIGINAL]的地址返回。這恰恰是一條連線跟蹤記錄初始方向連結串列的地址。Netfilter中有一條連結串列unconfirmed

,裡面儲存了所有目前還沒有收到過確認報文的連線跟蹤記錄,然後我們的ip_conntrack->tuplehash[IP_CT_DIR_ORIGINAL]就會被新增到unconfirmed連結串列中。

第四步:呼叫協議所提供的packet()函式,該函式承擔著最後向Netfilter框架返回值的使命,如果資料包不是連線中有效的部分,返回-1,否則返回NF_ACCEPT。也就是說,如果你要為自己的協議開發連線跟蹤功能,那麼在例項化一個ip_conntrack_protocol{}物件時必須對該結構中的packet()函式做仔細設計。

雖然我不逐行解釋程式碼,只分析原理,但有一句程式碼還是要提一下。

resolve_normal_ct()函式中有一行ct = tuplehash_to_ctrack(h)的程式碼,參見原始碼。其中h是已存在的或新建立的ip_conntrack_tuple_hash{}物件,ct是ip_conntrack{}型別的指標。不要誤以為這一句程式碼的是在建立ct物件,因為建立的工作在init_conntrack()函式中已經完成。本行程式碼的意思是根據ip_conntrack{}結構體中tuplehash[IP_CT_DIR_ORIGINAL]成員的地址,反過來計算其所在的結構體ip_conntrack{}物件的首地址,請大家注意。

大家也看到ip_conntrack_in()函式只是建立了用於儲存連線跟蹤記錄的ip_conntrack{}物件而已,並完成了對其相關屬性的填充和狀態的設定等工作。簡單來說,我們這個資料包目前已經拿到連線跟蹤系統辦法的“綠卡”ip_conntrack{}了,但是還沒有蓋章生效。

ip_conntrack_help()

大家只要把我前面關於鉤子函式在五個HOOK點所掛載情況的那張圖記住,就明白ip_conntrack_help()函式在其所註冊的hook點的位置了。當我們這個資料包所屬的協議在其提供的連線跟蹤模組時已經提供了ip_conntrack_helper{}模組,或是別人針對我們這種協議型別的資料包提供了擴充套件的功能模組,那麼接下來的事兒就很簡單了:

首先,判斷資料包是否拿到“綠卡”,即連線跟蹤是否為該型別協議的包生成了連線跟蹤記錄項ip_conntrack{};

其次,該資料包所屬的連線狀態不屬於一個已建連線的相關連線,在其響應方向。

兩個條件都成立,就用該helper模組提供的help()函式去處理我們這個資料包skb。最後,這個help()函式也必須向Netfilter框架返回NF_ACCEPT或NF_DROP等值。任意一個條件不成立則ip_conntrack_help()函式直接返回NF_ACCEPT,我們這個資料包繼續傳輸。

ip_confirm()

    該函式是我們離開Netfilter時遇到的最後一個傢伙了,如果我們這個資料包已經拿到了“綠卡”ip_conntrack{},並且我們這個資料包所屬的連線還沒收到過確認報文,並且該連線還未失效。然後,我們這個ip_confirm()函式要做的事就是:

    拿到連線跟蹤為該資料包生成ip_conntrack{}物件,根據連線“來”、“去”方向tuple計算其hash值,然後在連線跟蹤表ip_conntrack_hash[]見上圖中查詢是否已存在該tuple。如果已存在,該函式最後返回NF_DROP;如果不存在,則將該連線“來”、“去”方向tuple插入到連線跟蹤表ip_conntrack_hash[]裡,並向Netfilter框架返回NF_ACCEPT。之所以要再最後才將連線跟蹤記錄加入連線跟蹤表是考慮到資料包可能被過濾掉。

    至此,我們本次旅行就圓滿結束了。這裡我們只分析了轉發報文的情況。傳送給本機的報文流程與此一致,而對於所有從本機發送出去的報文,其流程上唯一的區別就是在呼叫ip_conntrack_in()的地方換成了ip_conntrack_local()函式。前面說過,ip_conntrack_local()裡面其實呼叫的還是ip_conntrack_in()。ip_conntrack_local()裡只是增加了一個特性:那就是對於從本機發出的小資料包不進行連線跟蹤。

    未完,待續…