小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

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

 womking 2009-04-12
Netfilter 連接跟蹤與狀態(tài)檢測(cè)的實(shí)現(xiàn)分析
 
作者:九賤
www.skynet.org.cn
原創(chuàng),歡迎轉(zhuǎn)載,轉(zhuǎn)載,請(qǐng)注明出處

內(nèi)核版本:2.6.12

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

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

2.連接跟蹤表
Netfilter使用一張連接跟蹤表,來(lái)描述整個(gè)連接狀態(tài),這個(gè)表在實(shí)現(xiàn)算法上采用了hash算法。我們先來(lái)看看這個(gè)hash 表的實(shí)現(xiàn)。
整個(gè)hash表用全局指針ip_conntrack_hash 指針來(lái)描述,它定義在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ì)算出來(lái)的。
        每一個(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 類型:

/* 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;
};

這個(gè)結(jié)構(gòu)有兩個(gè)成員,list 成員用于組織鏈表。多元組(tuple) 則用于描述具體的數(shù)據(jù)包。
每個(gè)數(shù)據(jù)包最基本的要素,就是“來(lái)源”和“目的”,從Socket套接字角度來(lái)講,連接兩端用“地址+端口”的形式來(lái)唯一標(biāo)識(shí)一個(gè)連接(對(duì)于沒(méi)有端口的協(xié)議,如ICMP,可以使用其它辦法替代),所以,這個(gè)數(shù)據(jù)包就可以表示為“來(lái)源地址/來(lái)源端口+目的地址/目的端口”,Netfilter用結(jié)構(gòu)struct ip_conntrack_tuple 結(jié)構(gòu)來(lái)封裝這個(gè)“來(lái)源”和“目的”,封裝好的struct ip_conntrack_tuple結(jié)構(gòu)節(jié)點(diǎn)在內(nèi)核中就稱為“tuple”。最終實(shí)現(xiàn)“封裝”,就是根據(jù)來(lái)源/目的地址、端口這些要素,來(lái)進(jìn)行一個(gè)具體網(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兩個(gè)成員,這兩個(gè)成員基本一致:包含ip以及各個(gè)協(xié)議的端口,值得注意的是,dst成員中有一個(gè)dir成員,dir是direction 的縮寫(xiě),標(biāo)識(shí)一個(gè)連接的方向,后面我們會(huì)看到它的用法。

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中看到它的定義:

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)的最后一個(gè)成員tuplehash,它是一個(gè)struct ip_conntrack_tuple_hash 類型的數(shù)組,我們前面說(shuō)了,該結(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ù)包的套接字對(duì)轉(zhuǎn)換成一個(gè)“初始的”tuple,賦值給tuplehash[IP_CT_DIR_ORIGINAL],然后對(duì)這個(gè)數(shù)據(jù)包“取反”,計(jì)算出“應(yīng)答”的tuple,賦值給tuplehash[IP_CT_DIR_REPLY],這樣,一條完整的連接已經(jīng)躍然紙上了。
最后一要注意的問(wèn)題,就是對(duì)于每一條連接,尋找鏈表在hash表的入口,也就是如計(jì)算hash值。我們關(guān)心的是一條連接,連接是由“請(qǐng)求”和“應(yīng)答”的數(shù)據(jù)包組成,數(shù)據(jù)包會(huì)被轉(zhuǎn)化成tuple,所以,hash值就是根據(jù)tuple,通過(guò)一定的hash算法實(shí)現(xiàn),這樣,整個(gè)hash表如下圖所示:
         

如圖,小結(jié)一下:
n        整個(gè)hash表用ip_conntrack_hash 指針數(shù)組來(lái)描述,它包含了ip_conntrack_htable_size個(gè)元素,用戶態(tài)可以在模塊插入時(shí)傳遞,默認(rèn)是根據(jù)內(nèi)存大小計(jì)算出來(lái)的;
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],雖然是分開(kāi)的,在兩個(gè)鏈表當(dāng)中,但是如前所述,它們同時(shí)又被封裝在ip_conntrack結(jié)構(gòu)的tuplehash數(shù)組中,這在圖中,并沒(méi)有標(biāo)注出來(lái);
n        鏈表的組織采用的是雙向鏈表,上圖中沒(méi)有完整表示出來(lái);

        當(dāng)然,具體的實(shí)現(xiàn)要稍微麻煩一點(diǎn),主要體現(xiàn)在一些復(fù)雜的應(yīng)用層協(xié)議上來(lái),例如主動(dòng)模式下的FTP協(xié)議,服務(wù)器在連接建立后,會(huì)主動(dòng)打開(kāi)高端口與客戶端進(jìn)行通訊,這樣,由于端口變換了,我們前面說(shuō)的連接表的實(shí)現(xiàn)就會(huì)遇到麻煩。Netfilter為這些協(xié)議提供了一個(gè)巧秒的解決辦法,我們?cè)诒菊轮?,先分析連接跟蹤的基本實(shí)現(xiàn),然后再來(lái)分析Netfilter對(duì)這些特殊的協(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表的初始化等三個(gè)方面的工作:

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)的對(duì)應(yīng)節(jié)點(diǎn)*/
#ifdef CONFIG_PROC_FS
        ……
#endif

/*為連接跟蹤注冊(cè)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)可以在模塊插入的時(shí)候,可以使用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)存的大小,來(lái)計(jì)算一個(gè)默認(rèn)值. ,hash表的大小,是使用全局變量ip_conntrack_htable_size 來(lái)描述*/
        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表的大小,計(jì)算最大的連接跟蹤表數(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));
       
