Linux协议栈-netfilter(2)-conntrack

文章转载------------------------------点击打开链接


连接跟踪(conntrack)用来跟踪和记录一个连接的状态,它为经过协议栈的数据包记录状态,这为防火墙检测连接状态提供了参考,同时在数据包需要做NAT时也为转换工作提供便利。

本文基于Linux内核2.6.31实现的conntrack进行源码分析。

1. conntrack模块初始化

1.1 conntrack模块入口

conntrack模块的初始化主要就是为必要的全局数据结构进行初始化,代码流程如下:


上面这些函数只是简单的调用关系,下面来看具体实现。

nf_conntrack_init_init_net()函数:

1. 根据内存的大小给全局变量nf_conntrack_htable_size和nf_conntrack_max赋值,他们分别指定了存放连接跟踪条目的hash表的大小以及系统可以创建的连接跟踪条目的数量。

2. 为nf_conn结构申请slab cache:

nf_conntrack_cachep= kmem_cache_create("nf_conntrack",

                             sizeof(struct nf_conn), 0, SLAB_DESTROY_BY_RCU, NULL);

这个缓存用于新建struct nf_conn结构,每个连接的状态都由这样一个结构体实例来描述,每个连接都分为original和reply两个方向,每个方向都用一个元组(tuple)表示,tuple中包含了这个方向上数据包的信息,如源IP、目的IP、源port、目的port等。

3. 给全局的nf_ct_l3protos[]赋上默认值:

nf_ct_l3protos[]数组中的每个元素都赋值为nf_conntrack_l3proto_generic,即不区分L3协议的处理函数,后续的初始化会为不同的L3协议赋上相应的值(例如1.2节初始化特定于IPv4协议的conntrack的时候)。由下面的定义可知这是一组操作函数。

该全局数据的定义为:

struct nf_conntrack_l3proto * nf_ct_l3protos[AF_MAX]__read_mostly;

  1. struct nf_conntrack_l3proto nf_conntrack_l3proto_generic __read_mostly = {
  2. .l3proto = PF_UNSPEC,
  3. .name = "unknown",
  4. .pkt_to_tuple = generic_pkt_to_tuple,
  5. .invert_tuple = generic_invert_tuple,
  6. .print_tuple = generic_print_tuple,
  7. .get_l4proto = generic_get_l4proto,
  8. };

4. 给全局的hash表nf_ct_helper_hash分配一个页大小的空间,通过nf_ct_alloc_hashtable()分配。

static struct hlist_head *nf_ct_helper_hash__read_mostly;

这个hash表用于存放helper类型的conntrack extension(见2.3节)。

这里要注意两个地方:

1) sizeof(struct hlist_nulls_head)和 sizeof(struct hlist_head)大小必须相等。

2) roundup(x, y)宏定义,这个宏保证了分配整个页的空间。

#defineroundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y))

意思是要把x调整为y的整数倍的值,如x=46,y=32,则返回64。

5. 把helper_extend注册进全局数组nf_ct_ext_types[]中,并调整helper_extend.alloc_size。

struct nf_ct_ext_type *nf_ct_ext_types[NF_CT_EXT_NUM];
  1. static struct nf_ct_ext_type helper_extend __read_mostly = {
  2. .len = sizeof(struct nf_conn_help),
  3. .align = __alignof__(struct nf_conn_help),
  4. .id = NF_CT_EXT_HELPER,
  5. };

conntrack extentions共有4种类型,即全局数组nf_ct_ext_types[]的元素个数,每个元素的含义如下定义:

  1. enum nf_ct_ext_id
  2. {
  3. NF_CT_EXT_HELPER,
  4. NF_CT_EXT_NAT,
  5. NF_CT_EXT_ACCT,
  6. NF_CT_EXT_ECACHE,
  7. NF_CT_EXT_NUM,
  8. };

struct nf_ct_ext_type结构定义如下:

  1. struct nf_ct_ext_type
  2. {
  3. /* Destroys relationships (can be NULL). */
  4. void (*destroy)(struct nf_conn *ct);
  5. /* Called when realloacted (can be NULL).
  6. Contents has already been moved. */
  7. void (*move)( void * new, void *old);
  8. enum nf_ct_ext_id id;
  9. unsigned int flags;
  10. /* Length and min alignment. */
  11. u8 len;
  12. u8 align;
  13. /* initial size of nf_ct_ext. */
  14. u8 alloc_size;
  15. };

nf_conntrack_init_net()函数:

该函数用来初始化net->ct成员。net是本地CPU的网络命名空间,在单CPU系统中就是全局变量init_net。它的ct成员的定义如下:

  1. struct netns_ct {
  2. atomic_t count;
  3. unsigned int expect_count;
  4. struct hlist_nulls_head *hash;
  5. struct hlist_head *expect_hash;
  6. struct hlist_nulls_head unconfirmed;
  7. struct hlist_nulls_head dying;
  8. struct ip_conntrack_stat *stat;
  9. int sysctl_events;
  10. unsigned int sysctl_events_retry_timeout;
  11. int sysctl_acct;
  12. int sysctl_checksum;
  13. unsigned int sysctl_log_invalid; /* Log invalid packets */
  14. #ifdef CONFIG_SYSCTL
  15. struct ctl_table_header *sysctl_header;
  16. struct ctl_table_header *acct_sysctl_header;
  17. struct ctl_table_header *event_sysctl_header;
  18. #endif
  19. int hash_vmalloc;
  20. int expect_vmalloc;
  21. };

函数分析:

  1. static int nf_conntrack_init_net(struct net *net)
  2. {
  3. int ret;
  4. atomic_set(&net->ct.count, 0); /* 设置计数器为0 */
  5. /* 初始化unconfirmed链表 */
  6. INIT_HLIST_NULLS_HEAD(&net->ct.unconfirmed, UNCONFIRMED_NULLS_VAL);
  7. /* 初始化dying链表 */
  8. INIT_HLIST_NULLS_HEAD(&net->ct.dying, DYING_NULLS_VAL);
  9. /* 给net->ct.stat分配空间并清0。统计数据 */
  10. net->ct.stat = alloc_percpu(struct ip_conntrack_stat);
  11. if (!net->ct.stat) {
  12. ret = -ENOMEM;
  13. goto err_stat;
  14. }
  15. /* 初始化conntrack hash table,表的大小上面已初始化过了。同样的,这里会调整nf_conntrack_htable_size为页大小的整数倍。 */
  16. net->ct.hash = nf_ct_alloc_hashtable(&nf_conntrack_htable_size,
  17. &net->ct.hash_vmalloc, 1);
  18. if (!net->ct.hash) {
  19. ret = -ENOMEM;
  20. printk(KERN_ERR "Unable to create nf_conntrack_hash\n");
  21. goto err_hash;
  22. }
  23. /* 初始化net->ct.expect_hash及缓存,并在/proc中创建相应文件,下面会单独说明该函数。 */
  24. ret = nf_conntrack_expect_init(net);
  25. if (ret < 0)
  26. goto err_expect;
  27. /* 将acct_extend注册进全局数组nf_ct_ext_types[NF_CT_EXT_ACCT]中。 */
  28. ret = nf_conntrack_acct_init(net);
  29. if (ret < 0)
  30. goto err_acct;
  31. ret = nf_conntrack_ecache_init(net); /* event cache of netfilter */
  32. if (ret < 0)
  33. goto err_ecache;
  34. /* 单独的fake conntrack */
  35. /* Set up fake conntrack:
  36. - to never be deleted, not in any hashes */
  37. atomic_set(&nf_conntrack_untracked.ct_general.use, 1);
  38. /* - and look it like as a confirmed connection */
  39. set_bit(IPS_CONFIRMED_BIT, &nf_conntrack_untracked.status);
  40. return 0;
  41. }

nf_conntrack_expect_init()函数,

  1. int nf_conntrack_expect_init(struct net *net)
  2. {
  3. int err = -ENOMEM;
  4. if (net_eq(net, &init_net)) {
  5. if (!nf_ct_expect_hsize) {
  6. /* 期望连接hash table的size */
  7. nf_ct_expect_hsize = nf_conntrack_htable_size / 256;
  8. if (!nf_ct_expect_hsize)
  9. nf_ct_expect_hsize = 1;
  10. }
  11. /* 期望连接的最大连接数 */
  12. nf_ct_expect_max = nf_ct_expect_hsize * 4;
  13. }
  14. /* 分配期望连接hash table。 */
  15. net->ct.expect_count = 0;
  16. net->ct.expect_hash = nf_ct_alloc_hashtable(&nf_ct_expect_hsize,
  17. &net->ct.expect_vmalloc, 0);
  18. if (net->ct.expect_hash == NULL)
  19. goto err1;
  20. if (net_eq(net, &init_net)) {
  21. /* 期望连接的slab cache。 */
  22. nf_ct_expect_cachep = kmem_cache_create( "nf_conntrack_expect",
  23. sizeof(struct nf_conntrack_expect),
  24. 0, 0, NULL);
  25. if (!nf_ct_expect_cachep)
  26. goto err2;
  27. }
  28. /* /proc/net/nf_conntrack_expect文件操作。 */
  29. err = exp_proc_init(net);
  30. if (err < 0)
  31. goto err3;
  32. return 0;
  33. }

需要先说一下struct hlist_nulls_head结构以及INIT_HLIST_NULLS_HEAD宏:

  1. /*
  2. * Special version of lists, where end of list is not a NULL pointer,
  3. * but a 'nulls' marker, which can have many different values.
  4. * (up to 2^31 different values guaranteed on all platforms)
  5. *
  6. * In the standard hlist, termination of a list is the NULL pointer.
  7. * In this special 'nulls' variant, we use the fact that objects stored in
  8. * a list are aligned on a word (4 or 8 bytes alignment).
  9. * We therefore use the last significant bit of 'ptr' :
  10. * Set to 1 : This is a 'nulls' end-of-list marker (ptr >> 1)
  11. * Set to 0 : This is a pointer to some object (ptr)
  12. */
  13. struct hlist_nulls_head {
  14. struct hlist_nulls_node *first;
  15. };
  16. struct hlist_nulls_node {
  17. struct hlist_nulls_node *next, **pprev;
  18. };
  19. /* 注意右值是first里面的值而不是地址。这个值都是奇数,即二进制的最后一位是1;
  20. 在普通链表中一般用NULL来判断链表结束,而linux内核中由于地址都是4或8字节对齐的,所以指针的末两位用不上,因此可以复用一下,最后一位是1表示链表结束,是0则未结束。
  21. 这个宏保证了最末位是1,而其他位可以是随意值,所以可以有2^31种可能值。
  22. 下面是对HLIST_NULLS_HEAD链表的初始化,所以给first赋值。
  23. */
  24. #define INIT_HLIST_NULLS_HEAD(ptr, nulls) \
  25. ((ptr)->first = (struct hlist_nulls_node *) (1UL | (((long)nulls) << 1)))

在上面初始化unconfirmed和dying链表时就使用了这一特性,unconfirmed链表如果发现一个节点的地址的末两位是二进制01就说明链表结束了,dying链表如果发现一个节点的地址的末两位是11就说明链表结束了。net->ct.hash中的每个冲突链表也通过INIT_HLIST_NULLS_HEAD宏做了初始化:

  1. for (i = 0; i < nr_slots; i++)
  2. INIT_HLIST_NULLS_HEAD(&hash[i], i);


总结一下初始化完成的数据结构:

  conntrack模块使用到的SLAB缓存:

1. nf_conntrack_cachep

nf_conntrack_cachep =kmem_cache_create("nf_conntrack",

                                          sizeof(struct nf_conn),

                                          0,SLAB_DESTROY_BY_RCU, NULL);

       构建nf_conn的SLAB缓存,所有的conntrack实例都从这个SLAB缓存中申请。

2. nf_ct_expect_cachep

nf_ct_expect_cachep =kmem_cache_create("nf_conntrack_expect",

                                   sizeof(struct nf_conntrack_expect),

                                   0,0, NULL);

构建nf_conntrack_expect的SLAB缓存,所有的conntrack期望连接实例都从这个SLAB缓存中申请。

net结构中的hash表:

net->ct.hash

用于缓存nf_conn实例,一个哈希链表,实际上里面存的是struct nf_conntrack_tuple_hash结构,想得到nf_conn实体,使用nf_ct_tuplehash_to_ctrack(hash)即可。original和reply方向的tuple有不同的哈希值。
  1. struct nf_conntrack_tuple_hash {
  2. struct hlist_nulls_node hnnode;
  3. struct nf_conntrack_tuple tuple;
  4. };

struct nf_conn结构:

  1. /* 唯一描述一个连接的状态 */
  2. struct nf_conn {
  3. ......
  4. /* 两个哈希表,分别为original和reply,新建一个连接就在这两个hash中添加一项nf_conntrack_tuple。*/
  5. struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
  6. union nf_conntrack_proto proto;
  7. ......
  8. };

struct nf_conntrack_tuple结构:

  1. /* 唯一标识一个连接 */
  2. struct nf_conntrack_tuple
  3. {
  4. struct nf_conntrack_man src;
  5. /* These are the parts of the tuple which are fixed. */
  6. struct {
  7. union nf_inet_addr u3;
  8. union {
  9. /* Add other protocols here. */
  10. __be16 all;
  11. struct {
  12. __be16 port;
  13. } tcp;
  14. struct {
  15. __be16 port;
  16. } udp;
  17. struct {
  18. u_int8_t type, code;
  19. } icmp;
  20. struct {
  21. __be16 port;
  22. } dccp;
  23. struct {
  24. __be16 port;
  25. } sctp;
  26. struct {
  27. __be16 key;
  28. } gre;
  29. } u;
  30. /* The protocol. */
  31. u_int8_t protonum;
  32. /* The direction (for tuplehash) */
  33. u_int8_t dir; /* original or reply */
  34. } dst;
  35. };

