首页 > 技术文章 > ip_conntrack 实现

super-king 2013-08-30 09:49 原文

启动时首先在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);
    }
}
[/序号处理解释]

 

推荐阅读