Linux Netfilter 表注册

作者:jerry_chg
来源:CSDN
原文:https://blog.csdn.net/lickylin/article/details/33347301
版权声明:本文为博主原创文章,转载请附上博文链接!

/* 注册过滤表 */

struct xt_table *ipt_register_table(struct net *net, struct xt_table *table,
				    const struct ipt_replace *repl)
{
	int ret;
	struct xt_table_info *newinfo;
	struct xt_table_info bootstrap
		= { 0, 0, 0, { 0 }, { 0 }, { } };
	void *loc_cpu_entry;
	struct xt_table *new_table;

    /* 为filter表申请存储空间,包括xt_table_info结构和存储规则的entry内存块 */
	newinfo = xt_alloc_table_info(repl->size);
	if (!newinfo) {
		ret = -ENOMEM;
		goto out;
	}

	/* choose the copy on our node/cpu, but dont care about preemption */
    /* 将filter表的规则入口地址赋值给loc_cpu_entry */
	loc_cpu_entry = newinfo->entries[raw_smp_processor_id()];

    /* 将repl中的所有规则全部拷贝到entries中 */
	memcpy(loc_cpu_entry, repl->entries, repl->size);

    /* 对规则进行边界检查,并对内存块首地址赋值 */
	ret = translate_table(table->name, table->valid_hooks,
			      newinfo, loc_cpu_entry, repl->size,
			      repl->num_entries,
			      repl->hook_entry,
			      repl->underflow);
	if (ret != 0)
		goto out_free;

    /* 注册filter表,将xt_table插入到xt_af[af]->tables中,并更新xt_table->private指针 */
	new_table = xt_register_table(net, table, &bootstrap, newinfo);
	if (IS_ERR(new_table)) {
		ret = PTR_ERR(new_table);
		goto out_free;
	}

	return new_table;

out_free:
	xt_free_table_info(newinfo);
out:
	return ERR_PTR(ret);
}

/* Checks and translates the user-supplied table segment (held in
newinfo) */

static int translate_table(const char *name,
        unsigned int valid_hooks,
        struct xt_table_info *newinfo,
        void *entry0,
        unsigned int size,
        unsigned int number,
        const unsigned int *hook_entries,
        const unsigned int *underflows)
{
    unsigned int i;
    int ret;

    newinfo->size = size;
    newinfo->number = number;

    /* Init all hooks to impossible value. */
    /* 对hook_entry和underflow赋初始值 */
    for (i = 0; i < NF_INET_NUMHOOKS; i++) {
        newinfo->hook_entry[i] = 0xFFFFFFFF;
        newinfo->underflow[i] = 0xFFFFFFFF;
    }

    duprintf("translate_table: size %u\n", newinfo->size);
    i = 0;

    /* Walk through entries, checking offsets. */
    /* 遍历所有ipt_entry,校验大小及边界
     * 1、检查从entry0到entry0+size之间每一个ipt_entry变量的大小是否符合要求,
     *    以及每一个ipt_entry的起始地址的对齐
     * 2、判断相邻的ipt_entry的offset值是否正确,在正确的情况下将offset值
     *    设置到相应的newinfo->hook_entry[]、newinfo->underflow[]
     */
    ret = IPT_ENTRY_ITERATE(entry0, newinfo->size,
            check_entry_size_and_hooks,
            newinfo,
            entry0,
            entry0 + size,
            hook_entries, underflows, &i);
    if (ret != 0)
        return ret;

    if (i != number) {
        duprintf("translate_table: %u not %u entries\n",
                i, number);
        return -EINVAL;
    }

    /* Check hooks all assigned */
    /* 对于本表支持的规则链,若出现规则链相应的hook_entry[i]未被赋值则返回出错 */
    for (i = 0; i < NF_INET_NUMHOOKS; i++) {
        /* Only hooks which are valid */
        if (!(valid_hooks & (1 << i)))
            continue;
        if (newinfo->hook_entry[i] == 0xFFFFFFFF) {
            duprintf("Invalid hook entry %u %u\n",
                    i, hook_entries[i]);
            return -EINVAL;
        }
        if (newinfo->underflow[i] == 0xFFFFFFFF) {
            duprintf("Invalid underflow %u %u\n",
                    i, underflows[i]);
            return -EINVAL;
        }
    }

    /* 检查首地址从entry0处开始的所有规则链中,是否存在环路链,
     * 若存在,则返回0
     * 若不存在,则返回1 */
    if (!mark_source_chains(newinfo, valid_hooks, entry0))
        return -ELOOP;

    /* Finally, each sanity check must pass */
    /* 遍历链表中的所有ipt_entry,对每一个ipt_entry,执行以下动作
     * 1.遍历该ipt_entry下的所有match,根据每个match的用户层的match名称,
     *   在kernel的xt[af].match链表中查找名称相同的match变量,将地址赋值给m->u.kernel.match,
     *   当满足合法性检查时,继续操作;
     *   若不满足合法性检查,则会返回失败,并释放创建的struct xt_table_info newinfo,
     *   说明创建ipt_table失败。
     * 2.遍历xt[af].target,查找符合条件的target并赋值给t->u.kernel.target
     * */
    i = 0;
    ret = IPT_ENTRY_ITERATE(entry0, newinfo->size,
            find_check_entry, name, size, &i);
    if (ret != 0) {
        IPT_ENTRY_ITERATE(entry0, newinfo->size,
                cleanup_entry, &i);
        return ret;
    }

    /* And one copy for every other CPU */
    /* 在经过了规则链的环路检查,规则的边界检查,match与target的赋值后,
     * 对于每一个cpu所对应的newinfo->entries,将新值进行拷贝 */
    for_each_possible_cpu(i) {
        if (newinfo->entries[i] && newinfo->entries[i] != entry0)
            memcpy(newinfo->entries[i], entry0, newinfo->size);
    }

    return ret;
}

