亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費(fèi)注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
12下一頁
最近訪問板塊 發(fā)新帖
查看: 15424 | 回復(fù): 15
打印 上一主題 下一主題

Netfilter 連接跟蹤與狀態(tài)檢測的實(shí)現(xiàn) [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報(bào)告]
發(fā)表于 2006-08-21 15:41 |只看該作者 |倒序?yàn)g覽
作者:九賤
www.skynet.org.cn
原創(chuàng),歡迎轉(zhuǎn)載,轉(zhuǎn)載,請注明出處

內(nèi)核版本:2.6.12

本文只是一部份,詳細(xì)分析了連接跟蹤的基本實(shí)現(xiàn),對于ALG部份,還沒有寫,在整理筆記,歡迎大家提意見,批評指正。

1.什么是連接跟蹤
連接跟蹤(CONNTRACK),顧名思義,就是跟蹤并且記錄連接狀態(tài)。Linux為每一個(gè)經(jīng)過網(wǎng)絡(luò)堆棧的數(shù)據(jù)包,生成一個(gè)新的連接記錄項(xiàng)(Connection entry)。此后,所有屬于此連接的數(shù)據(jù)包都被唯一地分配給這個(gè)連接,并標(biāo)識連接的狀態(tài)。連接跟蹤是防火墻模塊的狀態(tài)檢測的基礎(chǔ),同時(shí)也是地址轉(zhuǎn)換中實(shí)現(xiàn)SNAT和DNAT的前提。
那么Netfilter又是如何生成連接記錄項(xiàng)的呢?每一個(gè)數(shù)據(jù),都有“來源”與“目的”主機(jī),發(fā)起連接的主機(jī)稱為“來源”,響應(yīng)“來源”的請求的主機(jī)即為目的,所謂生成記錄項(xiàng),就是對每一個(gè)這樣的連接的產(chǎn)生、傳輸及終止進(jìn)行跟蹤記錄。由所有記錄項(xiàng)產(chǎn)生的表,即稱為連接跟蹤表。

2.連接跟蹤表
Netfilter使用一張連接跟蹤表,來描述整個(gè)連接狀態(tài),這個(gè)表在實(shí)現(xiàn)算法上采用了hash算法。我們先來看看這個(gè)hash 表的實(shí)現(xiàn)。
整個(gè)hash表用全局指針ip_conntrack_hash 指針來描述,它定義在ip_conntrack_core.c中:
struct list_head *ip_conntrack_hash;

這個(gè)hash表的大小是有限制的,表的大小由ip_conntrack_htable_size 全局變量決定,這個(gè)值,用戶態(tài)可以在模塊插入時(shí)傳遞,默認(rèn)是根據(jù)內(nèi)存大小計(jì)算出來的。
        每一個(gè)hash節(jié)點(diǎn),同時(shí)又是一條鏈表的首部,所以,連接跟蹤表就由ip_conntrack_htable_size 條鏈表構(gòu)成,整個(gè)連接跟蹤表大小使用全局變量ip_conntrack_max描述,與hash表的關(guān)系是ip_conntrack_max = 8 * ip_conntrack_htable_size。
鏈表的每個(gè)節(jié)點(diǎn),都是一個(gè)struct ip_conntrack_tuple_hash 類型:
  1. /* Connections have two entries in the hash table: one for each way */
  2. struct ip_conntrack_tuple_hash
  3. {
  4.         struct list_head list;

  5.         struct ip_conntrack_tuple tuple;
  6. };
復(fù)制代碼

這個(gè)結(jié)構(gòu)有兩個(gè)成員,list 成員用于組織鏈表。多元組(tuple) 則用于描述具體的數(shù)據(jù)包。
每個(gè)數(shù)據(jù)包最基本的要素,就是“來源”和“目的”,從Socket套接字角度來講,連接兩端用“地址+端口”的形式來唯一標(biāo)識一個(gè)連接(對于沒有端口的協(xié)議,如ICMP,可以使用其它辦法替代),所以,這個(gè)數(shù)據(jù)包就可以表示為“來源地址/來源端口+目的地址/目的端口”,Netfilter用結(jié)構(gòu)struct ip_conntrack_tuple 結(jié)構(gòu)來封裝這個(gè)“來源”和“目的”,封裝好的struct ip_conntrack_tuple結(jié)構(gòu)節(jié)點(diǎn)在內(nèi)核中就稱為“tuple”。最終實(shí)現(xiàn)“封裝”,就是根據(jù)來源/目的地址、端口這些要素,來進(jìn)行一個(gè)具體網(wǎng)絡(luò)封包到tuple的轉(zhuǎn)換。結(jié)構(gòu)定義如下:

  1. /* The protocol-specific manipulable parts of the tuple: always in
  2.    network order! */
  3. union ip_conntrack_manip_proto
  4. {
  5.         /* Add other protocols here. */
  6.         u_int16_t all;

  7.         struct {
  8.                 u_int16_t port;
  9.         } tcp;
  10.         struct {
  11.                 u_int16_t port;
  12.         } udp;
  13.         struct {
  14.                 u_int16_t id;
  15.         } icmp;
  16.         struct {
  17.                 u_int16_t port;
  18.         } sctp;
  19. };
復(fù)制代碼

  1. /* The manipulable part of the tuple. */
  2. struct ip_conntrack_manip
  3. {
  4.         u_int32_t ip;
  5.         union ip_conntrack_manip_proto u;
  6. };
復(fù)制代碼

  1. /* This contains the information to distinguish a connection. */
  2. struct ip_conntrack_tuple
  3. {
  4.         struct ip_conntrack_manip src;

  5.         /* These are the parts of the tuple which are fixed. */
  6.         struct {
  7.                 u_int32_t ip;
  8.                 union {
  9.                         /* Add other protocols here. */
  10.                         u_int16_t all;

  11.                         struct {
  12.                                 u_int16_t port;
  13.                         } tcp;
  14.                         struct {
  15.                                 u_int16_t port;
  16.                         } udp;
  17.                         struct {
  18.                                 u_int8_t type, code;
  19.                         } icmp;
  20.                         struct {
  21.                                 u_int16_t port;
  22.                         } sctp;
  23.                 } u;

  24.                 /* The protocol. */
  25.                 u_int8_t protonum;

  26.                 /* The direction (for tuplehash) */
  27.                 u_int8_t dir;
  28.         } dst;
  29. };
復(fù)制代碼


struct ip_conntrack_tuple 中僅包含了src、dst兩個(gè)成員,這兩個(gè)成員基本一致:包含ip以及各個(gè)協(xié)議的端口,值得注意的是,dst成員中有一個(gè)dir成員,dir是direction 的縮寫,標(biāo)識一個(gè)連接的方向,后面我們會看到它的用法。

tuple 結(jié)構(gòu)僅僅是一個(gè)數(shù)據(jù)包的轉(zhuǎn)換,并不是描述一條完整的連接狀態(tài),內(nèi)核中,描述一個(gè)包的連接狀態(tài),使用了struct ip_conntrack 結(jié)構(gòu),可以在ip_conntrack.h中看到它的定義:
  1. struct ip_conntrack
  2. {
  3.         ……
  4.         /* These are my tuples; original and reply */
  5.         struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
  6. };
復(fù)制代碼


