+ -
当前位置:首页 → 问答吧 → Netfilter 连接跟踪与状态检测的实现

Netfilter 连接跟踪与状态检测的实现

时间:2006-08-21

来源:互联网

作者:九贱
www.skynet.org.cn
原创,欢迎转载,转载,请注明出处

内核版本:2.6.12

本文只是一部份,详细分析了连接跟踪的基本实现,对于ALG部份,还没有写,在整理笔记,欢迎大家提意见,批评指正。

1.什么是连接跟踪
连接跟踪(CONNTRACK),顾名思义,就是跟踪并且记录连接状态。Linux为每一个经过网络堆栈的数据包,生成一个新的连接记录项(Connection entry)。此后,所有属于此连接的数据包都被唯一地分配给这个连接,并标识连接的状态。连接跟踪是防火墙模块的状态检测的基础,同时也是地址转换中实现SNAT和DNAT的前提。
那么Netfilter又是如何生成连接记录项的呢?每一个数据,都有“来源”与“目的”主机,发起连接的主机称为“来源”,响应“来源”的请求的主机即为目的,所谓生成记录项,就是对每一个这样的连接的产生、传输及终止进行跟踪记录。由所有记录项产生的表,即称为连接跟踪表。

2.连接跟踪表
Netfilter使用一张连接跟踪表,来描述整个连接状态,这个表在实现算法上采用了hash算法。我们先来看看这个hash 表的实现。
整个hash表用全局指针ip_conntrack_hash 指针来描述,它定义在ip_conntrack_core.c中:
struct list_head *ip_conntrack_hash;

这个hash表的大小是有限制的,表的大小由ip_conntrack_htable_size 全局变量决定,这个值,用户态可以在模块插入时传递,默认是根据内存大小计算出来的。
        每一个hash节点,同时又是一条链表的首部,所以,连接跟踪表就由ip_conntrack_htable_size 条链表构成,整个连接跟踪表大小使用全局变量ip_conntrack_max描述,与hash表的关系是ip_conntrack_max = 8 * ip_conntrack_htable_size。
链表的每个节点,都是一个struct ip_conntrack_tuple_hash 类型:
  1. /* Connections have two entries in the hash table: one for each way */
  2. struct ip_conntrack_tuple_hash
  3. {
  4.         struct list_head list;

  5.         struct ip_conntrack_tuple tuple;
  6. };
复制代码

这个结构有两个成员,list 成员用于组织链表。多元组(tuple) 则用于描述具体的数据包。
每个数据包最基本的要素,就是“来源”和“目的”,从Socket套接字角度来讲,连接两端用“地址+端口”的形式来唯一标识一个连接(对于没有端口的协议,如ICMP,可以使用其它办法替代),所以,这个数据包就可以表示为“来源地址/来源端口+目的地址/目的端口”,Netfilter用结构struct ip_conntrack_tuple 结构来封装这个“来源”和“目的”,封装好的struct ip_conntrack_tuple结构节点在内核中就称为“tuple”。最终实现“封装”,就是根据来源/目的地址、端口这些要素,来进行一个具体网络封包到tuple的转换。结构定义如下:
  1. /* The protocol-specific manipulable parts of the tuple: always in
  2.    network order! */
  3. union ip_conntrack_manip_proto
  4. {
  5.         /* Add other protocols here. */
  6.         u_int16_t all;

  7.         struct {
  8.                 u_int16_t port;
  9.         } tcp;
  10.         struct {
  11.                 u_int16_t port;
  12.         } udp;
  13.         struct {
  14.                 u_int16_t id;
  15.         } icmp;
  16.         struct {
  17.                 u_int16_t port;
  18.         } sctp;
  19. };
复制代码

  1. /* The manipulable part of the tuple. */
  2. struct ip_conntrack_manip
  3. {
  4.         u_int32_t ip;
  5.         union ip_conntrack_manip_proto u;
  6. };
复制代码
  1. /* This contains the information to distinguish a connection. */
  2. struct ip_conntrack_tuple
  3. {
  4.         struct ip_conntrack_manip src;

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

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

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

  26.                 /* The direction (for tuplehash) */
  27.                 u_int8_t dir;
  28.         } dst;
  29. };
复制代码


struct ip_conntrack_tuple 中仅包含了src、dst两个成员,这两个成员基本一致:包含ip以及各个协议的端口,值得注意的是,dst成员中有一个dir成员,dir是direction 的缩写,标识一个连接的方向,后面我们会看到它的用法。

tuple 结构仅仅是一个数据包的转换,并不是描述一条完整的连接状态,内核中,描述一个包的连接状态,使用了struct ip_conntrack 结构,可以在ip_conntrack.h中看到它的定义:
  1. struct ip_conntrack
  2. {
  3.         ……
  4.         /* These are my tuples; original and reply */
  5.         struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
  6. };
复制代码


这里仅仅是分析hash表的实现,所以,我们仅需注意struct ip_conntrack结构的最后一个成员tuplehash,它是一个struct ip_conntrack_tuple_hash 类型的数组,我们前面说了,该结构描述链表中的节点,这个数组包含“初始”和“应答”两个成员(tuplehash[IP_CT_DIR_ORIGINAL]和tuplehash[IP_CT_DIR_REPLY]),所以,当一个数据包进入连接跟踪模块后,先根据这个数据包的套接字对转换成一个“初始的”tuple,赋值给tuplehash[IP_CT_DIR_ORIGINAL],然后对这个数据包“取反”,计算出“应答”的tuple,赋值给tuplehash[IP_CT_DIR_REPLY],这样,一条完整的连接已经跃然纸上了。
最后一要注意的问题,就是对于每一条连接,寻找链表在hash表的入口,也就是如计算hash值。我们关心的是一条连接,连接是由“请求”和“应答”的数据包组成,数据包会被转化成tuple,所以,hash值就是根据tuple,通过一定的hash算法实现,这样,整个hash表如下图所示:
         

如图,小结一下:
n        整个hash表用ip_conntrack_hash 指针数组来描述,它包含了ip_conntrack_htable_size个元素,用户态可以在模块插入时传递,默认是根据内存大小计算出来的;
n        整个连接跟踪表的大小使用全局变量ip_conntrack_max描述,与hash表的关系是ip_conntrack_max = 8 * ip_conntrack_htable_size;
n        hash链表的每一个节点是一个struct ip_conntrack_tuple_hash结构,它有两个成员,一个是list,一个是tuple;
n        Netfilter将每一个数据包转换成tuple,再根据tuple计算出hash值,这样,就可以使用ip_conntrack_hash[hash_id]找到hash表中链表的入口,并组织链表;
n        找到hash表中链表入口后,如果链表中不存在此“tuple”,则是一个新连接,就把tuple插入到链表的合适位置;
n        图中两个节点tuple[ORIGINAL]和tuple[REPLY],虽然是分开的,在两个链表当中,但是如前所述,它们同时又被封装在ip_conntrack结构的tuplehash数组中,这在图中,并没有标注出来;
n        链表的组织采用的是双向链表,上图中没有完整表示出来;

        当然,具体的实现要稍微麻烦一点,主要体现在一些复杂的应用层协议上来,例如主动模式下的FTP协议,服务器在连接建立后,会主动打开高端口与客户端进行通讯,这样,由于端口变换了,我们前面说的连接表的实现就会遇到麻烦。Netfilter为这些协议提供了一个巧秒的解决办法,我们在本章中,先分析连接跟踪的基本实现,然后再来分析Netfilter对这些特殊的协议的支持的实现。

3.连接跟踪的初始化

3.1 初始化函数
ip_conntrack_standalone.c 是连接跟踪的主要模块:
  1. static int __init init(void)
  2. {
  3.         return init_or_cleanup(1);
  4. }
复制代码


初始化函数进一步调用init_or_cleanup() 进行模块的初始化,它主要完成hash表的初始化等三个方面的工作:
  1. static int init_or_cleanup(int init)
  2. {
  3.         /*初始化连接跟踪的一些变量、数据结构,如初始化连接跟踪表的大小,Hash表的大小等*/
  4.         ret = ip_conntrack_init();
  5.         if (ret < 0)
  6.                 goto cleanup_nothing;

  7. /*创建proc 文件系统的对应节点*/
  8. #ifdef CONFIG_PROC_FS
  9.         ……
  10. #endif

  11. /*为连接跟踪注册Hook */
  12.         ret = nf_register_hook(&ip_conntrack_defrag_ops);
  13.         if (ret < 0) {
  14.                 printk("ip_conntrack: can't register pre-routing defrag hook.\n");
  15.                 goto cleanup_proc_stat;
  16.         }
  17.         ……
  18. }
复制代码


3.2 ip_conntrack_init

ip_conntrack_init 函数用于初始化连接跟踪的包括hash表相关参数在内一些重要的变量:
  1. /*用户态可以在模块插入的时候,可以使用hashsize参数,指明hash 表的大小*/
  2. static int hashsize;
  3. module_param(hashsize, int, 0400);

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

  8.         /* 如果模块指明了hash表的大小,则使用指定值,否则,根据内存的大小,来计算一个默认值. ,hash表的大小,是使用全局变量ip_conntrack_htable_size 来描述*/
  9.         if (hashsize) {
  10.                 ip_conntrack_htable_size = hashsize;
  11.         } else {
  12.                 ip_conntrack_htable_size
  13.                         = (((num_physpages << PAGE_SHIFT) / 16384)
  14.                            / sizeof(struct list_head));
  15.                 if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE))
  16.                         ip_conntrack_htable_size = 8192;
  17.                 if (ip_conntrack_htable_size < 16)
  18.                         ip_conntrack_htable_size = 16;
  19.         }

  20. /*根据hash表的大小,计算最大的连接跟踪表数*/
  21.         ip_conntrack_max = 8 * ip_conntrack_htable_size;

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

  33.         /* 初始化内存分配标识变量 */
  34.         ip_conntrack_vmalloc = 0;

  35.         /*为hash表分配连续内存页*/
  36.         ip_conntrack_hash
  37.                 =(void*)__get_free_pages(GFP_KERNEL,
  38.                                          get_order(sizeof(struct list_head)
  39.                                                    *ip_conntrack_htable_size));
  40.         /*分配失败,尝试调用vmalloc重新分配*/
  41. if (!ip_conntrack_hash) {
  42.                 ip_conntrack_vmalloc = 1;
  43.                 printk(KERN_WARNING "ip_conntrack: falling back to vmalloc.\n");
  44.                 ip_conntrack_hash = vmalloc(sizeof(struct list_head)
  45.                                             * ip_conntrack_htable_size);
  46.         }
  47.         /*仍然分配失败*/
  48.         if (!ip_conntrack_hash) {
  49.                 printk(KERN_ERR "Unable to create ip_conntrack_hash\n");
  50.                 goto err_unreg_sockopt;
  51.         }

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

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

  66.         /* Don't NEED lock here, but good form anyway. */
  67.         WRITE_LOCK(&ip_conntrack_lock);
  68.        
  69. /* 注册协议。对不同协议,连接跟踪记录的参数不同,所以不同的协议定义了不同的 ip_conntrack_protocol结构来处理与协议相关的内容。这些结构被注册到一个全局的链表中,在使用时根据协议去查找,并调用相应的处理函数来完成相应的动作。*/
  70.         for (i = 0; i < MAX_IP_CT_PROTO; i++)
  71.                 ip_ct_protos[i] = &ip_conntrack_generic_protocol;
  72.         ip_ct_protos[IPPROTO_TCP] = &ip_conntrack_protocol_tcp;
  73.         ip_ct_protos[IPPROTO_UDP] = &ip_conntrack_protocol_udp;
  74.         ip_ct_protos[IPPROTO_ICMP] = &ip_conntrack_protocol_icmp;
  75.         WRITE_UNLOCK(&ip_conntrack_lock);
  76.        
  77.         /*初始化hash表*/
  78.         for (i = 0; i < ip_conntrack_htable_size; i++)
  79.                 INIT_LIST_HEAD(&ip_conntrack_hash[i]);

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

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

  87.         return ret;

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

  94.         return -ENOMEM;
  95. }
