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函数使用