net->ct.expect_hash

用于缓存ALG的nf_conn实体,即期望连接。

net->ct.unconfirmed

用于缓存还未被确认conntrack条目,数据包进入路由器后会经过两个conntrack相关的hook点,第一个hook点会新建conntrack条目,但这个条目还没有被确认,因此先放到unconfirm链表中,在经过了协议栈的处理和过滤,如果能够成功到达第二个hook点,才被添加进net->ct.hash中去。

net->ct.dying

用于存放超时并即将被删除的conntrack条目。

nf_ct_ext_types[]和nf_ct_helper_hash

conntrack extensions要用到的结构,extensions一共有4个type,其中helper类型的数据还需要一个额外的hash表,即nf_ct_helper_hash。

系统中conntrack条目可以通过proc文件/proc/net/nf_conntrack查看,文件中的内容可以参考ct_seq_show()函数的实现。

1.2 conntrack_ipv4模块入口

特定于IPv4协议的conntrack模块的初始化内容如下:



nf_conntrack_l3proto_ipv4_init()的工作如上图所示:

1.    初始化socket选项的get方法,可以通过getsockopt()来通过socket得到相应original方向的tuple。

2.    注册tcp4、udp4和icmp三个L4协议到全局的二维数组nf_ct_protos[][]中,其定义如下:

static struct nf_conntrack_l4proto **nf_ct_protos[PF_MAX]__read_mostly;

数组的每个成员都是struct nf_conntrack_l4proto类型的,这里注册的三个成员定义如下:

nf_conntrack_l4proto_tcp4(nf_conntrack_proto_tcp.c):

  1. struct nf_conntrack_l4proto nf_conntrack_l4proto_tcp4 __read_mostly =
  2. {
  3. .l3proto = PF_INET,
  4. .l4proto = IPPROTO_TCP,
  5. .name = "tcp",
  6. .pkt_to_tuple = tcp_pkt_to_tuple,
  7. .invert_tuple = tcp_invert_tuple,
  8. .print_tuple = tcp_print_tuple,
  9. .print_conntrack = tcp_print_conntrack,
  10. .packet = tcp_packet,
  11. . new = tcp_new,
  12. .error = tcp_error,
  13. # if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
  14. .to_nlattr = tcp_to_nlattr,
  15. .nlattr_size = tcp_nlattr_size,
  16. .from_nlattr = nlattr_to_tcp,
  17. .tuple_to_nlattr = nf_ct_port_tuple_to_nlattr,
  18. .nlattr_to_tuple = nf_ct_port_nlattr_to_tuple,
  19. .nlattr_tuple_size = tcp_nlattr_tuple_size,
  20. .nla_policy = nf_ct_port_nla_policy,
  21. #endif
  22. #ifdef CONFIG_SYSCTL
  23. .ctl_table_users = &tcp_sysctl_table_users,
  24. .ctl_table_header = &tcp_sysctl_header,
  25. .ctl_table = tcp_sysctl_table,
  26. #ifdef CONFIG_NF_CONNTRACK_PROC_COMPAT
  27. .ctl_compat_table = tcp_compat_sysctl_table,
  28. #endif
  29. #endif
  30. };

nf_conntrack_l4proto_udp4(nf_conntrack_proto_udp.c):

  1. struct nf_conntrack_l4proto nf_conntrack_l4proto_udp4 __read_mostly =
  2. {
  3. .l3proto = PF_INET,
  4. .l4proto = IPPROTO_UDP,
  5. .name = "udp",
  6. .pkt_to_tuple = udp_pkt_to_tuple,
  7. .invert_tuple = udp_invert_tuple,
  8. .print_tuple = udp_print_tuple,
  9. .packet = udp_packet,
  10. . new = udp_new,
  11. .error = udp_error,
  12. # if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
  13. .tuple_to_nlattr = nf_ct_port_tuple_to_nlattr,
  14. .nlattr_to_tuple = nf_ct_port_nlattr_to_tuple,
  15. .nlattr_tuple_size = nf_ct_port_nlattr_tuple_size,
  16. .nla_policy = nf_ct_port_nla_policy,
  17. #endif
  18. #ifdef CONFIG_SYSCTL
  19. .ctl_table_users = &udp_sysctl_table_users,
  20. .ctl_table_header = &udp_sysctl_header,
  21. .ctl_table = udp_sysctl_table,
  22. #ifdef CONFIG_NF_CONNTRACK_PROC_COMPAT
  23. .ctl_compat_table = udp_compat_sysctl_table,
  24. #endif
  25. #endif
  26. };

nf_conntrack_l4proto_icmp(nf_conntrack_proto_icmp.c):

  1. struct nf_conntrack_l4proto nf_conntrack_l4proto_icmp __read_mostly =
  2. {
  3. .l3proto = PF_INET,
  4. .l4proto = IPPROTO_ICMP,
  5. .name = "icmp",
  6. .pkt_to_tuple = icmp_pkt_to_tuple,
  7. .invert_tuple = icmp_invert_tuple,
  8. .print_tuple = icmp_print_tuple,
  9. .packet = icmp_packet,
  10. . new = icmp_new,
  11. .error = icmp_error,
  12. .destroy = NULL,
  13. .me = NULL,
  14. # if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
  15. .tuple_to_nlattr = icmp_tuple_to_nlattr,
  16. .nlattr_tuple_size = icmp_nlattr_tuple_size,
  17. .nlattr_to_tuple = icmp_nlattr_to_tuple,
  18. .nla_policy = icmp_nla_policy,
  19. #endif
  20. #ifdef CONFIG_SYSCTL
  21. .ctl_table_header = &icmp_sysctl_header,
  22. .ctl_table = icmp_sysctl_table,
  23. #ifdef CONFIG_NF_CONNTRACK_PROC_COMPAT
  24. .ctl_compat_table = icmp_compat_sysctl_table,
  25. #endif
  26. #endif
  27. };

注册是通过nf_conntrack_l4proto_register(l4proto)完成的,参数l4proto是要注册的struct nf_conntrack_l4proto结构,注册步骤为:

  1. 如果nf_ct_protos[l4proto->l3proto]还没有初始化,就给该数组元素中的所有数组元素赋值为nf_conntrack_l4proto_generic。
  2. 初始化nfnetlink attributes相关的l4proto->nla_size和l4proto->nla_size的值。
  3. 将l4proto赋值给nf_ct_protos[l4proto->l3proto][l4proto->l4proto]。

3.    L3协议nf_conntrack_l3proto_ipv4注册到全局数组nf_ct_l3protos[]中,数组定义如下:

struct nf_conntrack_l3proto *nf_ct_l3protos[AF_MAX]__read_mostly;

