iptables和netfilter的通信流程

iptables和netfilter通信采用的是setsockopt和getsockopt函数

一、用户态iptables代码

前面博客文章 https://blog.csdn.net/haolipengzhanshen/article/details/84888489

我们分析了iptables的主流程,相信大家会熟悉下面的代码

ret = do_command4(argc, argv, &table, &handle, false);
    if (ret) {
        ret = iptc_commit(handle);
        iptc_free(handle);
    }

iptables使用do_command4函数逐个解析完iptables命令行参数后,调用iptc_commit函数提交iptables命令规则

查找iptc_commit函数的实现,仅仅找到了    #define TC_COMMIT        iptc_commit

继续找TC_COMMIT函数的代码实现,定位到libiptc目录下的libiptc.c文件

在TC_COMMIT(struct xtc_handle *handle)函数的中间位置调用了setsockeopt将iptables规则传递给内核模块

ret = setsockopt(handle->sockfd, TC_IPPROTO, SO_SET_REPLACE, repl,sizeof(*repl) + repl->size);

 

在TC_COMMIT(struct xtc_handle *handle)函数的末尾调用了setsockopt用于计数功能

ret = setsockopt(handle->sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS,newcounters, counterlen);

 

按照我之前的猜想,对iptables规则的操作有添加和删除,那么命令起码有SO_SET_ADD和SO_SET_DEL

但是实际情况是,只有SO_SET_REPLACE命令,难道它可以替代添加和删除?为什么哦?

 

两次调用setsockopt函数的区别是SO_SET_REPLACE和SO_SET_ADD_COUNTERS命令的区别

前者是添加规则,后者是增加计数,因为我们研究的是添加规则,

#define SO_SET_REPLACE        IPT_SO_SET_REPLACE

所以我们探索下IPT_SO_SET_REPLACE是如何处理的

 

二、内核态netfilter代码

在netfilter代码中查找IPT_SO_SET_REPLACE的引用位置

定位到do_ipt_set_ctl函数

static int
do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
{
	int ret;

	if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
		return -EPERM;

	switch (cmd) {
	case IPT_SO_SET_REPLACE:
		ret = do_replace(sock_net(sk), user, len);
		break;

	case IPT_SO_SET_ADD_COUNTERS:
		ret = do_add_counters(sock_net(sk), user, len, 0);
		break;

	default:
		duprintf("do_ipt_set_ctl:  unknown request %i\n", cmd);
		ret = -EINVAL;
	}

	return ret;
}

IPT_SO_SET_REPLACE命令的响应函数是do_replace函数

static int
do_replace(struct net *net, const void __user *user, unsigned int len)
{
	int ret;
	struct ipt_replace tmp;
	struct xt_table_info *newinfo;
	void *loc_cpu_entry;
	struct ipt_entry *iter;

	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;
	tmp.name[sizeof(tmp.name)-1] = 0;

	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(net, newinfo, loc_cpu_entry, &tmp);
	if (ret != 0)
		goto free_newinfo;

	duprintf("Translated table\n");

	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:
	xt_entry_foreach(iter, loc_cpu_entry, newinfo->size)
		cleanup_entry(iter, net);
 free_newinfo:
	xt_free_table_info(newinfo);
	return ret;
}

copy_from_user将用户空间数据拷贝到内核态

copy_to_user是将内核态数据拷贝给用户态

 

调用__do_replace函数,并高亮newinfo变量,newinfo是新增的iptables规则,必然要对其进行替换操作

定位到net/netfilter/ipv4目录下的ip_tables.c文件

static int
__do_replace(struct net *net, const char *name, unsigned int valid_hooks,
	     struct xt_table_info *newinfo, unsigned int num_counters,
	     void __user *counters_ptr)
{
	int ret;
	struct xt_table *t;
	struct xt_table_info *oldinfo;
	struct xt_counters *counters;
	void *loc_cpu_old_entry;
	struct ipt_entry *iter;

	ret = 0;
	counters = vzalloc(num_counters * sizeof(struct xt_counters));
	if (!counters) {
		ret = -ENOMEM;
		goto out;
	}

	t = try_then_request_module(xt_find_table_lock(net, AF_INET, name),
				    "iptable_%s", name);
	if (IS_ERR_OR_NULL(t)) {
		ret = t ? PTR_ERR(t) : -ENOENT;
		goto free_newinfo_counters_untrans;
	}

	/* You lied! */
	if (valid_hooks != t->valid_hooks) {
		duprintf("Valid hook crap: %08X vs %08X\n",
			 valid_hooks, t->valid_hooks);
		ret = -EINVAL;
		goto put_module;
	}

	oldinfo = xt_replace_table(t, num_counters, newinfo, &ret);
	if (!oldinfo)
		goto put_module;

	/* Update module usage count based on number of rules */
	duprintf("do_replace: oldnum=%u, initnum=%u, newnum=%u\n",
		oldinfo->number, oldinfo->initial_entries, newinfo->number);
	if ((oldinfo->number > oldinfo->initial_entries) ||
	    (newinfo->number <= oldinfo->initial_entries))
		module_put(t->me);
	if ((oldinfo->number > oldinfo->initial_entries) &&
	    (newinfo->number <= oldinfo->initial_entries))
		module_put(t->me);

	/* Get the old counters, and synchronize with replace */
	get_counters(oldinfo, counters);

	/* Decrease module usage counts and free resource */
	loc_cpu_old_entry = oldinfo->entries[raw_smp_processor_id()];
	xt_entry_foreach(iter, loc_cpu_old_entry, oldinfo->size)
		cleanup_entry(iter, net);

	xt_free_table_info(oldinfo);
	if (copy_to_user(counters_ptr, counters,
			 sizeof(struct xt_counters) * num_counters) != 0)
		ret = -EFAULT;
	vfree(counters);
	xt_table_unlock(t);
	return ret;

 put_module:
	module_put(t->me);
	xt_table_unlock(t);
 free_newinfo_counters_untrans:
	vfree(counters);
 out:
	return ret;
}

