基于linux2.6.21
在上一节中分析了连接跟踪模块相关的数据结构,本节就开始分析连接跟踪模块相关的初始化,下一节理解连接跟踪模块的hook机制。
在分析连接跟踪模块代码之前,先说明几点:
1.连接跟踪模块的helper结构能够实现期望连接的建立以及相关协议的ALG功能。
2.连接跟踪为NAT或者状态防火墙的实现提供了依据
连接跟踪模块的初始化过程分别在三个地方进行,一个用于注册连接跟踪模块相关的hook回调函数;一个用于创建连接跟踪项与期望连接项相关的slab缓存;一个用于注册连接跟踪中协议相关的变量。
主要是 nf_conntrack_init进行初始化操作,该函数定义在nf_conntrack_core.c中。
主要完成以下功能:
a.设置nf_conntrack_htable_size、nf_conntrack_max的值
b.为nf_conntrack_hash申请内存并初始化
c.为连接跟踪项与期望连接跟踪项创建slab缓存。
int __init nf_conntrack_init(void) { unsigned int i; int ret; 当nf_conntrack_htable_size的值没有设置时,则在内存小于1GB时, 使用内存的1/16384作为hash数组的最大值;在内存大于1GB时 则最大hash数组的值为8192 */ if (!nf_conntrack_htable_size) { nf_conntrack_htable_size = (((num_physpages << PAGE_SHIFT) / 16384) / sizeof(struct list_head)); if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE)) nf_conntrack_htable_size = 8192; if (nf_conntrack_htable_size < 16) nf_conntrack_htable_size = 16; }
/* 设置nf_conntrack_max的值,即连接跟踪项的最大值。 该值取决于nf_conntrack_hash[]数组的最大值,即为 nf_conntrack_htable_size的8倍。 */ nf_conntrack_max = 8 * nf_conntrack_htable_size; printk("nf_conntrack version %s (%u buckets, %d max)\n", NF_CONNTRACK_VERSION, nf_conntrack_htable_size, nf_conntrack_max); /* hash链表的初始化,申请nf_conntrack_htable_size个hash链表 */ nf_conntrack_hash = alloc_hashtable(nf_conntrack_htable_size, &nf_conntrack_vmalloc); if (!nf_conntrack_hash) { printk(KERN_ERR "Unable to create nf_conntrack_hash\n"); goto err_out; } /*调用函数nf_conntrack_register_cache创建连接跟踪项相关的slab缓存*/ ret = nf_conntrack_register_cache(NF_CT_F_BASIC, "nf_conntrack:basic", sizeof(struct nf_conn), NULL); if (ret < 0) { printk(KERN_ERR "Unable to create nf_conn slab cache\n"); goto err_free_hash; } /*创建期望连接跟踪项相关的slab缓存*/ nf_conntrack_expect_cachep = kmem_cache_create("nf_conntrack_expect", sizeof(struct nf_conntrack_expect), 0, 0, NULL, NULL); if (!nf_conntrack_expect_cachep) { printk(KERN_ERR "Unable to create nf_expect slab cache\n"); goto err_free_conntrack_slab; } /* 将nf_ct_l3protos中每一个成员的变量都设置成nf_conntrack_generic_l3proto ,然后不同的连接跟踪的三层协议初始化时,即会将相应的 数组成员的值替换掉*/ write_lock_bh(&nf_conntrack_lock); for (i = 0; i < PF_MAX; i++) nf_ct_l3protos[i] = &nf_conntrack_generic_l3proto; write_unlock_bh(&nf_conntrack_lock); /* For use by REJECT target */ ip_ct_attach = __nf_conntrack_attach; /*将nf_conntrack_untracked的使用计数设置为1,使该数据连接项能不被删除掉*/ atomic_set(&nf_conntrack_untracked.ct_general.use, 1); /*设置变量nf_conntrack_untracked的状态为confirmed,当对一类数据包 不想进行连接跟踪时,就会添加如下命令, iptables -t raw -A PREROUTING -d x.x.x.x-j NOTRACK,这样在数据包首先进入PRE_ROUTING 、OUTPUT链时,就会首先进入raw模块对应注册的hook函数,并将 数据包的nfct指针指向存储nf_conntrack_untracked的内存地址*/ set_bit(IPS_CONFIRMED_BIT, &nf_conntrack_untracked.status); return ret; err_free_conntrack_slab: nf_conntrack_unregister_cache(NF_CT_F_BASIC); err_free_hash: free_conntrack_hash(nf_conntrack_hash, nf_conntrack_vmalloc, nf_conntrack_htable_size); err_out: return -ENOMEM; }
在nf_conntrack_standalone.c中定义的函数,主要是调用上1.1中介绍的函数进行初始化,然后在/proc/net文件系统中创建相应的文件以及在/proc/sys/net中创建连接跟踪模块相关的文件
/*
nf_conntrack_standalone的初始化与销毁函数
对于初始化:
1.调用nf_conntrack_init进行连接模块相关的初始化,主要是
设置连接跟踪数的最大值、为连接跟踪项或者期望连接跟踪
创建slab缓存等操作。
2.在proc/net目录下创建连接跟踪相关的文件,主要nf_conntrack、nf_conntrack_expect
查看如下:
# cat /proc/net/stat/nf_conntrack entries searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete 00000001 00000027 00000b0b 000035a4 000053e5 000049fb 000034d8 000003c3 000003ea 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 00000000 000008e9 00000067 00007eec 00007d2b 00000132 0000008d 00000067 00000000 00000000 00000000 00000000 00000000 00000000 00000000 # # cat /proc/net/nf_conntrack /proc/net/nf_conntrack /proc/net/nf_conntrack_expect # cat /proc/net/nf_conntrack ipv4 2 unknown 2 492 src=192.168.1.1 dst=224.0.0.1 [UNREPLIED] src=224.0.0.1 dst=192.168.1.1 use=2 # # # cat /proc/net/nf_conntrack_expect # #
3.在/proc/sys文件系统中创建连接跟踪相关的内容。
*/
static int init_or_cleanup(int init) { #ifdef CONFIG_PROC_FS struct proc_dir_entry *proc, *proc_exp, *proc_stat; #endif int ret = 0; if (!init) goto cleanup; ret = nf_conntrack_init(); if (ret < 0) goto cleanup_nothing; #ifdef CONFIG_PROC_FS proc = proc_net_fops_create("nf_conntrack", 0440, &ct_file_ops); if (!proc) goto cleanup_init; proc_exp = proc_net_fops_create("nf_conntrack_expect", 0440, &exp_file_ops); if (!proc_exp) goto cleanup_proc; proc_stat = create_proc_entry("nf_conntrack", S_IRUGO, proc_net_stat); if (!proc_stat) goto cleanup_proc_exp; proc_stat->proc_fops = &ct_cpu_seq_fops; proc_stat->owner = THIS_MODULE; #endif #ifdef CONFIG_SYSCTL nf_ct_sysctl_header = register_sysctl_table(nf_ct_net_table, 0); if (nf_ct_sysctl_header == NULL) { printk("nf_conntrack: can't register to sysctl.\n"); ret = -ENOMEM; goto cleanup_proc_stat; } #endif return ret; cleanup: #ifdef CONFIG_SYSCTL unregister_sysctl_table(nf_ct_sysctl_header); cleanup_proc_stat: #endif #ifdef CONFIG_PROC_FS remove_proc_entry("nf_conntrack", proc_net_stat); cleanup_proc_exp: proc_net_remove("nf_conntrack_expect"); cleanup_proc: proc_net_remove("nf_conntrack"); cleanup_init: #endif /* CNFIG_PROC_FS */ nf_conntrack_cleanup(); cleanup_nothing: return ret; }
Ipv4协议中,注册的连接跟踪模块相关的回调函数有如下几个:
/*
在连接跟踪PRE_ROUTING链上注册的hook回调函数,主要
是分段数据包进行重组。优先级高于ipv4_conntrack_in
*/
static struct nf_hook_ops ipv4_conntrack_defrag_ops = { .hook = ipv4_conntrack_defrag, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_PRE_ROUTING, .priority = NF_IP_PRI_CONNTRACK_DEFRAG, }; /*PREROUTING链上注册ipv4_conntrack_in,主要是实现为一条数据流 创建连接跟踪项等操作*/ static struct nf_hook_ops ipv4_conntrack_in_ops = { .hook = ipv4_conntrack_in, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_PRE_ROUTING, .priority = NF_IP_PRI_CONNTRACK, }; /* 在连接跟踪LOCAL_OUT链上注册的hook回调函数,主要 是分段数据包进行重组。优先级高于ipv4_conntrack_in */ static struct nf_hook_ops ipv4_conntrack_defrag_local_out_ops = { .hook = ipv4_conntrack_defrag, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_LOCAL_OUT, .priority = NF_IP_PRI_CONNTRACK_DEFRAG, }; /*LOCALOUT链上注册ipv4_conntrack_in*/ /*LOCAL_OUT链上注册ipv4_conntrack_local,主要是实现为本机发出的 一条数据流创建连接跟踪项等操作*/ static struct nf_hook_ops ipv4_conntrack_local_out_ops = { .hook = ipv4_conntrack_local, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_LOCAL_OUT, .priority = NF_IP_PRI_CONNTRACK, }; /* helpers */ /* 在POST_ROUTING链上注册ipv4_conntrack_help,根据传递的数据包, 找到该数据包关联的连接跟踪项,然后执行该连接跟踪 项关联的helper函数,实现期望连接的创建以及ALG等功能。 */ static struct nf_hook_ops ipv4_conntrack_helper_out_ops = { .hook = ipv4_conntrack_help, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_POST_ROUTING, .priority = NF_IP_PRI_CONNTRACK_HELPER, }; /* 在LOCAL_IN链上注册ipv4_conntrack_help,根据传递的数据包, 找到该数据包关联的连接跟踪项,然后执行该连接跟踪 项关联的helper函数,实现期望连接的创建以及ALG等功能。 */ static struct nf_hook_ops ipv4_conntrack_helper_in_ops = { .hook = ipv4_conntrack_help, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_LOCAL_IN, .priority = NF_IP_PRI_CONNTRACK_HELPER, }; /* Refragmenter; last chance. */ /* 在POST_ROUTING链上注册ipv4_confirm。若传递的数据包关联 的连接跟踪项还没有被确认,则执行确认操作 */ static struct nf_hook_ops ipv4_conntrack_out_ops = { .hook = ipv4_confirm, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_POST_ROUTING, .priority = NF_IP_PRI_CONNTRACK_CONFIRM, }; /* 在PRE_ROUTING链上注册ipv4_confirm。若传递的数据包关联 的连接跟踪项还没有被确认,则执行确认操作 */ static struct nf_hook_ops ipv4_conntrack_local_in_ops = { .hook = ipv4_confirm, .owner = THIS_MODULE, .pf = PF_INET, .hooknum = NF_IP_LOCAL_IN, .priority = NF_IP_PRI_CONNTRACK_CONFIRM, };
然后在nf_conntrack_l3proto_ipv4.c的init_or_cleanup里,通过nf_register_hook将以上的nf_hook_ops添加到nf_hooks[][]数组的相应的链表中。
定义ipv4协议相关的nf_conntrack_l3proto变量,其中比较重要的是ipv4_pkt_to_tuple、
ipv4_invert_tuple、ipv4_tuple_to_nfattr、 ipv4_nfattr_to_tuple。下面一一分析之。
struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 = { .l3proto = PF_INET, .name = "ipv4", .pkt_to_tuple = ipv4_pkt_to_tuple, .invert_tuple = ipv4_invert_tuple, .print_tuple = ipv4_print_tuple, .print_conntrack = ipv4_print_conntrack, .prepare = ipv4_prepare, .get_features = ipv4_get_features, #if defined(CONFIG_NF_CT_NETLINK) || \ defined(CONFIG_NF_CT_NETLINK_MODULE) .tuple_to_nfattr = ipv4_tuple_to_nfattr, .nfattr_to_tuple = ipv4_nfattr_to_tuple, #endif .me = THIS_MODULE, };
功能:根据ip头部获取源ip地址与目的ip地址,并写入tuple变量中。
static int ipv4_pkt_to_tuple(const struct sk_buff *skb, unsigned int nhoff, struct nf_conntrack_tuple *tuple) { u_int32_t _addrs[2], *ap; ap = skb_header_pointer(skb, nhoff + offsetof(struct iphdr, saddr), sizeof(u_int32_t) * 2, _addrs); if (ap == NULL) return 0; tuple->src.u3.ip = ap[0]; tuple->dst.u3.ip = ap[1]; return 1; }
功能:根据原始的tuple的源ip、目的ip值,设置reply的tuple值,新的tuple值的源、目的ip值与原始的tuple的源ip、目的ip值是反过来的。
static int ipv4_invert_tuple(struct nf_conntrack_tuple *tuple, const struct nf_conntrack_tuple *orig) { tuple->src.u3.ip = orig->dst.u3.ip; tuple->dst.u3.ip = orig->src.u3.ip; return 1; }
/*
1. 计算数据包的三层数据部分相对于skb->data的偏移量。
2.获取四层协议的协议号
*/
static int ipv4_prepare(struct sk_buff **pskb, unsigned int hooknum, unsigned int *dataoff, u_int8_t *protonum) { /* Never happen */ if ((*pskb)->nh.iph->frag_off & htons(IP_OFFSET)) { if (net_ratelimit()) { printk(KERN_ERR "ipv4_prepare: Frag of proto %u (hook=%u)\n", (*pskb)->nh.iph->protocol, hooknum); } return -NF_DROP; } *dataoff = (*pskb)->nh.raw - (*pskb)->data + (*pskb)->nh.iph->ihl*4; /*获取四层协议号*/ *protonum = (*pskb)->nh.iph->protocol; return NF_ACCEPT; }
功能:tuple结构中的三层源ip、目的ip地址按照nfnetlink规定的形式进行填充
static int ipv4_tuple_to_nfattr(struct sk_buff *skb, const struct nf_conntrack_tuple *tuple) { NFA_PUT(skb, CTA_IP_V4_SRC, sizeof(u_int32_t), &tuple->src.u3.ip); NFA_PUT(skb, CTA_IP_V4_DST, sizeof(u_int32_t), &tuple->dst.u3.ip); return 0; nfattr_failure: return -1; }
功能:将nfnetlink消息传递过来的变量,转换成tuple结构中的三层源ip、目的ip地址,
static int ipv4_nfattr_to_tuple(struct nfattr *tb[], struct nf_conntrack_tuple *t) { if (!tb[CTA_IP_V4_SRC-1] || !tb[CTA_IP_V4_DST-1]) return -EINVAL; if (nfattr_bad_size(tb, CTA_IP_MAX, cta_min_ip)) return -EINVAL; t->src.u3.ip = *(u_int32_t *)NFA_DATA(tb[CTA_IP_V4_SRC-1]); t->dst.u3.ip = *(u_int32_t *)NFA_DATA(tb[CTA_IP_V4_DST-1]); return 0; }
然后在nf_conntrack_l3proto_ipv4.c的init_or_cleanup里,通过nf_conntrack_l3proto_register将nf_conntrack_l3proto_ipv4的地址存放在nf_ct_l3protos[PF_INET]。
定义tcp协议相关的nf_conntrack_protocol 变量,其中比较重要的是tcp_pkt_to_tuple、
tcp_invert_tuple、nf_ct_port_tuple_to_nfattr、 nf_ct_port_nfattr_to_tuple。这几个函数实现的功能与nf_conntrack_l3proto_ipv4中相应的函数相似,只不过ipv4中针对的是ip地址,此处针对的是端口号而已。
而函数tcp_packet主要是针对tcp协议的状态变化而定义的,主要用于状态防火墙的。
struct nf_conntrack_protocol nf_conntrack_protocol_tcp4 = { .l3proto = PF_INET, .proto = IPPROTO_TCP, .name = "tcp", .pkt_to_tuple = tcp_pkt_to_tuple, .invert_tuple = tcp_invert_tuple, .print_tuple = tcp_print_tuple, .print_conntrack = tcp_print_conntrack, .packet = tcp_packet, .new = tcp_new, .error = tcp_error4, #if defined(CONFIG_NF_CT_NETLINK) || \ defined(CONFIG_NF_CT_NETLINK_MODULE) .to_nfattr = tcp_to_nfattr, .from_nfattr = nfattr_to_tcp, .tuple_to_nfattr = nf_ct_port_tuple_to_nfattr, .nfattr_to_tuple = nf_ct_port_nfattr_to_tuple, #endif };
然后在nf_conntrack_l3proto_ipv4.c的init_or_cleanup里,通过nf_conntrack_protocol_register将nf_conntrack_protocol_tcp4的地址存放在nf_ct_protos[PF_INET][TCP]。
至此,分析完了连接跟踪模块的初始化代码部分。