数组中的每个成员都是struct nf_conntrack_l3proto结构体类型的,这里注册的成员实例定义如下:
  1. struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 __read_mostly = {
  2. .l3proto = PF_INET,
  3. .name = "ipv4",
  4. .pkt_to_tuple = ipv4_pkt_to_tuple,
  5. .invert_tuple = ipv4_invert_tuple,
  6. .print_tuple = ipv4_print_tuple,
  7. .get_l4proto = ipv4_get_l4proto,
  8. # if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
  9. .tuple_to_nlattr = ipv4_tuple_to_nlattr,
  10. .nlattr_tuple_size = ipv4_nlattr_tuple_size,
  11. .nlattr_to_tuple = ipv4_nlattr_to_tuple,
  12. .nla_policy = ipv4_nla_policy,
  13. #endif
  14. # if defined(CONFIG_SYSCTL) && defined(CONFIG_NF_CONNTRACK_PROC_COMPAT)
  15. .ctl_table_path = nf_net_ipv4_netfilter_sysctl_path,
  16. .ctl_table = ip_ct_sysctl_table,
  17. #endif
  18. .me = THIS_MODULE,
  19. };

注册过程是通过nf_conntrack_l3proto_register(proto)完成的,参数proto是要注册的实例,注册步骤如下:

  1. 初始化nfnetlink attributes相关的proto->nla_size。
  2. 将proto赋值给nf_ct_l3protos[proto->l3proto]。

这里没有给每个成员赋初值,因为在nf_conntrack_init_init_net()函数中已经做过了。

可以看到结构体struct nf_conntrack_l3proto和结构体struct nf_conntrack_l4proto都有pkt_to_tuple()和invert_tuple()函数指针,他们分别作用在数据包的L3头部和L4头部,例如L3的pkt_to_tuple()用于获取源IP和目的IP来初始化tuple,L4的pkt_to_tuple()用于获取源port和目的port来初始化tuple。

总结一下这节初始化的内容:

1. 初始化L4层协议相关的nf_ct_protos[PF_INET][]数组中TCP, UDP, ICMP三个成员。

2. 初始化L3层协议相关的nf_ct_l3protos[PF_INET]。

3. 注册conntrack的四个hook函数。

2. conntrack处理数据包的过程

上一篇文章提到netfilter为conntrack注册了四个hook函数。


下面看一下和conntrack相关的hook函数,即上图中标记为浅蓝色的4个函数,其中ipv4_conntrack_in()和ipv4_conntrack_local()作用相似,他们都位于数据包传输的起点,且优先级仅次于defrag,两个ipv4_confirm()作用相似,他们都位于数据包传输的终点,且优先级最低。

2.1 nf_conntrack_in()

hook函数nf_conntrack_in()的实现如下:

  1. unsigned int
  2. nf_conntrack_in (struct net *net, u_int8_t pf, unsigned int hooknum,
  3. struct sk_buff *skb)
  4. {
  5. struct nf_conn *ct;
  6. enum ip_conntrack_info ctinfo;
  7. struct nf_conntrack_l3proto *l3proto;
  8. struct nf_conntrack_l4proto *l4proto;
  9. unsigned int dataoff;
  10. u_int8_t protonum;
  11. int set_reply = 0;
  12. int ret;
  13. /* Previously seen (loopback or untracked)? Ignore. */
  14. if (skb->nfct) { /* 如果skb已指向一个连接 */
  15. NF_CT_STAT_INC_ATOMIC(net, ignore);
  16. return NF_ACCEPT; /* 可能是环回,不做处理 */
  17. }
  18. /* 获取skb->data中L4层(TCP, UDP, etc)首部指针和协议类型,赋值给dataoff和protonum */
  19. l3proto = __nf_ct_l3proto_find(pf);
  20. ret = l3proto->get_l4proto(skb, skb_network_offset(skb),
  21. &dataoff, &protonum);
  22. if (ret <= 0) { /* 如果返回NF_DROP */
  23. pr_debug( "not prepared to track yet or error occured\n");
  24. NF_CT_STAT_INC_ATOMIC(net, error);
  25. NF_CT_STAT_INC_ATOMIC(net, invalid);
  26. return -ret;
  27. }
  28. /* 获取相应L4协议的struct nf_conntrack_l4proto对象。 */
  29. l4proto = __nf_ct_l4proto_find(pf, protonum);
  30. if (l4proto->error != NULL)
  31. {
  32. /* 检验L4头部最基本的合法性。如长度, checksum等 */
  33. ret = l4proto->error(net, skb, dataoff, &ctinfo, pf, hooknum);
  34. if (ret <= 0) {
  35. NF_CT_STAT_INC_ATOMIC(net, error);
  36. NF_CT_STAT_INC_ATOMIC(net, invalid);
  37. return -ret;
  38. }
  39. }
  40. /* 根据skb得到一个nf_conn结构 */
  41. ct = resolve_normal_ct(net, skb, dataoff, pf, protonum,
  42. l3proto, l4proto, &set_reply, &ctinfo);
  43. if (!ct) {
  44. /* Not valid part of a connection */
  45. NF_CT_STAT_INC_ATOMIC(net, invalid);
  46. return NF_DROP;
  47. }
  48. if (IS_ERR(ct)) {
  49. /* Too stressed to deal. */
  50. NF_CT_STAT_INC_ATOMIC(net, drop);
  51. return NF_DROP;
  52. }
  53. /* 这时skb应该已经指向一个连接了。 */
  54. NF_CT_ASSERT(skb->nfct);
  55. /* 对数据包中L4的字段进行合法性检查以及一些协议相关的处理,如修改ct的超时时间,返回处理结果(ACCEPT, DROP etc.) */
  56. ret = l4proto->packet(ct, skb, dataoff, ctinfo, pf, hooknum);
  57. if (ret <= 0) { /* 经过上面的packet函数处理失败,就把ct引用删掉 */
  58. /* Invalid: inverse of the return code tells
  59. * the netfilter core what to do */
  60. pr_debug( "nf_conntrack_in: Can't track with proto module\n");
  61. nf_conntrack_put(skb->nfct);
  62. skb->nfct = NULL;
  63. NF_CT_STAT_INC_ATOMIC(net, invalid);
  64. if (ret == -NF_DROP)
  65. NF_CT_STAT_INC_ATOMIC(net, drop);
  66. return -ret;
  67. }
  68. /* 如果set_reply=1,即这是reply方向的tuple,就设置ct->status = IPS_SEEN_REPLY_BIT。
  69. */
  70. if (set_reply && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status))
  71. nf_conntrack_event_cache(IPCT_STATUS, ct); //空函数
  72. return ret;
  73. }

