核心版本:2.6.12
1.連結跟蹤 conntrack

1.1.netfilter框架
5個鏈:

NF_IP_PRE_ROUTING:資料包進入路由表之前

NF_IP_LOCAL_IN:通過路由表後目的地為本機

NF_IP_FORWARD:通過路由表後,目的地不為本機

NF_IP+LOCAL_OUT:由本機產生,向外轉發

NF_IP_POST_ROUTING:傳送到網絡卡介面之前。

4個表:

filter,nat,mangle,raw,預設表是filter(沒有指定表的時候就是filter表)。

          filter:一般的過濾功能

          nat: 用於nat功能(埠對映,地址對映等)

          mangle: 用於對特定資料包的修改

          raw:優先順序最高,設定raw時一般是為了不再讓iptables做資料包的連結跟蹤處理,提高效能

表和鏈的關係:(raw連線跟蹤在下面單獨說明)

資料包流程: 當資料包到達防火牆時,如果MAC地址符合,就會由核心裡相應的驅動程式接收,然後會經過一系列操作,從而決定是傳送給本地的程式,還是轉發給其他機子,還是其他的什麼。
 

首先來看一個以本地為目的的資料包,它要經過以下步驟才能到達要接收它的程式 :

Step

Table

Chain

Comment

1

線上路上傳輸(比如,Internet)

2

進入介面 (比如,   eth0)

3

mangle

PREROUTING

這個鏈用來mangle資料包,比如改變TOS等

4

nat

PREROUTING

這個鏈主要用來做DNAT。不要在這個鏈做過慮操作,因為某些情況下包會溜過去。

5

路由判斷,比如,包是發往本地的,還是要轉發的。

6

mangle

INPUT

在路由之後,被送往本地程式之前,mangle資料包。

7

filter

INPUT

所有以本地為目的的包都要經過這個鏈,不管它們從哪兒來,對這些包的過濾條件就設在這裡。

8

到達本地程式了(比如,服務程式或客戶程式)

接著看看以以本地為源的資料包,它需要經過下面的步驟才能傳送出去:

Step

Table

Chain

Comment

1

本地程式(比如,服務程式或客戶程式)

2

路由判斷,要使用源地址,外出介面,還有其他一些資訊。

3

mangle

OUTPUT

在這兒可以mangle包。建議不要在這兒做過濾,可能有副作用。

4

nat

OUTPUT

這個鏈對從防火牆本身發出的包進行DNAT操作。

5

filter

OUTPUT

對本地發出的包過濾。

6

mangle

POSTROUTING

這條鏈主要在包DNAT之後(譯者注:作者把這一次DNAT稱作實際的路由,雖然在前面有一次路由。對於本地的包,一旦它被生成,就必須經過路由程式碼的處理,但這個包具體到哪兒去,要由NAT程式碼處理之後才能確定。所以把這稱作實際的路由。),離開本地之前,對包 mangle。有兩種包會經過這裡,防火牆所在機子本身產生的包,還有被轉發的包。

7

nat

POSTROUTING

在這裡做SNAT。但不要在這裡做過濾,因為有副作用,而且有些包是會溜過去的,即使你用了DROP策略。

8

離開介面(比如: eth0)

9

線上路上傳輸(比如,Internet)

最後我們看一個目的是另一個網路中的一臺機子:

Step

Table

Chain

Comment

1

線上路上傳輸(比如,Internet)

2

進入介面(比如, eth0)

3

mangle

PREROUTING

mangle資料包,,比如改變TOS等。

4

nat

PREROUTING

這個鏈主要用來做DNAT。不要在這個鏈做過慮操作,因為某些情況下包會溜過去。稍後會做SNAT。

5

路由判斷,比如,包是發往本地的,還是要轉發的。

6

mangle

FORWARD

包繼續被髮送至mangle表的FORWARD鏈,這是非常特殊的情況才會用到的。在這裡,包被mangle(還記得mangle的意思嗎)。這次mangle發生在最初的路由判斷之後,在最後一次更改包的目的之前(譯者注:就是下面的FORWARD鏈所做的,因其過濾功能,可能會改變一些包的目的地,如丟棄包)。

7

filter

FORWARD

包繼續被髮送至這條FORWARD鏈。只有需要轉發的包才會走到這裡,並且針對這些包的所有過濾也在這裡進行。注意,所有要轉發的包都要經過這裡,不管是外網到內網的還是內網到外網的。在你自己書寫規則時,要考慮到這一點。  

8