這里僅僅是分析hash表的實(shí)現(xiàn),所以,我們僅需注意struct ip_conntrack結(jié)構(gòu)的最后一個(gè)成員tuplehash,它是一個(gè)struct ip_conntrack_tuple_hash 類型的數(shù)組,我們前面說了,該結(jié)構(gòu)描述鏈表中的節(jié)點(diǎn),這個(gè)數(shù)組包含“初始”和“應(yīng)答”兩個(gè)成員(tuplehash[IP_CT_DIR_ORIGINAL]和tuplehash[IP_CT_DIR_REPLY]),所以,當(dāng)一個(gè)數(shù)據(jù)包進(jìn)入連接跟蹤模塊后,先根據(jù)這個(gè)數(shù)據(jù)包的套接字對轉(zhuǎn)換成一個(gè)“初始的”tuple,賦值給tuplehash[IP_CT_DIR_ORIGINAL],然后對這個(gè)數(shù)據(jù)包“取反”,計(jì)算出“應(yīng)答”的tuple,賦值給tuplehash[IP_CT_DIR_REPLY],這樣,一條完整的連接已經(jīng)躍然紙上了。
最后一要注意的問題,就是對于每一條連接,尋找鏈表在hash表的入口,也就是如計(jì)算hash值。我們關(guān)心的是一條連接,連接是由“請求”和“應(yīng)答”的數(shù)據(jù)包組成,數(shù)據(jù)包會被轉(zhuǎn)化成tuple,所以,hash值就是根據(jù)tuple,通過一定的hash算法實(shí)現(xiàn),這樣,整個(gè)hash表如下圖所示:
         

如圖,小結(jié)一下:
n        整個(gè)hash表用ip_conntrack_hash 指針數(shù)組來描述,它包含了ip_conntrack_htable_size個(gè)元素,用戶態(tài)可以在模塊插入時(shí)傳遞,默認(rèn)是根據(jù)內(nèi)存大小計(jì)算出來的;
n        整個(gè)連接跟蹤表的大小使用全局變量ip_conntrack_max描述,與hash表的關(guān)系是ip_conntrack_max = 8 * ip_conntrack_htable_size;
n        hash鏈表的每一個(gè)節(jié)點(diǎn)是一個(gè)struct ip_conntrack_tuple_hash結(jié)構(gòu),它有兩個(gè)成員,一個(gè)是list,一個(gè)是tuple;
n        Netfilter將每一個(gè)數(shù)據(jù)包轉(zhuǎn)換成tuple,再根據(jù)tuple計(jì)算出hash值,這樣,就可以使用ip_conntrack_hash[hash_id]找到hash表中鏈表的入口,并組織鏈表;
n        找到hash表中鏈表入口后,如果鏈表中不存在此“tuple”,則是一個(gè)新連接,就把tuple插入到鏈表的合適位置;
n        圖中兩個(gè)節(jié)點(diǎn)tuple[ORIGINAL]和tuple[REPLY],雖然是分開的,在兩個(gè)鏈表當(dāng)中,但是如前所述,它們同時(shí)又被封裝在ip_conntrack結(jié)構(gòu)的tuplehash數(shù)組中,這在圖中,并沒有標(biāo)注出來;
n        鏈表的組織采用的是雙向鏈表,上圖中沒有完整表示出來;

        當(dāng)然,具體的實(shí)現(xiàn)要稍微麻煩一點(diǎn),主要體現(xiàn)在一些復(fù)雜的應(yīng)用層協(xié)議上來,例如主動模式下的FTP協(xié)議,服務(wù)器在連接建立后,會主動打開高端口與客戶端進(jìn)行通訊,這樣,由于端口變換了,我們前面說的連接表的實(shí)現(xiàn)就會遇到麻煩。Netfilter為這些協(xié)議提供了一個(gè)巧秒的解決辦法,我們在本章中,先分析連接跟蹤的基本實(shí)現(xiàn),然后再來分析Netfilter對這些特殊的協(xié)議的支持的實(shí)現(xiàn)。

3.連接跟蹤的初始化

3.1 初始化函數(shù)
ip_conntrack_standalone.c 是連接跟蹤的主要模塊:
  1. static int __init init(void)
  2. {
  3.         return init_or_cleanup(1);
  4. }
復(fù)制代碼


初始化函數(shù)進(jìn)一步調(diào)用init_or_cleanup() 進(jìn)行模塊的初始化,它主要完成hash表的初始化等三個(gè)方面的工作:

  1. static int init_or_cleanup(int init)
  2. {
  3.         /*初始化連接跟蹤的一些變量、數(shù)據(jù)結(jié)構(gòu),如初始化連接跟蹤表的大小,Hash表的大小等*/
  4.         ret = ip_conntrack_init();
  5.         if (ret < 0)
  6.                 goto cleanup_nothing;

  7. /*創(chuàng)建proc 文件系統(tǒng)的對應(yīng)節(jié)點(diǎn)*/
  8. #ifdef CONFIG_PROC_FS
  9.         ……
  10. #endif

  11. /*為連接跟蹤注冊Hook */
  12.         ret = nf_register_hook(&ip_conntrack_defrag_ops);
  13.         if (ret < 0) {
  14.                 printk("ip_conntrack: can't register pre-routing defrag hook.\n");
  15.                 goto cleanup_proc_stat;
  16.         }
  17.         ……
  18. }
復(fù)制代碼


3.2 ip_conntrack_init

ip_conntrack_init 函數(shù)用于初始化連接跟蹤的包括hash表相關(guān)參數(shù)在內(nèi)一些重要的變量:
  1. /*用戶態(tài)可以在模塊插入的時(shí)候,可以使用hashsize參數(shù),指明hash 表的大小*/
  2. static int hashsize;
  3. module_param(hashsize, int, 0400);

  4. int __init ip_conntrack_init(void)
  5. {
  6.         unsigned int i;
  7.         int ret;

  8.         /* 如果模塊指明了hash表的大小,則使用指定值,否則,根據(jù)內(nèi)存的大小,來計(jì)算一個(gè)默認(rèn)值. ,hash表的大小,是使用全局變量ip_conntrack_htable_size 來描述*/
  9.         if (hashsize) {
  10.                 ip_conntrack_htable_size = hashsize;
  11.         } else {
  12.                 ip_conntrack_htable_size
  13.                         = (((num_physpages << PAGE_SHIFT) / 16384)
  14.                            / sizeof(struct list_head));
  15.                 if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE))
  16.                         ip_conntrack_htable_size = 8192;
  17.                 if (ip_conntrack_htable_size < 16)
  18.                         ip_conntrack_htable_size = 16;
  19.         }

  20. /*根據(jù)hash表的大小,計(jì)算最大的連接跟蹤表數(shù)*/
  21.         ip_conntrack_max = 8 * ip_conntrack_htable_size;

  22.         printk("ip_conntrack version %s (%u buckets, %d max)"
  23.                " - %Zd bytes per conntrack\n", IP_CONNTRACK_VERSION,
  24.                ip_conntrack_htable_size, ip_conntrack_max,
  25.                sizeof(struct ip_conntrack));
  26.        
  27. /*注冊socket選項(xiàng)*/
  28.         ret = nf_register_sockopt(&so_getorigdst);
  29.         if (ret != 0) {
  30.                 printk(KERN_ERR "Unable to register netfilter socket option\n");
  31.                 return ret;
  32.         }

  33.         /* 初始化內(nèi)存分配標(biāo)識變量 */
  34.         ip_conntrack_vmalloc = 0;

  35.         /*為hash表分配連續(xù)內(nèi)存頁*/
  36.         ip_conntrack_hash
  37.                 =(void*)__get_free_pages(GFP_KERNEL,
  38.                                          get_order(sizeof(struct list_head)
  39.                                                    *ip_conntrack_htable_size));
  40.         /*分配失敗,嘗試調(diào)用vmalloc重新分配*/
  41. if (!ip_conntrack_hash) {
  42.                 ip_conntrack_vmalloc = 1;
  43.                 printk(KERN_WARNING "ip_conntrack: falling back to vmalloc.\n");
  44.                 ip_conntrack_hash = vmalloc(sizeof(struct list_head)
  45.                                             * ip_conntrack_htable_size);
  46.         }
  47.         /*仍然分配失敗*/
  48.         if (!ip_conntrack_hash) {
  49.                 printk(KERN_ERR "Unable to create ip_conntrack_hash\n");
  50.                 goto err_unreg_sockopt;
  51.         }

  52.         ip_conntrack_cachep = kmem_cache_create("ip_conntrack",
  53.                                                 sizeof(struct ip_conntrack), 0,
  54.                                                 0, NULL, NULL);
  55.         if (!ip_conntrack_cachep) {
  56.                 printk(KERN_ERR "Unable to create ip_conntrack slab cache\n");
  57.                 goto err_free_hash;
  58.         }

  59.         ip_conntrack_expect_cachep = kmem_cache_create("ip_conntrack_expect",
  60.                                         sizeof(struct ip_conntrack_expect),
  61.                                         0, 0, NULL, NULL);
  62.         if (!ip_conntrack_expect_cachep) {
  63.                 printk(KERN_ERR "Unable to create ip_expect slab cache\n");
  64.                 goto err_free_conntrack_slab;
  65.         }

  66.         /* Don't NEED lock here, but good form anyway. */
  67.         WRITE_LOCK(&ip_conntrack_lock);
  68.        
  69. /* 注冊協(xié)議。對不同協(xié)議,連接跟蹤記錄的參數(shù)不同,所以不同的協(xié)議定義了不同的 ip_conntrack_protocol結(jié)構(gòu)來處理與協(xié)議相關(guān)的內(nèi)容。這些結(jié)構(gòu)被注冊到一個(gè)全局的鏈表中,在使用時(shí)根據(jù)協(xié)議去查找,并調(diào)用相應(yīng)的處理函數(shù)來完成相應(yīng)的動作。*/
  70.         for (i = 0; i < MAX_IP_CT_PROTO; i++)
  71.                 ip_ct_protos[i] = &ip_conntrack_generic_protocol;
  72.         ip_ct_protos[IPPROTO_TCP] = &ip_conntrack_protocol_tcp;
  73.         ip_ct_protos[IPPROTO_UDP] = &ip_conntrack_protocol_udp;
  74.         ip_ct_protos[IPPROTO_ICMP] = &ip_conntrack_protocol_icmp;
  75.         WRITE_UNLOCK(&ip_conntrack_lock);
  76.        
  77.         /*初始化hash表*/
  78.         for (i = 0; i < ip_conntrack_htable_size; i++)
  79.                 INIT_LIST_HEAD(&ip_conntrack_hash[i]);

  80.         /* For use by ipt_REJECT */
  81.         ip_ct_attach = ip_conntrack_attach;

  82.         /* Set up fake conntrack:
  83.             - to never be deleted, not in any hashes */
  84.         atomic_set(&ip_conntrack_untracked.ct_general.use, 1);
  85.         /*  - and look it like as a confirmed connection */
  86.         set_bit(IPS_CONFIRMED_BIT, &ip_conntrack_untracked.status);

  87.         return ret;

  88. err_free_conntrack_slab:
  89.         kmem_cache_destroy(ip_conntrack_cachep);
  90. err_free_hash:
  91.         free_conntrack_hash();
  92. err_unreg_sockopt:
  93.         nf_unregister_sockopt(&so_getorigdst);

  94.         return -ENOMEM;
  95. }