功能:
判断一个规则链中的所有规则有没有出现环路。
在这个函数里,e->comefrom被用来存储是否成环标志,出了这个函数以后这个值就不是用来存储是否成环的标志了。
原理:
在一个规则链中,在从头到尾遍历所有规则时,将每一个ipt_entry->comefrom的NF_IP_NUMHOOKS位置1;
当遍历到链的最后一个规则后,则从尾到头遍历所有规则,并依次清空ipt_entry->comefrom的NF_IP_NUMHOOKS位。
这样的话,如果规则链中有环路的话,则第一次从头到尾遍历时,就成环了,无法执行从尾到头遍历清空ipt_entry->comefrom的NF_IP_NUMHOOKS位,这样在第二次到达某一个ipt_entry时,判断ipt_entry->comefrom的NF_IP_NUMHOOKS位时就为1了,此时就知道该规则链出现环路了。

static int mark_source_chains(struct xt_table_info *newinfo,
        unsigned int valid_hooks, void *entry0)
{
    unsigned int hook;

    /* No recursion; use packet counter to save back ptrs (reset
       to 0 as we leave), and comefrom to save source hook bitmask */
    for (hook = 0; hook < NF_INET_NUMHOOKS; hook++)
    {
        unsigned int pos = newinfo->hook_entry[hook];

        /* 获取当前要遍历的规则链的首条规则地址 */
        struct ipt_entry *e = (struct ipt_entry *)(entry0 + pos);

        /* 仅遍历该表支持的hook链 */
        if (!(valid_hooks & (1 << hook)))
            continue;

        /* Set initial back pointer. */
        /* 初始返回地址即设置为首条规则的偏移地址 */
        e->counters.pcnt = pos;

        /* 遍历该规则链的所有规则 */
        for (;;) {
            struct ipt_standard_target *t
                = (void *)ipt_get_target(e);
            int visited = e->comefrom & (1 << hook);

            /* 该规则链中存在闭环,返回0 */
            if (e->comefrom & (1 << NF_INET_NUMHOOKS)) {
                printk("iptables: loop hook %u pos %u %08X.\n",
                        hook, pos, e->comefrom);
                return 0;
            }

            /* 进入规则前置位NF_IP_NUMHOOKS */
            e->comefrom |= ((1 << hook) | (1 << NF_INET_NUMHOOKS));

            /* Unconditional return/END. */
            if ((e->target_offset == sizeof(struct ipt_entry)
                        && (strcmp(t->target.u.user.name, IPT_STANDARD_TARGET) == 0)
                        && t->verdict < 0
                        && unconditional(&e->ip))
                    || visited)
            {
                unsigned int oldpos, size;

                if ((strcmp(t->target.u.user.name, IPT_STANDARD_TARGET) == 0)
                        && t->verdict < -NF_MAX_VERDICT - 1)
                {
                    duprintf("mark_source_chains: bad "
                            "negative verdict (%i)\n",
                            t->verdict);
                    return 0;
                }

                /* Return: backtrack through the last
                   big jump. */
                do {
                    /* 清除NF_IP_NUMHOOKS */
                    e->comefrom ^= (1<<NF_INET_NUMHOOKS);
#ifdef DEBUG_IP_FIREWALL_USER
                    if (e->comefrom & (1 << NF_INET_NUMHOOKS)) {
                        duprintf("Back unset " "on hook %u " "rule %u\n", hook, pos);
                    }
#endif
                    oldpos = pos;
                    pos = e->counters.pcnt;
                    e->counters.pcnt = 0;

                    /* We're at the start. */
                    /* 此处的判断很巧妙,
                     * 1.在for(;;)开始前,我们将初始的规则的counters.pcnt设置为其自己的偏移
                     * 2.然后,在for循环中,就是一直遍历规则链的最后一条规则,
                     *   并置位每条规则的NF_IP_NUMHOOKS。
                     * 3.在确定为规则链的最后一条规则时,则根据pos指针与e->counters.pcnt
                     *   从最后一条规则链递减遍历每一条规则,并清除规则的NF_IP_NUMHOOKS
                     * 4.当递减遍历到第一条规则时,根据第二条的e->counters.pcnt与
                     *   起始规则的e->counters.pcnt相等的关系,则成功退出dowhile循环,
                     *   且完成对每一条规则是否为循环的判断。
                     *
                     * 按照我们的分析,如果一条链中的规则出现了环,则不会是在最后一条规则里出现了环路,
                     * 因为最后一条规则都是默认规则,是没有跳转的。
                     * 这样的话,上面的判断思路就合理了,当链中出现环路时,则for(;;)只就不会进入结尾规则的处理语句内,
                     * 这样的话,若成环,则在对某个规则进行第二次访问时,便会进入if(e->comefrom&(1<<NF_IP_NUMHOOKS) 代码段,发现环路,函数返回0;
                     * 若没有环路,则最后都会进入结尾规则的处理语句内,清除每条规则的NF_IP_NUMHOOKS,程序返回1。
                     * */
                    if (pos == oldpos)
                        goto next;

                    e = (struct ipt_entry *)(entry0 + pos);
                } while (oldpos == pos + e->next_offset);

                /* Move along one */
                size = e->next_offset;
                e = (struct ipt_entry *)(entry0 + pos + size);
                e->counters.pcnt = pos;
                pos += size;
            } else {
                /* 对于非规则链尾的规则,主要有两种,分类方法为:
                 * 1.跳转到用户自定义链的规则
                 * 2.不需要跳转的标准target规则与扩张target规则 */
                int newpos = t->verdict;

                if (strcmp(t->target.u.user.name, IPT_STANDARD_TARGET) == 0 && newpos >= 0)
                {
                    if (newpos > newinfo->size - sizeof(struct ipt_entry)) {
                        duprintf("mark_source_chains: " "bad verdict (%i)\n", newpos);
                        return 0;
                    }
                    /* This a jump; chase it. */
                    duprintf("Jump rule %u -> %u\n", pos, newpos);
                } else {
                    /* ... this is a fallthru */
                    /* 对于不需要跳转到用户自定义链的规则来说,
                     * 下一条规则的地址即可根据e->next_offset计算出来*/
                    newpos = pos + e->next_offset;
                }

                e = (struct ipt_entry *)(entry0 + newpos);

                /* 设置返回地址 */
                e->counters.pcnt = pos;
                pos = newpos;
            }
        }
next:
        duprintf("Finished chain %u\n", hook);
    }
    return 1;
}