/*注冊(cè)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)識(shí)變量 */
        ip_conntrack_vmalloc = 0;

        /*為hash表分配連續(xù)內(nèi)存頁(yè)*/
        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);
       
/* 注冊(cè)協(xié)議。對(duì)不同協(xié)議,連接跟蹤記錄的參數(shù)不同,所以不同的協(xié)議定義了不同的 ip_conntrack_protocol結(jié)構(gòu)來(lái)處理與協(xié)議相關(guān)的內(nèi)容。這些結(jié)構(gòu)被注冊(cè)到一個(gè)全局的鏈表中,在使用時(shí)根據(jù)協(xié)議去查找,并調(diào)用相應(yīng)的處理函數(shù)來(lái)完成相應(yīng)的動(dò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;
}

在這個(gè)函數(shù)中,有兩個(gè)重點(diǎn)的地方值得注意,一個(gè)是hash表的相關(guān)變量的初始化、內(nèi)存空間的分析等等,另一個(gè)是協(xié)議的注冊(cè)。
        連接跟蹤由于針對(duì)每種協(xié)議的處理,都有些細(xì)微不同的地方,舉個(gè)例子,我們前面講到數(shù)據(jù)包至tuple的轉(zhuǎn)換,TCP的轉(zhuǎn)換與ICMP的轉(zhuǎn)換肯定不同的,因?yàn)镮CMP連端口的概念也沒(méi)有,所以,對(duì)于每種協(xié)議的一些特殊處理的函數(shù),需要進(jìn)行封裝,struct ip_conntrack_protocol 結(jié)構(gòu)就實(shí)現(xiàn)了這一封裝,在初始化工作中,針對(duì)最常見(jià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é)議值],找到注冊(cè)的協(xié)議節(jié)點(diǎn),就可以方便地調(diào)用協(xié)議對(duì)應(yīng)的處理函數(shù)了,我們?cè)诤竺鎸⒖吹竭@一調(diào)用過(guò)程。

3.2        鉤子函數(shù)的注冊(cè)
init_or_cleanup 函數(shù)在創(chuàng)建/proc文件系統(tǒng)完成后,會(huì)調(diào)用nf_register_hook 函數(shù)注冊(cè)鉤子,進(jìn)行連接跟蹤,按優(yōu)先級(jí)和Hook不同,注冊(cè)了多個(gè)鉤子:

        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;
        }
        ……

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


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

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

4.ip_conntrack_in

數(shù)據(jù)包進(jìn)入Netfilter后,會(huì)調(diào)用ip_conntrack_in函數(shù),以進(jìn)入連接跟蹤模塊,ip_conntrack_in 主要完成的工作就是判斷數(shù)據(jù)包是否已在連接跟蹤表中,如果不在,則為數(shù)據(jù)包分配ip_conntrack,并初始化它,然后,為這個(gè)數(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ù)包是否已被檢查過(guò)了 */
        if ((*pskb)->nfct) {
                CONNTRACK_STAT_INC(ignore);
                return NF_ACCEPT;
        }