復(fù)制代碼


在這個(gè)函數(shù)中,有兩個(gè)重點(diǎn)的地方值得注意,一個(gè)是hash表的相關(guān)變量的初始化、內(nèi)存空間的分析等等,另一個(gè)是協(xié)議的注冊。
        連接跟蹤由于針對每種協(xié)議的處理,都有些細(xì)微不同的地方,舉個(gè)例子,我們前面講到數(shù)據(jù)包至tuple的轉(zhuǎn)換,TCP的轉(zhuǎn)換與ICMP的轉(zhuǎn)換肯定不同的,因?yàn)镮CMP連端口的概念也沒有,所以,對于每種協(xié)議的一些特殊處理的函數(shù),需要進(jìn)行封裝,struct ip_conntrack_protocol 結(jié)構(gòu)就實(shí)現(xiàn)了這一封裝,在初始化工作中,針對最常見的TCP、UDP和ICMP協(xié)議,定義了ip_conntrack_protocol_tcp、ip_conntrack_protocol_udp和ip_conntrack_protocol_icmp三個(gè)該類型的全局變量,初始化函數(shù)中,將它們封裝至ip_ct_protos 數(shù)組,這些,在后面的數(shù)據(jù)包處理后,就可以根據(jù)包中的協(xié)議值,使用ip_ct_protos[協(xié)議值],找到注冊的協(xié)議節(jié)點(diǎn),就可以方便地調(diào)用協(xié)議對應(yīng)的處理函數(shù)了,我們在后面將看到這一調(diào)用過程。

3.2        鉤子函數(shù)的注冊
init_or_cleanup 函數(shù)在創(chuàng)建/proc文件系統(tǒng)完成后,會調(diào)用nf_register_hook 函數(shù)注冊鉤子,進(jìn)行連接跟蹤,按優(yōu)先級和Hook不同,注冊了多個(gè)鉤子:
  1.         ret = nf_register_hook(&ip_conntrack_defrag_ops);
  2.         if (ret < 0) {
  3.                 printk("ip_conntrack: can't register pre-routing defrag hook.\n");
  4.                 goto cleanup_proc_stat;
  5.         }
  6.         ret = nf_register_hook(&ip_conntrack_defrag_local_out_ops);
  7.         if (ret < 0) {
  8.                 printk("ip_conntrack: can't register local_out defrag hook.\n");
  9.                 goto cleanup_defragops;
  10.         }
  11.         ……
復(fù)制代碼


整個(gè)Hook注冊好后,如下圖所示:


上圖中,粗黑體標(biāo)識函數(shù)就是連接跟蹤注冊的鉤子函數(shù),除此之外,用于處理分片包和處理復(fù)雜協(xié)議的鉤子函數(shù)在上圖中沒有標(biāo)識出來。處理分片包的鉤子用于重組分片,用于保證數(shù)據(jù)在進(jìn)入連接跟蹤模塊不會是一個(gè)分片數(shù)據(jù)包。例如,在數(shù)據(jù)包進(jìn)入NF_IP_PRE_ROUTING Hook點(diǎn),主要的連接跟蹤函數(shù)是ip_conntrack_in,然而,在它之前,還注冊了ip_conntrack_defrag,用于處理分片數(shù)據(jù)包:

  1. static unsigned int ip_conntrack_defrag(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.         /* Gather fragments. */
  8.         if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
  9.                 *pskb = ip_ct_gather_frags(*pskb,
  10.                                            hooknum == NF_IP_PRE_ROUTING ?
  11.                                            IP_DEFRAG_CONNTRACK_IN :
  12.                                            IP_DEFRAG_CONNTRACK_OUT);
  13.                 if (!*pskb)
  14.                         return NF_STOLEN;
  15.         }
  16.         return NF_ACCEPT;
  17. }
復(fù)制代碼


對于我們本章的分析而言,主要是以“Linux做為一個(gè)網(wǎng)關(guān)主機(jī),轉(zhuǎn)發(fā)過往數(shù)據(jù)”為主線,更多關(guān)注的是在NF_IP_PRE_ROUTING和NF_IP_POSTROUTING兩個(gè)Hook點(diǎn)上注冊的兩個(gè)鉤子函數(shù)ip_conntrack_in和ip_refrag(這個(gè)函數(shù)主要執(zhí)行的是ip_confirm函數(shù))。
        鉤子的注冊的另一個(gè)值得注意的小問題,就是鉤子函數(shù)的優(yōu)先級,NF_IP_PRE_ROUTING上的優(yōu)先級是NF_IP_PRI_CONNTRACK ,意味著它的優(yōu)先級是很高的,這也意味著每個(gè)輸入數(shù)據(jù)包首先被傳輸?shù)竭B接跟蹤模塊,才會進(jìn)入其它優(yōu)先級較低的模塊。同樣地,NF_IP_POSTROUTING上的優(yōu)先級為NF_IP_PRI_CONNTRACK_CONFIRM,優(yōu)先級是很低的,也就是說,等到其它優(yōu)先級高的模塊處理完成后,才會做最后的處理,然后將數(shù)據(jù)包送出去。

