免费高清特黄a大片,九一h片在线免费看,a免费国产一级特黄aa大,国产精品国产主播在线观看,成人精品一区久久久久,一级特黄aa大片,俄罗斯无遮挡一级毛片

分享

Netfilter 連接跟蹤與狀態(tài)檢測的實(shí)現(xiàn)分析

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

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

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

/* Connections have two entries in the hash table: one for each way */
struct ip_conntrack_tuple_hash
{
        struct list_head list;

        struct ip_conntrack_tuple tuple;
};

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

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

        struct {
                u_int16_t port;
        } tcp;
        struct {
                u_int16_t port;
        } udp;
        struct {
                u_int16_t id;
        } icmp;
        struct {
                u_int16_t port;
        } sctp;
};



/* The manipulable part of the tuple. */
struct ip_conntrack_manip
{
        u_int32_t ip;
        union ip_conntrack_manip_proto u;
};



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

        /* These are the parts of the tuple which are fixed. */
        struct {
                u_int32_t ip;
                union {
                        /* Add other protocols here. */
                        u_int16_t all;

                        struct {
                                u_int16_t port;
                        } tcp;
                        struct {
                                u_int16_t port;
                        } udp;
                        struct {
                                u_int8_t type, code;
                        } icmp;
                        struct {
                                u_int16_t port;
                        } sctp;
                } u;

                /* The protocol. */
                u_int8_t protonum;

                /* The direction (for tuplehash) */
                u_int8_t dir;
        } dst;
};

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

tuple 結(jié)構(gòu)僅僅是一個數(shù)據(jù)包的轉(zhuǎn)換,并不是描述一條完整的連接狀態(tài),內(nèi)核中,描述一個包的連接狀態(tài),使用了struct ip_conntrack 結(jié)構(gòu),可以在ip_conntrack.h中看到它的定義:

struct ip_conntrack
{
        ……
        /* These are my tuples; original and reply */
        struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
};

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

如圖,小結(jié)一下:
n        整個hash表用ip_conntrack_hash 指針數(shù)組來描述,它包含了ip_conntrack_htable_size個元素,用戶態(tài)可以在模塊插入時傳遞,默認(rèn)是根據(jù)內(nèi)存大小計算出來的;
n        整個連接跟蹤表的大小使用全局變量ip_conntrack_max描述,與hash表的關(guān)系是ip_conntrack_max = 8 * ip_conntrack_htable_size;
n        hash鏈表的每一個節(jié)點(diǎn)是一個struct ip_conntrack_tuple_hash結(jié)構(gòu),它有兩個成員,一個是list,一個是tuple;
n        Netfilter將每一個數(shù)據(jù)包轉(zhuǎn)換成tuple,再根據(jù)tuple計算出hash值,這樣,就可以使用ip_conntrack_hash[hash_id]找到hash表中鏈表的入口,并組織鏈表;
n        找到hash表中鏈表入口后,如果鏈表中不存在此“tuple”,則是一個新連接,就把tuple插入到鏈表的合適位置;
n        圖中兩個節(jié)點(diǎn)tuple[ORIGINAL]和tuple[REPLY],雖然是分開的,在兩個鏈表當(dāng)中,但是如前所述,它們同時又被封裝在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é)議提供了一個巧秒的解決辦法,我們在本章中,先分析連接跟蹤的基本實(shí)現(xiàn),然后再來分析Netfilter對這些特殊的協(xié)議的支持的實(shí)現(xiàn)。

3.連接跟蹤的初始化

3.1 初始化函數(shù)
ip_conntrack_standalone.c 是連接跟蹤的主要模塊:

static int __init init(void)
{
        return init_or_cleanup(1);
}

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

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

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

/*為連接跟蹤注冊Hook */
        ret = nf_register_hook(&ip_conntrack_defrag_ops);
        if (ret < 0) {
                printk("ip_conntrack: can't register pre-routing defrag hook.\n");
                goto cleanup_proc_stat;
        }
        ……
}

3.2 ip_conntrack_init

ip_conntrack_init 函數(shù)用于初始化連接跟蹤的包括hash表相關(guān)參數(shù)在內(nèi)一些重要的變量:

/*用戶態(tài)可以在模塊插入的時候,可以使用hashsize參數(shù),指明hash 表的大小*/
static int hashsize;
module_param(hashsize, int, 0400);

