linux内核协议栈 netfilter 之 ip 层的 filter 表注册及规则的添加以及钩子函数

本文详细解析了Linux内核协议栈中Netfilter的filter表初始化、注册过程,包括iptable_filter_init()、iptable_filter_net_init()函数,以及规则的添加和删除。重点介绍了ipt_register_table()函数的核心逻辑,如xt_alloc_table_info()、translate_table()和xt_register_table(),同时讨论了iptable_filter_hook()钩子函数和ipt_do_table()的规则匹配功能。
摘要由CSDN通过智能技术生成

目录

1 filter 表初始化 iptable_filter_init()

2 filter表的注册 iptable_filter_net_init()

2.1 ipt_register_table() 入参

2.1.1 static struct xt_table packet_filter

2.1.2 struct ipt_replace repl

2.1.3 struct ipt_replace repl 初始化 ipt_alloc_initial_table()

2.2 ipt_register_table() 【核心】

2.2.1 xt_alloc_table_info()

2.2.2 translate_table()【核心】

2.2.3 xt_register_table()

3 filter表规则的添加 / 删除 do_replace()

4 filter表注册钩子函数 iptable_filter_hook()

4.1 ipt_do_table()

4.1.1 基本匹配ip_packet_match()

5 扩展match、target注册 xt_register_match() / xt_register_target()

6 小结(重要)


1 filter 表初始化 iptable_filter_init()

初始化函数即模块初始化函数 iptable_filter_init(),干了两件重要的事情:

  1. 注册filter表
  2. 注册三个钩子
//注册filter表
static int __net_init iptable_filter_net_init(struct net *net)
{
	struct ipt_replace *repl;

	repl = ipt_alloc_initial_table(&packet_filter);
	if (repl == NULL)
		return -ENOMEM;
	/* Entry 1 is the FORWARD hook */
	((struct ipt_standard *)repl->entries)[1].target.verdict =
		forward ? -NF_ACCEPT - 1 : -NF_DROP - 1;

	net->ipv4.iptable_filter =
		ipt_register_table(net, &packet_filter, repl);
	kfree(repl);
	return PTR_RET(net->ipv4.iptable_filter);
}


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;

	ret = register_pernet_subsys(&iptable_filter_net_ops);
	if (ret < 0)
		return ret;

	/* Register hooks */
   //向Netfilter框架注册三个钩子:INPUT、OUTPUT、FORWARD
	filter_ops = xt_hook_link(&packet_filter, iptable_filter_hook);
	if (IS_ERR(filter_ops)) {
		ret = PTR_ERR(filter_ops);
		unregister_pernet_subsys(&iptable_filter_net_ops);
	}

	return ret;
}

2 filter表的注册 iptable_filter_net_init()

2.1 ipt_register_table() 入参

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

2.1.1 static struct xt_table packet_filter

该结构正是Netfilter对table的定义

//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,
};

2.1.2 struct ipt_replace repl

和注册相关的核心数据结构是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];
};

《linux内核协议栈 netfilter 之 ip 层的table、rule、match、target结构分析》中关于struct xt_table和struct xt_table_info的介绍,可以发现struct ipt_replace差不多是这两个结构的综合,而且它们共同的字段的含义确实是一致的。

2.1.3 struct ipt_replace repl 初始化 ipt_alloc_initial_table()

这个接口调用 xt_alloc_initial_table() 把repl初始化成如下值(linux2.6.21老版全局初始化):

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.2 ipt_register_table() 【核心】

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