4.ip_conntrack_in

數(shù)據(jù)包進(jìn)入Netfilter后,會調(diào)用ip_conntrack_in函數(shù),以進(jìn)入連接跟蹤模塊,ip_conntrack_in 主要完成的工作就是判斷數(shù)據(jù)包是否已在連接跟蹤表中,如果不在,則為數(shù)據(jù)包分配ip_conntrack,并初始化它,然后,為這個(gè)數(shù)據(jù)包設(shè)置連接狀態(tài)。

  1. /* Netfilter hook itself. */
  2. unsigned int ip_conntrack_in(unsigned int hooknum,
  3.                              struct sk_buff **pskb,
  4.                              const struct net_device *in,
  5.                              const struct net_device *out,
  6.                              int (*okfn)(struct sk_buff *))
  7. {
  8.         struct ip_conntrack *ct;
  9.         enum ip_conntrack_info ctinfo;
  10.         struct ip_conntrack_protocol *proto;
  11.         int set_reply;
  12.         int ret;

  13.         /* 判斷當(dāng)前數(shù)據(jù)包是否已被檢查過了 */
  14.         if ((*pskb)->nfct) {
  15.                 CONNTRACK_STAT_INC(ignore);
  16.                 return NF_ACCEPT;
  17.         }

  18. /* 分片包當(dāng)會在前一個(gè)Hook中被處理,事實(shí)上,并不會觸發(fā)該條件 */
  19.         if ((*pskb)->nh.iph->frag_off & htons(IP_OFFSET)) {
  20.                 if (net_ratelimit()) {
  21.                 printk(KERN_ERR "ip_conntrack_in: Frag of proto %u (hook=%u)\n",
  22.                        (*pskb)->nh.iph->protocol, hooknum);
  23.                 }
  24.                 return NF_DROP;
  25.         }

  26. /* 將當(dāng)前數(shù)據(jù)包設(shè)置為未修改 */
  27.         (*pskb)->nfcache |= NFC_UNKNOWN;

  28. /*根據(jù)當(dāng)前數(shù)據(jù)包的協(xié)議,查找與之相應(yīng)的struct ip_conntrack_protocol結(jié)構(gòu)*/
  29.         proto = ip_ct_find_proto((*pskb)->nh.iph->protocol);

  30.         /* 沒有找到對應(yīng)的協(xié)議. */
  31.         if (proto->error != NULL
  32.             && (ret = proto->error(*pskb, &ctinfo, hooknum)) <= 0) {
  33.                 CONNTRACK_STAT_INC(error);
  34.                 CONNTRACK_STAT_INC(invalid);
  35.                 return -ret;
  36.         }

  37. /*在全局的連接表中,查找與當(dāng)前包相匹配的連接結(jié)構(gòu),返回的是struct ip_conntrack *類型指針,它用于描述一個(gè)數(shù)據(jù)包的連接狀態(tài)*/
  38.         if (!(ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo))) {
  39.                 /* Not valid part of a connection */
  40.                 CONNTRACK_STAT_INC(invalid);
  41.                 return NF_ACCEPT;
  42.         }

  43.         if (IS_ERR(ct)) {
  44.                 /* Too stressed to deal. */
  45.                 CONNTRACK_STAT_INC(drop);
  46.                 return NF_DROP;
  47.         }

  48.         IP_NF_ASSERT((*pskb)->nfct);

  49. /*Packet函數(shù)指針,為數(shù)據(jù)包返回一個(gè)判斷,如果數(shù)據(jù)包不是連接中有效的部分,返回-1,否則返回NF_ACCEPT。*/
  50.         ret = proto->packet(ct, *pskb, ctinfo);
  51.         if (ret < 0) {
  52.                 /* Invalid: inverse of the return code tells
  53.                  * the netfilter core what to do*/
  54.                 nf_conntrack_put((*pskb)->nfct);
  55.                 (*pskb)->nfct = NULL;
  56.                 CONNTRACK_STAT_INC(invalid);
  57.                 return -ret;
  58.         }

  59. /*設(shè)置應(yīng)答狀態(tài)標(biāo)志位*/
  60.         if (set_reply)
  61.                 set_bit(IPS_SEEN_REPLY_BIT, &ct->status);

  62.         return ret;
  63. }
復(fù)制代碼


在初始化的時(shí)候,我們就提過,連接跟蹤模塊將所有支持的協(xié)議,都使用struct ip_conntrack_protocol 結(jié)構(gòu)封裝,注冊至全局?jǐn)?shù)組ip_ct_protos,這里首先調(diào)用函數(shù)ip_ct_find_proto根據(jù)當(dāng)前數(shù)據(jù)包的協(xié)議值,找到協(xié)議注冊對應(yīng)的模塊。然后調(diào)用resolve_normal_ct 函數(shù)進(jìn)一步處理。

[ 本帖最后由 獨(dú)孤九賤 于 2006-8-22 08:48 編輯 ]

論壇徽章:
0
2 [報(bào)告]
發(fā)表于 2006-08-21 15:41 |只看該作者
5.resolve_normal_ct
        resolve_normal_ct 函數(shù)是連接跟蹤中最重要的函數(shù)之一,它的主要功能就是判斷數(shù)據(jù)包在連接跟蹤表是否存在,如果不存在,則為數(shù)據(jù)包分配相應(yīng)的連接跟蹤節(jié)點(diǎn)空間并初始化,然后設(shè)置連接狀態(tài):

  1. /* 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 *proto,
  5.                   int *set_reply,
  6.                   unsigned int hooknum,
  7.                   enum ip_conntrack_info *ctinfo)
  8. {
  9.         struct ip_conntrack_tuple tuple;
  10.         struct ip_conntrack_tuple_hash *h;
  11.         struct ip_conntrack *ct;

  12.         IP_NF_ASSERT((skb->nh.iph->frag_off & htons(IP_OFFSET)) == 0);

  13. /*前面提到過,需要將一個(gè)數(shù)據(jù)包轉(zhuǎn)換成tuple,這個(gè)轉(zhuǎn)換,就是通過ip_ct_get_tuple函數(shù)實(shí)現(xiàn)的*/
  14.         if (!ip_ct_get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4,
  15.                                 &tuple,proto))
  16.                 return NULL;

  17.         /*查看數(shù)據(jù)包對應(yīng)的tuple在連接跟蹤表中是否存在 */
  18.         h = ip_conntrack_find_get(&tuple, NULL);
  19.         if (!h) {
  20.                 /*如果不存在,初始化之*/
  21. h = init_conntrack(&tuple, proto, skb);
  22.                 if (!h)
  23.                         return NULL;
  24.                 if (IS_ERR(h))
  25.                         return (void *)h;
  26.         }
  27. /*根據(jù)hash表節(jié)點(diǎn),取得數(shù)據(jù)包對應(yīng)的連接跟蹤結(jié)構(gòu)*/
  28.         ct = tuplehash_to_ctrack(h);

  29.         /* 判斷連接的方向 */
  30.         if (DIRECTION(h) == IP_CT_DIR_REPLY) {
  31.                 *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
  32.                 /* Please set reply bit if this packet OK */
  33.                 *set_reply = 1;
  34.         } else {
  35.                 /* Once we've had two way comms, always ESTABLISHED. */
  36.                 if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
  37.                         DEBUGP("ip_conntrack_in: normal packet for %p\n",
  38.                                ct);
  39.                         *ctinfo = IP_CT_ESTABLISHED;
  40.                 } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
  41.                         DEBUGP("ip_conntrack_in: related packet for %p\n",
  42.                                ct);
  43.                         *ctinfo = IP_CT_RELATED;
  44.                 } else {
  45.                         DEBUGP("ip_conntrack_in: new packet for %p\n",
  46.                                ct);
  47.                         *ctinfo = IP_CT_NEW;
  48.                 }
  49.                 *set_reply = 0;
  50.         }
  51. /*設(shè)置skb的對應(yīng)成員,如使用計(jì)數(shù)器、數(shù)據(jù)包狀態(tài)標(biāo)記*/
  52.         skb->nfct = &ct->ct_general;
  53.         skb->nfctinfo = *ctinfo;
  54.         return ct;
  55. }