int __init ip_conntrack_init(void)
{
        unsigned int i;
        int ret;

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

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

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

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

        /*為hash表分配連續(xù)內(nèi)存頁*/
        ip_conntrack_hash
                =(void*)__get_free_pages(GFP_KERNEL,
                                         get_order(sizeof(struct list_head)
                                                   *ip_conntrack_htable_size));
        /*分配失敗,嘗試調(diào)用vmalloc重新分配*/
if (!ip_conntrack_hash) {
                ip_conntrack_vmalloc = 1;
                printk(KERN_WARNING "ip_conntrack: falling back to vmalloc.\n");
                ip_conntrack_hash = vmalloc(sizeof(struct list_head)
                                            * ip_conntrack_htable_size);
        }
        /*仍然分配失敗*/
        if (!ip_conntrack_hash) {
                printk(KERN_ERR "Unable to create ip_conntrack_hash\n");
                goto err_unreg_sockopt;
        }

        ip_conntrack_cachep = kmem_cache_create("ip_conntrack",
                                                sizeof(struct ip_conntrack), 0,
                                                0, NULL, NULL);
        if (!ip_conntrack_cachep) {
                printk(KERN_ERR "Unable to create ip_conntrack slab cache\n");
                goto err_free_hash;
        }

        ip_conntrack_expect_cachep = kmem_cache_create("ip_conntrack_expect",
                                        sizeof(struct ip_conntrack_expect),
                                        0, 0, NULL, NULL);
        if (!ip_conntrack_expect_cachep) {
                printk(KERN_ERR "Unable to create ip_expect slab cache\n");
                goto err_free_conntrack_slab;
        }

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

        /* For use by ipt_REJECT */
        ip_ct_attach = ip_conntrack_attach;

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

        return ret;

err_free_conntrack_slab:
        kmem_cache_destroy(ip_conntrack_cachep);
err_free_hash:
        free_conntrack_hash();
err_unreg_sockopt:
        nf_unregister_sockopt(&so_getorigdst);

        return -ENOMEM;
}

