Skip to the content.

[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不存在时的加载和初始化操作。此处省略。
}

  1. 根据表名,去xt_net->tables[af]链表中查找对应的xt_table。iptables对应的af = NFPROTO_IPV4 = 2。对应 raw, nat, filter, mangle表的xt_table都在这个链上。
  2. 通过t->private获取对应的xt_table_info。这个结构中存储了iptables中的规则信息。
  3. 根据xt_table和xt_table_info,设置ipt_getinfo结构相应选项
  4. 将设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;
}

本文访问次数:...