struct xt_table *ipt_register_table(struct net *net,
				    const struct xt_table *table,
				    const struct ipt_replace *repl)
{
	int ret;
	struct xt_table_info *newinfo;
	//该结构就是为了复用表替换代码而虚构出来的表
	struct xt_table_info bootstrap = {0};
	void *loc_cpu_entry;
	struct xt_table *new_table;
	//按照规则大小分配struct xt_table_info结构
	//还需要申请一块内存块用于存放所有的规则
    //即:分别申请sizeof(xt_table_info)与repl->size两个内存块。
	newinfo = xt_alloc_table_info(repl->size);
	if (!newinfo) {
		ret = -ENOMEM;
		goto out;
	}

	/* choose the copy on our node/cpu, but dont care about preemption */
	//将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(net, newinfo, loc_cpu_entry, repl);
	if (ret != 0)
		goto out_free;
		
	//将该xt_table插入到net->xt[pf].tables链表中,并将指针table->private指向newinfo
	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.2.1 xt_alloc_table_info()

该函数的作用就是申请一个xt_table_info,然后根据传递的size值,为指针数组xt_table_info.entries[num_cpu]中的每一个指针申请size大小的内存空间,用于存放规则链中的所有规则。

功能:申请一个新的xt_table_info结构大小的内存,且根据size的大小,为xt_table_info->entries[cpu]数组的每一个成员申请size大小的内存(即为每一个cpu都申请一个size大小的内存)
size:一个xt_table_info在每一个cpu中所对应的的规则数的总内存。

struct xt_table_info *xt_alloc_table_info(unsigned int size)
{
	struct xt_table_info *newinfo;
	int cpu;

	/* Pedantry: prevent them from hitting BUG() in vmalloc.c --RR */
	if ((SMP_ALIGN(size) >> PAGE_SHIFT) + 2 > totalram_pages)
		return NULL;

	newinfo = kzalloc(XT_TABLE_INFO_SZ, GFP_KERNEL);
	if (!newinfo)
		return NULL;

	newinfo->size = size;

	for_each_possible_cpu(cpu) {
		if (size <= PAGE_SIZE)
			newinfo->entries[cpu] = kmalloc_node(size,
							GFP_KERNEL,
							cpu_to_node(cpu));
		else
			newinfo->entries[cpu] = vmalloc_node(size,
							cpu_to_node(cpu));

		if (newinfo->entries[cpu] == NULL) {
			xt_free_table_info(newinfo);
			return NULL;
		}
	}

	return newinfo;
}
EXPORT_SYMBOL(xt_alloc_table_info);

2.2.2 translate_table()【核心】

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

  1. 初始化newinfo->hook_entry[]、newinfo->underflow[]
  2. 调用 xt_entry_foreach,遍历从entry0开始的所有ipt_entry,对每一个ipt_entry调用函数 check_entry_size_and_hooks(),判断其首地址边界对其是否正确、该ipt_entry的大小是否超过了最大值、当前ipt_entry相对于首个ipt_entry首地址的偏移量是否与某一个newinfo->hook_entry[]的值相等。
  3. 调用mark_source_chains() 链中的规则是否存在检查环路
  4. 调用 xt_entry_foreach,遍历从entry0开始的所有ipt_entry,对每一个ipt_entry调用函数 find_check_entry(),对该规则对应的match与target结构进行设置。
  5. 若以上3步检查均通过,则将该cpu对应的entriers指向的内存空间的内容,拷贝到其它cpu的entries指针指向的内存空间中。

说明:这里的entry0指的是repl->entries初始化值

/* Checks and translates the user-supplied table segment (held in
   newinfo) */
static int
translate_table(struct net *net, struct xt_table_info *newinfo, void *entry0,
                const struct ipt_replace *repl)
{
	struct ipt_entry *iter;
	unsigned int i;
	int ret = 0;
    //规则总的占用字节数和规则数目
	newinfo->size = repl->size;
	newinfo->number = repl->num_entries;

	/* 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;
	//1.检查规则内的偏移量成员指定的是否准确
	/* Walk through entries, checking offsets. */
	xt_entry_foreach(iter, entry0, newinfo->size) {
		ret = check_entry_size_and_hooks(iter, newinfo, entry0,
						 entry0 + repl->size,
						 repl->hook_entry,
						 repl->underflow,
						 repl->valid_hooks);
		if (ret != 0)
			return ret;
		++i;
		if (strcmp(ipt_get_target(iter)->u.user.name,
		    XT_ERROR_TARGET) == 0)
			++newinfo->stacksize;
	}

	if (i != repl->num_entries) {
		duprintf("translate_table: %u not %u entries\n",
			 i, repl->num_entries);
		return -EINVAL;
	}
	//在上一步的检测过程中如果规则检测pass,会对hook_entry和underflow赋值,
	//这里检查是否所有有效的HOOK点都已经指定了合理的值
	/* Check hooks all assigned */
	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
		/* Only hooks which are valid */
		if (!(repl->valid_hooks & (1 << i)))
			continue;
		if (newinfo->hook_entry[i] == 0xFFFFFFFF) {
			duprintf("Invalid hook entry %u %u\n",
				 i, repl->hook_entry[i]);
			return -EINVAL;
		}
		if (newinfo->underflow[i] == 0xFFFFFFFF) {
			duprintf("Invalid underflow %u %u\n",
				 i, repl->underflow[i]);
			return -EINVAL;
		}
	}
	
	//2.检查表中的规则是否构成环路
	if (!mark_source_chains(newinfo, repl->valid_hooks, entry0))
		return -ELOOP;
	
	//3.无论是match还是target,都有对应的check回调,这里检查每条规则的match和target
	/* Finally, each sanity check must pass */
	i = 0;
	xt_entry_foreach(iter, entry0, newinfo->size) {
		ret = find_check_entry(iter, net, repl->name, repl->size);
		if (ret != 0)
			break;
		++i;
	}
	
	if (ret != 0) {
		xt_entry_foreach(iter, entry0, newinfo->size) {
			if (i-- == 0)
				break;
			cleanup_entry(iter, net);
		}
		return ret;
	}
	//4.通过了所有的检查,将规则信息拷贝到其它CPU的内存中
	/* And one copy for every other CPU */
	for_each_possible_cpu(i) {
		if (newinfo->entries[i] && newinfo->entries[i] != entry0)
			memcpy(newinfo->entries[i], entry0, newinfo->size);
	}

	return ret;
}

