防火墙之filter表(一)---AF_INET协议族

这篇笔记记录了AF_INET协议族的filter表的初始化,通过filter表,可以看出协议族是如何与Netfilter框架配合,完成防火墙规则的过滤和管理的。filter表涉及内容较多,分两篇笔记完成,涉及的核心代码文件有:

代码路径 说明
net/ipv4/netfilter/iptable_filter.c IPv4 filter表实现

filter表以模块的形式存在,其控制开关为:CONFIG_IP_NF_FILTER。

1. 初始化

初始化函数即模块初始化函数,如下:

static int __net_init iptable_filter_net_init(struct net *net)
{
	//注册filter表
	net->ipv4.iptable_filter =
		ipt_register_table(net, &packet_filter, &initial_table.repl);
	if (IS_ERR(net->ipv4.iptable_filter))
		return PTR_ERR(net->ipv4.iptable_filter);
	return 0;
}
static struct pernet_operations iptable_filter_net_ops = {
	.init = iptable_filter_net_init,
	.exit = iptable_filter_net_exit,
};
static int __init iptable_filter_init(void)
{
	int ret;

	if (forward < 0 || forward > NF_MAX_VERDICT) {
		printk("iptables forward must be 0 or 1\n");
		return -EINVAL;
	}
	/* Entry 1 is the FORWARD hook */
	initial_table.entries[1].target.verdict = -forward - 1;
	ret = register_pernet_subsys(&iptable_filter_net_ops);
	if (ret < 0)
		return ret;
	//向Netfilter框架注册三个钩子:INPUT、OUTPUT、FORWARD
	ret = nf_register_hooks(ipt_ops, ARRAY_SIZE(ipt_ops));
	if (ret < 0)
		goto cleanup_table;
	return ret;
...
}

初始化干了两件重要的事情:

  1. 注册filter表
  2. 注册钩子

内容较多,这篇笔记只看filter表的注册过程,钩子的注册过程见下一篇笔记。

2. filter表的注册

在看具体的实现之前,需要先来看看注册表时传入的两个参数:packet_filter和initial_table.repl。

//filter表中的规则只能在LOCAL_IN、FORWARD、LOCAL_OUT三个点工作
#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \
			    (1 << NF_INET_FORWARD) | \
			    (1 << NF_INET_LOCAL_OUT))
//packet_filter就是Netfilter框架定义的表结构struct xt_table对象,这里表名字为filter
static struct xt_table packet_filter = {
	.name		= "filter",
	.valid_hooks	= FILTER_VALID_HOOKS,
	.lock		= RW_LOCK_UNLOCKED,
	.me		= THIS_MODULE,
	.af		= AF_INET,
};

该结构正是Netfilter对table的定义,那么额外的initial_table.repl又是干什么的呢?

static struct
{
	struct ipt_replace repl;
	//这个组织是很有讲究的,紧挨着在repl的后面放了4条规则,因为repl的最后一个成员
	//entries是零长度数组,所以其直接指向了这里的规则。filter表注册时内置了4调规则
	struct ipt_standard entries[3];
	struct ipt_error term;
} initial_table;

和注册相关的核心数据结构是struct ipt_replace。iptables工具在更新规则时实际上是整体替换的,即先从内核dump所有规则,然后修改,最后整体替换,并非单独修改某一条规则。注册也可以理解为是一种替换,只是要替换表原本不存在而已,所以只要设计上面在某些地方做些许兼容,注册和替换的代码逻辑是可以复用的。而struct ipt_replace结构就是AF_INET协议族专门为了实现table替换而定义的辅助结构。

/* The argument to IPT_SO_SET_REPLACE. */
struct ipt_replace
{
	char name[IPT_TABLE_MAXNAMELEN];
	/* Which hook entry points are valid: bitmask.  You can't change this. */
	unsigned int valid_hooks;
	/* Number of entries */
	unsigned int num_entries;
	/* Total size of new entries */
	unsigned int size;
	/* Hook entry points. */
	unsigned int hook_entry[NF_INET_NUMHOOKS];
	/* Underflow points. */
	unsigned int underflow[NF_INET_NUMHOOKS];
	/* Information about old entries: */
	/* Number of counters (must be equal to current number of entries). */
	unsigned int num_counters;
	/* The old entries' counters. */
	struct xt_counters __user *counters;
	//末尾指针很重要,指向规则,对于initial_table来说就是repl后面的4条规则
	struct ipt_entry entries[0];
};

如Netfilter之table、rule、match、target数据结构中关于struct xt_table和struct xt_table_info的介绍,可以发现struct ipt_replace差不多是这两个结构的综合,而且它们共同的字段的含义确实是一致的。

回头再来看filter表对struct ipt_replace的初始化:

.repl = {
	.name = "filter",
	.valid_hooks = FILTER_VALID_HOOKS,
	//初始化时有4条规则,如上,initial_table中在repl后面定义了4条规则
	.num_entries = 4,
	//size大小就是所有规则大小的累加
	.size = sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
	//因为hook_entry[]和underflow[]分别记录的是该表在每个hook点上的第一条和
	//最后一条规则距离表起点的偏移量,并且当前表中每个hook点只有一条规则,所以
	//它们的初始化值都是相同的
	.hook_entry = {
		[NF_INET_LOCAL_IN] = 0,
		[NF_INET_FORWARD] = sizeof(struct ipt_standard),
		[NF_INET_LOCAL_OUT] = sizeof(struct ipt_standard) * 2,
	},
	.underflow = {
		[NF_INET_LOCAL_IN] = 0,
		[NF_INET_FORWARD] = sizeof(struct ipt_standard),
		[NF_INET_LOCAL_OUT] = sizeof(struct ipt_standard) * 2,
	},
	//下面是对默认的4条规则的初始化。虽然entries和term并不属于repl,但是因为它们
	//在内存上面是连续的,所以可以使用这种方式进行初始化,内核中有很多这样的小技巧

	//三个hook点各放一条无条件接受的规则。这三条规则实际上就是hook点的策略规则,
	//它们会永远位于给hook点上所有规则的末尾,当其它规则都不匹配时,交由该规则
	//觉得数据包最后的命运
	.entries = {
		IPT_STANDARD_INIT(NF_ACCEPT),	/* LOCAL_IN */
		IPT_STANDARD_INIT(NF_ACCEPT),	/* FORWARD */
		IPT_STANDARD_INIT(NF_ACCEPT),	/* LOCAL_OUT */
	},
	//这里放置了一条错误规则。个人理解:这条规则应该永远都不会被遍历到,因为在遍历
	//规则时是根据hook点遍历的,而hook点规则由hook_entry[]和underflow[]定界,永远
	//都不应该走到这里,放着这么一条应该是为了对代码异常场景提供一种保护
	.term = IPT_ERROR_INIT,			/* ERROR */
},

2.1 ipt_register_table()

表的注册并不是直接调用Netfilter框架提供的xt_register_table(),AF_INET协议族对注册过程有特殊处理,所以对注册过程用ipt_register_table()进行了封装。

struct xt_table *ipt_register_table(struct net *net, struct xt_table *table,
				    const struct ipt_replace *repl)
{
	int ret;
	struct xt_table_info *newinfo;
	//该结构就是为了复用表替换代码而虚构出来的表
	struct xt_table_info bootstrap
		= { 0, 0, 0, { 0 }, { 0 }, { } };
	void *loc_cpu_entry;
	struct xt_table *new_table;

	//按照规则大小分配struct xt_table_info结构
	newinfo = xt_alloc_table_info(repl->size);
	if (!newinfo) {
		ret = -ENOMEM;
		goto out;
	}
	//将repl末尾保存的规则拷贝到struct xt_table_info中本地CPU的指针位置
	loc_cpu_entry = newinfo->entries[raw_smp_processor_id()];
	memcpy(loc_cpu_entry, repl->entries, repl->size);
	//用指定的信息检查新table中的规则信息(newinfo指向)
	ret = translate_table(table->name, table->valid_hooks,
		  		newinfo, loc_cpu_entry, repl->size,
			    repl->num_entries, repl->hook_entry, repl->underflow);
	if (ret != 0)
		goto out_free;
	//调用Netfilter框架的tablle注册接口将filter表注册到系统中
	new_table = xt_register_table(net, table, &bootstrap, newinfo);
	if (IS_ERR(new_table)) {
		ret = PTR_ERR(new_table);
		goto out_free;
	}
	return new_table;
...
}

2.1.1 translate_table()

如注释所述,该函数的作用就是用调用者指定的信息校验并初始化newinfo,其实就是对要注册的表内容进行合法性检查。

/* Checks and translates the user-supplied table segment (held in newinfo) */
static int translate_table(const char *name, unsigned int valid_hooks,
		struct xt_table_info *newinfo, void *entry0, unsigned int size,
		unsigned int number, const unsigned int *hook_entries,
		const unsigned int *underflows)
{
	unsigned int i;
	int ret;
	//规则总的占用字节数和规则数目
	newinfo->size = size;
	newinfo->number = number;
	/* Init all hooks to impossible value. */
	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
		newinfo->hook_entry[i] = 0xFFFFFFFF;
		newinfo->underflow[i] = 0xFFFFFFFF;
	}
	duprintf("translate_table: size %u\n", newinfo->size);
	i = 0;
	//检查规则内的偏移量成员指定的是否准确
	ret = IPT_ENTRY_ITERATE(entry0, newinfo->size, check_entry_size_and_hooks,
				newinfo, entry0, entry0 + size, hook_entries, underflows, &i);
	if (ret != 0)
		return ret;
	//i记录的是检测通过的规则数目,如果和指定的数字不一致,说明有规则没有通过检查
	if (i != number) {
		duprintf("translate_table: %u not %u entries\n", i, number);
		return -EINVAL;
	}
	//在上一步的检测过程中如果规则检测pass,会对hook_entry和underflow赋值,
	//这里检查是否所有有效的HOOK点都已经指定了合理的值
	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
		/* Only hooks which are valid */
		if (!(valid_hooks & (1 << i)))
			continue;
		if (newinfo->hook_entry[i] == 0xFFFFFFFF) {
			duprintf("Invalid hook entry %u %u\n", i, hook_entries[i]);
			return -EINVAL;
		}
		if (newinfo->underflow[i] == 0xFFFFFFFF) {
			duprintf("Invalid underflow %u %u\n", i, underflows[i]);
			return -EINVAL;
		}
	}
	//检查表中的规则是否构成环路
	if (!mark_source_chains(newinfo, valid_hooks, entry0))
		return -ELOOP;
	//无论是match还是target,都有对应的check回调,这里检查每条规则的match和target
	i = 0;
	ret = IPT_ENTRY_ITERATE(entry0, newinfo->size, find_check_entry, name, size, &i);
	if (ret != 0) {
		//没有通过检查,清除所有的规则
		IPT_ENTRY_ITERATE(entry0, newinfo->size, cleanup_entry, &i);
		return ret;
	}
	//通过了所有的检查,将规则信息拷贝到其它CPU的内存中
	for_each_possible_cpu(i) {
		if (newinfo->entries[i] && newinfo->entries[i] != entry0)
			memcpy(newinfo->entries[i], entry0, newinfo->size);
	}
	return ret;
}

