ip_conntrack 實現

啓動時首先在ip_conntrack_standalone.c中調用
static int __init ip_conntrack_standalone_init(void) //proc相關部分省略
{
    ......
    int ret = 0;

    ret = ip_conntrack_init(); //大部分初始化工做
    if (ret < 0)
        return ret;

    ......
    //註冊hook函數,添加到二圍數組連表中, ip_conntrack_ops 定義看下面
    ret = nf_register_hooks(ip_conntrack_ops, ARRAY_SIZE(ip_conntrack_ops));
    if (ret < 0) {
        printk("ip_conntrack: can't register hooks.\n");
        goto cleanup_proc_stat;
    }
    ......
    return ret;
}
struct nf_sockopt_ops是在系統調用get/set sockopt中引用的數據結構, 實現用戶空間對規則的添加,刪除,修改,查詢等動做.以上的結構在使用之
前必須先註冊到系統中才能被引用
static struct nf_sockopt_ops so_getorigdst = {
    .pf             = PF_INET,
    .get_optmin     = SO_ORIGINAL_DST,
    .get_optmax     = SO_ORIGINAL_DST+1,
    .get            = &getorigdst,
};
int __init ip_conntrack_init(void)
{
    unsigned int i;
    int ret;

    if (!ip_conntrack_htable_size) { //全局變量,開始爲0
        //根據內存大小計算hash table大小
        ip_conntrack_htable_size = (((num_physpages << PAGE_SHIFT) / 16384) / sizeof(struct list_head));
        if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE)) //內存大於1G
            ip_conntrack_htable_size = 8192;

        if (ip_conntrack_htable_size < 16)
            ip_conntrack_htable_size = 16;
    }

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

    ret = nf_register_sockopt(&so_getorigdst); //添加結構到全局連表中
    if (ret != 0) {
        printk(KERN_ERR "Unable to register netfilter socket option\n");
        return ret;
    }
    //分配hash table內存,若是使用了vmalloc那麼ip_conntrack_vmalloc置1
    ip_conntrack_hash = alloc_hashtable(ip_conntrack_htable_size, &ip_conntrack_vmalloc);
    if (!ip_conntrack_cachep) {
        printk(KERN_ERR "Unable to create ip_conntrack slab cache\n");
        goto err_free_hash;
    }
    //expect高速緩存初始化
    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;
    }

    //conntrack對每種協議數據的處理,都有不一樣的地方,例如,tuple中提取的內容,TCP的與ICMP的確定不一樣的,由於ICMP連端口的概念也沒有,
    //因此,對於每種協議的一些特殊處理的函數,須要進行封裝,struct ip_conntrack_protocol 結構就實現了這一封裝,這樣,在之後的數據包處理後,
    //就能夠根據包中的協議值,使用ip_ct_protos[協議值],找到註冊的協議節點,調用協議對應的處理函數了
    write_lock_bh(&ip_conntrack_lock);
    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_bh(&ip_conntrack_lock);

    ip_ct_attach = ip_conntrack_attach; //ipt_REJECT使用
    //設置僞造的conntrack,從不刪除,也再也不任何hash table
    atomic_set(&ip_conntrack_untracked.ct_general.use, 1);
    //它仍是一個被證明的鏈接
    set_bit(IPS_CONFIRMED_BIT, &ip_conntrack_untracked.status);
    return ret;
    ......
}

NF_IP_PRE_ROUTING,在報文做路由之前執行;
NF_IP_FORWARD,在報文轉向另外一個NIC之前執行;
NF_IP_POST_ROUTING,在報文流出之前執行;
NF_IP_LOCAL_IN,在流入本地的報文做路由之後執行;
NF_IP_LOCAL_OUT,在本地報文作流出路由前執行;

#define INT_MAX               ((int)(~0U>>1))
#define INT_MIN               (-INT_MAX - 1)

enum nf_ip_hook_priorities {
    NF_IP_PRI_FIRST = INT_MIN,
    NF_IP_PRI_CONNTRACK_DEFRAG = -400,
    NF_IP_PRI_RAW = -300,
    NF_IP_PRI_SELINUX_FIRST = -225,
    NF_IP_PRI_CONNTRACK = -200,
    NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD = -175,
    NF_IP_PRI_MANGLE = -150,
    NF_IP_PRI_NAT_DST = -100,
    NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT = -50,
    NF_IP_PRI_FILTER = 0,
    NF_IP_PRI_NAT_SRC = 100,
    NF_IP_PRI_SELINUX_LAST = 225,
    NF_IP_PRI_CONNTRACK_HELPER = INT_MAX - 2,
    NF_IP_PRI_NAT_SEQ_ADJUST = INT_MAX - 1,
    NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
    NF_IP_PRI_LAST = INT_MAX,
};
NF_ACCEPT  :繼續正常的報文處理;
NF_DROP    :將報文丟棄;
NF_STOLEN  :由鉤子函數處理了該報文,不要再繼續傳送;
NF_QUEUE   :將報文入隊,一般交由用戶程序處理;
NF_REPEAT  :再次調用該鉤子函數。
NF_STOP     :中止檢測,再也不進行下一個Hook函數

static struct nf_hook_ops ip_conntrack_ops[] = {
    {
        .hook           = ip_conntrack_defrag,             //處理函數
        .owner          = THIS_MODULE,
        .pf             = PF_INET,                         //協議
        .hooknum        = NF_IP_PRE_ROUTING,           //5個hook類型之一
        .priority       = NF_IP_PRI_CONNTRACK_DEFRAG,  //權限,調用順序
    },
    {
        .hook           = ip_conntrack_in,
        .owner          = THIS_MODULE,
        .pf             = PF_INET,
        .hooknum        = NF_IP_PRE_ROUTING,
        .priority       = NF_IP_PRI_CONNTRACK,
    },
    {
        .hook           = ip_conntrack_defrag,
        .owner          = THIS_MODULE,
        .pf             = PF_INET,
        .hooknum        = NF_IP_LOCAL_OUT,
        .priority       = NF_IP_PRI_CONNTRACK_DEFRAG,
    },
    {
        .hook           = ip_conntrack_local,
        .owner          = THIS_MODULE,
        .pf             = PF_INET,
        .hooknum        = NF_IP_LOCAL_OUT,
        .priority       = NF_IP_PRI_CONNTRACK,
    },
    {
        .hook           = ip_conntrack_help,
        .owner          = THIS_MODULE,
        .pf             = PF_INET,
        .hooknum        = NF_IP_POST_ROUTING,
        .priority       = NF_IP_PRI_CONNTRACK_HELPER,
    },
    {
        .hook           = ip_conntrack_help,
        .owner          = THIS_MODULE,
        .pf             = PF_INET,
        .hooknum        = NF_IP_LOCAL_IN,
        .priority       = NF_IP_PRI_CONNTRACK_HELPER,
    },
    {
        .hook           = ip_confirm,
        .owner          = THIS_MODULE,
        .pf             = PF_INET,
        .hooknum        = NF_IP_POST_ROUTING,
        .priority       = NF_IP_PRI_CONNTRACK_CONFIRM,
    },
    {
        .hook           = ip_confirm,
        .owner          = THIS_MODULE,
        .pf             = PF_INET,
        .hooknum        = NF_IP_LOCAL_IN,
        .priority       = NF_IP_PRI_CONNTRACK_CONFIRM,
    },
};
協議         hook類型
struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];

#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)
#define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh)             \
    (          {int __ret;                                                                   \
           if ((__ret=nf_hook_thresh(pf, hook, &(skb), indev, outdev, okfn, thresh, 1)) == 1)\
           __ret = (okfn)(skb);                                                   \
           __ret;})

static inline int nf_hook_thresh(int pf, unsigned int hook, struct sk_buff **pskb, struct net_device *indev, struct net_device *outdev,
        int (*okfn)(struct sk_buff *), int thresh, int cond)
{
    if (!cond)
        return 1;
#ifndef CONFIG_NETFILTER_DEBUG
    if (list_empty(&nf_hooks[pf][hook]))
        return 1;
#endif
    return nf_hook_slow(pf, hook, pskb, indev, outdev, okfn, thresh);
}
int nf_hook_slow(int pf, unsigned int hook, struct sk_buff **pskb, struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *),
        int hook_thresh)
{
    struct list_head *elem;
    unsigned int verdict;
    int ret = 0;

    rcu_read_lock();

    elem = &nf_hooks[pf][hook];

next_hook:
    //定位hook連表
    verdict = nf_iterate(&nf_hooks[pf][hook], pskb, hook, indev, outdev, &elem, okfn, hook_thresh);
    if (verdict == NF_ACCEPT || verdict == NF_STOP) { //容許經過
        ret = 1;
        goto unlock;
    } else if (verdict == NF_DROP) { //丟棄
        kfree_skb(*pskb);
        ret = -EPERM;
    } else if ((verdict & NF_VERDICT_MASK)  == NF_QUEUE) { //入隊發送到用戶空間
        if (!nf_queue(pskb, elem, pf, hook, indev, outdev, okfn, verdict >> NF_VERDICT_BITS))
            goto next_hook;
    }
unlock:
    rcu_read_unlock();
    return ret;
}
unsigned int nf_iterate(struct list_head *head, struct sk_buff **skb, int hook, const struct net_device *indev,
        const struct net_device *outdev, struct list_head **i, int (*okfn)(struct sk_buff *), int hook_thresh)
{
    unsigned int verdict;
    list_for_each_continue_rcu(*i, head) { //循環連表
        struct nf_hook_ops *elem = (struct nf_hook_ops *)*i; //指向元素
        if (hook_thresh > elem->priority) //若是元素中的權限小於參數的權限,忽略這個hook函數
            continue;

        verdict = elem->hook(hook, skb, indev, outdev, okfn); //調用相關hook函數
        if (verdict != NF_ACCEPT) { //不在繼續處理
            if (verdict != NF_REPEAT) //不重複
                return verdict;
            *i = (*i)->prev; //在此調用相同的hook函數
        }
    }
}
下面咱們就來一個一個看相關的hook函數.首先
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 *))
{
#if !defined(CONFIG_IP_NF_NAT) && !defined(CONFIG_IP_NF_NAT_MODULE)
    //已經看見過這個數據包
    if ((*pskb)->nfct)
        return NF_ACCEPT;
#endif
    if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
        //在這個函數中最主要就是調用 skb = ip_defrag(skb, user); 進行ip重組,參考個人ip重組文章.
        *pskb = ip_ct_gather_frags(*pskb, hooknum == NF_IP_PRE_ROUTING ? IP_DEFRAG_CONNTRACK_IN : IP_DEFRAG_CONNTRACK_OUT);
        if (!*pskb) //數據包已經由hook處理
            return NF_STOLEN;
    }
    return NF_ACCEPT;
}
重組完成後
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 = 0;
    int ret;

    //已經看見過這個數據包
    if ((*pskb)->nfct) {
        CONNTRACK_STAT_INC(ignore);
        return NF_ACCEPT;
    }

    //又看見ip碎片包?應該從不發生
    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;
    }
    //根據ip報文所攜帶數據的協議號,獲取相應的協議封裝,該數據結構封裝了對協議私有數據處理的函數和屬性
    proto = __ip_conntrack_proto_find((*pskb)->nh.iph->protocol); //實現爲return ip_ct_protos[protocol];
    //調用該協議相應的error處理函數,檢查報文是否正確(長度,校驗和之類的比較簡單)
    if (proto->error != NULL && (ret = proto->error(*pskb, &ctinfo, hooknum)) <= 0) {
        CONNTRACK_STAT_INC(error);
        CONNTRACK_STAT_INC(invalid);
        return -ret;
    }
    //在全局鏈接表中,查找與該報文相應的鏈接狀態,返回的是ip_conntrack的指針,用於描述和記錄鏈接的狀態;
    //若該鏈接尚不存在,則建立相應的結構,並進行初始化
    if (!(ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo))) {  
        CONNTRACK_STAT_INC(invalid);
        return NF_ACCEPT;
    }
    if (IS_ERR(ct)) {
        /* Too stressed to deal. */
        CONNTRACK_STAT_INC(drop);
        return NF_DROP;
    }
    //調用相應協議的packet處理函數,判斷報文是否屬於有效鏈接,並更新鏈接狀態;返回值若不爲NF_ACCEPT,則報文不合法
    ret = proto->packet(ct, *pskb, ctinfo);
    if (ret < 0) {
        nf_conntrack_put((*pskb)->nfct);
        (*pskb)->nfct = NULL;
        CONNTRACK_STAT_INC(invalid);
        return -ret;
    }
    //設置應答位,ip_conntrack_event_cache,用戶要求對鏈接跟蹤進行更詳細的控制(數據包是REPLY),使用event_cache機制
    if (set_reply && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status))
        ip_conntrack_event_cache(IPCT_STATUS, *pskb);

    return ret;
}
如今咱們假設是tcp協議
struct ip_conntrack_protocol ip_conntrack_protocol_tcp =
{
    .proto                  = IPPROTO_TCP,
    .name                   = "tcp",
    .pkt_to_tuple           = tcp_pkt_to_tuple,      //其指向函數的做用是將協議的端口信息加入到ip_conntrack_tuple的結構中
    .invert_tuple           = tcp_invert_tuple,       //其指向函數的做用是將源和目的多元組中協議部分的值進行互換,包括端口等
    .print_tuple            = tcp_print_tuple,        //打印多元組中的協議信息
    .print_conntrack        = tcp_print_conntrack,  //打印整個鏈接記錄
    .packet                 = tcp_packet,          //判斷數據包是否合法,並調整相應鏈接的信息,也就是實現各協議的狀態檢測,
    //對於UDP等自己是無鏈接的協議 的判斷比較簡單,netfilter創建一個虛擬鏈接,
    //每一個新發包都是合法包,只等待迴應包到後鏈接都結束;但對於TCP之類的有
    //狀態協議必須檢查數據是 否符合協議的狀態轉換過程,這是靠一個狀態轉換數組實現的.
    //返回數據報的verdict值
    .new                    = tcp_new,           //當此協議的一個新鏈接發生時,調用其指向的這個函數,調用返回true時再繼續調用packet()函數
    .error                  = tcp_error,           //判斷數據包是否正確,長度,校驗和等
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
    defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
    .to_nfattr              = tcp_to_nfattr,
    .from_nfattr            = nfattr_to_tcp,
    .tuple_to_nfattr        = ip_ct_port_tuple_to_nfattr,
    .nfattr_to_tuple        = ip_ct_port_nfattr_to_tuple,
#endif
};
enum ip_conntrack_dir
{
    IP_CT_DIR_ORIGINAL,
    IP_CT_DIR_REPLY,
    IP_CT_DIR_MAX
};

struct ip_conntrack_tuple 結構僅僅用來標識一個鏈接,並非描述一條完整的鏈接狀態,netfilter將數據包轉換成tuple結構,
並根據其計算hash,在相應的鏈表上查詢,獲取相應的鏈接狀態,若是沒有查到,則表示是一個新的鏈接.
    ip_conntrack_in ->
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;
    //將數據包的內容轉化成相應的tuple,對於和協議相關的部分,如端口、ip,調用相關協議的處理函數pkt_to_tuple
    if (!ip_ct_get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4, &tuple, proto))
        return NULL;
    //在全局鏈接表中查找和tuple相同的hash項,全局鏈接表以tuple計算出相應的hash值,每個hash項所保存的元素也是相應的tuple
    h = ip_conntrack_find_get(&tuple, NULL);
    if (!h) {
        //若在全局鏈接表中沒法查到tuple所對應的hash項,即相應的鏈接狀態不存在,
        //系統調用init_conntrack建立並初始化ip_conntrack,並返回其相應的tuple結構指針
        h = init_conntrack(&tuple, proto, skb);
        if (!h)
            return NULL;

        if (IS_ERR(h))
            return (void *)h;
    }
    //根據全局鏈接表所得到tuple_hash,獲取其對應的ip_conntrack結構,由於tuple_hash結構嵌入在ip_conntrack中因此使用container_of就能夠了
    ct = tuplehash_to_ctrack(h);
    //判斷鏈接方向,如果reply方向,設置相應的應答標識和數據包狀態標識
    if (DIRECTION(h) == IP_CT_DIR_REPLY) {
        *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;                  //syn+ack迴應
        *set_reply = 1;
    } else { //如果origin方向,根據ip_conntrack中的status,設置相應的應答標識和數據包狀態標識
        if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
            *ctinfo = IP_CT_ESTABLISHED;                              //最後的ack到來時
        } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
            *ctinfo = IP_CT_RELATED;
        } else {
            *ctinfo = IP_CT_NEW;                                       //當syn包來時
        }
        *set_reply = 0;
    }
    //設置skb的對應成員,數據包對應的鏈接狀態結構和數據包鏈接狀態標記
    skb->nfct = &ct->ct_general;
    skb->nfctinfo = *ctinfo;
    return ct;
}
    resolve_normal_ct->
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)
{
    //不該該看到ip碎片
    if (iph->frag_off & htons(IP_OFFSET)) {
        printk("ip_conntrack_core: Frag of proto %u.\n", iph->protocol);
        return 0;
    }
    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);
}

    resolve_normal_ct->
static struct ip_conntrack_tuple_hash * init_conntrack(struct ip_conntrack_tuple *tuple, struct ip_conntrack_protocol *protocol, struct sk_buff *skb)
{
    struct ip_conntrack *conntrack;
    struct ip_conntrack_tuple repl_tuple;
    struct ip_conntrack_expect *exp;
    //獲取tuple的反向信息,就是把源,目的端口和地址相反的保存到repl_tuple中
    if (!ip_ct_invert_tuple(&repl_tuple, tuple, protocol)) {
        return NULL;
    }
    //分配,並用相關參數初始化conntrack,其中最主要的就是
    //conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *orig;
    //conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = *repl;
    conntrack = ip_conntrack_alloc(tuple, &repl_tuple);
    if (conntrack == NULL || IS_ERR(conntrack))
        return (struct ip_conntrack_tuple_hash *)conntrack;
    //調用協議相關的new函數進一步初始化conntrack, 對於tcp來講就是tcp_new函數
    if (!protocol->new(conntrack, skb)) {
        ip_conntrack_free(conntrack);
        return NULL;
    }

    write_lock_bh(&ip_conntrack_lock);
    //查找但願,看下面ftp實現
    exp = find_expectation(tuple);
    if (exp) { //找到
        __set_bit(IPS_EXPECTED_BIT, &conntrack->status);
        conntrack->master = exp->master;//指向主conntrack
        ......
        nf_conntrack_get(&conntrack->master->ct_general); //增長引用計數
        CONNTRACK_STAT_INC(expect_new);
    } esle { //沒有找到
        conntrack->helper = __ip_conntrack_helper_find(&repl_tuple); //在全局helpers連表中查找helper, 參看ftp實現
        CONNTRACK_STAT_INC(new);
    }
    //添加到未證明連表中
    list_add(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list, &unconfirmed); //static LIST_HEAD(unconfirmed);
    write_unlock_bh(&ip_conntrack_lock);

    if (exp) { //若是找到但願, 參看ftp實現
        if (exp->expectfn)
            exp->expectfn(conntrack, exp);
        ip_conntrack_expect_put(exp);
    }
    //返回相應的tuple_hash結構
    return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
}

static unsigned int ip_conntrack_local(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in,
        const struct net_device *out, int (*okfn)(struct sk_buff *))
{
    //處理raw sockets
    if ((*pskb)->len < sizeof(struct iphdr) || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
        if (net_ratelimit())
            printk("ipt_hook: happy cracking.\n");
        return NF_ACCEPT;
    }
    //進入in
    return ip_conntrack_in(hooknum, pskb, in, out, okfn);
}
    判斷報文所屬的模式ip_conntrack是否已經存在系統哈希中,不然加入到系統的hash中
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);
}
static inline int ip_conntrack_confirm(struct sk_buff **pskb)
{
    struct ip_conntrack *ct = (struct ip_conntrack *)(*pskb)->nfct;
    int ret = NF_ACCEPT;

    if (ct) {
        if (!is_confirmed(ct)) //測試IPS_CONFIRMED_BIT是否置位
            ret = __ip_conntrack_confirm(pskb);
        ip_ct_deliver_cached_events(ct);
    }
    return ret;
}
int __ip_conntrack_confirm(struct sk_buff **pskb)
{
    unsigned int hash, repl_hash;
    struct ip_conntrack *ct;
    enum ip_conntrack_info ctinfo;

    ct = ip_conntrack_get(*pskb, &ctinfo); //獲取conntrack結構

    if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
        return NF_ACCEPT;
    //計算hash值
    hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
    repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);

    write_lock_bh(&ip_conntrack_lock);
    //ip_conntrack_hash中查找,參看上面初始化過程
    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)) {
        //從unconfirmed連表中刪除,參看上面
        list_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list);
        //根據兩個hash值插入相應的hash連表,當相反方向的數據包到來就能夠在以repl_hash爲hash值的hash連表中查找到
        __ip_conntrack_hash_insert(ct, hash, repl_hash);

        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_bh(&ip_conntrack_lock);
        if (ct->helper)
            ip_conntrack_event_cache(IPCT_HELPER, *pskb);
        ......
#ifdef CONFIG_IP_NF_NAT_NEEDED
        if (test_bit(IPS_SRC_NAT_DONE_BIT, &ct->status) || test_bit(IPS_DST_NAT_DONE_BIT, &ct->status))
            ip_conntrack_event_cache(IPCT_NATINFO, *pskb);
#endif
        ip_conntrack_event_cache(master_ct(ct) ? IPCT_RELATED : IPCT_NEW, *pskb);
        return NF_ACCEPT;

    }
    CONNTRACK_STAT_INC(insert_failed);
    write_unlock_bh(&ip_conntrack_lock);

    return NF_DROP;
}
這部分咱們看上面列出的tcp協議實現相關部分,關於ip_conntrack_help的實現咱們看下面ftp實現
static int tcp_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff, struct ip_conntrack_tuple *tuple)
{
    struct tcphdr _hdr, *hp;
    /* Actually only need first 8 bytes. */
    hp = skb_header_pointer(skb, dataoff, 8, &_hdr);
    if (hp == NULL)
        return 0;

    tuple->src.u.tcp.port = hp->source;
    tuple->dst.u.tcp.port = hp->dest;
    return 1;
}
    端口互換
static int tcp_invert_tuple(struct ip_conntrack_tuple *tuple, const struct ip_conntrack_tuple *orig)
{
    tuple->src.u.tcp.port = orig->dst.u.tcp.port;
    tuple->dst.u.tcp.port = orig->src.u.tcp.port;
    return 1;
}
判斷數據包是否合法,並調整相應鏈接的信息,也就是實現各協議的狀態檢測,
對於UDP等自己是無鏈接的協議 的判斷比較簡單,netfilter創建一個虛擬鏈接,
每一個新發包都是合法包,只等待迴應包到後鏈接都結束;但對於TCP之類的有
狀態協議必須檢查數據是否符合協議的狀態轉換過程,這是靠一個狀態轉換數組實現的.
    返回數據報的verdict值
static int tcp_packet(struct ip_conntrack *conntrack, const struct sk_buff *skb, enum ip_conntrack_info ctinfo)
{
    enum tcp_conntrack new_state, old_state;
    enum ip_conntrack_dir dir;
    struct iphdr *iph = skb->nh.iph;
    struct tcphdr *th, _tcph;
    unsigned long timeout;
    unsigned int index;

    th = skb_header_pointer(skb, iph->ihl * 4, sizeof(_tcph), &_tcph); //獲取tcp頭

    write_lock_bh(&tcp_lock);
    old_state = conntrack->proto.tcp.state;
    dir = CTINFO2DIR(ctinfo); //取方向
    index = get_conntrack_index(th); //返回相應標誌,根據協議
    //static const enum tcp_conntrack tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = { //3圍數組在ip_conntrack_proto_tcp.c中
    new_state = tcp_conntracks[dir][index][old_state]; //轉換狀態

    switch (new_state) {
        case TCP_CONNTRACK_IGNORE:
            //好像是處理同時鏈接(syn),那麼阻止一段的syn_ack,只保持一條鏈接
            if (index == TCP_SYNACK_SET && conntrack->proto.tcp.last_index == TCP_SYN_SET
                    && conntrack->proto.tcp.last_dir != dir && ntohl(th->ack_seq) == conntrack->proto.tcp.last_end) {
                write_unlock_bh(&tcp_lock);
                if (LOG_INVALID(IPPROTO_TCP))
                    nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: killing out of sync session ");
                if (del_timer(&conntrack->timeout)) //刪除定時器和這個鏈接
                    conntrack->timeout.function((unsigned long)conntrack);
                return -NF_DROP;
            }
            //記錄相關信息
            conntrack->proto.tcp.last_index = index;
            conntrack->proto.tcp.last_dir = dir;
            conntrack->proto.tcp.last_seq = ntohl(th->seq);
            conntrack->proto.tcp.last_end = segment_seq_plus_len(ntohl(th->seq), skb->len, iph, th); //計算結束序號
            write_unlock_bh(&tcp_lock);

            if (LOG_INVALID(IPPROTO_TCP))
                nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: invalid packet ignored ");
        case TCP_CONNTRACK_MAX: //無效的數據包
            write_unlock_bh(&tcp_lock);
            if (LOG_INVALID(IPPROTO_TCP))
                nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: invalid state ");
            return -NF_ACCEPT;

        case TCP_CONNTRACK_SYN_SENT:
            if (old_state < TCP_CONNTRACK_TIME_WAIT) //原狀態有效
                break;
            //試圖從新打開一個關閉的鏈接
            if ((conntrack->proto.tcp.seen[dir].flags & IP_CT_TCP_FLAG_CLOSE_INIT)
                    || after(ntohl(th->seq), conntrack->proto.tcp.seen[dir].td_end)) {
                write_unlock_bh(&tcp_lock);
                if (del_timer(&conntrack->timeout))
                    conntrack->timeout.function((unsigned long)conntrack); //刪除這個鏈接
                return -NF_REPEAT;
            } else { //syn就無效
                write_unlock_bh(&tcp_lock);
                if (LOG_INVALID(IPPROTO_TCP))
                    nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: invalid SYN");
                return -NF_ACCEPT;
            }
        case TCP_CONNTRACK_CLOSE:
            if (index == TCP_RST_SET && ((test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status)
                            && conntrack->proto.tcp.last_index == TCP_SYN_SET)
                        || (!test_bit(IPS_ASSURED_BIT, &conntrack->status)
                            && conntrack->proto.tcp.last_index == TCP_ACK_SET))
                    && ntohl(th->ack_seq) == conntrack->proto.tcp.last_end) {
                goto in_window;
            }
        default: //一切正常
            break;
    }
    //若是tcp序號不在窗口內
    if (!tcp_in_window(&conntrack->proto.tcp, dir, index, skb, iph, th)) {
        write_unlock_bh(&tcp_lock);
        return -NF_ACCEPT;
    }
in_window:
    conntrack->proto.tcp.last_index = index; //記錄最後狀態索引
    conntrack->proto.tcp.state = new_state; //記錄新狀態
    //若是新狀態是關閉
    if (old_state != new_state && (new_state == TCP_CONNTRACK_FIN_WAIT || new_state == TCP_CONNTRACK_CLOSE))
        conntrack->proto.tcp.seen[dir].flags |= IP_CT_TCP_FLAG_CLOSE_INIT;
    //計算超時時間
    timeout = conntrack->proto.tcp.retrans >= ip_ct_tcp_max_retrans && *tcp_timeouts[new_state] > ip_ct_tcp_timeout_max_retrans
        ? ip_ct_tcp_timeout_max_retrans : *tcp_timeouts[new_state];
    write_unlock_bh(&tcp_lock);

    ip_conntrack_event_cache(IPCT_PROTOINFO_VOLATILE, skb);
    if (new_state != old_state)
        ip_conntrack_event_cache(IPCT_PROTOINFO, skb);

    if (!test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status)) {
        if (th->rst) { //若是應答是一個rst,刪除鏈接和定時器
            if (del_timer(&conntrack->timeout))
                conntrack->timeout.function((unsigned long)conntrack);
            return NF_ACCEPT;
        }
    } else if (!test_bit(IPS_ASSURED_BIT, &conntrack->status) && (old_state == TCP_CONNTRACK_SYN_RECV
                || old_state == TCP_CONNTRACK_ESTABLISHED) && new_state == TCP_CONNTRACK_ESTABLISHED) { //鏈接已經創建
        set_bit(IPS_ASSURED_BIT, &conntrack->status);
        ip_conntrack_event_cache(IPCT_STATUS, skb);
    }
    //刷新conntrack的定時器時間
    ip_ct_refresh_acct(conntrack, ctinfo, skb, timeout);
    return NF_ACCEPT;
}
    tcp->packet -> //根據協議返回相應標誌
static unsigned int get_conntrack_index(const struct tcphdr *tcph)
{
    if (tcph->rst) return TCP_RST_SET;
    else if (tcph->syn) return (tcph->ack ? TCP_SYNACK_SET : TCP_SYN_SET);
    else if (tcph->fin) return TCP_FIN_SET;
    else if (tcph->ack) return TCP_ACK_SET;
    else return TCP_NONE_SET;
}
static int tcp_new(struct ip_conntrack *conntrack, const struct sk_buff *skb)
{
    enum tcp_conntrack new_state;
    struct iphdr *iph = skb->nh.iph;
    struct tcphdr *th, _tcph;
    //獲取tcp頭
    th = skb_header_pointer(skb, iph->ihl * 4, sizeof(_tcph), &_tcph);
    //初始化一個最新的狀態
    new_state = tcp_conntracks[0][get_conntrack_index(th)][TCP_CONNTRACK_NONE];
    if (new_state >= TCP_CONNTRACK_MAX) { //新狀態無效
        return 0;
    }
    if (new_state == TCP_CONNTRACK_SYN_SENT) { //syn包
        conntrack->proto.tcp.seen[0].td_end = segment_seq_plus_len(ntohl(th->seq), skb->len, iph, th); //記錄結束序號
        conntrack->proto.tcp.seen[0].td_maxwin = ntohs(th->window); //記錄窗口
        if (conntrack->proto.tcp.seen[0].td_maxwin == 0)
            conntrack->proto.tcp.seen[0].td_maxwin = 1;
        conntrack->proto.tcp.seen[0].td_maxend = conntrack->proto.tcp.seen[0].td_end;
        //記錄tcp選項
        tcp_options(skb, iph, th, &conntrack->proto.tcp.seen[0]);
        conntrack->proto.tcp.seen[1].flags = 0;
        conntrack->proto.tcp.seen[0].loose =
            conntrack->proto.tcp.seen[1].loose = 0;
    } else if (ip_ct_tcp_loose == 0) { //鏈接跟蹤丟失,再也不試着追蹤
        return 0;
    } else { //試圖去從新追蹤一個已經創建好的鏈接
        conntrack->proto.tcp.seen[0].td_end = segment_seq_plus_len(ntohl(th->seq), skb->len, iph, th);
        conntrack->proto.tcp.seen[0].td_maxwin = ntohs(th->window);
        if (conntrack->proto.tcp.seen[0].td_maxwin == 0)
            conntrack->proto.tcp.seen[0].td_maxwin = 1;

        conntrack->proto.tcp.seen[0].td_maxend =  conntrack->proto.tcp.seen[0].td_end + conntrack->proto.tcp.seen[0].td_maxwin;
        conntrack->proto.tcp.seen[0].td_scale = 0;

        conntrack->proto.tcp.seen[0].flags = conntrack->proto.tcp.seen[1].flags = IP_CT_TCP_FLAG_SACK_PERM;
        conntrack->proto.tcp.seen[0].loose = conntrack->proto.tcp.seen[1].loose = ip_ct_tcp_loose;
    }
    //初始化另外一個方向
    conntrack->proto.tcp.seen[1].td_end = 0;
    conntrack->proto.tcp.seen[1].td_maxend = 0;
    conntrack->proto.tcp.seen[1].td_maxwin = 1;
    conntrack->proto.tcp.seen[1].td_scale = 0;

    /// tcp_packet 會修改這些設置
    conntrack->proto.tcp.state = TCP_CONNTRACK_NONE;
    conntrack->proto.tcp.last_index = TCP_NONE_SET;
    return 1;
}
static int tcp_error(struct sk_buff *skb, enum ip_conntrack_info *ctinfo, unsigned int hooknum)
{
    struct iphdr *iph = skb->nh.iph;
    struct tcphdr _tcph, *th;
    unsigned int tcplen = skb->len - iph->ihl * 4;
    u_int8_t tcpflags;

    th = skb_header_pointer(skb, iph->ihl * 4, sizeof(_tcph), &_tcph); //獲取tcp頭
    if (th == NULL) {
        if (LOG_INVALID(IPPROTO_TCP))
            nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: short packet ");
        return -NF_ACCEPT;
    }
    if (th->doff*4 < sizeof(struct tcphdr) || tcplen < th->doff*4) { //tcp頭不完整或
        if (LOG_INVALID(IPPROTO_TCP))
            nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: truncated/malformed packet ");
        return -NF_ACCEPT;
    }
    //校驗和不對
    if (ip_conntrack_checksum && hooknum == NF_IP_PRE_ROUTING && nf_ip_checksum(skb, hooknum, iph->ihl * 4, IPPROTO_TCP)) {
        if (LOG_INVALID(IPPROTO_TCP))
            nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: bad TCP checksum ");
        return -NF_ACCEPT;
    }
    //檢測tcp標誌
    tcpflags = (((u_int8_t *)th)[13] & ~(TH_ECE|TH_CWR));
    if (!tcp_valid_flags[tcpflags]) {
        if (LOG_INVALID(IPPROTO_TCP))
            nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL, "ip_ct_tcp: invalid TCP flag combination ");
        return -NF_ACCEPT;
    }
    return NF_ACCEPT;
}
FTP 實現
static unsigned int ip_conntrack_help(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;

    ct = ip_conntrack_get(*pskb, &ctinfo); //獲取conntrack
    if (ct && ct->helper && ctinfo != IP_CT_RELATED + IP_CT_IS_REPLY) {
        unsigned int ret;
        //調用相關的help函數
        ret = ct->helper->help(pskb, ct, ctinfo);
        if (ret != NF_ACCEPT)
            return ret;
    }
    return NF_ACCEPT;
}

FTP協議大多數協議最大的一個不一樣是:它使用雙向的多個鏈接,並且使用的端口很難預計。
FTP鏈接包含一個控制鏈接(control connection)。這個鏈接用於傳遞客戶端的命令和服務器端對命
令的響應。它使用FTP協議衆所周知的21端口(固然也可以使用其它端口),生存期是整個FTP會話時間。
還包含幾個數據鏈接(data connection)。這些鏈接用於傳輸文件和其它數據,例如:目錄列表等。這種鏈接在須要數據傳輸時創建,而一旦數據傳輸完畢就關閉,
每次使用的端口 也不必定相同。並且,數據鏈接既多是客戶端發起的,也多是服務器端發起的。
根據創建數據鏈接發起方的不一樣,FTP可分爲兩種不一樣的模式:主動(Port Mode)模式和被動模式(Pasv Mode)。這兩種不一樣模式數據鏈接創建方式分別以下:
(假設客戶端爲C,服務端爲S)