在這個函數(shù)中,有兩個重點(diǎn)的地方值得注意,一個是hash表的相關(guān)變量的初始化、內(nèi)存空間的分析等等,另一個是協(xié)議的注冊。
        連接跟蹤由于針對每種協(xié)議的處理,都有些細(xì)微不同的地方,舉個例子,我們前面講到數(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三個該類型的全局變量,初始化函數(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不同,注冊了多個鉤子:

        ret = nf_register_hook(&ip_conntrack_defrag_ops);
        if (ret < 0) {
                printk("ip_conntrack: can't register pre-routing defrag hook.\n");
                goto cleanup_proc_stat;
        }
        ret = nf_register_hook(&ip_conntrack_defrag_local_out_ops);
        if (ret < 0) {
                printk("ip_conntrack: can't register local_out defrag hook.\n");
                goto cleanup_defragops;
        }
        ……

整個Hook注冊好后,如下圖所示:


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

static unsigned int ip_conntrack_defrag(unsigned int hooknum,
                                        struct sk_buff **pskb,
                                        const struct net_device *in,
                                        const struct net_device *out,
                                        int (*okfn)(struct sk_buff *))
{
        /* Gather fragments. */
        if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
                *pskb = ip_ct_gather_frags(*pskb,
                                           hooknum == NF_IP_PRE_ROUTING ?
                                           IP_DEFRAG_CONNTRACK_IN :
                                           IP_DEFRAG_CONNTRACK_OUT);
                if (!*pskb)
                        return NF_STOLEN;
        }
        return NF_ACCEPT;
}

對于我們本章的分析而言,主要是以“Linux做為一個網(wǎng)關(guān)主機(jī),轉(zhuǎn)發(fā)過往數(shù)據(jù)”為主線,更多關(guān)注的是在NF_IP_PRE_ROUTING和NF_IP_POSTROUTING兩個Hook點(diǎn)上注冊的兩個鉤子函數(shù)ip_conntrack_in和ip_refrag(這個函數(shù)主要執(zhí)行的是ip_confirm函數(shù))。
        鉤子的注冊的另一個值得注意的小問題,就是鉤子函數(shù)的優(yōu)先級,NF_IP_PRE_ROUTING上的優(yōu)先級是NF_IP_PRI_CONNTRACK ,意味著它的優(yōu)先級是很高的,這也意味著每個輸入數(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,并初始化它,然后,為這個數(shù)據(jù)包設(shè)置連接狀態(tài)。

/* Netfilter hook itself. */
unsigned int ip_conntrack_in(unsigned int hooknum,
                             struct sk_buff **pskb,
                             const struct net_device *in,
                             const struct net_device *out,
                             int (*okfn)(struct sk_buff *))
{
        struct ip_conntrack *ct;
        enum ip_conntrack_info ctinfo;
        struct ip_conntrack_protocol *proto;
        int set_reply;
        int ret;

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

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

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

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

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

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

        if (IS_ERR(ct)) {
                /* Too stressed to deal. */
                CONNTRACK_STAT_INC(drop);
                return NF_DROP;
        }

        IP_NF_ASSERT((*pskb)->nfct);

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

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

        return ret;
}

在初始化的時候,我們就提過,連接跟蹤模塊將所有支持的協(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)一步處理。
5.resolve_normal_ct
        resolve_normal_ct 函數(shù)是連接跟蹤中最重要的函數(shù)之一,它的主要功能就是判斷數(shù)據(jù)包在連接跟蹤表是否存在,如果不存在,則為數(shù)據(jù)包分配相應(yīng)的連接跟蹤節(jié)點(diǎn)空間并初始化,然后設(shè)置連接狀態(tài):

/* On success, returns conntrack ptr, sets skb->nfct and ctinfo */
static inline struct ip_conntrack *
resolve_normal_ct(struct sk_buff *skb,
                  struct ip_conntrack_protocol *proto,
                  int *set_reply,
                  unsigned int hooknum,
                  enum ip_conntrack_info *ctinfo)
{
        struct ip_conntrack_tuple tuple;
        struct ip_conntrack_tuple_hash *h;
        struct ip_conntrack *ct;

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

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

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

        /* 判斷連接的方向 */
        if (DIRECTION(h) == IP_CT_DIR_REPLY) {
                *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
                /* Please set reply bit if this packet OK */
                *set_reply = 1;
        } else {
                /* Once we've had two way comms, always ESTABLISHED. */
                if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
                        DEBUGP("ip_conntrack_in: normal packet for %p\n",
                               ct);
                        *ctinfo = IP_CT_ESTABLISHED;
                } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
                        DEBUGP("ip_conntrack_in: related packet for %p\n",
                               ct);
                        *ctinfo = IP_CT_RELATED;
                } else {
                        DEBUGP("ip_conntrack_in: new packet for %p\n",
                               ct);
                        *ctinfo = IP_CT_NEW;
                }
                *set_reply = 0;
        }
/*設(shè)置skb的對應(yīng)成員,如使用計數(shù)器、數(shù)據(jù)包狀態(tài)標(biāo)記*/
        skb->nfct = &ct->ct_general;
        skb->nfctinfo = *ctinfo;
        return ct;
}

這個函數(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)換,這個轉(zhuǎn)換,主要是根據(jù)數(shù)據(jù)包的套接字對來進(jìn)行轉(zhuǎn)換的:

int ip_ct_get_tuple(const struct iphdr *iph,
                const struct sk_buff *skb,
                unsigned int dataoff,
                struct ip_conntrack_tuple *tuple,
                const struct ip_conntrack_protocol *protocol)
{
                /* Never happen */
                if (iph->frag_off & htons(IP_OFFSET)) {
                        printk("ip_conntrack_core: Frag of proto %u.\n",
                       iph->protocol);
                        return 0;
        }
/*設(shè)置來源、目的地址*/
                tuple->src.ip = iph->saddr;
                tuple->dst.ip = iph->daddr;
        tuple->dst.protonum = iph->protocol;
                tuple->dst.dir = IP_CT_DIR_ORIGINAL;

        return protocol->pkt_to_tuple(skb, dataoff, tuple);
}

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

static int tcp_pkt_to_tuple(const struct sk_buff *skb,
                            unsigned int dataoff,
                            struct ip_conntrack_tuple *tuple)
{
                struct tcphdr _hdr, *hp;

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

        return 1;
}

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

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

unsigned int hash = hash_conntrack(tuple);

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

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

        return h;
}

鏈表是內(nèi)核中一個標(biāo)準(zhǔn)的雙向鏈表,可以調(diào)用宏list_for_each_entry 進(jìn)遍歷鏈表:

static struct ip_conntrack_tuple_hash *
__ip_conntrack_find(const struct ip_conntrack_tuple *tuple,
                    const struct ip_conntrack *ignored_conntrack)
{
        struct ip_conntrack_tuple_hash *h;
        unsigned int hash = hash_conntrack(tuple);

        MUST_BE_READ_LOCKED(&ip_conntrack_lock);
        list_for_each_entry(h, &ip_conntrack_hash[hash], list) {
                if (conntrack_tuple_cmp(h, tuple, ignored_conntrack)) {
                        CONNTRACK_STAT_INC(found);
                        return h;
                }
                CONNTRACK_STAT_INC(searched);
        }

        return NULL;
}

list_for_each_entry在以&ip_conntrack_hash[hash]為起始地址的鏈表中,逐個搜索其成員,比較這個節(jié)點(diǎn)中的tuple是否與待查找的tuple是否一致,這個比較過程,是通過conntrack_tuple_cmp 函數(shù)實(shí)現(xiàn)的:

conntrack_tuple_cmp(const struct ip_conntrack_tuple_hash *i,
                    const struct ip_conntrack_tuple *tuple,
                    const struct ip_conntrack *ignored_conntrack)
{
        MUST_BE_READ_LOCKED(&ip_conntrack_lock);
        return tuplehash_to_ctrack(i) != ignored_conntrack
                && ip_ct_tuple_equal(tuple, &i->tuple);
}

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

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

static inline int ip_ct_tuple_dst_equal(const struct ip_conntrack_tuple *t1,
                                        const struct ip_conntrack_tuple *t2)
{
        return t1->dst.ip == t2->dst.ip
                && t1->dst.u.all == t2->dst.u.all
                && t1->dst.protonum == t2->dst.protonum;
}

static inline int ip_ct_tuple_equal(const struct ip_conntrack_tuple *t1,
                                    const struct ip_conntrack_tuple *t2)
{
        return ip_ct_tuple_src_equal(t1, t2) && ip_ct_tuple_dst_equal(t1, t2);
}

這里的比較,除了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)來描述一個數(shù)據(jù)包的連接狀態(tài),init_conntrack函數(shù)就是在連接狀態(tài)表中不存在當(dāng)前數(shù)據(jù)包時,初始化一個ip_conntrack結(jié)構(gòu),此結(jié)構(gòu)被Netfilter用來描述一條連接,前面分析hash表時,已經(jīng)分析了它的tuplehash成員:

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

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

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

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

        /* Current number of expected connections */
        unsigned int expecting;

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

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

        union ip_conntrack_help help;

#ifdef CONFIG_IP_NF_NAT_NEEDED
        struct {
                struct ip_nat_info info;
#if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \
        defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)
                int masq_index;
#endif
        } nat;
#endif /* CONFIG_IP_NF_NAT_NEEDED */

#if defined(CONFIG_IP_NF_CONNTRACK_MARK)
        unsigned long mark;
#endif

        struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
};


static struct ip_conntrack_tuple_hash *
init_conntrack(const struct ip_conntrack_tuple *tuple,
               struct ip_conntrack_protocol *protocol,
               struct sk_buff *skb)
{
        struct ip_conntrack *conntrack;
        struct ip_conntrack_tuple repl_tuple;
        size_t hash;
        struct ip_conntrack_expect *exp;

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

        /*計算hash值*/
        hash = hash_conntrack(tuple);
       
        /*判斷連接跟蹤表是否已滿*/
        if (ip_conntrack_max
            && atomic_read(&ip_conntrack_count) >= ip_conntrack_max) {
                /* Try dropping from this hash chain. */
                if (!early_drop(&ip_conntrack_hash[hash])) {
                        if (net_ratelimit())
                                printk(KERN_WARNING
                                       "ip_conntrack: table full, dropping"
                                       " packet.\n");
                        return ERR_PTR(-ENOMEM);
                }
        }

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

        WRITE_LOCK(&ip_conntrack_lock);
        exp = find_expectation(tuple);