復(fù)制代碼



這個(gè)函數(shù)包含了連接跟蹤中許多重要的步驟
n        調(diào)用ip_ct_get_tuple函數(shù),把數(shù)據(jù)包轉(zhuǎn)換為tuple;
n        ip_conntrack_find_get函數(shù),根據(jù)tuple查找連接跟蹤表;
n        init_conntrack函數(shù),初始化一條連接;
n        判斷連接方向,設(shè)置連接狀態(tài);

5.1 數(shù)據(jù)包的轉(zhuǎn)換
ip_ct_get_tuple 實(shí)現(xiàn)數(shù)據(jù)包至tuple的轉(zhuǎn)換,這個(gè)轉(zhuǎn)換,主要是根據(jù)數(shù)據(jù)包的套接字對來進(jìn)行轉(zhuǎn)換的:

  1. int ip_ct_get_tuple(const struct iphdr *iph,
  2.                 const struct sk_buff *skb,
  3.                 unsigned int dataoff,
  4.                 struct ip_conntrack_tuple *tuple,
  5.                 const struct ip_conntrack_protocol *protocol)
  6. {
  7.                 /* Never happen */
  8.                 if (iph->frag_off & htons(IP_OFFSET)) {
  9.                         printk("ip_conntrack_core: Frag of proto %u.\n",
  10.                        iph->protocol);
  11.                         return 0;
  12.         }
  13. /*設(shè)置來源、目的地址*/
  14.                 tuple->src.ip = iph->saddr;
  15.                 tuple->dst.ip = iph->daddr;
  16.         tuple->dst.protonum = iph->protocol;
  17.                 tuple->dst.dir = IP_CT_DIR_ORIGINAL;

  18.         return protocol->pkt_to_tuple(skb, dataoff, tuple);
  19. }
復(fù)制代碼


回憶一下我們前面分析協(xié)議的初始化中協(xié)議初始化的部份,pkt_to_tuple 函數(shù)指針,以每種協(xié)議的不同而不同,以TCP協(xié)議為例:

  1. static int tcp_pkt_to_tuple(const struct sk_buff *skb,
  2.                             unsigned int dataoff,
  3.                             struct ip_conntrack_tuple *tuple)
  4. {
  5.                 struct tcphdr _hdr, *hp;

  6.                 /* 獲取TCP報(bào)頭*/
  7. hp = skb_header_pointer(skb, dataoff, 8, &_hdr);
  8.         if (hp == NULL)
  9.                         return 0;
  10. /*根據(jù)報(bào)頭的端口信息,設(shè)置tuple對應(yīng)成員*/
  11.                 tuple->src.u.tcp.port = hp->source;
  12.         tuple->dst.u.tcp.port = hp->dest;

  13.         return 1;
  14. }
復(fù)制代碼

TCP協(xié)議中,根據(jù)來源和目的端口設(shè)置,其它協(xié)議類似,讀者可以對比分析。

5.2 Hash 表的搜索
要對Hash表進(jìn)行遍歷,首要需要找到hash表的入口,然后來遍歷該入口指向的鏈表。每個(gè)鏈表的節(jié)點(diǎn)是struct ip_conntrack_tuple_hash,它封裝了tuple,所謂封裝,就是把待查找的tuple與節(jié)點(diǎn)中已存的tuple相比較,我們來看這一過程的實(shí)現(xiàn)。
計(jì)算hash值,是調(diào)用hash_conntrack函數(shù),根據(jù)數(shù)據(jù)包對應(yīng)的tuple實(shí)現(xiàn)的:
  1. unsigned int hash = hash_conntrack(tuple);

  2.         這樣,tuple對應(yīng)的hash表入口即為ip_conntrack_hash[hash],也就是鏈表的首節(jié)點(diǎn),然后調(diào)用ip_conntrack_find_get函數(shù)進(jìn)行查找:
  3. struct ip_conntrack_tuple_hash *
  4. ip_conntrack_find_get(const struct ip_conntrack_tuple *tuple,
  5.                       const struct ip_conntrack *ignored_conntrack)
  6. {
  7.         struct ip_conntrack_tuple_hash *h;

  8.         READ_LOCK(&ip_conntrack_lock);
  9.         /*搜索鏈表*/
  10.         h = __ip_conntrack_find(tuple, ignored_conntrack);
  11.         if (h)                /*查找到了,使用計(jì)數(shù)器累加*/
  12.                 atomic_inc(&tuplehash_to_ctrack(h)->ct_general.use);
  13.         READ_UNLOCK(&ip_conntrack_lock);

  14.         return h;
  15. }
復(fù)制代碼


鏈表是內(nèi)核中一個(gè)標(biāo)準(zhǔn)的雙向鏈表,可以調(diào)用宏list_for_each_entry 進(jìn)遍歷鏈表:
  1. static struct ip_conntrack_tuple_hash *
  2. __ip_conntrack_find(const struct ip_conntrack_tuple *tuple,
  3.                     const struct ip_conntrack *ignored_conntrack)
  4. {
  5.         struct ip_conntrack_tuple_hash *h;
  6.         unsigned int hash = hash_conntrack(tuple);

  7.         MUST_BE_READ_LOCKED(&ip_conntrack_lock);
  8.         list_for_each_entry(h, &ip_conntrack_hash[hash], list) {
  9.                 if (conntrack_tuple_cmp(h, tuple, ignored_conntrack)) {
  10.                         CONNTRACK_STAT_INC(found);
  11.                         return h;
  12.                 }
  13.                 CONNTRACK_STAT_INC(searched);
  14.         }

  15.         return NULL;
  16. }
復(fù)制代碼


list_for_each_entry在以&ip_conntrack_hash[hash]為起始地址的鏈表中,逐個(gè)搜索其成員,比較這個(gè)節(jié)點(diǎn)中的tuple是否與待查找的tuple是否一致,這個(gè)比較過程,是通過conntrack_tuple_cmp 函數(shù)實(shí)現(xiàn)的:
  1. conntrack_tuple_cmp(const struct ip_conntrack_tuple_hash *i,
  2.                     const struct ip_conntrack_tuple *tuple,
  3.                     const struct ip_conntrack *ignored_conntrack)
  4. {
  5.         MUST_BE_READ_LOCKED(&ip_conntrack_lock);
  6.         return tuplehash_to_ctrack(i) != ignored_conntrack
  7.                 && ip_ct_tuple_equal(tuple, &i->tuple);
  8. }
復(fù)制代碼


tuplehash_to_ctrack 函數(shù)主要是取連接跟蹤ip_conntrack中的連接方向,判斷它是否等于ignored_conntrack,對與這里的比較而言,ignored_conntrack傳遞過來的為NULL。
主要的比較函數(shù)是ip_ct_tuple_equal函數(shù),函數(shù)分為“來源”和“目的”進(jìn)行比較:

  1. static inline int ip_ct_tuple_src_equal(const struct ip_conntrack_tuple *t1,
  2.                                         const struct ip_conntrack_tuple *t2)
  3. {
  4.         return t1->src.ip == t2->src.ip
  5.                 && t1->src.u.all == t2->src.u.all;
  6. }

  7. static inline int ip_ct_tuple_dst_equal(const struct ip_conntrack_tuple *t1,
  8.                                         const struct ip_conntrack_tuple *t2)
  9. {
  10.         return t1->dst.ip == t2->dst.ip
  11.                 && t1->dst.u.all == t2->dst.u.all
  12.                 && t1->dst.protonum == t2->dst.protonum;
  13. }

  14. static inline int ip_ct_tuple_equal(const struct ip_conntrack_tuple *t1,
  15.                                     const struct ip_conntrack_tuple *t2)
  16. {
  17.         return ip_ct_tuple_src_equal(t1, t2) && ip_ct_tuple_dst_equal(t1, t2);
  18. }