复制代码


在这个函数中,有两个重点的地方值得注意,一个是hash表的相关变量的初始化、内存空间的分析等等,另一个是协议的注册。
        连接跟踪由于针对每种协议的处理,都有些细微不同的地方,举个例子,我们前面讲到数据包至tuple的转换,TCP的转换与ICMP的转换肯定不同的,因为ICMP连端口的概念也没有,所以,对于每种协议的一些特殊处理的函数,需要进行封装,struct ip_conntrack_protocol 结构就实现了这一封装,在初始化工作中,针对最常见的TCP、UDP和ICMP协议,定义了ip_conntrack_protocol_tcp、ip_conntrack_protocol_udp和ip_conntrack_protocol_icmp三个该类型的全局变量,初始化函数中,将它们封装至ip_ct_protos 数组,这些,在后面的数据包处理后,就可以根据包中的协议值,使用ip_ct_protos[协议值],找到注册的协议节点,就可以方便地调用协议对应的处理函数了,我们在后面将看到这一调用过程。

3.2        钩子函数的注册
init_or_cleanup 函数在创建/proc文件系统完成后,会调用nf_register_hook 函数注册钩子,进行连接跟踪,按优先级和Hook不同,注册了多个钩子:
  1.         ret = nf_register_hook(&ip_conntrack_defrag_ops);
  2.         if (ret < 0) {
  3.                 printk("ip_conntrack: can't register pre-routing defrag hook.\n");
  4.                 goto cleanup_proc_stat;
  5.         }
  6.         ret = nf_register_hook(&ip_conntrack_defrag_local_out_ops);
  7.         if (ret < 0) {
  8.                 printk("ip_conntrack: can't register local_out defrag hook.\n");
  9.                 goto cleanup_defragops;
  10.         }
  11.         ……
复制代码


整个Hook注册好后,如下图所示:


上图中,粗黑体标识函数就是连接跟踪注册的钩子函数,除此之外,用于处理分片包和处理复杂协议的钩子函数在上图中没有标识出来。处理分片包的钩子用于重组分片,用于保证数据在进入连接跟踪模块不会是一个分片数据包。例如,在数据包进入NF_IP_PRE_ROUTING Hook点,主要的连接跟踪函数是ip_conntrack_in,然而,在它之前,还注册了ip_conntrack_defrag,用于处理分片数据包:
  1. static unsigned int ip_conntrack_defrag(unsigned int hooknum,
  2.                                         struct sk_buff **pskb,
  3.                                         const struct net_device *in,
  4.                                         const struct net_device *out,
  5.                                         int (*okfn)(struct sk_buff *))
  6. {
  7.         /* Gather fragments. */
  8.         if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
  9.                 *pskb = ip_ct_gather_frags(*pskb,
  10.                                            hooknum == NF_IP_PRE_ROUTING ?
  11.                                            IP_DEFRAG_CONNTRACK_IN :
  12.                                            IP_DEFRAG_CONNTRACK_OUT);
  13.                 if (!*pskb)
  14.                         return NF_STOLEN;
  15.         }
  16.         return NF_ACCEPT;
  17. }
复制代码


对于我们本章的分析而言,主要是以“Linux做为一个网关主机,转发过往数据”为主线,更多关注的是在NF_IP_PRE_ROUTING和NF_IP_POSTROUTING两个Hook点上注册的两个钩子函数ip_conntrack_in和ip_refrag(这个函数主要执行的是ip_confirm函数)。
        钩子的注册的另一个值得注意的小问题,就是钩子函数的优先级,NF_IP_PRE_ROUTING上的优先级是NF_IP_PRI_CONNTRACK ,意味着它的优先级是很高的,这也意味着每个输入数据包首先被传输到连接跟踪模块,才会进入其它优先级较低的模块。同样地,NF_IP_POSTROUTING上的优先级为NF_IP_PRI_CONNTRACK_CONFIRM,优先级是很低的,也就是说,等到其它优先级高的模块处理完成后,才会做最后的处理,然后将数据包送出去。

4.ip_conntrack_in

数据包进入Netfilter后,会调用ip_conntrack_in函数,以进入连接跟踪模块,ip_conntrack_in 主要完成的工作就是判断数据包是否已在连接跟踪表中,如果不在,则为数据包分配ip_conntrack,并初始化它,然后,为这个数据包设置连接状态。
  1. /* Netfilter hook itself. */
  2. unsigned int ip_conntrack_in(unsigned int hooknum,
  3.                              struct sk_buff **pskb,
  4.                              const struct net_device *in,
  5.                              const struct net_device *out,
  6.                              int (*okfn)(struct sk_buff *))
  7. {
  8.         struct ip_conntrack *ct;
  9.         enum ip_conntrack_info ctinfo;
  10.         struct ip_conntrack_protocol *proto;
  11.         int set_reply;
  12.         int ret;

  13.         /* 判断当前数据包是否已被检查过了 */
  14.         if ((*pskb)->nfct) {
  15.                 CONNTRACK_STAT_INC(ignore);
  16.                 return NF_ACCEPT;
  17.         }

  18. /* 分片包当会在前一个Hook中被处理,事实上,并不会触发该条件 */
  19.         if ((*pskb)->nh.iph->frag_off & htons(IP_OFFSET)) {
  20.                 if (net_ratelimit()) {
  21.                 printk(KERN_ERR "ip_conntrack_in: Frag of proto %u (hook=%u)\n",
  22.                        (*pskb)->nh.iph->protocol, hooknum);
  23.                 }
  24.                 return NF_DROP;
  25.         }

  26. /* 将当前数据包设置为未修改 */
  27.         (*pskb)->nfcache |= NFC_UNKNOWN;

  28. /*根据当前数据包的协议,查找与之相应的struct ip_conntrack_protocol结构*/
  29.         proto = ip_ct_find_proto((*pskb)->nh.iph->protocol);

  30.         /* 没有找到对应的协议. */
  31.         if (proto->error != NULL
  32.             && (ret = proto->error(*pskb, &ctinfo, hooknum)) <= 0) {
  33.                 CONNTRACK_STAT_INC(error);
  34.                 CONNTRACK_STAT_INC(invalid);
  35.                 return -ret;
  36.         }

  37. /*在全局的连接表中,查找与当前包相匹配的连接结构,返回的是struct ip_conntrack *类型指针,它用于描述一个数据包的连接状态*/
  38.         if (!(ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo))) {
  39.                 /* Not valid part of a connection */
  40.                 CONNTRACK_STAT_INC(invalid);
  41.                 return NF_ACCEPT;
  42.         }

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

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

  49. /*Packet函数指针,为数据包返回一个判断,如果数据包不是连接中有效的部分,返回-1,否则返回NF_ACCEPT。*/
  50.         ret = proto->packet(ct, *pskb, ctinfo);
  51.         if (ret < 0) {
  52.                 /* Invalid: inverse of the return code tells
  53.                  * the netfilter core what to do*/
  54.                 nf_conntrack_put((*pskb)->nfct);
  55.                 (*pskb)->nfct = NULL;
  56.                 CONNTRACK_STAT_INC(invalid);
  57.                 return -ret;
  58.         }

  59. /*设置应答状态标志位*/
  60.         if (set_reply)
  61.                 set_bit(IPS_SEEN_REPLY_BIT, &ct->status);

  62.         return ret;
  63. }
