helper和nat有用到连接跟踪子系统中的extend,连接跟踪信息块中的ext字段就指向这种扩展信息,在看helper和nat之前,有必要先来看看和扩展部分操作相关的内容。涉及的核心文件如下:
代码路径 | 说明 |
---|---|
net/netfilter/nf_conntrack_extend.c | 扩展实现文件 |
include/net/netfilter/nf_conntrack_extend.h | 扩展头文件 |
1. 数据结构
扩展相关的数据结构以及它们之间的关系如下图:
连接跟踪信息块中的ext字段的类型为struct nf_ct_ext,它指向的内存区域包含了一个用于管理扩展信息的头部以及当前添加的所有扩展。offset[i]表示的ID为i的扩展距离data指针的偏移,len表示ext所指整块内存的长度;每个扩展有两部分组成:为了对齐可能有的paddng以及扩展本身。
注意,扩展具体是什么类型是由注册该扩展的模块决定的,每个需要使用扩展的模块都会有一个ID,以及注册一个扩展类型,即图片上方的struct nf_ct_ext_type,系统中所有的扩展类型保存在全局数组nf_ct_ext_types中,ext所指内存中的扩展就是根据全局数组中的扩展类型创建的。
1.1 struct nf_ct_ext
连接跟踪信息块中该结构来记录连接上所有的扩展信息。
//当前版本就helper和nat两种扩展
enum nf_ct_ext_id
{
NF_CT_EXT_HELPER,
NF_CT_EXT_NAT,
NF_CT_EXT_NUM,
};
#define NF_CT_EXT_HELPER_TYPE struct nf_conn_help
#define NF_CT_EXT_NAT_TYPE struct nf_conn_nat
/* Extensions: optional stuff which isn't permanently in struct. */
struct nf_ct_ext {
//一个连接可以同时有多个扩展存在(见下方,当前版本有nat和helper),
//offset记录录了每种类型扩展的结构在data中的偏移
u8 offset[NF_CT_EXT_NUM];
//data数组长度
u8 len;
//零长度数组,真正的扩展从这里开始
char data[0];
};
1.2 扩展类型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;
//实际扩展结构的长度
u8 len;
//实际扩展结构需要几字节对齐
u8 align;
//实际一个扩展结构需要分配的内存大小,由三部分组成:struct nf_ct_ext + 对齐填充 + len
u8 alloc_size;
};
系统将所有的扩展组织成一个数组:
static struct nf_ct_ext_type *nf_ct_ext_types[NF_CT_EXT_NUM];
static DEFINE_MUTEX(nf_ct_ext_type_mutex);
2. 扩展类型的注册
扩展都是可选的,如果开启,对应的模块会向连接跟踪子系统注册各自的扩展。当前只有helper和nat两种扩展。比如helper是在helper的初始化函数中注册的,代码如下:
//指定了实际扩展结构的大小、对其方式和id,也能看出helper的
//实际扩展结构是struct nf_conn_help
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,
};
int nf_conntrack_helper_init(void)
{
...
err = nf_ct_extend_register(&helper_extend);
...
}
2.1 注册函数nf_ct_extend_register()
int nf_ct_extend_register(struct nf_ct_ext_type *type)
{
int ret = 0;
mutex_lock(&nf_ct_ext_type_mutex);
//每种类型的扩展只能注册一种
if (nf_ct_ext_types[type->id]) {
ret = -EBUSY;
goto out;
}
//计算实际需要为扩展结构分配的内存的大小
type->alloc_size = ALIGN(sizeof(struct nf_ct_ext), type->align) + type->len;
//将要注册的扩展类型注册到全局数组中
rcu_assign_pointer(nf_ct_ext_types[type->id], type);
//由于所有的扩展最终都要在连接跟踪信息块的ext->data中保存,它们的
//内存是一起分配的,所以每注册一个新的扩展类型,需要更新所有其它所
//有以注册扩展类型的alloc_size,这样才能保证分配的内存足够用
update_alloc_size(type);
out:
mutex_unlock(&nf_ct_ext_type_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(nf_ct_extend_register);
2.1.1 update_alloc_size()
static void update_alloc_size(struct nf_ct_ext_type *type)
{
int i, j;
struct nf_ct_ext_type *t1, *t2;
enum nf_ct_ext_id min = 0, max = NF_CT_EXT_NUM - 1;
//扩展类型设定了PREALLOC标记,那么只需更新自己(why?)
if ((type->flags & NF_CT_EXT_F_PREALLOC) == 0) {
min = type->id;
max = type->id;
}
/* This assumes that extended areas in conntrack for the types
whose NF_CT_EXT_F_PREALLOC bit set are allocated in order */
for (i = min; i <= max; i++) {
t1 = nf_ct_ext_types[i];
if (!t1)
continue;
t1->alloc_size = sizeof(struct nf_ct_ext)
+ ALIGN(sizeof(struct nf_ct_ext), t1->align)
+ t1->len;
for (j = 0; j < NF_CT_EXT_NUM; j++) {
t2 = nf_ct_ext_types[j];
if (t2 == NULL || t2 == t1 ||
(t2->flags & NF_CT_EXT_F_PREALLOC) == 0)
continue;
//在t1的总大小基础上+t2的对齐填充+t2需要的大小
t1->alloc_size = ALIGN(t1->alloc_size, t2->align)
+ t2->len;
}
}
}
3. 扩展的添加
以helper为例,来看下扩展是如何与ext建立关系的。在创建新连接函数init_conntrack()中,如果新的连接不是一个期望连接,那么会查找该连接是否有helper模块关注,如果有,那么会调用nf_ct_helper_ext_add()为新的连接跟踪信息块设置ext字段,相关代码如下:
struct nf_conn_help *nf_ct_helper_ext_add(struct nf_conn *ct, gfp_t gfp)
{
struct nf_conn_help *help;
//注意第二个参数表示扩展类型,这里为helper
help = nf_ct_ext_add(ct, NF_CT_EXT_HELPER, gfp);
if (help)
INIT_HLIST_HEAD(&help->expectations);
return help;
}
EXPORT_SYMBOL_GPL(nf_ct_helper_ext_add);
#define nf_ct_ext_add(ct, id, gfp) \
((id##_TYPE *)__nf_ct_ext_add((ct), (id), (gfp)))
//id = NF_CT_EXt_HELPE_TYPE
void *__nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
{
struct nf_ct_ext *new;
int i, newlen, newoff;
struct nf_ct_ext_type *t;
//之前该连接跟踪信息块上还没有任何扩展,那么按照注册时的对齐以及
//扩展大小分配内存,对于刚分配的连接跟踪信息块,肯定属于这种情况
if (!ct->ext)
return nf_ct_ext_create(&ct->ext, id, gfp);
//对于非首次添加扩展,走下面的流程
//不能在同一个连接上重复添加相同类型的扩展
if (nf_ct_ext_exist(ct, id))
return NULL;
rcu_read_lock();
//从全局数组中找到扩展类型,该扩展类型之前已经注册
t = rcu_dereference(nf_ct_ext_types[id]);
BUG_ON(t == NULL);
//新扩展添加到ext->data数组的末尾,计算偏移量和新的总大小
newoff = ALIGN(ct->ext->len, t->align);
newlen = newoff + t->len;
rcu_read_unlock();
//需要重新分配
if (newlen >= ksize(ct->ext)) {
new = kmalloc(newlen, gfp);
if (!new)
return NULL;
//将之前ext的内容拷贝到新内存区域的开始
memcpy(new, ct->ext, ct->ext->len);
for (i = 0; i < NF_CT_EXT_NUM; i++) {
if (!nf_ct_ext_exist(ct, i))
continue;
rcu_read_lock();
t = rcu_dereference(nf_ct_ext_types[i]);
//执行move()回调
if (t && t->move)
t->move((void *)new + new->offset[i], (void *)ct->ext + ct->ext->offset[i]);
rcu_read_unlock();
}
//将原来的内存区域释放并ext指向新的内存
kfree(ct->ext);
ct->ext = new;
}
//将要添加的扩展添加到ext中
ct->ext->offset[id] = newoff;
ct->ext->len = newlen;
memset((void *)ct->ext + newoff, 0, newlen - newoff);
return (void *)ct->ext + newoff;
}
EXPORT_SYMBOL(__nf_ct_ext_add);
对于首次分配,因为当前连接跟踪信息块只关联了一个扩展,所以分配都很直接:
static void* nf_ct_ext_create(struct nf_ct_ext **ext, enum nf_ct_ext_id id, gfp_t gfp)
{
unsigned int off, len;
struct nf_ct_ext_type *t;
rcu_read_lock();
t = rcu_dereference(nf_ct_ext_types[id]);
BUG_ON(t == NULL);
off = ALIGN(sizeof(struct nf_ct_ext), t->align);
len = off + t->len;
rcu_read_unlock();
*ext = kzalloc(t->alloc_size, gfp);
if (!*ext)
return NULL;
(*ext)->offset[id] = off;
(*ext)->len = len;
return (void *)(*ext) + off;
}