        if (exp) {
                DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",
                        conntrack, exp);
                /* Welcome, Mr. Bond.  We've been expecting you... */
                __set_bit(IPS_EXPECTED_BIT, &conntrack->status);
                conntrack->master = exp->master;
#if CONFIG_IP_NF_CONNTRACK_MARK
                conntrack->mark = exp->master->mark;
#endif
                nf_conntrack_get(&conntrack->master->ct_general);
                CONNTRACK_STAT_INC(expect_new);
        } else {
                conntrack->helper = ip_ct_find_helper(&repl_tuple);

                CONNTRACK_STAT_INC(new);
        }

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

        atomic_inc(&ip_conntrack_count);
        WRITE_UNLOCK(&ip_conntrack_lock);

        if (exp) {
                if (exp->expectfn)
                        exp->expectfn(conntrack, exp);
                destroy_expect(exp);
        }

        /*返回的是初始方向的hash節(jié)點(diǎn)*/
        return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
}

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

在這個函數(shù)中,有三個重要的地方需要注意,一個是根據(jù)當(dāng)前tuple,計算出應(yīng)答方向的tuple,它是調(diào)用ip_ct_invert_tuple 函數(shù)實(shí)現(xiàn)的:

int
ip_ct_invert_tuple(struct ip_conntrack_tuple *inverse,
                   const struct ip_conntrack_tuple *orig,
                   const struct ip_conntrack_protocol *protocol)
{
        inverse->src.ip = orig->dst.ip;
        inverse->dst.ip = orig->src.ip;
        inverse->dst.protonum = orig->dst.protonum;
        inverse->dst.dir = !orig->dst.dir;

        return protocol->invert_tuple(inverse, orig);
}

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

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

第三個地方是調(diào)用協(xié)議的new函數(shù):
        if (!protocol->new(conntrack, skb)) {
                kmem_cache_free(ip_conntrack_cachep, conntrack);
                return NULL;
        }
new 函數(shù)指定在每個封包第一次創(chuàng)建連接時被調(diào)用,它根據(jù)協(xié)議的不同,所處理的過程不同,以ICMP協(xié)議為例:

/* Called when a new connection for this protocol found. */
static int icmp_new(struct ip_conntrack *conntrack,
                    const struct sk_buff *skb)
{
        static u_int8_t valid_new[]
                = { [ICMP_ECHO] = 1,
                    [ICMP_TIMESTAMP] = 1,
                    [ICMP_INFO_REQUEST] = 1,
                    [ICMP_ADDRESS] = 1 };

        if (conntrack->tuplehash[0].tuple.dst.u.icmp.type >= sizeof(valid_new)
            || !valid_new[conntrack->tuplehash[0].tuple.dst.u.icmp.type]) {
                /* Can't create a new ICMP `conn' with this. */
                DEBUGP("icmp: can't create new conn with type %u\n",
                       conntrack->tuplehash[0].tuple.dst.u.icmp.type);
                DUMP_TUPLE(&conntrack->tuplehash[0].tuple);
                return 0;
        }
        atomic_set(&conntrack->proto.icmp.count, 0);
        return 1;
}

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

5.4 連接狀態(tài)的判斷
resolve_normal_ct 函數(shù)的最后一個重要的工作是對連接狀態(tài)的判斷,tuple中包含一個“方向”成員dst.dir,對于一個初始連接,它是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都是枚舉變量:

enum ip_conntrack_dir
{
        IP_CT_DIR_ORIGINAL,
        IP_CT_DIR_REPLY,
        IP_CT_DIR_MAX
};

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

        /* 如果是一個應(yīng)答包 ,設(shè)置狀態(tài)為已建+應(yīng)答*/
        if (DIRECTION(h) == IP_CT_DIR_REPLY) {
                *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
                /* 設(shè)置應(yīng)答標(biāo)志變量 */
                *set_reply = 1;
        } else {
                /* 新建連接方過來的數(shù)據(jù)包,對面向連接的協(xié)議而言,可能是一個已建連接,判斷其標(biāo)志位*/
                if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
                        DEBUGP("ip_conntrack_in: normal packet for %p\n",
                               ct);
                        *ctinfo = IP_CT_ESTABLISHED;
                } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
                        DEBUGP("ip_conntrack_in: related packet for %p\n",
                               ct);
                        *ctinfo = IP_CT_RELATED;                        //關(guān)聯(lián)連接
                } else {
                        DEBUGP("ip_conntrack_in: new packet for %p\n",
                               ct);
                        *ctinfo = IP_CT_NEW;                                //否則,則為一個新建連接
                }
                *set_reply = 0;
        }
       
        /*設(shè)置數(shù)據(jù)包skb與連接狀態(tài)的關(guān)聯(lián)*/
        skb->nfct = &ct->ct_general;
        /*每個sk_buff都將與ip_conntrack的一個狀態(tài)關(guān)聯(lián),所以從sk_buff可以得到相應(yīng)ip_conntrack的狀態(tài),即數(shù)據(jù)包的狀態(tài)*/
        skb->nfctinfo = *ctinfo;
        return ct;

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