复制代码


在初始化的时候,我们就提过,连接跟踪模块将所有支持的协议,都使用struct ip_conntrack_protocol 结构封装,注册至全局数组ip_ct_protos,这里首先调用函数ip_ct_find_proto根据当前数据包的协议值,找到协议注册对应的模块。然后调用resolve_normal_ct 函数进一步处理。

[ 本帖最后由 独孤九贱 于 2006-8-22 08:48 编辑 ]

作者: 独孤九贱   发布时间: 2006-08-21

5.resolve_normal_ct
        resolve_normal_ct 函数是连接跟踪中最重要的函数之一,它的主要功能就是判断数据包在连接跟踪表是否存在,如果不存在,则为数据包分配相应的连接跟踪节点空间并初始化,然后设置连接状态:
  1. /* On success, returns conntrack ptr, sets skb->nfct and ctinfo */
  2. static inline struct ip_conntrack *
  3. resolve_normal_ct(struct sk_buff *skb,
  4.                   struct ip_conntrack_protocol *proto,
  5.                   int *set_reply,
  6.                   unsigned int hooknum,
  7.                   enum ip_conntrack_info *ctinfo)
  8. {
  9.         struct ip_conntrack_tuple tuple;
  10.         struct ip_conntrack_tuple_hash *h;
  11.         struct ip_conntrack *ct;

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

  13. /*前面提到过,需要将一个数据包转换成tuple,这个转换,就是通过ip_ct_get_tuple函数实现的*/
  14.         if (!ip_ct_get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4,
  15.                                 &tuple,proto))
  16.                 return NULL;

  17.         /*查看数据包对应的tuple在连接跟踪表中是否存在 */
  18.         h = ip_conntrack_find_get(&tuple, NULL);
  19.         if (!h) {
  20.                 /*如果不存在,初始化之*/
  21. h = init_conntrack(&tuple, proto, skb);
  22.                 if (!h)
  23.                         return NULL;
  24.                 if (IS_ERR(h))
  25.                         return (void *)h;
  26.         }
  27. /*根据hash表节点,取得数据包对应的连接跟踪结构*/
  28.         ct = tuplehash_to_ctrack(h);

  29.         /* 判断连接的方向 */
  30.         if (DIRECTION(h) == IP_CT_DIR_REPLY) {
  31.                 *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
  32.                 /* Please set reply bit if this packet OK */
  33.                 *set_reply = 1;
  34.         } else {
  35.                 /* Once we've had two way comms, always ESTABLISHED. */
  36.                 if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
  37.                         DEBUGP("ip_conntrack_in: normal packet for %p\n",
  38.                                ct);
  39.                         *ctinfo = IP_CT_ESTABLISHED;
  40.                 } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
  41.                         DEBUGP("ip_conntrack_in: related packet for %p\n",
  42.                                ct);
  43.                         *ctinfo = IP_CT_RELATED;
  44.                 } else {
  45.                         DEBUGP("ip_conntrack_in: new packet for %p\n",
  46.                                ct);
  47.                         *ctinfo = IP_CT_NEW;
  48.                 }
  49.                 *set_reply = 0;
  50.         }
  51. /*设置skb的对应成员,如使用计数器、数据包状态标记*/
  52.         skb->nfct = &ct->ct_general;
  53.         skb->nfctinfo = *ctinfo;
  54.         return ct;
  55. }
复制代码



这个函数包含了连接跟踪中许多重要的步骤
n        调用ip_ct_get_tuple函数,把数据包转换为tuple;
n        ip_conntrack_find_get函数,根据tuple查找连接跟踪表;
n        init_conntrack函数,初始化一条连接;
n        判断连接方向,设置连接状态;

5.1 数据包的转换
ip_ct_get_tuple 实现数据包至tuple的转换,这个转换,主要是根据数据包的套接字对来进行转换的:
  1. int ip_ct_get_tuple(const struct iphdr *iph,
  2.                 const struct sk_buff *skb,
  3.                 unsigned int dataoff,
  4.                 struct ip_conntrack_tuple *tuple,
  5.                 const struct ip_conntrack_protocol *protocol)
  6. {
  7.                 /* Never happen */
  8.                 if (iph->frag_off & htons(IP_OFFSET)) {
  9.                         printk("ip_conntrack_core: Frag of proto %u.\n",
  10.                        iph->protocol);
  11.                         return 0;
  12.         }
  13. /*设置来源、目的地址*/
  14.                 tuple->src.ip = iph->saddr;
  15.                 tuple->dst.ip = iph->daddr;
  16.         tuple->dst.protonum = iph->protocol;
  17.                 tuple->dst.dir = IP_CT_DIR_ORIGINAL;

  18.         return protocol->pkt_to_tuple(skb, dataoff, tuple);
  19. }