復(fù)制代碼


這里的比較,除了IP地址之外,并沒有直接比較“端口”,這是因?yàn)橄馡CMP協(xié)議這樣的并沒有“端口”協(xié)議,struct ip_conntrack_tuple 結(jié)構(gòu)中,與協(xié)議相關(guān)的,如端口等,都定義成union類型,這樣,就可以直接使用u.all,而不用再去管TCP,UDP還是ICMP了。

5.3 連接初始化
內(nèi)核使用ip_conntrack結(jié)構(gòu)來描述一個(gè)數(shù)據(jù)包的連接狀態(tài),init_conntrack函數(shù)就是在連接狀態(tài)表中不存在當(dāng)前數(shù)據(jù)包時(shí),初始化一個(gè)ip_conntrack結(jié)構(gòu),此結(jié)構(gòu)被Netfilter用來描述一條連接,前面分析hash表時(shí),已經(jīng)分析了它的tuplehash成員:

  1. struct ip_conntrack
  2. {
  3.         /* 包含了使用計(jì)數(shù)器和指向刪除連接的函數(shù)的指針 */
  4.         struct nf_conntrack ct_general;

  5.         /* 連接狀態(tài)位,它通常是一個(gè)ip_conntrack_status類型的枚舉變量,如IPS_SEEN_REPLY_BIT等*/
  6.         unsigned long status;

  7.         /* 內(nèi)核的定時(shí)器,用于處理連接超時(shí) */
  8.         struct timer_list timeout;

  9. #ifdef CONFIG_IP_NF_CT_ACCT
  10.         /* Accounting Information (same cache line as other written members) */
  11.         struct ip_conntrack_counter counters[IP_CT_DIR_MAX];
  12. #endif
  13.         /* If we were expected by an expectation, this will be it */
  14.         struct ip_conntrack *master;

  15.         /* Current number of expected connections */
  16.         unsigned int expecting;

  17.         /* Helper, if any. */
  18.         struct ip_conntrack_helper *helper;

  19.         /* Storage reserved for other modules: */
  20.         union ip_conntrack_proto proto;

  21.         union ip_conntrack_help help;

  22. #ifdef CONFIG_IP_NF_NAT_NEEDED
  23.         struct {
  24.                 struct ip_nat_info info;
  25. #if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \
  26.         defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)
  27.                 int masq_index;
  28. #endif
  29.         } nat;
  30. #endif /* CONFIG_IP_NF_NAT_NEEDED */

  31. #if defined(CONFIG_IP_NF_CONNTRACK_MARK)
  32.         unsigned long mark;
  33. #endif

  34.         /* Traversed often, so hopefully in different cacheline to top */
  35.         /* These are my tuples; original and reply */
  36.         struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
  37. };


  38. /* Allocate a new conntrack: we return -ENOMEM if classification
  39.    failed due to stress.  Otherwise it really is unclassifiable. */
  40. static struct ip_conntrack_tuple_hash *
  41. init_conntrack(const struct ip_conntrack_tuple *tuple,
  42.                struct ip_conntrack_protocol *protocol,
  43.                struct sk_buff *skb)
  44. {
  45.         struct ip_conntrack *conntrack;
  46.         struct ip_conntrack_tuple repl_tuple;
  47.         size_t hash;
  48.         struct ip_conntrack_expect *exp;

  49.         /*如果計(jì)算hash值的隨機(jī)數(shù)種子沒有被初始化,則初始化之*/
  50.         if (!ip_conntrack_hash_rnd_initted) {
  51.                 get_random_bytes(&ip_conntrack_hash_rnd, 4);
  52.                 ip_conntrack_hash_rnd_initted = 1;
  53.         }

  54.         /*計(jì)算hash值*/
  55.         hash = hash_conntrack(tuple);
  56.        
  57.         /*判斷連接跟蹤表是否已滿*/
  58.         if (ip_conntrack_max
  59.             && atomic_read(&ip_conntrack_count) >= ip_conntrack_max) {
  60.                 /* Try dropping from this hash chain. */
  61.                 if (!early_drop(&ip_conntrack_hash[hash])) {
  62.                         if (net_ratelimit())
  63.                                 printk(KERN_WARNING
  64.                                        "ip_conntrack: table full, dropping"
  65.                                        " packet.\n");
  66.                         return ERR_PTR(-ENOMEM);
  67.                 }
  68.         }

  69.         /*根據(jù)當(dāng)前的tuple取反,計(jì)算該數(shù)據(jù)包的“應(yīng)答”的tuple*/
  70.         if (!ip_ct_invert_tuple(&repl_tuple, tuple, protocol)) {
  71.                 DEBUGP("Can't invert tuple.\n");
  72.                 return NULL;
  73.         }
  74.         /*為數(shù)據(jù)包對應(yīng)的連接分配空間*/
  75.         conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);
  76.         if (!conntrack) {
  77.                 DEBUGP("Can't allocate conntrack.\n");
  78.                 return ERR_PTR(-ENOMEM);
  79.         }
  80.         /*初始化該結(jié)構(gòu)*/
  81.         memset(conntrack, 0, sizeof(*conntrack));
  82.         /*使用計(jì)數(shù)器累加*/
  83.         atomic_set(&conntrack->ct_general.use, 1);
  84.         /*設(shè)置destroy函數(shù)指針*/
  85.         conntrack->ct_general.destroy = destroy_conntrack;
  86.         /*設(shè)置正反兩個(gè)方向的tuple*/
  87. conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *tuple;
  88.         conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = repl_tuple;
  89.         if (!protocol->new(conntrack, skb)) {
  90.                 kmem_cache_free(ip_conntrack_cachep, conntrack);
  91.                 return NULL;
  92.         }
  93.         /* 初始化時(shí)間計(jì)數(shù)器,并設(shè)置超時(shí)初始函數(shù) */
  94.         init_timer(&conntrack->timeout);
  95.         conntrack->timeout.data = (unsigned long)conntrack;
  96.         conntrack->timeout.function = death_by_timeout;

  97.         WRITE_LOCK(&ip_conntrack_lock);
  98.         exp = find_expectation(tuple);

  99.         if (exp) {
  100.                 DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",
  101.                         conntrack, exp);
  102.                 /* Welcome, Mr. Bond.  We've been expecting you... */
  103.                 __set_bit(IPS_EXPECTED_BIT, &conntrack->status);
  104.                 conntrack->master = exp->master;
  105. #if CONFIG_IP_NF_CONNTRACK_MARK
  106.                 conntrack->mark = exp->master->mark;
  107. #endif
  108.                 nf_conntrack_get(&conntrack->master->ct_general);
  109.                 CONNTRACK_STAT_INC(expect_new);
  110.         } else {
  111.                 conntrack->helper = ip_ct_find_helper(&repl_tuple);

  112.                 CONNTRACK_STAT_INC(new);
  113.         }

  114.         /* 這里,并沒有直接就把該連接加入hash表,而是先加入到unconfirmed鏈表中. */
  115.         list_add(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list, &unconfirmed);

  116.         atomic_inc(&ip_conntrack_count);
  117.         WRITE_UNLOCK(&ip_conntrack_lock);

  118.         if (exp) {
  119.                 if (exp->expectfn)
  120.                         exp->expectfn(conntrack, exp);
  121.                 destroy_expect(exp);
  122.         }

  123.         /*返回的是初始方向的hash節(jié)點(diǎn)*/
  124.         return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
  125. }
復(fù)制代碼



在前文中提到過,一條完整的連接,采用struct ip_conntrack 結(jié)構(gòu)描述,初始化函數(shù)的主要功能,就是分配一個(gè)這樣的空間,然后初始化它的一些成員。