2.2.3 xt_register_table()

该函数实现xt_table插入到xt[table->af].tables链表,并更新xt_table->private指针,具体实施核心步骤如下:

  1. 获取xt[table->af].mutex信号量
  2. 调用list_named_find,查找新的xt_table是否已存在xt[table->af].tables链表中,若存在,则返回存在标志;若不存在,则执行3
  3. 调用xt_replace_table,替换xt_table中的private
  4. 更新新的xt_table->private->initial_entries的值
  5. 将该xt_table插入到xt[table->af].tables链表中
struct xt_table *xt_register_table(struct net *net,
				   const struct xt_table *input_table,
				   struct xt_table_info *bootstrap,
				   struct xt_table_info *newinfo)
{
	int ret;
	struct xt_table_info *private;
	struct xt_table *t, *table;

	/* Don't add one object to multiple lists. */
	//将参数指定的table拷贝一份
	table = kmemdup(input_table, sizeof(struct xt_table), GFP_KERNEL);
	if (!table) {
		ret = -ENOMEM;
		goto out;
	}
	//如上所述,全局数组xt[table->af]保存的该协议族的所有match和target,
	//但是这里持锁后访问了net->xt.tables[table->af]变量,该变量保存的是
	//该协议族所有的table。这把锁的定义位置并不合适,其本意是要保护table、
	//match、target,但是table的定义却在net中(更高版本已将match和target
	//也移到了net中)
	ret = mutex_lock_interruptible(&xt[table->af].mutex);
	if (ret != 0)
		goto out_free;
    
	//检查该协议族的已定义table中是否已有指定名字的table,可见表名字需要协议族内部唯一
	/* Don't autoload: we'd eat our tail... */
	list_for_each_entry(t, &net->xt.tables[table->af], list) {
		if (strcmp(t->name, table->name) == 0) {
			ret = -EEXIST;
			goto unlock;
		}
	}

	/* Simplifies replace_table code. */
	table->private = bootstrap;
    //用newinfo替换table中原来的private指针
	if (!xt_replace_table(table, 0, newinfo, &ret))
		goto unlock;

	private = table->private;
	pr_debug("table->private->number = %u\n", private->number);

	/* save number of initial entries */
	private->initial_entries = private->number;
	
	//将table加入到该协议族的table集合中,注册完毕
	list_add(&table->list, &net->xt.tables[table->af]);
	mutex_unlock(&xt[table->af].mutex);
	return table;

 unlock:
	mutex_unlock(&xt[table->af].mutex);
out_free:
	kfree(table);
out:
	return ERR_PTR(ret);
}

3 filter表规则的添加 / 删除 do_replace()

当用户层通过iptables -A添加一条规则或者通过iptables -D删除一条规则时,最终都会调用函数do_replace函数实现新的规则的添加或者规则的删除(其实就是替换xt_table->private,即需要创建一个新的xt_table_info和一个新的内存块,用于存放新的所有的规则)。

do_replace的定义如下,主要就是拷贝用户侧传递的ipt_replace值(注册新的xt_table时,也是传递ipt_replace的变量);然后调用函数xt_alloc_table_info申请内存空间;接着调用函数translate_table对新申请的内存空间中的每一个规则进行设置操作;最后调用xt_replace_table替换xt_table->private。

与表的注册函数相比,少了将xt_table插入到 net->xt.tables[table->af] 链表的步骤,其他的与表注册大致是相同的。

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;
}

4 filter表注册钩子函数 iptable_filter_hook()

通过上面的分析我们知道,我们通过iptables -A操作添加的规则,都会保存在一个xt_table->private->entries[]中,所以当数据到来后,协议栈执行NF_HOOK操作时,肯定需要遍历xt_table->private->entries中的规则,找到一个匹配的规则,然后对进来的数据包执行该规则的target操作。从xt_table的通用性,我们可以猜到,肯定会有一个通用的表规则匹配函数接口,被filter、nat、mangle表的hook回调函数调用。那这个函数是谁呢?这个函数就是iptable_filter_hook()。这里可以回顾一下在《linux内核协议栈 netfilter 之 ip 层的 hook 注册概述以及 hook 的调用场景》中介绍到filter表的三个钩子注册是通过调用xt_hook_link()接口进行批量注册,且三个钩子的钩子函数均为 iptable_filter_hook()。

static unsigned int
iptable_filter_hook(unsigned int hook, struct sk_buff *skb,
		    const struct net_device *in, const struct net_device *out,
		    int (*okfn)(struct sk_buff *))
{
	const struct net *net;

	if (hook == NF_INET_LOCAL_OUT &&
	    (skb->len < sizeof(struct iphdr) ||
	     ip_hdrlen(skb) < sizeof(struct iphdr)))
		/* root is playing with raw sockets. */
		return NF_ACCEPT;

	net = dev_net((in != NULL) ? in : out);
	return ipt_do_table(skb, hook, in, out, net->ipv4.iptable_filter);
}

4.1 ipt_do_table()

该函数的功能为遍历xt_table表中相应默认规则链里的所有规则,找到一个匹配的规则,并返回target结果。

/* Returns one of the generic firewall policies, like NF_ACCEPT. */
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)
{
	static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
	const struct iphdr *ip;
	/* Initializing verdict to NF_DROP keeps gcc happy. */
	unsigned int verdict = NF_DROP;
	const char *indev, *outdev;
	const void *table_base;
	struct ipt_entry *e, **jumpstack;
	unsigned int *stackptr, origptr, cpu;
	const struct xt_table_info *private;
	struct xt_action_param acpar;
	unsigned int addend;

	/* Initialization */
	ip = ip_hdr(skb);
	indev = in ? in->name : nulldevname;
	outdev = out ? out->name : nulldevname;
	/* We handle fragments by dealing with the first fragment as
	 * if it was a normal packet.  All other fragments are treated
	 * normally, except that they will NEVER match rules that ask
	 * things we don't know, ie. tcp syn flag or ports).  If the
	 * rule is also a fragment-specific rule, non-fragments won't
	 * match it. */
	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]);

	pr_debug("Entering %s(hook %u); sp at %u (UF %p)\n",
		 table->name, hook, origptr,
		 get_entry(table_base, private->underflow[hook]));

	do {
		const struct xt_entry_target *t;
		const struct xt_entry_match *ematch;

		IP_NF_ASSERT(e);
		
		//ip_packet_match()对skb进行基本条件匹配,如果ip地址、网卡	
		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()回调函数,这里注册是icmp_match()
			if (!acpar.match->match(skb, &acpar))
				goto no_match;
		}
		
		//所有匹配都已命中,累加计数器
		ADD_COUNTER(e->counters, skb->len, 1);
		
		//获取本条规则的target
		t = ipt_get_target(e);
		IP_NF_ASSERT(t->u.kernel.target);

#if defined(CONFIG_NETFILTER_XT_TARGET_TRACE) || \
    defined(CONFIG_NETFILTER_XT_TARGET_TRACE_MODULE)
		/* The packet is traced: log it */
		if (unlikely(skb->nf_trace))
			trace_packet(skb, hook, in, out,
				     table->name, private, e);
#endif
		/* Standard target? */
		if (!t->u.kernel.target->target) {
			int v;
			//xt_target中的target回调函数指针为空表示是标准target
			v = ((struct xt_standard_target *)t)->verdict;
			if (v < 0) {
				/* Pop from stack? */
				
				//verdict小于0,并且不是 XT_RETURN,说明属于ACCEPT或者DROP,
				//这已经是一种判决结果了,结束整个表在该HOOK点的处理过程
				if (v != XT_RETURN) {
					verdict = (unsigned int)(-v) - 1;
					break;
				}
				if (*stackptr <= origptr) {
					//verdict是 XT_RETURN ,说明是自定义链的返回。
					//这里,指针back和comefrom实现了一种类似于栈的操作,back为栈顶元素
					//栈中的每个元素用规则中的comefrom指向
					e = get_entry(table_base,
					    private->underflow[hook]);
					pr_debug("Underflow (this is normal) "
						 "to %p\n", e);
				} else {
					e = jumpstack[--*stackptr];
					pr_debug("Pulled %p out from pos %u\n",
						 e, *stackptr);
					e = ipt_next_entry(e);
				}
				continue;
			}
			//target是自定义链,back入栈,然后跳转到自定义链的规则入口
			if (table_base + v != ipt_next_entry(e) &&
			    !(e->ip.flags & IPT_F_GOTO)) {
				if (*stackptr >= private->stacksize) {
					verdict = NF_DROP;
					break;
				}
				jumpstack[(*stackptr)++] = e;
				pr_debug("Pushed %p into pos %u\n",
					 e, *stackptr - 1);
			}
			//对于自定义链,v大于0,保存的是该自定义链距离table中第一条规则的偏移
			e = get_entry(table_base, v);
			continue;
		}

		acpar.target   = t->u.kernel.target;
		acpar.targinfo = t->data;
		
		//这是一个扩展target,直接调用其target()回调
		verdict = t->u.kernel.target->target(skb, &acpar);
		/* Target might have changed stuff. */
		ip = ip_hdr(skb);
		
		//如果target的返回结果是 XT_CONTINUE,那么会继续遍历下一条,
		//否则结束整个table的变量
		if (verdict == XT_CONTINUE)
			e = ipt_next_entry(e);
		else
			/* Verdict */
			break;
	//如果中间有match设置了hotdrop为true,那么可以提前结束遍历		
	} while (!acpar.hotdrop);
	pr_debug("Exiting %s; resetting sp from %u to %u\n",
		 __func__, *stackptr, origptr);
	*stackptr = origptr;
 	xt_write_recseq_end(addend);
 	local_bh_enable();

#ifdef DEBUG_ALLOW_ALL
	return NF_ACCEPT;
#else
	//hotdrop设置为true,会最终导致数据包被丢弃
	if (acpar.hotdrop)
		return NF_DROP;
	else return verdict;
#endif
}

4.1.1 基本匹配ip_packet_match()

该函数根据skb头部和struct ipt_entry中的struct ipt_ip结构进行比较,基本意思就是逐个比较。具体是通过自定义的宏FWINV,巧妙的处理了ip地址匹配、协议号匹配、接口名称匹配与取反匹配双重判断后的匹配结果。这个宏设计的很巧妙。

/* Returns whether matches rule or not. */
/* Performance critical - called for every packet */
static inline bool
ip_packet_match(const struct iphdr *ip,
		const char *indev,
		const char *outdev,
		const struct ipt_ip *ipinfo,
		int isfrag)
{
	unsigned long ret;

#define FWINV(bool, invflg) ((bool) ^ !!(ipinfo->invflags & (invflg)))

	/*
	下面这句话的意思是:
	当到达分组的src ip地址经过掩码处理后与规则的src ip不等时,
	且没有对src ip进行取反操作后,说明match不匹配,返回0
	当到达分组的src ip地址经过掩码处理后与规则的src相等时,
	其有对src ip地址进行取反操作后,说明match不匹配,返回0

	当到达分组的dst ip地址经过掩码处理后与规则的dst ip不等时,
	且没有对dst ip进行取反操作后,说明match不匹配,返回0
	当到达分组的src ip地址经过掩码处理后与规则的dst ip相等时,
	其有对dst ip地址进行取反操作后,说明match不匹配,返回0
	*/
	if (FWINV((ip->saddr&ipinfo->smsk.s_addr) != ipinfo->src.s_addr,
		  IPT_INV_SRCIP) ||
	    FWINV((ip->daddr&ipinfo->dmsk.s_addr) != ipinfo->dst.s_addr,
		  IPT_INV_DSTIP)) {
		dprintf("Source or dest mismatch.\n");

		dprintf("SRC: %pI4. Mask: %pI4. Target: %pI4.%s\n",
			&ip->saddr, &ipinfo->smsk.s_addr, &ipinfo->src.s_addr,
			ipinfo->invflags & IPT_INV_SRCIP ? " (INV)" : "");
		dprintf("DST: %pI4 Mask: %pI4 Target: %pI4.%s\n",
			&ip->daddr, &ipinfo->dmsk.s_addr, &ipinfo->dst.s_addr,
			ipinfo->invflags & IPT_INV_DSTIP ? " (INV)" : "");
		return false;
	}

	ret = ifname_compare_aligned(indev, ipinfo->iniface, ipinfo->iniface_mask);

	if (FWINV(ret != 0, IPT_INV_VIA_IN)) {
		dprintf("VIA in mismatch (%s vs %s).%s\n",
			indev, ipinfo->iniface,
			ipinfo->invflags&IPT_INV_VIA_IN ?" (INV)":"");
		return false;
	}

	ret = ifname_compare_aligned(outdev, ipinfo->outiface, ipinfo->outiface_mask);

	if (FWINV(ret != 0, IPT_INV_VIA_OUT)) {
		dprintf("VIA out mismatch (%s vs %s).%s\n",
			outdev, ipinfo->outiface,
			ipinfo->invflags&IPT_INV_VIA_OUT ?" (INV)":"");
		return false;
	}

	/* Check specific protocol */
	if (ipinfo->proto &&
	    FWINV(ip->protocol != ipinfo->proto, IPT_INV_PROTO)) {
		dprintf("Packet protocol %hi does not match %hi.%s\n",
			ip->protocol, ipinfo->proto,
			ipinfo->invflags&IPT_INV_PROTO ? " (INV)":"");
		return false;
	}

	/* If we have a fragment rule but the packet is not a fragment
	 * then we return zero */
	if (FWINV((ipinfo->flags&IPT_F_FRAG) && !isfrag, IPT_INV_FRAG)) {
		dprintf("Fragment rule but not fragment.%s\n",
			ipinfo->invflags & IPT_INV_FRAG ? " (INV)" : "");
		return false;
	}

	return true;
}

5 扩展match、target注册 xt_register_match() / xt_register_target()



static struct xt_match ipt_builtin_mt[] __read_mostly = {
	{
		.name       = "icmp",
		.match      = icmp_match,
		.matchsize  = sizeof(struct ipt_icmp),
		.checkentry = icmp_checkentry,
		.proto      = IPPROTO_ICMP,
		.family     = NFPROTO_IPV4,
	},
};

static struct xt_target ipt_builtin_tg[] __read_mostly = {
	{
		.name             = XT_STANDARD_TARGET,
		.targetsize       = sizeof(int),
		.family           = NFPROTO_IPV4,
#ifdef CONFIG_COMPAT
		.compatsize       = sizeof(compat_int_t),
		.compat_from_user = compat_standard_from_user,
		.compat_to_user   = compat_standard_to_user,
#endif
	},
	{
		.name             = XT_ERROR_TARGET,
		.target           = ipt_error,
		.targetsize       = XT_FUNCTION_MAXNAMELEN,
		.family           = NFPROTO_IPV4,
	},
};


static int __init ip_tables_init(void)
{
	int ret;

	ret = register_pernet_subsys(&ip_tables_net_ops);
	if (ret < 0)
		goto err1;

	/* No one else will be downing sem now, so we won't sleep */
	ret = xt_register_targets(ipt_builtin_tg, ARRAY_SIZE(ipt_builtin_tg));
	if (ret < 0)
		goto err2;
	ret = xt_register_matches(ipt_builtin_mt, ARRAY_SIZE(ipt_builtin_mt));
	if (ret < 0)
		goto err4;

	/* Register setsockopt */
	ret = nf_register_sockopt(&ipt_sockopts);
	if (ret < 0)
		goto err5;

	pr_info("(C) 2000-2006 Netfilter Core Team\n");
	return 0;

err5:
	xt_unregister_matches(ipt_builtin_mt, ARRAY_SIZE(ipt_builtin_mt));
err4:
	xt_unregister_targets(ipt_builtin_tg, ARRAY_SIZE(ipt_builtin_tg));
err2:
	unregister_pernet_subsys(&ip_tables_net_ops);
err1:
	return ret;
}

6 小结(重要)

《linux内核协议栈 netfilter 之 ip 层的table、rule、match、target结构分析》中分析数据结构时,我们应该有注意到一个细节,即xt_table_info->entries[] 是指向每一个CPU对应于该 xt_table 的所有规则的首地址,而一个表里面所有的规则是按照连续内存存取的。由于表里的规则是存储在连续内存里,那么将一个新的规则插入到一个规则链中就变得比较麻烦,所以现有的做法基本上是重新申请一块内存,根据应用层传递过来的新的规则分布,对新的内存进行规则的赋值等操作,最后将原有规则内存释放掉。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值