mangle

POSTROUTING

這個鏈也是針對一些特殊型別的包(譯者注:參考第6步,我們可以發現,在轉發包時,mangle表的兩個鏈都用在特殊的應用上)。這一步mangle是在所有更改包的目的地址的操作完成之後做的,但這時包還在本地上。

9

nat

POSTROUTING

這個鏈就是用來做SNAT的,當然也包括Masquerade(偽裝)。但不要在這兒做過濾,因為某些包即使不滿足條件也會通過。

10

離開介面(比如: eth0)

11

又線上路上傳輸了(比如,LAN)  

就如你所見的,包要經歷很多步驟,而且它們可以被阻攔在任何一條鏈上,或者是任何有問題的地方。

1.2.連線跟蹤(CONNTRACK),顧名思義,就是跟蹤並且記錄連線狀態。Linux為每一個經過網路堆疊的資料包,生成一個新的連線記錄項 (Connection entry)。此後,所有屬於此連線的資料包都被唯一地分配給這個連線,並標識連線的狀態。連線跟蹤是防火牆模組的狀態檢測的基礎,同時也是地址轉換中實 現SNAT和DNAT的前提。那麼Netfilter又是如何生成連線記錄項的呢?每一個數據,都有“來源”與“目的”主機,發起連線的主機稱為“來源”,響應“來源”的請求的主機即為目的,所謂生成記錄項,就是對每一個這樣的連線的產生、傳輸及終止進行跟蹤記錄。由所有記錄項產生的表,即稱為連線跟蹤表。

1.2.1連線記錄

在 Linux 核心中,連線記錄由ip_conntrack結構表示,其結構如下圖所示。在該結構中,包含一個nf_conntrack型別的結構,其記錄了連線記錄被公開應用的計數,也方便其他地方對連線跟蹤的引用。每個連線記錄都對應一個指向連線超時的函式指標,當較長時間內未使用該連線,將呼叫該指標所指向的函式。如果針對某種協議的連線跟蹤需要擴充套件模組的輔助,則在連線記錄中會有一指向ip_conntrack_helper 結構體的指標。連線記錄中的結構體ip_conntrack_tuple_hash實際記錄了連線所跟蹤的地址資訊(源和目的地址)和協議的特定資訊(埠)。所有連線記錄的ip_conntrack_tuple_hash以雜湊形式儲存在連線跟蹤表中(ip_conntrack記錄存放在堆裡面)。

1.2.3連結跟蹤表

連線跟蹤表是記錄所有連線記錄的散列表,其由全域性變數ip_conntrack_hash所指向。連線跟蹤表實際是一個以雜湊值排列的雙向連結串列陣列,連結串列中的元素即為連線記錄所包含的ip_conntrack_tuple_hash結構。

1.3傳輸協議

連線跟蹤機制可以支援多種傳輸協議,不同的協議所採用的跟蹤方式會有所不同。傳輸協議用結構ip_conntrack_protocol 儲存,所有的已註冊的傳輸協議列表由全域性變數ip_ct_protos 所指向的一維陣列儲存,且按照協議號的順序依次排列。函式ip_conntrack_protocol_register()和ip_conntrack_protocol_unregister()用於向協議列表中新增或刪除一個協議。

資料結構部分總結:
1.整個hash表用ip_conntrack_hash 指標陣列來描述,它包含了ip_conntrack_htable_size個元素,預設是根據記憶體大小計算出來的;
2. 整個連線跟蹤表的大小使用全域性變數ip_conntrack_max描述,與hash表的關係是ip_conntrack_max = 8 * ip_conntrack_htable_size;
3. hash連結串列的每一個節點是一個struct ip_conntrack_tuple_hash結構,它有兩個成員,一個是list,一個是tuple;
4.Netfilter將每一個數據包轉換成tuple,再根據tuple計算出hash值,這樣,就可以使用ip_conntrack_hash[hash_id]找到hash表中連結串列的入口,並組織連結串列;
5. 找到hash表中連結串列入口後,如果連結串列中不存在此“tuple”,則是一個新連線,就把tuple插入到連結串列的合適位置;
6. 圖中兩個節點tuple[ORIGINAL]和tuple[REPLY],雖然是分開的,在兩個連結串列當中,但是如前所述,它們同時又被封裝在ip_conntrack結構的tuplehash陣列中;

大家感興趣的肯定是怎麼實現連結跟蹤的,由於篇幅,我省略去連結跟蹤的初始化等等部分,重點講解一下連結跟蹤的實現。