如上,注册过程中,会逐个检查规则信息,而遍历过程都是用宏IPT_ENTRY_ITERATE实现的,该宏的定义比较复杂,难以理解,这里直接看展开后的结果:

{
	unsigned int __i, __n;
	int __ret = 0;
	struct ipt_entry *__entry;
	//size是表中所有规则的大小,__entry->next_offset保存的是距下一条规则的偏移
	for (__i = 0, __n = 0; __i < (size); __i += __entry->next_offset, __n++) {
		//指向一条规则的起点
		__entry = (void *)(entries) + __i;
		//__n在本场景不可能会小于0
		if (__n < 0)
			continue;
		//如果回调函数fn()返回非0,结束遍历,并且返回非0值
		__ret = fn(__entry , ## args);
		if (__ret != 0)	
			break;
	}
	__ret;
}

2.1.1.1 规则大小检查:check_entry_size_and_hooks()

static int check_entry_size_and_hooks(struct ipt_entry *e,
	struct xt_table_info *newinfo, unsigned char *base, unsigned char *limit,
	const unsigned int *hook_entries, const unsigned int *underflows,
	unsigned int *i)
{
	unsigned int h;
	//边界对齐和规则长度是否超过了总的大小
	if ((unsigned long)e % __alignof__(struct ipt_entry) != 0
	    || (unsigned char *)e + sizeof(struct ipt_entry) >= limit) {
		duprintf("Bad offset %p\n", e);
		return -EINVAL;
	}
	//距下一条规则的偏移不合理,因为一条规则至少包含struct ipt_entry和struct
	//ipt_entry_target,所以next_offset一定大于这两个结构体的长度
	if (e->next_offset < sizeof(struct ipt_entry) + sizeof(struct ipt_entry_target)) {
		duprintf("checking: element %p size %u\n", e, e->next_offset);
		return -EINVAL;
	}
	//如果指定规则属于某个HOOK点的边界规则,赋值hook_entry和underflow
	for (h = 0; h < NF_INET_NUMHOOKS; h++) {
		if ((unsigned char *)e - base == hook_entries[h])
			newinfo->hook_entry[h] = hook_entries[h];
		if ((unsigned char *)e - base == underflows[h])
			newinfo->underflow[h] = underflows[h];
	}
	//规则命中的计数器清零
	e->counters = ((struct xt_counters) { 0, 0 });
	e->comefrom = 0;
	//规则检查通过,计数器累加1
	(*i)++;
	return 0;
}

2.1.1.2 校验规则:find_check_entry()

每条规则都是有0个或多个match以及1个target组成,每个match和target都有自己特有的参数,在校验规则时需要回调match和target的check_xxx()回调对规则进行校验。

static int find_check_entry(struct ipt_entry *e, const char *name, 
	unsigned int size, unsigned int *i)
{
	struct ipt_entry_target *t;
	struct xt_target *target;
	int ret;
	unsigned int j;
	//对规则中的标准匹配以及target之间的偏移量进行校验
	ret = check_entry(e, name);
	if (ret)
		return ret;
	//对规则中match进行校验,包括该match是否注册以及调用match自身check()回调
	j = 0;
	ret = IPT_MATCH_ITERATE(e, find_check_match, name, &e->ip, e->comefrom, &j);
	if (ret != 0)
		goto cleanup_matches;
	//获取规则中的target,检查该target是否存在
	t = ipt_get_target(e);
	target = try_then_request_module(xt_find_target(AF_INET, t->u.user.name,
				t->u.user.revision), "ipt_%s", t->u.user.name);
	if (IS_ERR(target) || !target) {
		duprintf("find_check_entry: `%s' not found\n", t->u.user.name);
		ret = target ? PTR_ERR(target) : -ENOENT;
		goto cleanup_matches;
	}
	//找到对应的target模块,用u.kernel.target指向该target
	t->u.kernel.target = target;
	//调用target的check()回调校验target
	ret = check_target(e, name);
	if (ret)
		goto err;
	(*i)++;
	return 0;
...
}

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