在上一篇笔记Netfilter之table、rule、match、target数据结构中,看到了内核对这些对象的定义,这篇笔记记录了内核对这些数据结构的组织,以及一些相关的核心函数分析。设计的代码文件主要有:
代码路径 | 说明 |
---|---|
/net/netfilter/x_tables.c | netfilter框架对table、match、target的核心实现 |
1. table的管理
如下,struct net中的xt成员为每个协议族维护了一个链表,用来组织该协议族注册的table,具体的协议族都是在协议族初始化过程中向框架注册自己的table。
struct net {
...
#ifdef CONFIG_NETFILTER
struct netns_xt xt;
#endif
};
struct netns_xt {
struct list_head tables[NPROTO];
};
1.1 table的注册
注册之前,需要先看看struct xt_table_info对象的分配,因为该对象才是真正保存表内容的结构。
1.1.1 xt_alloc_table_info()
//size表示table中规则需要占用的内存大小
struct xt_table_info *xt_alloc_table_info(unsigned int size)
{
struct xt_table_info *newinfo;
int cpu;
//实际分配的内存需要按照页边界对齐
if ((SMP_ALIGN(size) >> PAGE_SHIFT) + 2 > num_physpages)
return NULL;
//为struct xt_table_info对象本身分配内存,不包括规则占用部分
newinfo = kzalloc(XT_TABLE_INFO_SZ, GFP_KERNEL);
if (!newinfo)
return NULL;
newinfo->size = size;
//为每个CPU都分配一块内存保存规则,每块内存用entries[i]指针索引
for_each_possible_cpu(cpu) {
if (size <= PAGE_SIZE)
newinfo->entries[cpu] = kmalloc_node(size, GFP_KERNEL, cpu_to_node(cpu));
else
newinfo->entries[cpu] = vmalloc_node(size, cpu_to_node(cpu));
if (newinfo->entries[cpu] == NULL) {
xt_free_table_info(newinfo);
return NULL;
}
}
return newinfo;
}
EXPORT_SYMBOL(xt_alloc_table_info);
上面很重要的一点就是表中的规则对于每个CPU都有一份相同的拷贝,这样可以避免多个CPU在访问规则的时候进行额外的锁操作。
1.1.2 xt_register_table()
各协议族调用xt_register_table()向框架注册自己的table。bootstrap参数的存在仅仅是为了复用xt_replace_table()的逻辑而已,因为xt_replace_table()函数本身是用来做table替换的。
struct xt_table *xt_register_table(struct net *net, struct xt_table *table,
struct xt_table_info *bootstrap, struct xt_table_info *newinfo)
{
int ret;
struct xt_table_info *private;
struct xt_table *t;
//将参数指定的table拷贝一份
table = kmemdup(table, sizeof(struct xt_table), GFP_KERNEL);
if (!table) {
ret = -ENOMEM;
goto out;
}
//如上所述,全局数组xt[table->af]保存的该协议族的所有match和target,
//但是这里持锁后访问了net->xt.tables[table->af]变量,该变量保存的是
//该协议族所有的table。这把锁的定义位置并不合适,其本意是要保护table、
//match、target,但是table的定义却在net中(更高版本已将match和target
//也移到了net中)
ret = mutex_lock_interruptible(&xt[table->af].mutex);
if (ret != 0)
goto out_free;
//检查该协议族的已定义table中是否已有指定名字的table,可见表名字需要协议族内部唯一
list_for_each_entry(t, &net->xt.tables[table->af], list) {
if (strcmp(t->name, table->name) == 0) {
ret = -EEXIST;
goto unlock;
}
}
/* Simplifies replace_table code. */
table->private = bootstrap;
rwlock_init(&table->lock);
//用newinfo替换table中原来的private指针
if (!xt_replace_table(table, 0, newinfo, &ret))
goto unlock;
private = table->private;
duprintf("table->private->number = %u\n", private->number);
/* save number of initial entries */
private->initial_entries = private->number;
//将table加入到该协议族的table集合中,注册完毕
list_add(&table->list, &net->xt.tables[table->af]);
mutex_unlock(&xt[table->af].mutex);
return table;
unlock:
mutex_unlock(&xt[table->af].mutex);
out_free:
kfree(table);
out:
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(xt_register_table);
1.1.3 xt_replace_table()
该函数用@newinfo替换@table中原来的private指向的内容,参数num_counters必须是原来table中规则的数目,否则替换将失败。
struct xt_table_info* xt_replace_table(struct xt_table *table,
unsigned int num_counters, struct xt_table_info *newinfo, int *error)
{
struct xt_table_info *oldinfo, *private;
/* Do the substitution. */
write_lock_bh(&table->lock);
private = table->private;
//个人理解:这只是一种简单的安全检查
if (num_counters != private->number) {
write_unlock_bh(&table->lock);
*error = -EAGAIN;
return NULL;
}
//替换privaet指针
oldinfo = private;
table->private = newinfo;
//初始规则数目保持和旧的相同
newinfo->initial_entries = oldinfo->initial_entries;
write_unlock_bh(&table->lock);
//返回旧的private指针
return oldinfo;
}
EXPORT_SYMBOL_GPL(xt_replace_table);
关于table的注册需要多啰嗦几句,个人理解:xt_regitster_table()之所以需要两个struct xt_table_info参数,是因为table的注册过程要复用函数xt_replace_table(),该函数本身是用来做table替换的。
表的去注册就是xt_unregister_table(),就是注册的反操作,不再赘述。
2. match和target的管理
如下,全局的struct xt_af变量维护了两个链表,分别来保存系统中已注册match和target,链表中的元素就是struct xt_match和struct xt_target。
2.1 struct xt_af
//全局变量xt会保存系统中所有协议族的match和target
struct xt_af {
struct mutex mutex;
struct list_head match;
struct list_head target;
};
static struct xt_af *xt;
2.2.match的注册
match的注册就相当简单了,直接将struct xt_match加入到对应协议族的match链表尾部。
int xt_register_match(struct xt_match *match)
{
int ret, af = match->family;
//和table一样,match用xt[af].mutext保护
ret = mutex_lock_interruptible(&xt[af].mutex);
if (ret != 0)
return ret;
//match链接到链表尾部
list_add(&match->list, &xt[af].match);
mutex_unlock(&xt[af].mutex);
return ret;
}
EXPORT_SYMBOL(xt_register_match);
也可以通过xt_register_matches()一次注册多个match。相应的,去注册函数有xt_unregister_match()和xt_unregister_matches()。
2.3 target的注册
int xt_register_target(struct xt_target *target)
{
int ret, af = target->family;
ret = mutex_lock_interruptible(&xt[af].mutex);
if (ret != 0)
return ret;
list_add(&target->list, &xt[af].target);
mutex_unlock(&xt[af].mutex);
return ret;
}
EXPORT_SYMBOL(xt_register_target);
和match类似,target也有xt_register_targets()、xt_unregister_target()和xt_unregister_targets()接口。
3 模块初始化
static int __net_init xt_net_init(struct net *net)
{
int i;
//初始化所有协议族保存table的链表
for (i = 0; i < NPROTO; i++)
INIT_LIST_HEAD(&net->xt.tables[i]);
return 0;
}
static struct pernet_operations xt_net_ops = {
.init = xt_net_init,
};
static int __init xt_init(void)
{
int i, rv;
//各个协议族分别和xt[pf]对应
xt = kmalloc(sizeof(struct xt_af) * NPROTO, GFP_KERNEL);
if (!xt)
return -ENOMEM;
//初始化锁、target链表、match链表
for (i = 0; i < NPROTO; i++) {
mutex_init(&xt[i].mutex);
INIT_LIST_HEAD(&xt[i].target);
INIT_LIST_HEAD(&xt[i].match);
}
rv = register_pernet_subsys(&xt_net_ops);
if (rv < 0)
kfree(xt);
return rv;
}
module_init(xt_init);