/* 分片包當(dāng)會(huì)在前一個(gè)Hook中被處理,事實(shí)上,并不會(huì)觸發(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);

        /* 沒(méi)有找到對(duì)應(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 *類型指針,它用于描述一個(gè)數(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ù)包返回一個(gè)判斷,如果數(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;
}

在初始化的時(shí)候,我們就提過(guò),連接跟蹤模塊將所有支持的協(xié)議,都使用struct ip_conntrack_protocol 結(jié)構(gòu)封裝,注冊(cè)至全局?jǐn)?shù)組ip_ct_protos,這里首先調(diào)用函數(shù)ip_ct_find_proto根據(jù)當(dāng)前數(shù)據(jù)包的協(xié)議值,找到協(xié)議注冊(cè)對(duì)應(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);

/*前面提到過(guò),需要將一個(gè)數(shù)據(jù)包轉(zhuǎn)換成tuple,這個(gè)轉(zhuǎn)換,就是通過(guò)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ù)包對(duì)應(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ù)包對(duì)應(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的對(duì)應(yīng)成員,如使用計(jì)數(shù)器、數(shù)據(jù)包狀態(tài)標(biāo)記*/
        skb->nfct = &ct->ct_general;
        skb->nfctinfo = *ctinfo;
        return ct;
}

這個(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ù)包的套接字對(duì)來(lái)進(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è)置來(lái)源、目的地址*/
                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報(bào)頭*/
hp = skb_header_pointer(skb, dataoff, 8, &_hdr);
        if (hp == NULL)
                        return 0;
/*根據(jù)報(bào)頭的端口信息,設(shè)置tuple對(duì)應(yīng)成員*/
                tuple->src.u.tcp.port = hp->source;
        tuple->dst.u.tcp.port = hp->dest;

        return 1;
}

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

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

unsigned int hash = hash_conntrack(tuple);

        這樣,tuple對(duì)應(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)                /*查找到了,使用計(jì)數(shù)器累加*/
                atomic_inc(&tuplehash_to_ctrack(h)->ct_general.use);
        READ_UNLOCK(&ip_conntrack_lock);

        return h;
}