1、vzalloc为计数器申请内存空间

2、try_then_request_module会检查模块是否已经存在,如果不存在则使用request_module(mod)加载模块。内核模块引用内核模块,一般使用request_module(mod)

3、oldinfo = xt_replace_table(t, num_counters, newinfo, &ret);

使用xt_replace_table函数将旧内容替换成新内容newinfo结构体,这里是核心哦,划重点了哈 哈哈

4、将计数器指针提供给用户态访问

if (copy_to_user(counters_ptr, counters,sizeof(struct xt_counters) * num_counters) != 0)

 

三、内核态netfilter和用户态iptables的通信结构体


netfilter是如何接收到iptables传递的规则参数呢?
.match = set_match_v0
之前在netfilter内核模块处,match回调函数被注册为set_match_v0()


static bool
set_match_v0(const struct sk_buff *skb, struct xt_action_param *par)
{
const struct xt_set_info_match_v0 *info = par->matchinfo;
ADT_OPT(opt, par->family, info->match_set.u.compat.dim,
info->match_set.u.compat.flags, 0, UINT_MAX);

return match_set(info->match_set.index, skb, par, &opt,
         info->match_set.u.compat.flags & IPSET_INV_MATCH);
}
从set_match_v0的xt_action_param结构体指针中获取用户态程序设置的iptables规则,真的是这样吗?

我们只要找到match回调函数的调用位置,然后是如何给match回调函数传参数的,就能验证我的猜想是否正确。

经过一段时间的寻找,终于在net/ipv4/netfilter目录下的ip_tables.c文件中的ipt_do_table函数中,找到了调用match函数的地方。

    unsigned int
    ipt_do_table(struct sk_buff *skb,
    unsigned int hook,
    const struct net_device *in,
    const struct net_device *out,
    struct xt_table *table)
    {
    //代码省略
    
    /* Initialization */
    ip = ip_hdr(skb);
    indev = in ? in->name : nulldevname;
    outdev = out ? out->name : nulldevname;
    
    acpar.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
    acpar.thoff   = ip_hdrlen(skb);
    acpar.hotdrop = false;
    acpar.in      = in;
    acpar.out     = out;
    acpar.family  = NFPROTO_IPV4;
    acpar.hooknum = hook;
    
    IP_NF_ASSERT(table->valid_hooks & (1 << hook));
    local_bh_disable();
    addend = xt_write_recseq_begin();
    private = table->private;
    cpu        = smp_processor_id();
    table_base = private->entries[cpu];
    jumpstack  = (struct ipt_entry **)private->jumpstack[cpu];
    stackptr   = per_cpu_ptr(private->stackptr, cpu);
    origptr    = *stackptr;
    
    e = get_entry(table_base, private->hook_entry[hook]);
    
    do {
        //对应iptables规则中的taget和match规则
        const struct xt_entry_target *t;
        const struct xt_entry_match *ematch;
    
        IP_NF_ASSERT(e);
        if (!ip_packet_match(ip, indev, outdev,
            &e->ip, acpar.fragoff)) {
    no_match:
    e = ipt_next_entry(e);
    continue;
    }
    
    xt_ematch_foreach(ematch, e) {
        acpar.match     = ematch->u.kernel.match;
        acpar.matchinfo = ematch->data;
        //match函数被调用位置!!!
        if (!acpar.match->match(skb, &acpar))
            goto no_match;
    }

ipt_do_table函数中一直在给struct xt_action_param *类型的acpar结构体的各个成员不断赋值,然后调用match函数时,作为参数传入函数,其中acpar.matchinfo = ematch->data,acpar.matchinfo之中存储的就是用户态的规则数据

正印证了我的猜想:netfilter从xt_action_param结构体获取iptables中已经设置的规则

 

结论:netfilter内核模块和iptables用户态之间通信的结构体是 struct xt_action_param结构体,规则内容存储在par->matchinfo成员变量中

参考链接

copy_from_user和copy_to_user函数使用

https://blog.csdn.net/ce123_zhouwei/article/details/8454226

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值