在笔记Netfilter之协议族初始化–AF_INET中,有看到在AF_INET的初始化过程中,有向Netfilter框架注册setsockopt()/getsockopt()接口,这是AF_INET协议族和用户空间相关程序(如iptables)沟通的接口,这篇笔记就来看看这组接口的实现。涉及的代码文件主要是:
代码路径 | 说明 |
---|---|
net/ipv4/netfilter/ip_tables.c | IPv4 Netfilter核心文件 |
如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,
};
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;
}
修改计数器很简单,直接更新规则中的计数字段即可,下面重点看规则的替换。
这里有用到一个很重要的辅助数据结构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协议族。
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;
...
}