resolve_normal_ct()函数用来处理数据包,查找或新建一个tuple,生成一个nf_conn结构:

  1. static inline struct nf_conn *
  2. resolve_normal_ct (struct net *net,
  3. struct sk_buff *skb,
  4. unsigned int dataoff,
  5. u_int16_t l3num,
  6. u_int8_t protonum,
  7. struct nf_conntrack_l3proto *l3proto,
  8. struct nf_conntrack_l4proto *l4proto,
  9. int *set_reply,
  10. enum ip_conntrack_info *ctinfo)
  11. {
  12. struct nf_conntrack_tuple tuple;
  13. struct nf_conntrack_tuple_hash *h = NULL;
  14. struct nf_conn *ct = NULL;
  15. /* 由skb得出一个original方向的tuple,赋值给tuple,这是一个struct nf_conntrack_tuple结构,这里给其所有成员都赋值了,以TCP包为例:
  16. tuple->src.l3num = l3num;
  17. tuple->src.u3.ip = srcip;
  18. tuple->dst.u3.ip = dstip;
  19. tuple->dst.protonum = protonum;
  20. tuple->dst.dir = IP_CT_DIR_ORIGINAL;
  21. tuple->src.u.tcp.port = srcport;
  22. tuple->dst.u.tcp.port = destport;
  23. */
  24. if (!nf_ct_get_tuple(skb, skb_network_offset(skb),
  25. dataoff, l3num, protonum, &tuple, l3proto,
  26. l4proto)) {
  27. pr_debug( "resolve_normal_ct: Can't get tuple\n");
  28. return NULL;
  29. }
  30. /* 在hash表net->ct.hash中匹配tuple,并初始化其外围的nf_conn结构 */
  31. h = nf_conntrack_find_get(net, &tuple);
  32. if (!h) {
  33. /* 如果没有找到,则创建一对新的tuple(两个方向的tuple是同时创建的)及其
  34. nf_conn结构,并添加到unconfirmed链表中。 */
  35. h = init_conntrack(net, &tuple, l3proto, l4proto, skb, dataoff);
  36. if (!h)
  37. return NULL;
  38. if (IS_ERR(h))
  39. return ( void *)h;
  40. }
  41. ct = nf_ct_tuplehash_to_ctrack(h); /* 获得tuple的nf_conn结构 */
  42. /* 修改数据包的ct状态。 */
  43. if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY) {
  44. /* 如果这个tuple是reply方向的 */
  45. *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
  46. /* Please set reply bit if this packet OK */
  47. *set_reply = 1; /* nf_conntrack_in()要用到这个值=1的时候 */
  48. } else {
  49. /* Once we've had two way comms, always ESTABLISHED. */
  50. if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
  51. pr_debug( "nf_conntrack_in: normal packet for %p\n", ct);
  52. *ctinfo = IP_CT_ESTABLISHED;
  53. } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
  54. /* 如果在expect链表中找到 */
  55. pr_debug( "nf_conntrack_in: related packet for %p\n",
  56. ct);
  57. *ctinfo = IP_CT_RELATED;
  58. } else {
  59. pr_debug( "nf_conntrack_in: new packet for %p\n", ct);
  60. *ctinfo = IP_CT_NEW;
  61. }
  62. *set_reply = 0;
  63. }
  64. /* 这里取的地址,由于ct_general是ct的第一个成员,所以skb->nfct保存的是ct的地址。 */
  65. skb->nfct = &ct->ct_general;
  66. /* ct的状态 */
  67. skb->nfctinfo = *ctinfo;
  68. return ct;
  69. }

init_conntrack()函数用于分配并初始化一个新的nf_conn结构实例:

  1. /* Allocate a new conntrack: we return -ENOMEM if classification
  2. failed due to stress. Otherwise it really is unclassifiable. */
  3. static struct nf_conntrack_tuple_hash *
  4. init_conntrack (struct net *net,
  5. struct nf_conntrack_tuple *tuple,
  6. struct nf_conntrack_l3proto *l3proto,
  7. struct nf_conntrack_l4proto *l4proto,
  8. struct sk_buff *skb,
  9. unsigned int dataoff
  10. )
  11. {
  12. struct nf_conn *ct;
  13. struct nf_conn_help *help;
  14. struct nf_conntrack_tuple repl_tuple;
  15. struct nf_conntrack_expect *exp;
  16. /* 根据tuple制作一个repl_tuple。主要是调用L3和L4的invert_tuple方法 */
  17. if (!nf_ct_invert_tuple(&repl_tuple, tuple, l3proto, l4proto)) {
  18. pr_debug( "Can't invert tuple.\n");
  19. return NULL;
  20. }
  21. /* 在cache中申请一个nf_conn结构,把tuple和repl_tuple赋值给ct的tuplehash[]数组,并初始化ct.timeout定时器函数为death_by_timeout(),但不启动定时器。 */
  22. ct = nf_conntrack_alloc(net, tuple, &repl_tuple, GFP_ATOMIC);
  23. if (IS_ERR(ct)) {
  24. pr_debug( "Can't allocate conntrack.\n");
  25. return (struct nf_conntrack_tuple_hash *)ct;
  26. }
  27. /* 对tcp来说,下面函数就是将L4层字段如window, ack等字段
  28. 赋给ct->proto.tcp.seen[0],由于新建立的连接才调这里,所以
  29. 不用给reply方向的ct->proto.tcp.seen[1]赋值 */
  30. if (!l4proto-> new(ct, skb, dataoff)) {
  31. nf_conntrack_free(ct);
  32. pr_debug( "init conntrack: can't track with proto module\n");
  33. return NULL;
  34. }
  35. /* 为acct和ecache两个ext分配空间。不过之后一般不会被初始化,所以用不到 */
  36. nf_ct_acct_ext_add(ct, GFP_ATOMIC);
  37. nf_ct_ecache_ext_add(ct, GFP_ATOMIC);
  38. spin_lock_bh(&nf_conntrack_lock);
  39. exp = nf_ct_find_expectation(net, tuple);
  40. /* 如果在期望连接链表中 */
  41. if ( exp) { /* 将exp的值赋给ct->master */
  42. pr_debug( "conntrack: expectation arrives ct=%p exp=%p\n",
  43. ct, exp);
  44. /* Welcome, Mr. Bond. We've been expecting you... */
  45. __set_bit(IPS_EXPECTED_BIT, &ct->status);
  46. ct->master = exp->master;
  47. if ( exp->helper) {
  48. /* helper的ext以及help链表分配空间 */
  49. help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
  50. if (help)
  51. rcu_assign_pointer(help->helper, exp->helper);
  52. }
  53. /* 如果在expect链表中,将master引用计数加1 */
  54. nf_conntrack_get(&ct->master->ct_general);
  55. NF_CT_STAT_INC(net, expect_new);
  56. } else {
  57. __nf_ct_try_assign_helper(ct, GFP_ATOMIC);
  58. NF_CT_STAT_INC(net, new);
  59. }
  60. /* 将这个tuple添加到unconfirmed链表中,因为数据包还没有出去,所以不知道是否会被丢弃,所以暂时先不添加到conntrack hash中 */
  61. hlist_nulls_add_head_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode,
  62. &net->ct.unconfirmed);
  63. spin_unlock_bh(&nf_conntrack_lock);
  64. if ( exp) {
  65. /* expect连接的引用计数减1 */
  66. if ( exp->expectfn)
  67. exp->expectfn(ct, exp);
  68. nf_ct_expect_put( exp);
  69. }
  70. return &ct->tuplehash[IP_CT_DIR_ORIGINAL];
  71. }

