连接跟踪(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;struct nf_conntrack_l3proto nf_conntrack_l3proto_generic __read_mostly = {
.l3proto = PF_UNSPEC,
.name = "unknown",
.pkt_to_tuple = generic_pkt_to_tuple,
.invert_tuple = generic_invert_tuple,
.print_tuple = generic_print_tuple,
.get_l4proto = generic_get_l4proto,
};
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];static struct nf_ct_ext_type helper_extend __read_mostly = {
.len = sizeof(struct nf_conn_help),
.align = __alignof__(struct nf_conn_help),
.id = NF_CT_EXT_HELPER,
};
conntrack extentions共有4种类型,即全局数组nf_ct_ext_types[]的元素个数,每个元素的含义如下定义:
enum nf_ct_ext_id
{
NF_CT_EXT_HELPER,
NF_CT_EXT_NAT,
NF_CT_EXT_ACCT,
NF_CT_EXT_ECACHE,
NF_CT_EXT_NUM,
};
struct nf_ct_ext_type结构定义如下:
struct nf_ct_ext_type
{
/* Destroys relationships (can be NULL). */
void (*destroy)(struct nf_conn *ct);
/* Called when realloacted (can be NULL).
Contents has already been moved. */
void (*move)(void *new, void *old);
enum nf_ct_ext_id id;
unsigned int flags;
/* Length and min alignment. */
u8 len;
u8 align;
/* initial size of nf_ct_ext. */
u8 alloc_size;
};
nf_conntrack_init_net()函数:
该函数用来初始化net->ct成员。net是本地CPU的网络命名空间,在单CPU系统中就是全局变量init_net。它的ct成员的定义如下:
struct netns_ct {
atomic_t count;
unsigned int expect_count;
struct hlist_nulls_head *hash;
struct hlist_head *expect_hash;
struct hlist_nulls_head unconfirmed;
struct hlist_nulls_head dying;
struct ip_conntrack_stat *stat;
int sysctl_events;
unsigned int sysctl_events_retry_timeout;
int sysctl_acct;
int sysctl_checksum;
unsigned int sysctl_log_invalid; /* Log invalid packets */
#ifdef CONFIG_SYSCTL
struct ctl_table_header *sysctl_header;
struct ctl_table_header *acct_sysctl_header;
struct ctl_table_header *event_sysctl_header;
#endif
int hash_vmalloc;
int expect_vmalloc;
};
函数分析:
static int nf_conntrack_init_net(struct net *net)
{
int ret;
atomic_set(&net->ct.count, 0); /* 设置计数器为0 */
/* 初始化unconfirmed链表 */
INIT_HLIST_NULLS_HEAD(&net->ct.unconfirmed, UNCONFIRMED_NULLS_VAL);
/* 初始化dying链表 */
INIT_HLIST_NULLS_HEAD(&net->ct.dying, DYING_NULLS_VAL);
/* 给net->ct.stat分配空间并清0。统计数据 */
net->ct.stat = alloc_percpu(struct ip_conntrack_stat);
if (!net->ct.stat) {
ret = -ENOMEM;
goto err_stat;
}
/* 初始化conntrack hash table,表的大小上面已初始化过了。同样的,这里会调整nf_conntrack_htable_size为页大小的整数倍。 */
net->ct.hash = nf_ct_alloc_hashtable(&nf_conntrack_htable_size,
&net->ct.hash_vmalloc, 1);
if (!net->ct.hash) {
ret = -ENOMEM;
printk(KERN_ERR "Unable to create nf_conntrack_hash\n");
goto err_hash;
}
/* 初始化net->ct.expect_hash及缓存,并在/proc中创建相应文件,下面会单独说明该函数。 */
ret = nf_conntrack_expect_init(net);
if (ret < 0)
goto err_expect;
/* 将acct_extend注册进全局数组nf_ct_ext_types[NF_CT_EXT_ACCT]中。 */
ret = nf_conntrack_acct_init(net);
if (ret < 0)
goto err_acct;
ret = nf_conntrack_ecache_init(net); /* event cache of netfilter */
if (ret < 0)
goto err_ecache;
/* 单独的fake conntrack */
/* Set up fake conntrack:
- to never be deleted, not in any hashes */
atomic_set(&nf_conntrack_untracked.ct_general.use, 1);
/* - and look it like as a confirmed connection */
set_bit(IPS_CONFIRMED_BIT, &nf_conntrack_untracked.status);
return 0;
}
nf_conntrack_expect_init()函数,
int nf_conntrack_expect_init(struct net *net)
{
int err = -ENOMEM;
if (net_eq(net, &init_net)) {
if (!nf_ct_expect_hsize) {
/* 期望连接hash table的size */
nf_ct_expect_hsize = nf_conntrack_htable_size / 256;
if (!nf_ct_expect_hsize)
nf_ct_expect_hsize = 1;
}
/* 期望连接的最大连接数 */
nf_ct_expect_max = nf_ct_expect_hsize * 4;
}
/* 分配期望连接hash table。 */
net->ct.expect_count = 0;
net->ct.expect_hash = nf_ct_alloc_hashtable(&nf_ct_expect_hsize,
&net->ct.expect_vmalloc, 0);
if (net->ct.expect_hash == NULL)
goto err1;
if (net_eq(net, &init_net)) {
/* 期望连接的slab cache。 */
nf_ct_expect_cachep = kmem_cache_create("nf_conntrack_expect",
sizeof(struct nf_conntrack_expect),
0, 0, NULL);
if (!nf_ct_expect_cachep)
goto err2;
}
/* /proc/net/nf_conntrack_expect文件操作。 */
err = exp_proc_init(net);
if (err < 0)
goto err3;
return 0;
}
需要先说一下struct hlist_nulls_head结构以及INIT_HLIST_NULLS_HEAD宏:
/*
* Special version of lists, where end of list is not a NULL pointer,
* but a 'nulls' marker, which can have many different values.
* (up to 2^31 different values guaranteed on all platforms)
*
* In the standard hlist, termination of a list is the NULL pointer.
* In this special 'nulls' variant, we use the fact that objects stored in
* a list are aligned on a word (4 or 8 bytes alignment).
* We therefore use the last significant bit of 'ptr' :
* Set to 1 : This is a 'nulls' end-of-list marker (ptr >> 1)
* Set to 0 : This is a pointer to some object (ptr)
*/
struct hlist_nulls_head {
struct hlist_nulls_node *first;
};
struct hlist_nulls_node {
struct hlist_nulls_node *next, **pprev;
};
/* 注意右值是first里面的值而不是地址。这个值都是奇数,即二进制的最后一位是1;
在普通链表中一般用NULL来判断链表结束,而linux内核中由于地址都是4或8字节对齐的,所以指针的末两位用不上,因此可以复用一下,最后一位是1表示链表结束,是0则未结束。
这个宏保证了最末位是1,而其他位可以是随意值,所以可以有2^31种可能值。
下面是对HLIST_NULLS_HEAD链表的初始化,所以给first赋值。
*