1.4連結跟蹤的實現—ip_conntrack_in()

資料包進入Netfilter後,會呼叫ip_conntrack_in函式,以進入連線跟蹤模組,ip_conntrack_in 主要完成的工作就是判斷資料包是否已在連線跟蹤表中,如果不在,則為資料包分配ip_conntrack,並初始化它,然後,為這個資料包設定連線狀態。

複製程式碼
 1 unsigned int ip_conntrack_in(unsigned int hooknum,
 2       struct sk_buff **pskb,
 3       const struct net_device *in,
 4       const struct net_device *out,
 5       int (*okfn)(struct sk_buff *))
 6 {
 7 struct ip_conntrack *ct;
 8 enum ip_conntrack_info ctinfo;
 9 struct ip_conntrack_protocol *proto;
10 int set_reply;
11 int ret;
12 
13 /* 判斷當前資料包是否已被檢查過了 */
14 if ((*pskb)->nfct) {
15 CONNTRACK_STAT_INC(ignore);
16 return NF_ACCEPT;
17 }
18 
19 /* 分片包當會在前一個Hook中被處理,事實上,並不會觸發該條件 */
20 if ((*pskb)->nh.iph->frag_off & htons(IP_OFFSET)) { 
21 if (net_ratelimit()) {
22 printk(KERN_ERR "ip_conntrack_in: Frag of proto %u (hook=%u)\n",
23           (*pskb)->nh.iph->protocol, hooknum);
24 }
25 return NF_DROP;
26 }
27 
28 /* 將當前資料包設定為未修改 */
29 (*pskb)->nfcache |= NFC_UNKNOWN;
30 
31 /*根據當前資料包的協議,查詢與之相應的struct ip_conntrack_protocol結構*/
32 proto = ip_ct_find_proto((*pskb)->nh.iph->protocol);
33 
34 /* 沒有找到對應的協議. */
35 if (proto->error != NULL 
36        && (ret = proto->error(*pskb, &ctinfo, hooknum)) <= 0) {
37 CONNTRACK_STAT_INC(error);
38 CONNTRACK_STAT_INC(invalid);
39 return -ret;
40 }
41 
42 /*在全域性的連線表中,查詢與當前包相匹配的連線結構,返回的是struct ip_conntrack *型別指標,它用於描述一個數據包的連線狀態*/
43 if (!(ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo))) {
44 /* Not valid part of a connection */
45 CONNTRACK_STAT_INC(invalid);
46 return NF_ACCEPT;
47 }
48 
49 if (IS_ERR(ct)) {
50 /* Too stressed to deal. */
51 CONNTRACK_STAT_INC(drop);
52 return NF_DROP;
53 }
54 
55 IP_NF_ASSERT((*pskb)->nfct);
56 
57 /*Packet函式指標,為資料包返回一個判斷,如果資料包不是連線中有效的部分,返回-1,否則返回NF_ACCEPT。*/
58 ret = proto->packet(ct, *pskb, ctinfo);
59 if (ret < 0) {
60 /* Invalid: inverse of the return code tells
61     * the netfilter core what to do*/
62 nf_conntrack_put((*pskb)->nfct);
63 (*pskb)->nfct = NULL;
64 CONNTRACK_STAT_INC(invalid);
65 return -ret;
66 }
67 
68 /*設定應答狀態標誌位*/
69 if (set_reply)
70 set_bit(IPS_SEEN_REPLY_BIT, &ct->status);
71 return ret;
72 }
複製程式碼

在初始化的時候,我們就提過,連線跟蹤模組將所有支援的 協議,都使用struct ip_conntrack_protocol 結構封裝,註冊至全域性陣列ip_ct_protos,這裡首先呼叫函式ip_ct_find_proto根據當前資料包的協議值,找到協議註冊對應的模 塊。然後呼叫resolve_normal_ct 函式進一步處理.

接下來我們再看一下resolve_normal_ct是怎麼實現的:
resolve_normal_ct 函式是連線跟蹤中最重要的函式之一,它的主要功能就是判斷資料包在連線跟蹤表是否存在,如果不存在,則為資料包分配相應的連線跟蹤節點空間並初始化,然後設定連線狀態:
 

複製程式碼
 1 CODE:/* On success, returns conntrack ptr, sets skb->nfct and ctinfo */
 2 static inline struct ip_conntrack *
 3 resolve_normal_ct(struct sk_buff *skb,
 4    struct ip_conntrack_protocol *相關文章