复制代码


回忆一下我们前面分析协议的初始化中协议初始化的部份,pkt_to_tuple 函数指针,以每种协议的不同而不同,以TCP协议为例:
  1. static int tcp_pkt_to_tuple(const struct sk_buff *skb,
  2.                             unsigned int dataoff,
  3.                             struct ip_conntrack_tuple *tuple)
  4. {
  5.                 struct tcphdr _hdr, *hp;

  6.                 /* 获取TCP报头*/
  7. hp = skb_header_pointer(skb, dataoff, 8, &_hdr);
  8.         if (hp == NULL)
  9.                         return 0;
  10. /*根据报头的端口信息,设置tuple对应成员*/
  11.                 tuple->src.u.tcp.port = hp->source;
  12.         tuple->dst.u.tcp.port = hp->dest;

  13.         return 1;
  14. }
复制代码

TCP协议中,根据来源和目的端口设置,其它协议类似,读者可以对比分析。

5.2 Hash 表的搜索
要对Hash表进行遍历,首要需要找到hash表的入口,然后来遍历该入口指向的链表。每个链表的节点是struct ip_conntrack_tuple_hash,它封装了tuple,所谓封装,就是把待查找的tuple与节点中已存的tuple相比较,我们来看这一过程的实现。
计算hash值,是调用hash_conntrack函数,根据数据包对应的tuple实现的:
  1. unsigned int hash = hash_conntrack(tuple);

  2.         这样,tuple对应的hash表入口即为ip_conntrack_hash[hash],也就是链表的首节点,然后调用ip_conntrack_find_get函数进行查找:
  3. struct ip_conntrack_tuple_hash *
  4. ip_conntrack_find_get(const struct ip_conntrack_tuple *tuple,
  5.                       const struct ip_conntrack *ignored_conntrack)
  6. {
  7.         struct ip_conntrack_tuple_hash *h;

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

  14.         return h;
  15. }
复制代码


链表是内核中一个标准的双向链表,可以调用宏list_for_each_entry 进遍历链表:
  1. static struct ip_conntrack_tuple_hash *
  2. __ip_conntrack_find(const struct ip_conntrack_tuple *tuple,
  3.                     const struct ip_conntrack *ignored_conntrack)
  4. {
  5.         struct ip_conntrack_tuple_hash *h;
  6.         unsigned int hash = hash_conntrack(tuple);

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

  15.         return NULL;
  16. }
复制代码


list_for_each_entry在以&ip_conntrack_hash[hash]为起始地址的链表中,逐个搜索其成员,比较这个节点中的tuple是否与待查找的tuple是否一致,这个比较过程,是通过conntrack_tuple_cmp 函数实现的:
  1. conntrack_tuple_cmp(const struct ip_conntrack_tuple_hash *i,
  2.                     const struct ip_conntrack_tuple *tuple,
  3.                     const struct ip_conntrack *ignored_conntrack)
  4. {
  5.         MUST_BE_READ_LOCKED(&ip_conntrack_lock);
  6.         return tuplehash_to_ctrack(i) != ignored_conntrack
  7.                 && ip_ct_tuple_equal(tuple, &i->tuple);
  8. }
复制代码


tuplehash_to_ctrack 函数主要是取连接跟踪ip_conntrack中的连接方向,判断它是否等于ignored_conntrack,对与这里的比较而言,ignored_conntrack传递过来的为NULL。
主要的比较函数是ip_ct_tuple_equal函数,函数分为“来源”和“目的”进行比较:

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

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

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


这里的比较,除了IP地址之外,并没有直接比较“端口”,这是因为像ICMP协议这样的并没有“端口”协议,struct ip_conntrack_tuple 结构中,与协议相关的,如端口等,都定义成union类型,这样,就可以直接使用u.all,而不用再去管TCP,UDP还是ICMP了。

5.3 连接初始化
内核使用ip_conntrack结构来描述一个数据包的连接状态,init_conntrack函数就是在连接状态表中不存在当前数据包时,初始化一个ip_conntrack结构,此结构被Netfilter用来描述一条连接,前面分析hash表时,已经分析了它的tuplehash成员:

  1. struct ip_conntrack
  2. {
  3.         /* 包含了使用计数器和指向删除连接的函数的指针 */
  4.         struct nf_conntrack ct_general;

  5.         /* 连接状态位,它通常是一个ip_conntrack_status类型的枚举变量,如IPS_SEEN_REPLY_BIT等*/
  6.         unsigned long status;

  7.         /* 内核的定时器,用于处理连接超时 */
  8.         struct timer_list timeout;

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

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

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

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

  21.         union ip_conntrack_help help;

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

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

  34.         struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
  35. };


  36. static struct ip_conntrack_tuple_hash *
  37. init_conntrack(const struct ip_conntrack_tuple *tuple,
  38.                struct ip_conntrack_protocol *protocol,
  39.                struct sk_buff *skb)
  40. {
  41.         struct ip_conntrack *conntrack;
  42.         struct ip_conntrack_tuple repl_tuple;
  43.         size_t hash;
  44.         struct ip_conntrack_expect *exp;

  45.         /*如果计算hash值的随机数种子没有被初始化,则初始化之*/
  46.         if (!ip_conntrack_hash_rnd_initted) {
  47.                 get_random_bytes(&ip_conntrack_hash_rnd, 4);
  48.                 ip_conntrack_hash_rnd_initted = 1;
  49.         }

  50.         /*计算hash值*/
  51.         hash = hash_conntrack(tuple);
  52.        
  53.         /*判断连接跟踪表是否已满*/
  54.         if (ip_conntrack_max
  55.             && atomic_read(&ip_conntrack_count) >= ip_conntrack_max) {
  56.                 /* Try dropping from this hash chain. */
  57.                 if (!early_drop(&ip_conntrack_hash[hash])) {
  58.                         if (net_ratelimit())
  59.                                 printk(KERN_WARNING
  60.                                        "ip_conntrack: table full, dropping"
  61.                                        " packet.\n");
  62.                         return ERR_PTR(-ENOMEM);
  63.                 }
  64.         }

  65.         /*根据当前的tuple取反,计算该数据包的“应答”的tuple*/
  66.         if (!ip_ct_invert_tuple(&repl_tuple, tuple, protocol)) {
  67.                 DEBUGP("Can't invert tuple.\n");
  68.                 return NULL;
  69.         }
  70.         /*为数据包对应的连接分配空间*/
  71.         conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);
  72.         if (!conntrack) {
  73.                 DEBUGP("Can't allocate conntrack.\n");
  74.                 return ERR_PTR(-ENOMEM);
  75.         }
  76.         /*初始化该结构*/
  77.         memset(conntrack, 0, sizeof(*conntrack));
  78.         /*使用计数器累加*/
  79.         atomic_set(&conntrack->ct_general.use, 1);
  80.         /*设置destroy函数指针*/
  81.         conntrack->ct_general.destroy = destroy_conntrack;
  82.         /*设置正反两个方向的tuple*/
  83. conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *tuple;
  84.         conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = repl_tuple;
  85.         if (!protocol->new(conntrack, skb)) {
  86.                 kmem_cache_free(ip_conntrack_cachep, conntrack);
  87.                 return NULL;
  88.         }
  89.         /* 初始化时间计数器,并设置超时初始函数 */
  90.         init_timer(&conntrack->timeout);
  91.         conntrack->timeout.data = (unsigned long)conntrack;
  92.         conntrack->timeout.function = death_by_timeout;

  93.         WRITE_LOCK(&ip_conntrack_lock);
  94.         exp = find_expectation(tuple);

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

  108.                 CONNTRACK_STAT_INC(new);
  109.         }

  110.         /* 这里,并没有直接就把该连接加入hash表,而是先加入到unconfirmed链表中. */
  111.         list_add(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list, &unconfirmed);

  112.         atomic_inc(&ip_conntrack_count);
  113.         WRITE_UNLOCK(&ip_conntrack_lock);

  114.         if (exp) {
  115.                 if (exp->expectfn)
  116.                         exp->expectfn(conntrack, exp);
  117.                 destroy_expect(exp);
  118.         }

  119.         /*返回的是初始方向的hash节点*/
  120.         return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
  121. }
