Netfilter之table、rule、match、target的组织管理

    xiaoxiao2023-12-04  167

    在上一篇笔记Netfilter之table、rule、match、target数据结构中,看到了内核对这些对象的定义,这篇笔记记录了内核对这些数据结构的组织,以及一些相关的核心函数分析。设计的代码文件主要有:

    代码路径说明/net/netfilter/x_tables.cnetfilter框架对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);
    最新回复(0)