ipv4_conntrack_local()做的事情和nf_conntrack_in()基本相同。可以看到在入口的两个hook点上,conntrack的工作主要是:

1. 由skb得到一个tuple,对数据包做合法性检查。

2. 查找net->ct.hash表是否已记录这个tuple。如果没有记录,则新建一个tuple及nf_conn并添加到unconfirmed链表中。

3. 对ct做一些协议相关的特定处理和检查。

4. 更新conntrack的状态ct->status和skb的状态skb->nfctinfo。

conntrack和skb的状态定义如下:

  1. /* Connection state tracking for netfilter. This is separated from,
  2. but required by, the NAT layer; it can also be used by an iptables
  3. extension. */
  4. /* 一共5个状态,下面四个,加上IP_CT_RELATED + IP_CT_IS_REPLY */
  5. /* 这些值是skb->nfctinfo使用的 */
  6. enum ip_conntrack_info
  7. {
  8. /* Part of an established connection (either direction). */
  9. IP_CT_ESTABLISHED, /* enum从0开始。 */
  10. /* Like NEW, but related to an existing connection, or ICMP error
  11. (in either direction). */
  12. IP_CT_RELATED,
  13. /* Started a new connection to track (only
  14. IP_CT_DIR_ORIGINAL); may be a retransmission. */
  15. IP_CT_NEW,
  16. /* >= this indicates reply direction */
  17. IP_CT_IS_REPLY,
  18. /* Number of distinct IP_CT types (no NEW in reply dirn). */
  19. IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1
  20. };
  21. /* Bitset representing status of connection. */
  22. /* 这些值是ct->status使用的 */
  23. enum ip_conntrack_status {
  24. /* It's an expected connection: bit 0 set. This bit never changed */
  25. IPS_EXPECTED_BIT = 0,
  26. IPS_EXPECTED = ( 1 << IPS_EXPECTED_BIT),
  27. /* We've seen packets both ways: bit 1 set. Can be set, not unset. */
  28. IPS_SEEN_REPLY_BIT = 1,
  29. IPS_SEEN_REPLY = ( 1 << IPS_SEEN_REPLY_BIT),
  30. /* Conntrack should never be early-expired. */
  31. IPS_ASSURED_BIT = 2,
  32. IPS_ASSURED = ( 1 << IPS_ASSURED_BIT),
  33. /* Connection is confirmed: originating packet has left box */
  34. IPS_CONFIRMED_BIT = 3,
  35. IPS_CONFIRMED = ( 1 << IPS_CONFIRMED_BIT),
  36. /* Connection needs src nat in orig dir. This bit never changed. */
  37. IPS_SRC_NAT_BIT = 4,
  38. IPS_SRC_NAT = ( 1 << IPS_SRC_NAT_BIT),
  39. /* Connection needs dst nat in orig dir. This bit never changed. */
  40. IPS_DST_NAT_BIT = 5,
  41. IPS_DST_NAT = ( 1 << IPS_DST_NAT_BIT),
  42. /* Both together. */
  43. IPS_NAT_MASK = (IPS_DST_NAT | IPS_SRC_NAT),
  44. /* Connection needs TCP sequence adjusted. */
  45. IPS_SEQ_ADJUST_BIT = 6,
  46. IPS_SEQ_ADJUST = ( 1 << IPS_SEQ_ADJUST_BIT),
  47. /* NAT initialization bits. */
  48. IPS_SRC_NAT_DONE_BIT = 7,
  49. IPS_SRC_NAT_DONE = ( 1 << IPS_SRC_NAT_DONE_BIT),
  50. IPS_DST_NAT_DONE_BIT = 8,
  51. IPS_DST_NAT_DONE = ( 1 << IPS_DST_NAT_DONE_BIT),
  52. /* Both together */
  53. IPS_NAT_DONE_MASK = (IPS_DST_NAT_DONE | IPS_SRC_NAT_DONE),
  54. /* Connection is dying (removed from lists), can not be unset. */
  55. IPS_DYING_BIT = 9,
  56. IPS_DYING = ( 1 << IPS_DYING_BIT),
  57. /* Connection has fixed timeout. */
  58. IPS_FIXED_TIMEOUT_BIT = 10,
  59. IPS_FIXED_TIMEOUT = ( 1 << IPS_FIXED_TIMEOUT_BIT),
  60. };

我们注意这样一个赋值过程:

skb->nfct = &ct->ct_general;

在struct nf_conn结构体中,ct_general是其第一个成员,所以它的地址和整个结构体的地址相同,所以skb->nfct的值实际上就是skb对应的conntrack条目的地址,因此通过(struct nf_conn *)skb->nfct就可以通过skb得到它的conntrack条目。

2.2 ipv4_confirm()

hook函数ipv4_confirm()的函数实现如下:

  1. static unsigned int ipv4_confirm(unsigned int hooknum,
  2. struct sk_buff *skb,
  3. const struct net_device *in,
  4. const struct net_device *out,
  5. int (*okfn) (struct sk_buff *))
  6. {
  7. struct nf_conn *ct;
  8. enum ip_conntrack_info ctinfo;
  9. const struct nf_conn_help *help;
  10. const struct nf_conntrack_helper *helper;
  11. unsigned int ret;
  12. /* This is where we call the helper: as the packet goes out. */
  13. /* ct结构从下面获得: (struct nf_conn *)skb->nfct; */
  14. ct = nf_ct_get(skb, &ctinfo);
  15. /* 这种状态的不确认 */
  16. if (!ct || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY)
  17. goto out;
  18. /* 获得ct->ext中的NF_CT_EXT_HELPER数据。一般都是没有的。ftp,tftp,pptp等这些协议的数据包就会有helper。 */
  19. help = nfct_help(ct);
  20. if (!help)
  21. goto out;
  22. /* rcu_read_lock()ed by nf_hook_slow */
  23. helper = rcu_dereference(help->helper);
  24. if (!helper)
  25. goto out;
  26. ret = helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb),
  27. ct, ctinfo);
  28. if (ret != NF_ACCEPT)
  29. return ret;
  30. if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status)) {
  31. /* TCP,调整做NAT之后的seq number */
  32. typeof(nf_nat_seq_adjust_hook) seq_adjust;
  33. seq_adjust = rcu_dereference(nf_nat_seq_adjust_hook);
  34. if (!seq_adjust || !seq_adjust(skb, ct, ctinfo)) {
  35. NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop);
  36. return NF_DROP;
  37. }
  38. }
  39. out:
  40. /* We've seen it coming out the other side: confirm it */
  41. return nf_conntrack_confirm(skb);
  42. }