复制代码



在前文中提到过,一条完整的连接,采用struct ip_conntrack 结构描述,初始化函数的主要功能,就是分配一个这样的空间,然后初始化它的一些成员。

在这个函数中,有三个重要的地方需要注意,一个是根据当前tuple,计算出应答方向的tuple,它是调用ip_ct_invert_tuple 函数实现的:
  1. int
  2. ip_ct_invert_tuple(struct ip_conntrack_tuple *inverse,
  3.                    const struct ip_conntrack_tuple *orig,
  4.                    const struct ip_conntrack_protocol *protocol)
  5. {
  6.         inverse->src.ip = orig->dst.ip;
  7.         inverse->dst.ip = orig->src.ip;
  8.         inverse->dst.protonum = orig->dst.protonum;
  9.         inverse->dst.dir = !orig->dst.dir;

  10.         return protocol->invert_tuple(inverse, orig);
  11. }
复制代码



这个函数事实上,与前面讲的tuple的转换是一样的,只是来了个乾坤大挪移,把来源和目的,以及方向对调了。

另一个重点的是函数对特殊协议的支持,我们这里暂时跳过了这部份。

第三个地方是调用协议的new函数:
        if (!protocol->new(conntrack, skb)) {
                kmem_cache_free(ip_conntrack_cachep, conntrack);
                return NULL;
        }
new 函数指定在每个封包第一次创建连接时被调用,它根据协议的不同,所处理的过程不同,以ICMP协议为例:
  1. /* Called when a new connection for this protocol found. */
  2. static int icmp_new(struct ip_conntrack *conntrack,
  3.                     const struct sk_buff *skb)
  4. {
  5.         static u_int8_t valid_new[]
  6.                 = { [ICMP_ECHO] = 1,
  7.                     [ICMP_TIMESTAMP] = 1,
  8.                     [ICMP_INFO_REQUEST] = 1,
  9.                     [ICMP_ADDRESS] = 1 };

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

对于ICMP协议而言,仅有ICMP 请求回显、时间戳请求、信息请求(已经很少用了)、地址掩码请求这四个“请求”,可能是一个“新建”的连接,所以,ICMP协议的new函数判断是否是一个全法的ICMP新建连接,如果是非法的,则返回0,否则,初始化协议使用计数器,返回1。

5.4 连接状态的判断
resolve_normal_ct 函数的最后一个重要的工作是对连接状态的判断,tuple中包含一个“方向”成员dst.dir,对于一个初始连接,它是IP_CT_DIR_ORIGINAL:
tuple->dst.dir = IP_CT_DIR_ORIGINAL;
而它的应答包的tuple,则为IP_CT_DIR_REPLY:
inverse->dst.dir = !orig->dst.dir;
IP_CT_DIR_ORIGINAL 和IP_CT_DIR_REPLY都是枚举变量:
  1. enum ip_conntrack_dir
  2. {
  3.         IP_CT_DIR_ORIGINAL,
  4.         IP_CT_DIR_REPLY,
  5.         IP_CT_DIR_MAX
  6. };
复制代码


宏DIRECTION 就根据tuple中对应成员的值,判断数据包的方向,
/* If we're the first tuple, it's the original dir. */
#define DIRECTION(h) ((enum ip_conntrack_dir)(h)->tuple.dst.dir)

但是,还有一些特殊地方,比如TCP协议,它是一个面向连接的协议,所以,它的“初始”或“应答”包,并不一定就是“新建”或单纯的“应答”包,而是在一个连接过程中的“已建连接包”,另一个,如FTP等 复杂协议,它们还存在一些“关联”的连接,当然这两部份目前还没有涉及到,但并不影响我们分析如下这段代码:
  1.         /* 如果是一个应答包 ,设置状态为已建+应答*/
  2.         if (DIRECTION(h) == IP_CT_DIR_REPLY) {
  3.                 *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
  4.                 /* 设置应答标志变量 */
  5.                 *set_reply = 1;
  6.         } else {
  7.                 /* 新建连接方过来的数据包,对面向连接的协议而言,可能是一个已建连接,判断其标志位*/
  8.                 if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
  9.                         DEBUGP("ip_conntrack_in: normal packet for %p\n",
  10.                                ct);
  11.                         *ctinfo = IP_CT_ESTABLISHED;
  12.                 } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
  13.                         DEBUGP("ip_conntrack_in: related packet for %p\n",
  14.                                ct);
  15.                         *ctinfo = IP_CT_RELATED;                        //关联连接
  16.                 } else {
  17.                         DEBUGP("ip_conntrack_in: new packet for %p\n",
  18.                                ct);
  19.                         *ctinfo = IP_CT_NEW;                                //否则,则为一个新建连接
  20.                 }
  21.                 *set_reply = 0;
  22.         }
  23.        
  24.         /*设置数据包skb与连接状态的关联*/
  25.         skb->nfct = &ct->ct_general;
  26.         /*每个sk_buff都将与ip_conntrack的一个状态关联,所以从sk_buff可以得到相应ip_conntrack的状态,即数据包的状态*/
  27.         skb->nfctinfo = *ctinfo;
  28.         return ct;