Port模式:
當客戶端C與服務端S創建控制鏈接後,若使用Port模式,那麼客戶端C會發送一條命令告訴服務端S:客戶端C在本地打開了一個端口N在等着你進行數據鏈接。
當服務端S收到這個Port命令後就會向客戶端打開的那個端口N進行鏈接,這種數據鏈接就創建了。
例:客戶端->服務器端:PORT 192,168,1,1,15,176
客戶端<-服務器端:200 PORT command successful.
在上面的例子中192,168,1,1構成IP地址,15,176構成端口號(15*256+176)。
Pasv模式:
當客戶端C與服務端S創建控制鏈接後,若使用Pasv模式,那麼客戶端C會向服務端S發送一條Pasv命令,服務端S會對該命令發送迴應信息,
這個信息是:服務端S在本地打開了一個端口M,你如今去鏈接我吧.當客戶端C收到這個信息後,就能夠向服務端S的M端口進行鏈接,鏈接成功後,數據鏈接也就創建了。
例:客戶端->服務器端:PASV
客戶端<-服務器端:227 Entering Passive Mode (192,168,1,2,14,26).

從上面的解釋中,能夠看到兩種模式主要的不一樣是數據鏈接創建的不一樣.
對於Port模式,是客戶端C在本地打開一個端口等服務端S去鏈接而創建數據鏈接;
而Pasv模式則是服務端S打開一個端口等待客戶端C去創建一個數據鏈接.

在net/ipv4/netfilter/ip_conntrack_ftp.c中
static char *ftp_buffer;
#define MAX_PORTS 8
static unsigned short ports[MAX_PORTS];
static int ports_c;
static struct ip_conntrack_helper ftp[MAX_PORTS];
static char ftp_names[MAX_PORTS][sizeof("ftp-65535")];

struct ip_conntrack_helper
{
    struct list_head list;          //將該結構掛接到多鏈接協議跟蹤鏈表helpers中, helpers鏈表在ip_conntrack_core.c文件中定義
    //static LIST_HEAD(helpers);注意是static的,只在該文件範圍有效

    const char *name;               //協議名稱,字符串常量
    struct module *me;              //指向模塊自己,統計模塊是否被使用

    unsigned int max_expected;      //子鏈接的數量,這只是表示主鏈接在每一個時刻所擁有的的子鏈接的數量,而不是
    //主鏈接的整個生存期內總共生成的子鏈接的數量,如FTP,不論傳多少個文件,創建
    //多少個子鏈接,每一個時刻主鏈接最多隻有一個子鏈接,一個子鏈接結束前按協議是
    //不能再派生出第二個子鏈接的,因此初始時該值爲1
    unsigned int timeout;           //超時,指在多少時間範圍內子鏈接沒有創建的話子鏈接跟蹤失效

    //這兩個參數用來描述子鏈接,判斷一個新來的鏈接是不是主鏈接期待的子鏈接,之因此要有mask參數,是由於
    //子鏈接的某些參數不能肯定,如被動模式的FTP傳輸,只能獲得子鏈接的目的端口而不能肯定源端口,因此源端口部分要用mask來進行泛匹配
    struct ip_conntrack_tuple tuple;
    struct ip_conntrack_tuple mask;

    //鏈接跟蹤基本函數,解析主鏈接的通訊內容,提取出關於子鏈接的信息,將子鏈接信息填充到一個struct ip_conntrack_expect結構中,
    //而後將此結構經過調用函數ip_conntrack_expect_related()把子鏈接的信息添加到系統的期待子鏈接鏈表ip_conntrack_expect_list中。
    //返回值是NF_DROP或-1表示協議數據非法。 該函數在ip_conntrack_help中調用,這個函數是一個hook函數在ip_conntrack_standalone_init中註冊.
    int (*help)(struct sk_buff **pskb, struct ip_conntrack *ct, enum ip_conntrack_info conntrackinfo);
    int (*to_nfattr)(struct sk_buff *skb, const struct ip_conntrack *ct);
};
struct ip_conntrack_expect
{
    /* 鏈表頭,在被某鏈接引用以前,全部expect結構都由此鏈表維護 */
    struct list_head list;   
    /* 引用計數 */
    atomic_t use;          
    /* 主鏈接的預期的子鏈接的鏈表 */
    struct list_head expected_list;     
    /* 期待者,即預期鏈接對應的主鏈接,換句話說就是將此鏈接看成是其預期鏈接的鏈接... */
    struct ip_conntrack *expectant;
    /* 預期鏈接對應的真實的子鏈接 */
    struct ip_conntrack *sibling;
    /* 鏈接的tuple值 */
    struct ip_conntrack_tuple ct_tuple;
    /* 定時器 */
    struct timer_list timeout;
    /* 預期鏈接的tuple和mask,搜索預期鏈接時要用到的 */
    struct ip_conntrack_tuple tuple, mask;
    /* 預期鏈接函數,通常是NULL,有特殊須要時才定義 */
    int (*expectfn)(struct ip_conntrack *new);
    /* TCP協議時,主鏈接中描述子鏈接的數據起始處對應的序列號值 */
    u_int32_t seq;
    /* 跟蹤各個多鏈接IP層協議相關的數據 */
    union ip_conntrack_expect_proto proto;
    /*  跟蹤各個多鏈接應用層協議相關的數據 */
    union ip_conntrack_expect_help help;
};
struct ip_conntrack_expect
{
    struct list_head list;                           //鏈表,在被某鏈接引用以前,全部expect結構都由此鏈表維護
    struct ip_conntrack_tuple tuple, mask;        //預期鏈接的tuple和mask,搜索預期鏈接時要用到的
    //預期鏈接函數,通常是NULL,有特殊須要時才定義
    void (*expectfn)(struct ip_conntrack *new, struct ip_conntrack_expect *this);
    struct ip_conntrack *master;                 //主鏈接的conntrack
    struct timer_list timeout;                     //定時器
    atomic_t use;                                //引用計數
    unsigned int id;                              //惟一id
    unsigned int flags;
#ifdef CONFIG_IP_NF_NAT_NEEDED
    u_int32_t saved_ip;          /* This is the original per-proto part, used to map the expected connection the way the recipient expects. */
    union ip_conntrack_manip_proto saved_proto;
    enum ip_conntrack_dir dir;                  //主鏈接相關的方向
#endif
};
static int __init ip_conntrack_ftp_init(void)
{
    int i, ret;
    char *tmpname;

    ftp_buffer = kmalloc(65536, GFP_KERNEL);
    if (!ftp_buffer)
        return -ENOMEM;

    if (ports_c == 0)
        ports[ports_c++] = FTP_PORT; //   #define FTP_PORT        21

    //正常狀況如今 ports_c = 1
    for (i = 0; i < ports_c; i++) {
        ftp[i].tuple.src.u.tcp.port = htons(ports[i]);
        ftp[i].tuple.dst.protonum = IPPROTO_TCP;
        ftp[i].mask.src.u.tcp.port = 0xFFFF; //描述子鏈接端口
        ftp[i].mask.dst.protonum = 0xFF;  
        ftp[i].max_expected = 1;  //看上面結構中說明
        ftp[i].timeout = 5 * 60; // 5分鐘
        ftp[i].me = THIS_MODULE;
        ftp[i].help = help; //關鍵函數

        tmpname = &ftp_names[i][0];

        if (ports[i] == FTP_PORT)
            sprintf(tmpname, "ftp"); //標準21端口
        else
            sprintf(tmpname, "ftp-%d", ports[i]);

        ftp[i].name = tmpname;//指向名字

        //把這個helper添加到helpers連表中
        ret = ip_conntrack_helper_register(&ftp[i]);
        if (ret) {
            ip_conntrack_ftp_fini();
            return ret;
        }
    }

    return 0;
}
    下面咱們看關鍵help函數