nf_conntrack_confirm()函数:

  1. /* Confirm a connection: returns NF_DROP if packet must be dropped. */
  2. static inline int nf_conntrack_confirm(struct sk_buff *skb)
  3. {
  4. struct nf_conn *ct = (struct nf_conn *)skb->nfct;
  5. int ret = NF_ACCEPT;
  6. if (ct && ct != &nf_conntrack_untracked) {
  7. /* 如果未被确认 */
  8. if (!nf_ct_is_confirmed(ct) && !nf_ct_is_dying(ct))
  9. ret = __nf_conntrack_confirm(skb); /* 则进行确认 */
  10. if (likely(ret == NF_ACCEPT))
  11. nf_ct_deliver_cached_events(ct); /* 空 */
  12. }
  13. return ret;
  14. }
__nf_conntrack_confirm()函数:
  1. /* Confirm a connection given skb; places it in hash table */
  2. int
  3. __nf_conntrack_confirm(struct sk_buff *skb)
  4. {
  5. unsigned int hash, repl_hash;
  6. struct nf_conntrack_tuple_hash *h;
  7. struct nf_conn *ct;
  8. struct nf_conn_help *help;
  9. struct hlist_nulls_node *n;
  10. enum ip_conntrack_info ctinfo;
  11. struct net *net;
  12. ct = nf_ct_get(skb, &ctinfo);
  13. net = nf_ct_net(ct);
  14. /* ipt_REJECT uses nf_conntrack_attach to attach related
  15. ICMP/TCP RST packets in other direction. Actual packet
  16. which created connection will be IP_CT_NEW or for an
  17. expected connection, IP_CT_RELATED. */
  18. /* 如果不是original方向的包,直接返回 */
  19. if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
  20. return NF_ACCEPT;
  21. /* 计算hash key,用于添加进hash表中 */
  22. hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
  23. repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
  24. spin_lock_bh(&nf_conntrack_lock);
  25. /* See if there's one in the list already, including reverse:
  26. NAT could have grabbed it without realizing, since we're
  27. not in the hash. If there is, we lost race. */
  28. /* 待确认的连接如果已经在conntrack的hash表中(有一个方向存在就视为存在),就不再插入了,丢弃它 */
  29. hlist_nulls_for_each_entry(h, n, &net->ct.hash[hash], hnnode)
  30. if(nf_ct_tuple_equal(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple,
  31. &h->tuple)) {
  32. goto out;
  33. }
  34. hlist_nulls_for_each_entry(h, n, &net->ct.hash[repl_hash], hnnode)
  35. if (nf_ct_tuple_equal(&ct->tuplehash[IP_CT_DIR_REPLY].tuple,
  36. &h->tuple)) {
  37. goto out;
  38. }
  39. /*从unconfirmed链表中删除该连接 */
  40. /* Remove from unconfirmed list */
  41. hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode);
  42. /* Timer relative to confirmation time, not original
  43. setting time, otherwise we'd get timer wrap in
  44. weird delay cases. */
  45. /* 启动定时器,注意这里的超时时间是L4协议处理后的指定的,如tcp_packet()中由nf_ct_refresh_acct()更新的。 */
  46. ct->timeout.expires += jiffies;
  47. add_timer(&ct->timeout);
  48. /* use加1,设置IPS_CONFIRMED_BIT标志 */
  49. atomic_inc(&ct->ct_general.use);
  50. set_bit(IPS_CONFIRMED_BIT, &ct->status);
  51. /* Since the lookup is lockless, hash insertion must be done after
  52. * starting the timer and setting the CONFIRMED bit. The RCU barriers
  53. * guarantee that no other CPU can find the conntrack before the above
  54. * stores are visible.
  55. */
  56. /* 将连接添加进conntrack的hash表(双向链表)中 */
  57. __nf_conntrack_hash_insert(ct, hash, repl_hash);
  58. NF_CT_STAT_INC(net, insert);
  59. spin_unlock_bh(&nf_conntrack_lock);
  60. return NF_ACCEPT;
  61. out:
  62. NF_CT_STAT_INC(net, insert_failed);
  63. spin_unlock_bh(&nf_conntrack_lock);
  64. return NF_DROP;
  65. }

一个nf_conn的超时处理函数death_by_timeout(),即超时后会执行这个函数:

  1. static void death_by_timeout(unsigned long ul_conntrack)
  2. {
  3. struct nf_conn *ct = (void *)ul_conntrack;
  4. if (!test_bit(IPS_DYING_BIT, &ct->status) &&
  5. unlikely(nf_conntrack_event(IPCT_DESTROY, ct) < 0)) {
  6. /* destroy event was not delivered */
  7. nf_ct_delete_from_lists(ct);
  8. nf_ct_insert_dying_list(ct);
  9. return;
  10. }
  11. set_bit(IPS_DYING_BIT, &ct->status);
  12. nf_ct_delete_from_lists(ct);
  13. nf_ct_put(ct);
  14. }

通过上面的代码,可知在这个hook点上的工作为:

1. 如果该连接存在helper类型的extension数据,就执行其helper函数。

2. 将该conntrack条目从unconfirmed链表中删除,并加入到已确认的链表中,为该条目启动定时器,即该条目已经生效了。

2.3 conntrack extension

conntrack extension用来完成正常的conntrack流程没有考虑的问题,如一些数据包做完NAT后需要调整数据包内容,又如ftp,pptp这些应用层协议需要额外的expect连接。这些工作针对协议有特殊性,无法放在通用处理流程中,于是就出现了conntrack extension。

nf_conn结构中有一个指向本连接extension数据的成员:

  1. struct nf_conn {
  2. ......
  3. /* Extensions */
  4. struct nf_ct_ext *ext;
  5. ......
  6. };

这是一个struct nf_ct_ext结构体类型的成员,

  1. struct nf_ct_ext {
  2. struct rcu_head rcu;
  3. u8 offset[NF_CT_EXT_NUM];
  4. u8 len;
  5. char data[ 0];
  6. };

从这个结构体的定义可以看出,其数据data是可变长度的,并且可以分为固定的几部分。offset数组指定了每部分数据的位置。

从下面的enum定义可知,data可以由固定位置的4部分组成。当然,一个连接并不需要所有的这些extension数据,一般的UDP/TCP连接只需要NAT数据即可,需要用到期望连接的连接则必须要有HELPER数据。

  1. enum nf_ct_ext_id
  2. {
  3. NF_CT_EXT_HELPER,
  4. NF_CT_EXT_NAT,
  5. NF_CT_EXT_ACCT,
  6. NF_CT_EXT_ECACHE,
  7. NF_CT_EXT_NUM,
  8. };

这四种数据分别有自己的数据格式,如下:

  1. #define NF_CT_EXT_HELPER_TYPE struct nf_conn_help
  2. #define NF_CT_EXT_NAT_TYPE struct nf_conn_nat
  3. #define NF_CT_EXT_ACCT_TYPE struct nf_conn_counter
  4. #define NF_CT_EXT_ECACHE_TYPE struct nf_conntrack_ecache

即data中存放的就是这些结构体。

给一个nf_conn->ext赋值都是通过下面函数来完成的:

void * nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_tgfp);

  • 参数ct是nf_conn对象。
  • 参数id是extension数据的类型,即上面4个其中的一种。
  • 参数gfp是kmalloc的时候的分配方式,这里不关心。
  • 函数的返回值是新添加的ext数据指针,即data中新分配空间的位置。返回void *类型,根据参数id可以转换成struct nf_conn_help、struct nf_conn_nat、struct nf_conn_counter或structnf_conntrack_ecache其中的一种类型。

由struct nf_ct_ext结构体定义可知,在给nf_conn->ext分配空间的时候,最主要的一点就是需要知道要添加的类型需要放在data的什么位置,即offset的值。这个值是根据下面的全局数组来确定的:

static struct nf_ct_ext_type *nf_ct_ext_types[NF_CT_EXT_NUM];

这个数组在内核初始化的时候被赋值,初始化完成后这个数组的内容如下。注意,在使用nf_ct_extend_register()注册这几个数组元素时,每个元素的alloc_size会发生改变,总之,这些值在系统启动完成后已经是固定的了。

  1. static struct nf_ct_ext_type *nf_ct_ext_types[NF_CT_EXT_NUM] = {
  2. {
  3. .len = sizeof(struct nf_conn_help),
  4. .align = __alignof__(struct nf_conn_help),
  5. .id = NF_CT_EXT_HELPER,
  6. .alloc_size = ALIGN( sizeof(struct nf_ct_ext), align)
  7. + len,
  8. },
  9. {
  10. .len = sizeof(struct nf_conn_nat),
  11. .align = __alignof__(struct nf_conn_nat),
  12. .destroy = nf_nat_cleanup_conntrack,
  13. .move = nf_nat_move_storage,
  14. .id = NF_CT_EXT_NAT,
  15. .flags = NF_CT_EXT_F_PREALLOC,
  16. .alloc_size = ALIGN( sizeof(struct nf_ct_ext), align)
  17. + len,
  18. },
  19. {
  20. .len = sizeof(struct nf_conn_counter[IP_CT_DIR_MAX]),
  21. .align = __alignof__(struct nf_conn_counter[IP_CT_DIR_MAX]),
  22. .id = NF_CT_EXT_ACCT,
  23. .alloc_size = ALIGN( sizeof(struct nf_ct_ext), align)
  24. + len,
  25. },
  26. /* 如果没有开启CONFIG_NF_CONNTRACK_EVENTS选项,所以这一项是NULL。
  27. {
  28. .len = sizeof(struct nf_conntrack_ecache),
  29. .align = __alignof__(struct nf_conntrack_ecache),
  30. .id = NF_CT_EXT_ECACHE,
  31. .alloc_size = ALIGN(sizeof(struct nf_ct_ext), align)
  32. + len,
  33. },
  34. */
  35. };

下面来看一下nf_ct_ext_add()函数的实现:

  1. /* 根据id给ct->ext分配一个新的ext数据,新数据清0。 */
  2. void *__nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
  3. {
  4. struct nf_ct_ext *new;
  5. int i, newlen, newoff;
  6. struct nf_ct_ext_type *t;
  7. /* 如果连ct->ext都还没有,说明肯定没有ext数据。所以先分配一个ct->ext,然后分配类型为id的ext数据,数据位置由ct->ext->offset[id]指向。*/
  8. if (!ct->ext)
  9. return nf_ct_ext_create(&ct->ext, id, gfp);
  10. /* --- ct->ext不为NULL,说明不是第一次分配了 --- */
  11. /* 如果类型为id的数据已经存在,直接返回。 */
  12. if (nf_ct_ext_exist(ct, id))
  13. return NULL;
  14. rcu_read_lock();
  15. t = rcu_dereference(nf_ct_ext_types[id]);
  16. BUG_ON(t == NULL);
  17. /* 找到已有数据的尾端,从这里开始分配新的id的数据。 */
  18. newoff = ALIGN(ct->ext->len, t->align);
  19. newlen = newoff + t->len;
  20. rcu_read_unlock();
  21. new = __krealloc(ct->ext, newlen, gfp);
  22. if (! new)
  23. return NULL;
  24. /* 由于上面是realloc,可能起始地址改变了,如果变了地址,就先把原有数据考过去。 */
  25. if ( new != ct->ext) {
  26. for (i = 0; i < NF_CT_EXT_NUM; i++) {
  27. if (!nf_ct_ext_exist(ct, i))
  28. continue;
  29. rcu_read_lock();
  30. t = rcu_dereference(nf_ct_ext_types[i]);
  31. if (t && t->move)
  32. t->move(( void *) new + new->offset[i],
  33. ( void *)ct->ext + ct->ext->offset[i]);
  34. rcu_read_unlock();
  35. }
  36. call_rcu(&ct->ext->rcu, __nf_ct_ext_free_rcu);
  37. ct->ext = new;
  38. }
  39. /* 调整ct->ext的offset[id]和len */
  40. new->offset[id] = newoff;
  41. new->len = newlen;
  42. /* 清0。 */
  43. memset(( void *) new + newoff, 0, newlen - newoff);
  44. return ( void *) new + newoff;
  45. }

nf_ct_ext_create()函数:

  1. staticvoid *
  2. nf_ct_ext_create (structnf_ct_ext **ext, enum nf_ct_ext_id id, gfp_t gfp)
  3. {
  4. unsigned int off, len;
  5. struct nf_ct_ext_type *t;
  6. rcu_read_lock();
  7. t = rcu_dereference(nf_ct_ext_types[id]);
  8. BUG_ON(t == NULL);
  9. off = ALIGN( sizeof(struct nf_ct_ext),t->align);
  10. len = off + t->len;
  11. rcu_read_unlock();
  12. *ext = kzalloc(t->alloc_size, gfp);
  13. if (!*ext)
  14. return NULL;
  15. INIT_RCU_HEAD(&(*ext)->rcu);
  16. (*ext)->offset[id] = off;
  17. (*ext)->len = len;
  18. /* 由于上面用kzalloc分配时就清0了,所以这里不用清。 */
  19. return ( void *)(*ext) + off;
  20. }

其中NF_CT_EXT_HELPER类型的ext还有维护了一个全局的hash表nf_ct_helper_hash[],用来保存所有已注册的helper函数,如ftp、pptp、tftp、sip、rtsp等协议的helper函数都注册到了这个表中。注册函数为nf_conntrack_helper_register()。NF_CT_EXT_HELPER是用来生成expect连接的。


在下一篇博文介绍NAT时会给出一个conntrack的例子,并在之后给出一个期望连接的例子。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值