Netfilter之协议族与用户空间接口--AF_INET

在笔记Netfilter之协议族初始化–AF_INET中,有看到在AF_INET的初始化过程中,有向Netfilter框架注册setsockopt()/getsockopt()接口,这是AF_INET协议族和用户空间相关程序(如iptables)沟通的接口,这篇笔记就来看看这组接口的实现。涉及的代码文件主要是:

代码路径 说明
net/ipv4/netfilter/ip_tables.c IPv4 Netfilter核心文件

1. 用户空间接口

如ip_tables_net_init()函数,用户空间接口为ipt_sockopts,所以SET操作最后由do_ipt_set_ctl()实现,GET操作由do_ipt_get_ctl()实现。

static struct nf_sockopt_ops ipt_sockopts = {
	.pf		= PF_INET,
	.set_optmin	= IPT_BASE_CTL,
	.set_optmax	= IPT_SO_SET_MAX+1,
	.set		= do_ipt_set_ctl,
#ifdef CONFIG_COMPAT
	.compat_set	= compat_do_ipt_set_ctl,
#endif
	.get_optmin	= IPT_BASE_CTL,
	.get_optmax	= IPT_SO_GET_MAX+1,
	.get		= do_ipt_get_ctl,
#ifdef CONFIG_COMPAT
	.compat_get	= compat_do_ipt_get_ctl,
#endif
	.owner		= THIS_MODULE,
};

2. 设置规则:do_ipt_set_ctl()

static int do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
{
	int ret;
	//调用者必须要有网络管理员权限,通常是root
	if (!capable(CAP_NET_ADMIN))
		return -EPERM;
	//目前仅支持两个SET命令
	switch (cmd) {
	case IPT_SO_SET_REPLACE:
		//该命令用于设置规则,这里要注意的是,设计防火墙规则的修改时,用户空间
		//和内核空间是交换整个table的内容,并非单单修改指定的规则
		ret = do_replace(sk->sk_net, user, len);
		break;
	case IPT_SO_SET_ADD_COUNTERS:
		//该命令用于设置规则的计数器
		ret = do_add_counters(sk->sk_net, user, len, 0);
		break;
	default:
		duprintf("do_ipt_set_ctl:  unknown request %i\n", cmd);
		ret = -EINVAL;
	}
	return ret;
}

修改计数器很简单,直接更新规则中的计数字段即可,下面重点看规则的替换。

2.1 替换规则do_replace()

这里有用到一个很重要的辅助数据结构struct ipt_replace,该结构是由用户空间程序程序填充,并且传递个内核。内核中各个table在初始化时也会使用该结构,并且会对其进行赋值,我们在相关笔记中再展开看该结构。

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;
	//用户空间传入的数据结构就是struct 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;
	//为struct xt_table_info分配空间,包括规则部分(每个CPU有一份规则拷贝),
	//传入的参数指定的是规则部分占用的空间
	newinfo = xt_alloc_table_info(tmp.size);
	if (!newinfo)
		return -ENOMEM;
	//拷贝规则部分到当前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;
	//完成table的替换
	ret = __do_replace(net, tmp.name, tmp.valid_hooks, newinfo,
			   tmp.num_counters, tmp.counters);
	if (ret)
		goto free_newinfo_untrans;
	return 0;
...
}

函数translate_table()对表中的规则进行检查,该函数的分析见笔记防火墙之filter表(一)—AF_INET协议族。

2.1.1 替换规则:__do_replace()

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;

	ret = 0;
	counters = vmalloc(num_counters * sizeof(struct xt_counters));
	if (!counters) {
		ret = -ENOMEM;
		goto out;
	}
	//找到对应的table
	t = try_then_request_module(xt_find_table_lock(net, AF_INET, name),
				    "iptable_%s", name);
	if (!t || IS_ERR(t)) {
		ret = t ? PTR_ERR(t) : -ENOENT;
		goto free_newinfo_counters_untrans;
	}
	//能工作的hook点要保持一致
	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. */
	get_counters(oldinfo, counters);
	/* Decrease module usage counts and free resource */
	loc_cpu_old_entry = oldinfo->entries[raw_smp_processor_id()];
	IPT_ENTRY_ITERATE(loc_cpu_old_entry, oldinfo->size, cleanup_entry,
			  NULL);
	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;
...
}

你可能感兴趣的:(Netfilter,Linux,Netfilter代码分析)