当用户层通过iptables -A添加一条规则或者通过iptables -D删除一条规则时,最终都会调用函数do_release函数实现新的规则的添加或者规则删除(其实就是替换xt_table->private,即需要创建一个新的xt_table_info和一个新的内存块,用于存放新的所有的规则)。
与表的注册相比,少了将xt_table插入到xt[table->af].tables链表的步骤。

  static int do_replace(struct net *net, void __user *user, unsigned int len)
    {
    	int ret;
    	struct ipt_replace tmp;
    	struct xt_table_info *newinfo;
    	void *loc_cpu_entry;
    
        /* 拷贝用户侧传递的ipt_replace值 */
    	if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
    		return -EFAULT;
    
    	/* overflow check */
    	if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
    		return -ENOMEM;
    
        /* 为新规则申请内存空间 */
    	newinfo = xt_alloc_table_info(tmp.size);
    	if (!newinfo)
    		return -ENOMEM;
    
    	/* choose the copy that is on our node/cpu */
    	loc_cpu_entry = newinfo->entries[raw_smp_processor_id()];
    	if (copy_from_user(loc_cpu_entry, user + sizeof(tmp),
    			   tmp.size) != 0) {
    		ret = -EFAULT;
    		goto free_newinfo;
    	}
    
        /* 对新申请的内存空间中的每一个规则进行设置操作 */
    	ret = translate_table(tmp.name, tmp.valid_hooks,
    			      newinfo, loc_cpu_entry, tmp.size, tmp.num_entries,
    			      tmp.hook_entry, tmp.underflow);
    	if (ret != 0)
    		goto free_newinfo;
    
    	duprintf("ip_tables: Translated table\n");
    
        /* 替换xt_table->private */
    	ret = __do_replace(net, tmp.name, tmp.valid_hooks, newinfo,
    			   tmp.num_counters, tmp.counters);
    	if (ret)
    		goto free_newinfo_untrans;
    	return 0;
    
     free_newinfo_untrans:
    	IPT_ENTRY_ITERATE(loc_cpu_entry, newinfo->size, cleanup_entry, NULL);
     free_newinfo:
    	xt_free_table_info(newinfo);
    	return ret;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值