鏈表是內(nèi)核中一個(gè)標(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]為起始地址的鏈表中,逐個(gè)搜索其成員,比較這個(gè)節(jié)點(diǎn)中的tuple是否與待查找的tuple是否一致,這個(gè)比較過(guò)程,是通過(guò)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,對(duì)與這里的比較而言,ignored_conntrack傳遞過(guò)來(lái)的為NULL。
主要的比較函數(shù)是ip_ct_tuple_equal函數(shù),函數(shù)分為“來(lái)源”和“目的”進(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地址之外,并沒(méi)有直接比較“端口”,這是因?yàn)橄馡CMP協(xié)議這樣的并沒(méi)有“端口”協(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)來(lái)描述一個(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用來(lái)描述一條連接,前面分析hash表時(shí),已經(jīng)分析了它的tuplehash成員:

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

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

        /* 內(nèi)核的定時(shí)器,用于處理連接超時(shí) */
        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;

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

        /*計(jì)算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取反,計(jì)算該數(shù)據(jù)包的“應(yīng)答”的tuple*/
        if (!ip_ct_invert_tuple(&repl_tuple, tuple, protocol)) {
                DEBUGP("Can't invert tuple.\n");
                return NULL;
        }
        /*為數(shù)據(jù)包對(duì)應(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));
        /*使用計(jì)數(shù)器累加*/
        atomic_set(&conntrack->ct_general.use, 1);
        /*設(shè)置destroy函數(shù)指針*/
        conntrack->ct_general.destroy = destroy_conntrack;
        /*設(shè)置正反兩個(gè)方向的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í)間計(jì)數(shù)器,并設(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);
        }

        /* 這里,并沒(méi)有直接就把該連接加入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];
}

在前文中提到過(guò),一條完整的連接,采用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)的:

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);
}

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

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

第三個(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é)議的不同,所處理的過(guò)程不同,以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;
}

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

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

enum ip_conntrack_dir
{
        IP_CT_DIR_ORIGINAL,
        IP_CT_DIR_REPLY,
        IP_CT_DIR_MAX
};

宏DIRECTION 就根據(jù)tuple中對(duì)應(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è)連接過(guò)程中的“已建連接包”,另一個(gè),如FTP等 復(fù)雜協(xié)議,它們還存在一些“關(guān)聯(lián)”的連接,當(dāng)然這兩部份目前還沒(méi)有涉及到,但并不影響我們分析如下這段代碼:

        /* 如果是一個(gè)應(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 {
                /* 新建連接方過(guò)來(lái)的數(shù)據(jù)包,對(duì)面向連接的協(xié)議而言,可能是一個(gè)已建連接,判斷其標(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;                                //否則,則為一個(gè)新建連接
                }
                *set_reply = 0;
        }
       
        /*設(shè)置數(shù)據(jù)包skb與連接狀態(tài)的關(guān)聯(lián)*/
        skb->nfct = &ct->ct_general;
        /*每個(gè)sk_buff都將與ip_conntrack的一個(gè)狀態(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í)上都很簡(jiǎn)單,基本思路是:
一個(gè)包來(lái)了,轉(zhuǎn)換其tuple,看其在連接跟蹤表中沒(méi)有,有的話,更新其狀態(tài),以其做一些與協(xié)議相關(guān)的工作,如果沒(méi)有,則分配一個(gè)新的連接表項(xiàng),并與skb_buff關(guān)連,但是問(wèn)題是,這個(gè)表項(xiàng),還沒(méi)有被加入連接表當(dāng)中來(lái)。其實(shí)這樣做的理由很簡(jiǎn)單,因?yàn)檫@個(gè)時(shí)候,這個(gè)包是否有機(jī)會(huì)活命還是個(gè)未知數(shù),例如被其它模塊給Drop了……所以,要等到一切安全了,再來(lái)將這個(gè)表項(xiàng)插入至連接跟蹤表。
這個(gè)“一切安全”當(dāng)然是Netfilter所有的模塊處理完了,最完全了。

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

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;

        /* 在連接跟蹤開(kāi)始之前,對(duì)分片包進(jìn)行了重組,這里判斷數(shù)據(jù)包是否需要分片,如果要分片,就調(diào)用ip_fragment分片函數(shù)將數(shù)據(jù)包分片發(fā)送出去,因?yàn)閿?shù)據(jù)包已經(jīng)被發(fā)送走了,所以,在它之后的任何Hook函數(shù)已經(jīng)沒(méi)有意思了 */
        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)跟蹤的另一個(gè)重要的函數(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ù)處理過(guò)了,它是通過(guò)IPS_CONFIRMED_BIT 標(biāo)志位來(lái)判斷,而這個(gè)標(biāo)志位當(dāng)然是在__ip_conntrack_confirm函數(shù)中來(lái)設(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)前包不是一個(gè)初始方向的封包,則直接返回. */
        if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
                return NF_ACCEPT;

/*計(jì)算初始及應(yīng)答兩個(gè)方向tuple對(duì)應(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)中……


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

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多