static int help(struct sk_buff **pskb, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo)
{
    unsigned int dataoff, datalen;
    struct tcphdr _tcph, *th;
    char *fb_ptr;
    int ret;
    u32 seq, array[6] = { 0 };
    int dir = CTINFO2DIR(ctinfo);
    unsigned int matchlen, matchoff;
    struct ip_ct_ftp_master *ct_ftp_info = &ct->help.ct_ftp_info;
    struct ip_conntrack_expect *exp;
    unsigned int i;
    int found = 0, ends_in_nl;

    if (ctinfo != IP_CT_ESTABLISHED  && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
        DEBUGP("ftp: Conntrackinfo = %u\n", ctinfo);
        return NF_ACCEPT;
    }
    //取出tcp頭,看下面函數說明
    th = skb_header_pointer(*pskb, (*pskb)->nh.iph->ihl*4, sizeof(_tcph), &_tcph);
    if (th == NULL)
        return NF_ACCEPT;

    dataoff = (*pskb)->nh.iph->ihl*4 + th->doff*4;  //數據開始位置
    if (dataoff >= (*pskb)->len) { //沒有數據
        DEBUGP("ftp: pskblen = %u\n", (*pskb)->len);
        return NF_ACCEPT;
    }
    datalen = (*pskb)->len - dataoff; //數據長度

    spin_lock_bh(&ip_ftp_lock);
    fb_ptr = skb_header_pointer(*pskb, dataoff, (*pskb)->len - dataoff, ftp_buffer); //指向tcp數據開始

    ends_in_nl = (fb_ptr[datalen - 1] == '\n'); //最後一個字符是不是 \n
    seq = ntohl(th->seq) + datalen;             //結束序號

    //檢查序列號是不是但願的序號,防止序列號問題, 看下面序號處理解釋
    if (!find_nl_seq(ntohl(th->seq), ct_ftp_info, dir)) {
        ret = NF_ACCEPT;
        goto out_update_nl;
    }

    array[0] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 24) & 0xFF;
    array[1] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 16) & 0xFF;
    array[2] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 8) & 0xFF;
    array[3] = ntohl(ct->tuplehash[dir].tuple.src.ip) & 0xFF;
    //解析ftp命令
    for (i = 0; i < ARRAY_SIZE(search[dir]); i++) {
        found = find_pattern(fb_ptr, (*pskb)->len - dataoff,
                search[dir][i].pattern,
                search[dir][i].plen,
                search[dir][i].skip,
                search[dir][i].term,
                &matchoff, &matchlen,
                array,
                search[dir][i].getnum);

        if (found)
            break;
    }
    if (found == -1) {
        ret = NF_DROP;
        goto out;
    } else if (found == 0) { //不匹配
        ret = NF_ACCEPT;
        goto out_update_nl;
    }
    //分配指望
    //之因此稱爲指望鏈接,是由於如今還處於控制鏈接階段,數據鏈接此時還未創建,但未來是會創建的.
    //如此處理以後,咱們就能夠預先獲取創建數據鏈接的信息,當真正的數據鏈接須要創建時,咱們只需
    //在指望鏈接表中進行查找,保證了多鏈接協議的正確處理,同時還提升了效率.
    exp = ip_conntrack_expect_alloc(ct);
    if (exp == NULL) {
        ret = NF_DROP;
        goto out;
    }

    exp->tuple.dst.ip = ct->tuplehash[!dir].tuple.dst.ip;

    if (htonl((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]) != ct->tuplehash[dir].tuple.src.ip) {
        if (!loose) {
            ret = NF_ACCEPT;
            goto out_put_expect;
        }
        exp->tuple.dst.ip = htonl((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]);
    }

    exp->tuple.src.ip = ct->tuplehash[!dir].tuple.src.ip;
    exp->tuple.dst.u.tcp.port = htons(array[4] << 8 | array[5]);  //port = x * 256 + y
    exp->tuple.src.u.tcp.port = 0; /* Don't care. */
    exp->tuple.dst.protonum = IPPROTO_TCP;
    exp->mask = ((struct ip_conntrack_tuple) { 0xFFFFFFFF, { 0 } , { 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFF }});
    exp->expectfn = NULL;
    exp->flags = 0;

    if (ip_nat_ftp_hook) //若是註冊了NAT修改函數,在此直接調用,該hook函數在ip_nat_ftp.c中定義,咱們在下面看
    ret = ip_nat_ftp_hook(pskb, ctinfo, search[dir][i].ftptype, matchoff, matchlen, exp, &seq);
    else {
        if (ip_conntrack_expect_related(exp) != 0) //註冊這個指望
            ret = NF_DROP;
        else
            ret = NF_ACCEPT;
    }
out_put_expect:
    ip_conntrack_expect_put(exp);
out_update_nl:
    if (ends_in_nl)
        update_nl_seq(seq, ct_ftp_info,dir, *pskb); //看下面序號處理解釋
out:
    spin_unlock_bh(&ip_ftp_lock);
return ret;
}
    分配一個指望
struct ip_conntrack_expect *ip_conntrack_expect_alloc(struct ip_conntrack *me)
{
    struct ip_conntrack_expect *new;

    new = kmem_cache_alloc(ip_conntrack_expect_cachep, GFP_ATOMIC);
    if (!new) {
        DEBUGP("expect_related: OOM allocating expect\n");
        return NULL;
    }
    new->master = me; //指向主ip conntrack
    atomic_set(&new->use, 1);
    return new;
}
int ip_conntrack_expect_related(struct ip_conntrack_expect *expect)
{
    struct ip_conntrack_expect *i;
    int ret;

    write_lock_bh(&ip_conntrack_lock);
    list_for_each_entry(i, &ip_conntrack_expect_list, list) {
        if (expect_matches(i, expect)) { //有一樣的指望
            if (refresh_timer(i)) { //刷新舊指望的定時器
                ret = 0;
                goto out;
            }
        } else if (expect_clash(i, expect)) { //指望衝突
            ret = -EBUSY;
            goto out;
        }
    }
    //超過個數限制
    if (expect->master->helper->max_expected && expect->master->expecting >= expect->master->helper->max_expected)
        evict_oldest_expect(expect->master);//回收舊指望

    ip_conntrack_expect_insert(expect); //插入指望到連表
    ip_conntrack_expect_event(IPEXP_NEW, expect);
    ret = 0;
out:
    write_unlock_bh(&ip_conntrack_lock);
    return ret;
}
    ip_nat_ftp_hook函數指針初始化在
