[iptables]Series 3: Kernel Modules and Processing
发表于 2025-04-03
内核对应模块和处理
这一章我们将通过分析内核源码来进一步理解iptables的规则下发机制。
1. IPT_SO_GET_INFO
我们先观察 getsockopt(7, SOL_IP, IPT_SO_GET_INFO, “filter\0\0”…, [84]) 到底从内核中获取了哪些信息。
static int get_info(struct net *net, void __user *user, const int *len)
{
char name[XT_TABLE_MAXNAMELEN];
struct xt_table *t;
int ret;
if (*len != sizeof(struct ipt_getinfo))
return -EINVAL;
if (copy_from_user(name, user, sizeof(name)) != 0)
return -EFAULT;
name[XT_TABLE_MAXNAMELEN-1] = '\0';
t = xt_request_find_table_lock(net, AF_INET, name); //寻找对应xt_table
if (!IS_ERR(t)) {
struct ipt_getinfo info;
const struct xt_table_info *private = t->private; //找到对应的xt_table_info,这个结构就是表在内核中的样子
memset(&info, 0, sizeof(info));
info.valid_hooks = t->valid_hooks;
memcpy(info.hook_entry, private->hook_entry,
sizeof(info.hook_entry));
memcpy(info.underflow, private->underflow,
sizeof(info.underflow));
info.num_entries = private->number;
info.size = private->size;
strcpy(info.name, name);
if (copy_to_user(user, &info, *len) != 0) //转换成ipt_getinfo格式,并拷贝到用户态
ret = -EFAULT;
else
ret = 0;
xt_table_unlock(t);
module_put(t->me);
} else
ret = PTR_ERR(t);
return ret;
}
struct xt_table *xt_find_table_lock(struct net *net, u_int8_t af,
const char *name)
{
struct xt_pernet *xt_net = net_generic(net, xt_pernet_id);
struct module *owner = NULL;
struct xt_template *tmpl;
struct xt_table *t;
mutex_lock(&xt[af].mutex);
list_for_each_entry(t, &xt_net->tables[af], list)
if (strcmp(t->name, name) == 0 && try_module_get(t->me))
return t;
//后面还有对应table不存在时的加载和初始化操作。此处省略。
}
- 根据表名,去xt_net->tables[af]链表中查找对应的xt_table。iptables对应的af = NFPROTO_IPV4 = 2。对应 raw, nat, filter, mangle表的xt_table都在这个链上。
- 通过t->private获取对应的xt_table_info。这个结构中存储了iptables中的规则信息。
- 根据xt_table和xt_table_info,设置ipt_getinfo结构相应选项
- 将设ipt_getinfo复制到用户空间。后续iptables就可以读取并在用户空间展示了。
filter表的name由用户提供,用来查找。valid_hooks,从xt_table中获取。剩下的变量从 xt_table_info中获取
#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \
(1 << NF_INET_FORWARD) | \
(1 << NF_INET_LOCAL_OUT))
static const struct xt_table packet_filter = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,
.me = THIS_MODULE,
.af = NFPROTO_IPV4,
.priority = NF_IP_PRI_FILTER,
};
| ipt_getinfo变量名 | 意义 |
|---|---|
| name | 表名,比如 “filter”, “nat”, “mangle” |
| valid_hooks | 有效 hook 的掩码(哪些 hook 被此表使用) |
| hook_entry | 每个 hook 点的规则入口偏移量, 表示链的起始规则位置(entry 的偏移) |
| underflow | 每个 hook 点的 underflow 位置(即默认返回点), 当没有规则匹配时,会跳转到这里处理 |
| num_entries | 当前表中规则条目的总数(链中所有规则加一起) |
| size | 所有规则条目的总字节大小(含 match/target 等可变结构) |
2. IPT_SO_GET_ENTRIES
通过 IPT_SO_GET_INFO 获取到表的基本信息后, iptables会再通过 IPT_SO_GET_ENTRIES 来得到具体的规则信息。
我们观察 getsockopt(7, SOL_IP, IPT_SO_GET_ENTRIES, "filter\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., [12744]) = 0 与内核的交互。
static int
get_entries(struct net *net, struct ipt_get_entries __user *uptr,
const int *len)
{
int ret;
struct ipt_get_entries get;
struct xt_table *t;
if (*len < sizeof(get))
return -EINVAL;
if (copy_from_user(&get, uptr, sizeof(get)) != 0)
return -EFAULT;
if (*len != sizeof(struct ipt_get_entries) + get.size)
return -EINVAL;
get.name[sizeof(get.name) - 1] = '\0';
t = xt_find_table_lock(net, AF_INET, get.name); //依然通过表名获取到对应的xt_table
if (!IS_ERR(t)) {
const struct xt_table_info *private = t->private; //得到对应的xt_table_info
if (get.size == private->size)
ret = copy_entries_to_user(private->size,
t, uptr->entrytable); //负责将内核规则逐个复制到用户态
else
ret = -EAGAIN;
module_put(t->me);
xt_table_unlock(t);
} else
ret = PTR_ERR(t);
return ret;
}
用户态传入ipt_get_entries,填入 nameh和size。内核通过name查找xt_table表,并验证size。 再将所有规则写入到ipt_entry
/* The argument to IPT_SO_GET_ENTRIES. */
struct ipt_get_entries {
/* Which table: user fills this in. */
char name[XT_TABLE_MAXNAMELEN];
/* User fills this in: total entry size. */
unsigned int size;
/* The entries. */
struct ipt_entry entrytable[];
};
| ipt_get_entries变量名 | 意义 |
|---|---|
| name | 表名,比如 “filter”, “nat”, “mangle” |
| size | 整个entrytable可变数组的大小 |
| entrytable | 可变长度数组,实际包含整张表的 entry 规则内容 |
下面我们观察 copy_entries_to_user 函数
static int
copy_entries_to_user(unsigned int total_size,
const struct xt_table *table,
void __user *userptr)
{
unsigned int off, num;
const struct ipt_entry *e;
struct xt_counters *counters;
const struct xt_table_info *private = table->private;
int ret = 0;
const void *loc_cpu_entry;
counters = alloc_counters(table); //获取真正的entry 统计
if (IS_ERR(counters))
return PTR_ERR(counters);
loc_cpu_entry = private->entries; //内核中entry的起始位置
/* FIXME: use iterator macros --RR */
/* ... then go back and fix counters and names */
for (off = 0, num = 0; off < total_size; off += e->next_offset, num++){
unsigned int i;
const struct xt_entry_match *m;
const struct xt_entry_target *t;
e = loc_cpu_entry + off;
if (copy_to_user(userptr + off, e, sizeof(*e))) { //将内核entry基础结构复制到用户侧
ret = -EFAULT;
goto free_counters;
}
if (copy_to_user(userptr + off
+ offsetof(struct ipt_entry, counters),
&counters[num],
sizeof(counters[num])) != 0) { //替换ipt_entry中的couters为真正的统计值
ret = -EFAULT;
goto free_counters;
}
for (i = sizeof(struct ipt_entry);
i < e->target_offset;
i += m->u.match_size) {
m = (void *)e + i;
if (xt_match_to_user(m, userptr + off + i)) { //如果存在match,则复制对应match到用户侧
ret = -EFAULT;
goto free_counters;
}
}
t = ipt_get_target_c(e);
if (xt_target_to_user(t, userptr + off + e->target_offset)) { //复制对应target到用户侧
ret = -EFAULT;
goto free_counters;
}
}
free_counters:
vfree(counters);
return ret;
}
ipt_get_entries结构布局图。
struct ipt_get_entries
├── name: "filter"
├── size: 512 (例如)
└── entrytable:
├── ipt_entry[0] //基础信息 -s 1.2.3.4 -d 5.6.7.8 等
│ ├── match(es) //-m <match>扩展匹配模块内容
│ └── target //-j <target>(ACCEPT,DROP,RETURN等)
├── ipt_entry[1]
│ ├── ...
│ └── ...
└── ...
3. IPT_SO_GET_REVISION_MATCH 和 IPT_SO_GET_REVISION_TARGET
对于 getsockopt(8, SOL_IP, IPT_SO_GET_REVISION_TARGET, "MARK\0\177\0\0B\344!_7\177\0\0\300\217&\230\377\177\0\0\0\0\0\0\0\2", [30]) 和 getsockopt(8, SOL_IP, IPT_SO_GET_REVISION_MATCH, "conntrack\0!_7\177\0\0h\270!_7\177\0\0\2\0\0\0\2\3", [30]) 用来检查版本是否一致。
用户态请求的结构
/* The argument to IPT_SO_GET_REVISION_*. Returns highest revision
* kernel supports, if >= revision. */
struct xt_get_revision {
char name[XT_EXTENSION_MAXNAMELEN]; //match的名字
__u8 revision; //match的版本
};
以match举例, 相关match在内核中都注册到xt[af].match链表上
static struct xt_target xt_mark_target = {
.name = "MARK", //match名字
.revision = 1, //版本信息
.family = NFPROTO_IPV4,
...
};
对应的内核处理函数
int xt_find_revision(u8 af, const char *name, u8 revision, int target,
int *err)
{
int have_rev, best = -1;
if (target == 1)
have_rev = target_revfn(af, name, revision, &best); //找到对应的target,返回对应的版本
else
have_rev = match_revfn(af, name, revision, &best); //找到对应的mtach,返回对应的版本
/* Nothing at all? Return 0 to try loading module. */
if (best == -1) {
*err = -ENOENT;
return 0;
}
*err = best;
if (!have_rev)
*err = -EPROTONOSUPPORT;
return 1;
}
本文访问次数:... 次