在這個(gè)函數(shù)中,有三個(gè)重要的地方需要注意,一個(gè)是根據(jù)當(dāng)前tuple,計(jì)算出應(yīng)答方向的tuple,它是調(diào)用ip_ct_invert_tuple 函數(shù)實(shí)現(xiàn)的:
  1. int
  2. ip_ct_invert_tuple(struct ip_conntrack_tuple *inverse,
  3.                    const struct ip_conntrack_tuple *orig,
  4.                    const struct ip_conntrack_protocol *protocol)
  5. {
  6.         inverse->src.ip = orig->dst.ip;
  7.         inverse->dst.ip = orig->src.ip;
  8.         inverse->dst.protonum = orig->dst.protonum;
  9.         inverse->dst.dir = !orig->dst.dir;

  10.         return protocol->invert_tuple(inverse, orig);
  11. }
復(fù)制代碼



這個(gè)函數(shù)事實(shí)上,與前面講的tuple的轉(zhuǎn)換是一樣的,只是來了個(gè)乾坤大挪移,把來源和目的,以及方向?qū)φ{(diào)了。

另一個(gè)重點(diǎn)的是函數(shù)對特殊協(xié)議的支持,我們這里暫時(shí)跳過了這部份。

第三個(gè)地方是調(diào)用協(xié)議的new函數(shù):
        if (!protocol->new(conntrack, skb)) {
                kmem_cache_free(ip_conntrack_cachep, conntrack);
                return NULL;
        }
new 函數(shù)指定在每個(gè)封包第一次創(chuàng)建連接時(shí)被調(diào)用,它根據(jù)協(xié)議的不同,所處理的過程不同,以ICMP協(xié)議為例:
  1. /* Called when a new connection for this protocol found. */
  2. static int icmp_new(struct ip_conntrack *conntrack,
  3.                     const struct sk_buff *skb)
  4. {
  5.         static u_int8_t valid_new[]
  6.                 = { [ICMP_ECHO] = 1,
  7.                     [ICMP_TIMESTAMP] = 1,
  8.                     [ICMP_INFO_REQUEST] = 1,
  9.                     [ICMP_ADDRESS] = 1 };

  10.         if (conntrack->tuplehash[0].tuple.dst.u.icmp.type >= sizeof(valid_new)
  11.             || !valid_new[conntrack->tuplehash[0].tuple.dst.u.icmp.type]) {
  12.                 /* Can't create a new ICMP `conn' with this. */
  13.                 DEBUGP("icmp: can't create new conn with type %u\n",
  14.                        conntrack->tuplehash[0].tuple.dst.u.icmp.type);
  15.                 DUMP_TUPLE(&conntrack->tuplehash[0].tuple);
  16.                 return 0;
  17.         }
  18.         atomic_set(&conntrack->proto.icmp.count, 0);
  19.         return 1;
  20. }
復(fù)制代碼

對于ICMP協(xié)議而言,僅有ICMP 請求回顯、時(shí)間戳請求、信息請求(已經(jīng)很少用了)、地址掩碼請求這四個(gè)“請求”,可能是一個(gè)“新建”的連接,所以,ICMP協(xié)議的new函數(shù)判斷是否是一個(gè)全法的ICMP新建連接,如果是非法的,則返回0,否則,初始化協(xié)議使用計(jì)數(shù)器,返回1。

5.4 連接狀態(tài)的判斷
resolve_normal_ct 函數(shù)的最后一個(gè)重要的工作是對連接狀態(tài)的判斷,tuple中包含一個(gè)“方向”成員dst.dir,對于一個(gè)初始連接,它是IP_CT_DIR_ORIGINAL:
tuple->dst.dir = IP_CT_DIR_ORIGINAL;
而它的應(yīng)答包的tuple,則為IP_CT_DIR_REPLY:
inverse->dst.dir = !orig->dst.dir;
IP_CT_DIR_ORIGINAL 和IP_CT_DIR_REPLY都是枚舉變量:
  1. enum ip_conntrack_dir
  2. {
  3.         IP_CT_DIR_ORIGINAL,
  4.         IP_CT_DIR_REPLY,
  5.         IP_CT_DIR_MAX
  6. };
復(fù)制代碼


宏DIRECTION 就根據(jù)tuple中對應(yīng)成員的值,判斷數(shù)據(jù)包的方向,
/* If we're the first tuple, it's the original dir. */
#define DIRECTION(h) ((enum ip_conntrack_dir)(h)->tuple.dst.dir)

但是,還有一些特殊地方,比如TCP協(xié)議,它是一個(gè)面向連接的協(xié)議,所以,它的“初始”或“應(yīng)答”包,并不一定就是“新建”或單純的“應(yīng)答”包,而是在一個(gè)連接過程中的“已建連接包”,另一個(gè),如FTP等 復(fù)雜協(xié)議,它們還存在一些“關(guān)聯(lián)”的連接,當(dāng)然這兩部份目前還沒有涉及到,但并不影響我們分析如下這段代碼:

  1.         /* 如果是一個(gè)應(yīng)答包 ,設(shè)置狀態(tài)為已建+應(yīng)答*/
  2.         if (DIRECTION(h) == IP_CT_DIR_REPLY) {
  3.                 *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
  4.                 /* 設(shè)置應(yīng)答標(biāo)志變量 */
  5.                 *set_reply = 1;
  6.         } else {
  7.                 /* 新建連接方過來的數(shù)據(jù)包,對面向連接的協(xié)議而言,可能是一個(gè)已建連接,判斷其標(biāo)志位*/
  8.                 if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
  9.                         DEBUGP("ip_conntrack_in: normal packet for %p\n",
  10.                                ct);
  11.                         *ctinfo = IP_CT_ESTABLISHED;
  12.                 } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
  13.                         DEBUGP("ip_conntrack_in: related packet for %p\n",
  14.                                ct);
  15.                         *ctinfo = IP_CT_RELATED;                        //關(guān)聯(lián)連接
  16.                 } else {
  17.                         DEBUGP("ip_conntrack_in: new packet for %p\n",
  18.                                ct);
  19.                         *ctinfo = IP_CT_NEW;                                //否則,則為一個(gè)新建連接
  20.                 }
  21.                 *set_reply = 0;
  22.         }
  23.        
  24.         /*設(shè)置數(shù)據(jù)包skb與連接狀態(tài)的關(guān)聯(lián)*/
  25.         skb->nfct = &ct->ct_general;
  26.         /*每個(gè)sk_buff都將與ip_conntrack的一個(gè)狀態(tài)關(guān)聯(lián),所以從sk_buff可以得到相應(yīng)ip_conntrack的狀態(tài),即數(shù)據(jù)包的狀態(tài)*/
  27.         skb->nfctinfo = *ctinfo;
  28.         return ct;
復(fù)制代碼


以上的代表所表示的發(fā)送或應(yīng)答的狀態(tài)如下圖所示:


6.        ip_confirm

當(dāng)數(shù)據(jù)包要離開Linux時(shí),它會穿過NF_IP_POST_ROUTING Hook點(diǎn),狀態(tài)跟蹤模塊在這里注冊了ip_refrag函數(shù):

  1. static unsigned int ip_refrag(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 rtable *rt = (struct rtable *)(*pskb)->dst;

  8.         /* ip_confirm函數(shù)用于處理將tuple加入hash表等重要的后續(xù)處理 */
  9.         if (ip_confirm(hooknum, pskb, in, out, okfn) != NF_ACCEPT)
  10.                 return NF_DROP;

  11.         /* 在連接跟蹤開始之前,對分片包進(jìn)行了重組,這里判斷數(shù)據(jù)包是否需要分片,如果要分片,就調(diào)用ip_fragment分片函數(shù)將數(shù)據(jù)包分片發(fā)送出去,因?yàn)閿?shù)據(jù)包已經(jīng)被發(fā)送走了,所以,在它之后的任何Hook函數(shù)已經(jīng)沒有意思了 */
  12.         if ((*pskb)->len > dst_mtu(&rt->u.dst) &&
  13.             !skb_shinfo(*pskb)->tso_size) {
  14.                 /* No hook can be after us, so this should be OK. */
  15.                 ip_fragment(*pskb, okfn);
  16.                 return NF_STOLEN;
  17.         }
  18.         return NF_ACCEPT;
  19. }