复制代码


以上的代表所表示的发送或应答的状态如下图所示:


6.        ip_confirm

以上的工作事实上都很简单,基本思路是:
一个包来了,转换其tuple,看其在连接跟踪表中没有,有的话,更新其状态,以其做一些与协议相关的工作,如果没有,则分配一个新的连接表项,并与skb_buff关连,但是问题是,这个表项,还没有被加入连接表当中来。其实这样做的理由很简单,因为这个时候,这个包是否有机会活命还是个未知数,例如被其它模块给Drop了……所以,要等到一切安全了,再来将这个表项插入至连接跟踪表。
这个“一切安全”当然是Netfilter所有的模块处理完了,最完全了。

当数据包要离开Netfilter时,它会穿过NF_IP_POST_ROUTING Hook点,状态跟踪模块在这里注册了ip_refrag函数(前面谈到过它的优先级是很低的)。这个Hook函数的工作,也可以猜测到了:“判断表项是否已经在连接跟踪表中了,如果没有,就将其插入表中”!

  1. static unsigned int ip_refrag(unsigned int hooknum,
  2.                               struct sk_buff **pskb,
  3.                               const struct net_device *in,
  4.                               const struct net_device *out,
  5.                               int (*okfn)(struct sk_buff *))
  6. {
  7.         struct rtable *rt = (struct rtable *)(*pskb)->dst;

  8.         /* ip_confirm函数用于处理将tuple加入hash表等重要的后续处理 */
  9.         if (ip_confirm(hooknum, pskb, in, out, okfn) != NF_ACCEPT)
  10.                 return NF_DROP;

  11.         /* 在连接跟踪开始之前,对分片包进行了重组,这里判断数据包是否需要分片,如果要分片,就调用ip_fragment分片函数将数据包分片发送出去,因为数据包已经被发送走了,所以,在它之后的任何Hook函数已经没有意思了 */
  12.         if ((*pskb)->len > dst_mtu(&rt->u.dst) &&
  13.             !skb_shinfo(*pskb)->tso_size) {
  14.                 /* No hook can be after us, so this should be OK. */
  15.                 ip_fragment(*pskb, okfn);
  16.                 return NF_STOLEN;
  17.         }
  18.         return NF_ACCEPT;
  19. }
复制代码


ip_confirm 函数是状态跟踪的另一个重要的函数:

  1. static unsigned int ip_confirm(unsigned int hooknum,
  2.                                struct sk_buff **pskb,
  3.                                const struct net_device *in,
  4.                                const struct net_device *out,
  5.                                int (*okfn)(struct sk_buff *))
  6. {
  7.         return ip_conntrack_confirm(pskb);
  8. }
复制代码

        函数仅是转向,将控制权转交给ip_conntrack_confirm函数:

  1. static inline int ip_conntrack_confirm(struct sk_buff **pskb)
  2. {
  3.         if ((*pskb)->nfct
  4.             && !is_confirmed((struct ip_conntrack *)(*pskb)->nfct))
  5.                 return __ip_conntrack_confirm(pskb);
  6.         return NF_ACCEPT;
  7. }
复制代码



is_comfirmed函数用于判断数据包是否已经被__ip_conntrack_confirm函数处理过了,它是通过IPS_CONFIRMED_BIT 标志位来判断,而这个标志位当然是在__ip_conntrack_confirm函数中来设置的:

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

        /* 如果当前包不是一个初始方向的封包,则直接返回. */
        if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
                return NF_ACCEPT;