6.        ip_confirm

以上的工作事實(shí)上都很簡單,基本思路是:
一個包來了,轉(zhuǎn)換其tuple,看其在連接跟蹤表中沒有,有的話,更新其狀態(tài),以其做一些與協(xié)議相關(guān)的工作,如果沒有,則分配一個新的連接表項(xiàng),并與skb_buff關(guān)連,但是問題是,這個表項(xiàng),還沒有被加入連接表當(dāng)中來。其實(shí)這樣做的理由很簡單,因?yàn)檫@個時候,這個包是否有機(jī)會活命還是個未知數(shù),例如被其它模塊給Drop了……所以,要等到一切安全了,再來將這個表項(xiàng)插入至連接跟蹤表。
這個“一切安全”當(dāng)然是Netfilter所有的模塊處理完了,最完全了。

當(dāng)數(shù)據(jù)包要離開Netfilter時,它會穿過NF_IP_POST_ROUTING Hook點(diǎn),狀態(tài)跟蹤模塊在這里注冊了ip_refrag函數(shù)(前面談到過它的優(yōu)先級是很低的)。這個Hook函數(shù)的工作,也可以猜測到了:“判斷表項(xiàng)是否已經(jīng)在連接跟蹤表中了,如果沒有,就將其插入表中”!

static unsigned int ip_refrag(unsigned int hooknum,
                              struct sk_buff **pskb,
                              const struct net_device *in,
                              const struct net_device *out,
                              int (*okfn)(struct sk_buff *))
{
        struct rtable *rt = (struct rtable *)(*pskb)->dst;

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

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

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

static unsigned int ip_confirm(unsigned int hooknum,
                               struct sk_buff **pskb,
                               const struct net_device *in,
                               const struct net_device *out,
                               int (*okfn)(struct sk_buff *))
{
        return ip_conntrack_confirm(pskb);
}

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

static inline int ip_conntrack_confirm(struct sk_buff **pskb)
{
        if ((*pskb)->nfct
            && !is_confirmed((struct ip_conntrack *)(*pskb)->nfct))
                return __ip_conntrack_confirm(pskb);
        return NF_ACCEPT;
}

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

[code
int
__ip_conntrack_confirm(struct sk_buff **pskb)
{
        unsigned int hash, repl_hash;
        struct ip_conntrack *ct;
        enum ip_conntrack_info ctinfo;

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

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

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

        /* IP_NF_ASSERT(atomic_read(&ct->ct_general.use) == 1); */

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

        WRITE_LOCK(&ip_conntrack_lock);

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

                /*主要的工作就在于此了:將當(dāng)前連接表項(xiàng)(初始和應(yīng)答的tuple)添加進(jìn)hash表*/
                list_prepend(&ip_conntrack_hash[hash],
                             &ct->tuplehash[IP_CT_DIR_ORIGINAL]);
                list_prepend(&ip_conntrack_hash[repl_hash],
                             &ct->tuplehash[IP_CT_DIR_REPLY]);
                /* Timer relative to confirmation time, not original
                   setting time, otherwise we'd get timer wrap in
                   weird delay cases. */
                ct->timeout.expires += jiffies;
                add_timer(&ct->timeout);
                atomic_inc(&ct->ct_general.use);
                set_bit(IPS_CONFIRMED_BIT, &ct->status);
                CONNTRACK_STAT_INC(insert);
                WRITE_UNLOCK(&ip_conntrack_lock);
                return NF_ACCEPT;
        }

        CONNTRACK_STAT_INC(insert_failed);
        WRITE_UNLOCK(&ip_conntrack_lock);

        return NF_DROP;
}[/code]

這樣,一條新建連接就被加入到表項(xiàng)當(dāng)中了。如果其有后續(xù)連接,如應(yīng)答,進(jìn)入連接跟蹤表,又轉(zhuǎn)換其tuple,然后查到此表項(xiàng),循環(huán)中……


    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多