static int __init ip_nat_ftp_init(void)
{
    ip_nat_ftp_hook = ip_nat_ftp;
    return 0;
}
static unsigned int ip_nat_ftp(struct sk_buff **pskb, enum ip_conntrack_info ctinfo, enum ip_ct_ftp_type type, unsigned int matchoff,
        unsigned int matchlen, struct ip_conntrack_expect *exp, u32 *seq)
{
    u_int32_t newip;
    u_int16_t port;
    int dir = CTINFO2DIR(ctinfo);
    struct ip_conntrack *ct = exp->master; //獲取主鏈接

    newip = ct->tuplehash[!dir].tuple.dst.ip; //目的ip
    exp->saved_proto.tcp.port = exp->tuple.dst.u.tcp.port; //保存解析出的端口
    exp->dir = !dir;//ftp相對於主鏈接來講是反方向的

    //該函數在初始化鏈接init_conntrack()函數中調用,用於爲子鏈接創建NAT信息
    exp->expectfn = ip_nat_follow_master;

    for (port = ntohs(exp->saved_proto.tcp.port); port != 0; port++) {
        //獲取一個新端口而後檢查是否能夠用此端口替代原來的端口
        exp->tuple.dst.u.tcp.port = htons(port);
        if (ip_conntrack_expect_related(exp) == 0)
            break;
    }
    if (port == 0)
        return NF_DROP;
    //修改ftp數據內容,包括IP端口而後調整tcp的序號,從新計算校驗和等
    //mangle是一個函數指針數組
    if (!mangle[type](pskb, newip, port, matchoff, matchlen, ct, ctinfo, seq)) {
        ip_conntrack_unexpect_related(exp);
        return NF_DROP;
    }
    return NF_ACCEPT;
}
指望幫助函數在init_conntrack中若是找到指望會調用指望的幫助函數
void ip_nat_follow_master(struct ip_conntrack *ct, struct ip_conntrack_expect *exp)
{
    struct ip_nat_range range;

    /* Change src to where master sends to */
    range.flags = IP_NAT_RANGE_MAP_IPS;
    //ct爲子鏈接,exp->dir與主鏈接相反,因此下面取的是子鏈接的源地址
    range.min_ip = range.max_ip = ct->master->tuplehash[!exp->dir].tuple.dst.ip;

    /* hook doesn't matter, but it has to do source manip */
    ip_nat_setup_info(ct, &range, NF_IP_POST_ROUTING); //函數參考Linux網絡地址轉換分析

    /* For DST manip, map port here to where it's expected. */
    range.flags = (IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED);
    range.min = range.max = exp->saved_proto; //原始目的端口
    //子鏈接的目的ip
    range.min_ip = range.max_ip = ct->master->tuplehash[!exp->dir].tuple.src.ip;

    /* hook doesn't matter, but it has to do destination manip */
    ip_nat_setup_info(ct, &range, NF_IP_PRE_ROUTING);
}
[skb_header_pointer]
skb:數據包struct sk_buff的指針
offset:相對數據起始頭(如IP頭)的偏移量
len:數據長度
buffer:緩衝區,大小不小於len
static inline unsigned int skb_headlen(const struct sk_buff *skb)
{
    return skb->len - skb->data_len;
}
其中skb->len是數據包長度,在IPv4中就是單個完整IP包的總長,但這些數據並不必定都在當前內存頁;skb->data_len表示在其餘頁的數據長度,
所以skb->len - skb->data_len表示在當前頁的數據大小.
若是skb->data_len不爲0,表示該IP包的數據分屬不一樣的頁,該數據包也就被成爲非線性化的,
函數skb_is_nonlinear()就是經過該參數判斷,通常剛進行完碎片重組的skb包就屬於此類.
那麼這函數就是先判斷要處理的數據是否都在當前頁面內,若是是,則返回能夠直接對數據處理,返回所求數據指針,不然用skb_copy_bits()函數進行拷貝.
static inline void *skb_header_pointer(const struct sk_buff *skb, int offset, int len, void *buffer)
{
    int hlen = skb_headlen(skb);

    if (hlen - offset >= len)
        return skb->data + offset;

    if (skb_copy_bits(skb, offset, buffer, len) < 0)
        return NULL;

    return buffer;
}
[/skb_header_pointer]
[序號處理解釋]
#define NUM_SEQ_TO_REMEMBER 2

FTP主鏈接中記錄相關信息的結構, 主要是記錄期待的序列號
struct ip_ct_ftp_master {
    //每一個方向各保存2個序列號值, 能夠容排序錯誤一次
    u_int32_t seq_aft_nl[IP_CT_DIR_MAX][NUM_SEQ_TO_REMEMBER];

    //每一個方向記錄的序列號的數量
    int seq_aft_nl_num[IP_CT_DIR_MAX];
};
這個函數判斷當前數據包的序列號是不是正在期待的序列號, 若是不是則跳過內容解析操做
static int find_nl_seq(u32 seq, const struct ip_ct_ftp_master *info, int dir)
{
    unsigned int i;
    //循環次數爲該方向上記錄的序列號的的數量
    for (i = 0; i < info->seq_aft_nl_num[dir]; i++)
        //若是當前數據包的序列號和期待的序列號中的任一個相同返回1
        if (info->seq_aft_nl[dir][i] == seq)
            return 1;
    //不然返回0表示失敗,失敗後雖然不解析包內容了,但仍然會調用下面的函數來調整期待的序列號
    return 0;
}
這個函數更新主鏈接所期待的序列號, 更換最老的一個(代碼有錯誤).
    nl_seq是要期待的序列號
static void update_nl_seq(u32 nl_seq, struct ip_ct_ftp_master *info, int dir, struct sk_buff *skb)
{
    unsigned int i, oldest = NUM_SEQ_TO_REMEMBER;
    //循環次數爲該方向上記錄的序列號的的數量
    for (i = 0; i < info->seq_aft_nl_num[dir]; i++) {
        if (info->seq_aft_nl[dir][i] == nl_seq)//若是當前數據包的序列號和期待的序列號相同則不用更新
            return;
        //第一個比較條件有問題, 當info->seq_aft_nl_num[dir]達到最大值(2)後 oldest將永遠賦值爲0, 也就是兩邊各發出2個包後oldest就不變了
        //第二個比較條件也幾乎沒有意義, oldest最大也就是2, 而info->seq_aft_nl表示序列號幾乎不可能小於2, 只有
        //初始狀況info->seq_aft_nl[dir][i]還爲0是纔可能爲真, 其餘基本永遠爲假
        if (oldest == info->seq_aft_nl_num[dir] || before(info->seq_aft_nl[dir][i], oldest))
            oldest = i;
    }
    //調整期待的序列號
    if (info->seq_aft_nl_num[dir] < NUM_SEQ_TO_REMEMBER) {
        info->seq_aft_nl[dir][info->seq_aft_nl_num[dir]++] = nl_seq;
        ip_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, skb);
    } else if (oldest != NUM_SEQ_TO_REMEMBER) {
        info->seq_aft_nl[dir][oldest] = nl_seq;
        ip_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, skb);
    }
}
[/序號處理解釋]
相關文章
相關標籤/搜索