/*计算初始及应答两个方向tuple对应的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表中查找初始及应答的节点*/
        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);

                /*主要的工作就在于此了:将当前连接表项(初始和应答的tuple)添加进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]

这样,一条新建连接就被加入到表项当中了。如果其有后续连接,如应答,进入连接跟踪表,又转换其tuple,然后查到此表项,循环中……

[ 本帖最后由 独孤九贱 于 2007-12-7 11:11 编辑 ]

作者: 独孤九贱   发布时间: 2006-08-21

哈,九贱兄要将 netfilter 和 iptables 玩到通通透啊

作者: platinum   发布时间: 2006-08-21



QUOTE:
原帖由 platinum 于 2006-8-21 18:47 发表
哈,九贱兄要将 netfilter 和 iptables 玩到通通透啊



还没有呢,ALG的代码还没有整理出来,这个状态检测,只是普通的状态检测的实现,如ICMP,UDP,麻烦点的TCP的处理,ALG,如FTP协议的处理,都只是拿下了原理,框架,写了笔记,有空的时候,就把代码分析继续贴上来,呵呵。

欢迎大家回贴讨论!

作者: 独孤九贱   发布时间: 2006-08-22

http://www.kernelchina.org/linux ... 8%BD%AC%E6%8D%A2%22,前些天从网上看到的,感觉不错,大家可以互相学习一下。

作者: liyanux   发布时间: 2006-08-22

ALG是啥?

作者: chenyajun5   发布时间: 2006-08-24



QUOTE:
原帖由 chenyajun5 于 2006-8-24 14:04 发表
ALG是啥?



ALG:application level gateway
一种在安全设备里面分析和修改应用层协议内容的技术
用于创建动态连接和修改协议内容。

这里仅指的连接跟踪中对动态协议,如FTP的支持;

作者: 独孤九贱   发布时间: 2006-08-24

顺便问个问题: 连接跟踪表的条目容量,可以在加载ip_conntrack时通过hashsize来指定,但是如果把这个模块编译进内核,是否就无法制定hashsize了?如果要加大连接跟踪表的容量,直接修改 ip_conntrack_max有用吗?跟指定ip_conntrack模块的hashsize有何区别?

作者: 急不通   发布时间: 2006-08-25



QUOTE:
原帖由 急不通 于 2006-8-25 10:12 发表
顺便问个问题: 连接跟踪表的条目容量,可以在加载ip_conntrack时通过hashsize来指定,但是如果把这个模块编译进内核,是否就无法制定hashsize了?如果要加大连接跟踪表的容量,直接修改 ip_conntrack_max有用吗? ...



只有直接改源码了……

作者: 独孤九贱   发布时间: 2006-08-26

路过看看

作者: lincoln_834100   发布时间: 2006-09-06

刚刚读完,由浅入深,写的真是好。期待九贱兄关于ALG和FTP等协议分析的大作赶紧完成。

作者: nevermind1997   发布时间: 2006-11-15

正在受益中........
九贱兄乃神人也

作者: alibase   发布时间: 2006-11-30

在受益中,有些地方不明白,希望楼主指教
1,连接跟踪表是一张链表,链表的每个节点是一个连接?

2,“表的大小由ip_conntrack_htable_size 全局变量决定”是否意味着有ip_conntrack_htable_size 个节点

3,“整个连接跟踪表大小使用全局变量ip_conntrack_max描述”中ip_conntrack_max和ip_conntrack_htable_size 有什么不同?

4,“整个连接跟踪表大小使用全局变量ip_conntrack_max描述,与hash表的关系是ip_conntrack_max = 8 * ip_conntrack_htable_size”,这里的hash表是什么东东?是和一个连接节点相关吗?还是和整个链表相关?也就hash表究竟代表什么?

不好意思,看我都说糊涂了
期待中...........

作者: alibase   发布时间: 2006-12-08



QUOTE:
原帖由 alibase 于 2006-12-8 17:42 发表
在受益中,有些地方不明白,希望楼主指教
1,连接跟踪表是一张链表,链表的每个节点是一个连接?

2,“表的大小由ip_conntrack_htable_size 全局变量决定”是否意味着有ip_conntrack_htable_size 个节点

3 ...



你确实说得很糊涂……

连接跟踪表是一张hash链表,仅此而已……
A1->node1->node2->node3……
A2->node1->node2->node3……
……
AN->node1->……->nodeN

每一个node是一个数据包对应的多元组,请求和应答两个多元组构成一个完整连接,另外,还要描述的是ALG的关联连接……

作者: 独孤九贱   发布时间: 2006-12-08

谢谢楼主了,理解中......

作者: alibase   发布时间: 2006-12-11

请问应该怎么设置才能文中说的那些图,谢谢:)

作者: hedandi   发布时间: 2006-12-30

好文章,一定要保留哈

作者: scuwb   发布时间: 2007-11-22

楼主文中说的那些图我怎么一个都看不到?

作者: mogo   发布时间: 2007-12-05

看不到图,期待楼主现身。

作者: kwest   发布时间: 2007-12-06



QUOTE:
原帖由 kwest 于 2007-12-6 14:00 发表
看不到图,期待楼主现身。


看不到图,是因为我不知道如何把图上传并插入到文章中

作者: 独孤九贱   发布时间: 2007-12-07

新的内核引入了 xt_table 这个东西,连 kernel 的 net 和 netfilter 目录结构都变了,有谁对这个有研究?
比如,有 ip_conntrack_ftp,也有 xt_conntrack_ftp,代码几乎相同,这样设计用意何在?

作者: platinum   发布时间: 2007-12-07

应该是为了向下兼容,对之前的程序提供接口吧。

作者: ShadowStar   发布时间: 2007-12-07



QUOTE:
原帖由 ShadowStar 于 2007-12-7 17:39 发表
应该是为了向下兼容,对之前的程序提供接口吧。


那 xt_table 的用途呢?以前没有,现在为什么要增加这个呢?“向下兼容”的好像有点说不通……

作者: platinum   发布时间: 2007-12-07

您使iptable,俺佩服的紧,
还要好事者使IPv6,拿个x来统一

作者: sisi8408   发布时间: 2007-12-09



QUOTE:
原帖由 platinum 于 2007-12-7 20:09 发表

那 xt_table 的用途呢?以前没有,现在为什么要增加这个呢?“向下兼容”的好像有点说不通……




QUOTE:
iptables release
The netfilter core team has released iptables-1.4.0rc1. This is the first release candidate of the new iptables branch 1.4. This release candidate adds support for the generic xtables infrastructure that strongly improves IPv6 support. Also several accumulated bugfixed are included. Test it!



貌似为了增强对ipv6协议的支持,引入了通用的xtables架构……

作者: 独孤九贱   发布时间: 2007-12-10

很不错,继续关注

作者: pywj777   发布时间: 2008-01-18

诚恳的问一句 :楼主是做什么的?工作内容主要是什么?

作者: linux_ha   发布时间: 2008-01-21



QUOTE:
原帖由 linux_ha 于 2008-1-21 15:48 发表
诚恳的问一句 :楼主是做什么的?工作内容主要是什么?



这很重要吗?我的工作很不起眼的,一个IT棒棒……

作者: 独孤九贱   发布时间: 2008-01-22

受教了  谢谢楼主!

作者: z-cf   发布时间: 2008-01-24

九贱兄的Netfilter和Iptable的文章让人受益匪浅啊。狂顶一下。然后拜读啊。

作者: Godbach   发布时间: 2008-07-22

热门下载

更多