復(fù)制代碼


ip_confirm 函數(shù)是狀態(tài)跟蹤的另一個(gè)重要的函數(shù):

  1. static unsigned int ip_confirm(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.         /* We've seen it coming out the other side: confirm it */
  8.         return ip_conntrack_confirm(pskb);
  9. }
復(fù)制代碼

        函數(shù)僅是轉(zhuǎn)向,將控制權(quán)轉(zhuǎn)交給ip_conntrack_confirm函數(shù):

  1. /* Confirm a connection: returns NF_DROP if packet must be dropped. */
  2. static inline int ip_conntrack_confirm(struct sk_buff **pskb)
  3. {
  4.         if ((*pskb)->nfct
  5.             && !is_confirmed((struct ip_conntrack *)(*pskb)->nfct))
  6.                 return __ip_conntrack_confirm(pskb);
  7.         return NF_ACCEPT;
  8. }
復(fù)制代碼



is_comfirmed函數(shù)用于判斷數(shù)據(jù)包是否已經(jīng)被__ip_conntrack_confirm函數(shù)處理過了,它是通過IPS_CONFIRMED_BIT 標(biāo)志位來判斷,而這個(gè)標(biāo)志位當(dāng)然是在__ip_conntrack_confirm函數(shù)中來設(shè)置的:

  1. /* Confirm a connection given skb; places it in hash table */
  2. int
  3. __ip_conntrack_confirm(struct sk_buff **pskb)
  4. {
  5.         unsigned int hash, repl_hash;
  6.         struct ip_conntrack *ct;
  7.         enum ip_conntrack_info ctinfo;

  8.         /*取得數(shù)據(jù)包的連接狀態(tài)*/
  9.         ct = ip_conntrack_get(*pskb, &ctinfo);

  10.         /* 如果當(dāng)前包不是一個(gè)初始方向的封包,則直接返回. */
  11.         if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
  12.                 return NF_ACCEPT;

  13. /*計(jì)算初始及應(yīng)答兩個(gè)方向tuple對應(yīng)的hash值*/
  14.         hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
  15.         repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);

  16.         /* We're not in hash table, and we refuse to set up related
  17.            connections for unconfirmed conns.  But packet copies and
  18.            REJECT will give spurious warnings here. */
  19.         /* IP_NF_ASSERT(atomic_read(&ct->ct_general.use) == 1); */

  20.         /* No external references means noone else could have
  21.            confirmed us. */
  22.         IP_NF_ASSERT(!is_confirmed(ct));
  23.         DEBUGP("Confirming conntrack %p\n", ct);

  24.         WRITE_LOCK(&ip_conntrack_lock);

  25.         /* 在hash表中查找初始及應(yīng)答的節(jié)點(diǎn)*/
  26.         if (!LIST_FIND(&ip_conntrack_hash[hash],
  27.                        conntrack_tuple_cmp,
  28.                        struct ip_conntrack_tuple_hash *,
  29.                        &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, NULL)
  30.             && !LIST_FIND(&ip_conntrack_hash[repl_hash],
  31.                           conntrack_tuple_cmp,
  32.                           struct ip_conntrack_tuple_hash *,
  33.                           &ct->tuplehash[IP_CT_DIR_REPLY].tuple, NULL)) {
  34.                 /* Remove from unconfirmed list */
  35.                 list_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list);

  36.                 /*將當(dāng)前連接(初始和應(yīng)答的tuple)添加進(jìn)hash表*/
  37.                 list_prepend(&ip_conntrack_hash[hash],
  38.                              &ct->tuplehash[IP_CT_DIR_ORIGINAL]);
  39.                 list_prepend(&ip_conntrack_hash[repl_hash],
  40.                              &ct->tuplehash[IP_CT_DIR_REPLY]);
  41.                 /* Timer relative to confirmation time, not original
  42.                    setting time, otherwise we'd get timer wrap in
  43.                    weird delay cases. */
  44.                 ct->timeout.expires += jiffies;
  45.                 add_timer(&ct->timeout);
  46.                 atomic_inc(&ct->ct_general.use);
  47.                 set_bit(IPS_CONFIRMED_BIT, &ct->status);
  48.                 CONNTRACK_STAT_INC(insert);
  49.                 WRITE_UNLOCK(&ip_conntrack_lock);
  50.                 return NF_ACCEPT;
  51.         }

  52.         CONNTRACK_STAT_INC(insert_failed);
  53.         WRITE_UNLOCK(&ip_conntrack_lock);

  54.         return NF_DROP;
  55. }
復(fù)制代碼

論壇徽章:
0
3 [報(bào)告]
發(fā)表于 2006-08-21 18:47 |只看該作者
哈,九賤兄要將 netfilter 和 iptables 玩到通通透啊

論壇徽章:
0
4 [報(bào)告]
發(fā)表于 2006-08-22 08:50 |只看該作者
原帖由 platinum 于 2006-8-21 18:47 發(fā)表
哈,九賤兄要將 netfilter 和 iptables 玩到通通透啊


還沒有呢,ALG的代碼還沒有整理出來,這個(gè)狀態(tài)檢測,只是普通的狀態(tài)檢測的實(shí)現(xiàn),如ICMP,UDP,麻煩點(diǎn)的TCP的處理,ALG,如FTP協(xié)議的處理,都只是拿下了原理,框架,寫了筆記,有空的時(shí)候,就把代碼分析繼續(xù)貼上來,呵呵。

歡迎大家回貼討論!

論壇徽章:
0
5 [報(bào)告]
發(fā)表于 2006-08-22 13:47 |只看該作者
http://www.kernelchina.org/linux ... 8%BD%AC%E6%8D%A2%22,前些天從網(wǎng)上看到的,感覺不錯,大家可以互相學(xué)習(xí)一下。

論壇徽章:
0
6 [報(bào)告]
發(fā)表于 2006-08-24 14:04 |只看該作者
ALG是啥?

論壇徽章:
0
7 [報(bào)告]
發(fā)表于 2006-08-24 20:43 |只看該作者
原帖由 chenyajun5 于 2006-8-24 14:04 發(fā)表
ALG是啥?


ALG:application level gateway
一種在安全設(shè)備里面分析和修改應(yīng)用層協(xié)議內(nèi)容的技術(shù)
用于創(chuàng)建動態(tài)連接和修改協(xié)議內(nèi)容。

這里僅指的連接跟蹤中對動態(tài)協(xié)議,如FTP的支持;

論壇徽章:
0
8 [報(bào)告]
發(fā)表于 2006-08-25 10:12 |只看該作者

好文!

順便問個(gè)問題: 連接跟蹤表的條目容量,可以在加載ip_conntrack時(shí)通過hashsize來指定,但是如果把這個(gè)模塊編譯進(jìn)內(nèi)核,是否就無法制定hashsize了?如果要加大連接跟蹤表的容量,直接修改 ip_conntrack_max有用嗎?跟指定ip_conntrack模塊的hashsize有何區(qū)別?

論壇徽章:
0
9 [報(bào)告]
發(fā)表于 2006-08-26 12:26 |只看該作者
原帖由 急不通 于 2006-8-25 10:12 發(fā)表
順便問個(gè)問題: 連接跟蹤表的條目容量,可以在加載ip_conntrack時(shí)通過hashsize來指定,但是如果把這個(gè)模塊編譯進(jìn)內(nèi)核,是否就無法制定hashsize了?如果要加大連接跟蹤表的容量,直接修改 ip_conntrack_max有用嗎? ...


只有直接改源碼了……

論壇徽章:
0
10 [報(bào)告]
發(fā)表于 2006-09-06 16:16 |只看該作者
路過看看
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報(bào)專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP