linux内核netfilter模块分析之:HOOKs点的注册及调用

-1: 为何要写这个东西?
最近在找工做,以前netfilter 这一块的代码也认真地研究过,应该每一个人都是这样的你懂 不必定你能很准确的表达出来。 故必定要化些时间把这相关的东西总结一下。
linux

0:相关文档数组

 linux 下 nf_conntrack_tuple 跟踪记录  其中能够根据内核提供的数据结构获取链接跟踪记录。网络

 iptables 中的NAT使用总结    iptable的在防火墙上面的应用。
数据结构

1:iptable中三个tables所挂接的HOOKs框架

其实这个问题很简单的运行iptables打开看看就知道,此处的hook与内核的hook是对应起来的。tcp



所以在内核中注册的5个HOOK点以下:函数

enum nf_inet_hooks {
NF_INET_PRE_ROUTING,
NF_INET_LOCAL_IN,
NF_INET_FORWARD,
NF_INET_LOCAL_OUT,
NF_INET_POST_ROUTING,

NF_INET_NUMHOOKS
};
ui

在向下看linux内核中的实现以前在看看一个数据包在进过linux内核中neitfilter的处理过程。this

其中这5个HOOK点的执行点说明以下:
数据报从进入系统,进行IP校验之后,首先通过第一个HOOK函数NF_IP_PRE_ROUTING进行处理;
而后就进入路由代码,其决定该数据报是须要转发仍是发给本机的;
若该数据报是发被本机的,则该数据通过HOOK函数NF_IP_LOCAL_IN处理之后而后传递给上层协议;
若该数据报应该被转发则它被NF_IP_FORWARD处理;
spa

通过转发的数据报通过最后一个HOOK函数NF_IP_POST_ROUTING处理之后,再传输到网络上。

本地产生的数据通过HOOK函数NF_IP_LOCAL_OUT 处理后,进行路由选择处理,而后通过NF_IP_POST_ROUTING处理后发送出去。




上面的图能够知道,一个数据包在内核中进行的hook的处理点。
2 :proc文件下的跟踪记录

上面的就是链接跟踪记录,其中记录linux系统创建的每一条链接,其中包括源IP,目的IP,源port,目的port,协议ID,其这些能够称为5元组。有关这个在linux内含中的定义是的结构体 struct nf_conn   中的变量 

   /* Connection tracking(连接跟踪)用来跟踪、记录每一个连接的信息(目前仅支持IP协议的链接跟踪)。
            每一个连接由“tuple”来惟一标识,这里的“tuple”对不一样的协议会有不一样的含义,例如对tcp,udp
                 来讲就是五元组: (源IP,源端口,目的IP, 目的端口,协议号),对ICMP协议来讲是: (源IP, 目
            的IP, id, type, code), 其中id,type与code都是icmp协议的信息。连接跟踪是防火墙实现状态检
            测的基础,不少功能都须要借助连接跟踪才能实现,例如NAT、快速转发、等等。*/

/* XXX should I move this to the tail ? - Y.K */
/* These are my tuples; original and reply */
struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX]; 此变量就保存着上面的跟踪记录,


3:hooks点的定义及注册


其中每一个不一样协议的不一样HOOK点最终都会注册到全局的nf_hooks链表变量之中:同时注册到同一个HOOK的处理函数会根据优先级的不一样的进行前后处理。

extern struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];

其中定义的协议以下:

enum {
NFPROTO_UNSPEC =  0,
NFPROTO_IPV4   =  2, //ipV4
NFPROTO_ARP    =  3, //ARP
NFPROTO_BRIDGE =  7, //brigde
NFPROTO_IPV6   = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,

};

NF_MAX_HOOKS 宏的定义已经在前面说明。HOOK点的定义。


与下面的HOOK的struct nf_hook_ops对比一下就能够看到差别:变量pf的值不一样,优先级不一样,即内核中根据不一样的协议类型能够注册不一样的挂载点进行不一样的优先级数据包的处理。


其注册使用函数为:nf_register_hooks()函数在内核中多个地方出现,由于用户能够根据本身的须要对特定的协议在特定的位置添加HOOK出现函数。


nf_register_hook()函数的实现就是:

int nf_register_hook(struct nf_hook_ops *reg)
{
	struct nf_hook_ops *elem;
	int err;

	err = mutex_lock_interruptible(&nf_hook_mutex);
	if (err < 0)
		return err;////遍历已经注册的的HOOK,OPS,将新加入的根据优先级添加到链表最后
	list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
		if (reg->priority < elem->priority)
			break;
	}
	list_add_rcu(®->list, elem->list.prev);
	mutex_unlock(&nf_hook_mutex);
	return 0;
}

上面就是HOOK点的注册函数,即根据协议类型和HOOK点注册到全局数组中nf_hooks[][]中。

4:注册的HOOK点什么时候被使用?

在linux内核中当须要使用注册的HOOK点时,使用函数:

#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)


NF_HOOK->NF_HOOK_THRESH->nf_hook_thresh->nf_hook_slow——这个是最终的执行函数。

先看看下面函数都返回值:

/* Responses from hook functions. */
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
#define NF_MAX_VERDICT NF_STOP

int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
		 struct net_device *indev,
		 struct net_device *outdev,
		 int (*okfn)(struct sk_buff *),
		 int hook_thresh)
{
	struct list_head *elem;
	unsigned int verdict;
	int ret = 0;

	/* We may already have this, but read-locks nest anyway */
	rcu_read_lock();

	elem = &nf_hooks[pf][hook];//是否是很熟悉就是上面的全局变量专门用来注册全局HOOK点的变量。
next_hook:/* 开始遍历对应的netfilter的规则,即对应的proto和hook挂载点 */
	verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,outdev, &elem, okfn, hook_thresh);
	if (verdict == NF_ACCEPT || verdict == NF_STOP) {
		ret = 1;
	} else if (verdict == NF_DROP) {
		kfree_skb(skb);
		ret = -EPERM;
	} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
		if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
			      verdict >> NF_VERDICT_BITS))
			goto next_hook;
	}
	rcu_read_unlock();
	return ret;
}


如今来看看nf_iterate()函数:

unsigned int nf_iterate(struct list_head *head,
			struct sk_buff *skb,
			unsigned int hook,
			const struct net_device *indev,
			const struct net_device *outdev,
			struct list_head **i,
			int (*okfn)(struct sk_buff *),
			int hook_thresh)
{
	unsigned int verdict;
//其中head就是全局的2维数组nf_hooks,
	/*
	 * The caller must not block between calls to this
	 * function because of risk of continuing from deleted element.
	 */
	list_for_each_continue_rcu(*i, head) {
		struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;

		if (hook_thresh > elem->priority)
			continue;

		/* Optimization: we don't need to hold module
		   reference here, since function can't sleep. --RR */
		verdict = elem->hook(hook, skb, indev, outdev, okfn);//根据协议和HOOK点执行挂在的处理函数
		if (verdict != NF_ACCEPT) {//返回结果进行判断。
#ifdef CONFIG_NETFILTER_DEBUG
			if (unlikely((verdict & NF_VERDICT_MASK)
							> NF_MAX_VERDICT)) {
				NFDEBUG("Evil return from %p(%u).\n",
					elem->hook, hook);
				continue;
			}
#endif
			if (verdict != NF_REPEAT)
				return verdict;
			*i = (*i)->prev;
		}
	}
	return NF_ACCEPT;
}

对各个返回值的解释以下:

NF_DROP:直接drop掉这个数据包;
NF_ACCEPT:数据包经过了挂载点的全部规则;
NF_STOLEN:这个还未出现,留在之后解释;
NF_QUEUE:将数据包enque到用户空间的enque handler;
NF_REPEAT:为netfilter的一个内部断定结果,须要重复该条规则的断定,直至不为NF_REPEAT;
NF_STOP:数据包经过了挂载点的全部规则。但与NF_ACCEPT不一样的一点时,当某条规则的断定结果为NF_STOP,那么能够直接返回结果NF_STOP,无需进行后面的断定了。而NF_ACCEPT须要因此的规则都为ACCEPT,才能返回NF_ACCEPT。

在数据包流经内核协议栈的整个过程当中,在内中定义的HOOK中的如:PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING会根据数据包的协议簇PF_INET到这些关键点去查找是否注册有钩子函数。若是没有,则直接返回okfn函数指针所指向的函数继续走协议栈;若是有,则调用nf_hook_slow函数,从而进入到Netfilter框架中去进一步调用已注册在该过滤点下的钩子函数,再根据其返回值来肯定是否继续执行由函数指针okfn所指向的函数

一个IP数据包的接受过程以下: