如果我们想加入自己的代码,便要用nf_register_hook函数,其函数原型为:
int nf_register_hook(struct nf_hook_ops *reg)
struct nf_hook_ops://结构
struct nf_hook_ops
{
struct list_head list;
/* User fills in from here down. */
nf_hookfn *hook;
int pf;
int hooknum;
/* Hooks are ordered in ascending priority. */
int priority;
};
hook是提供的处理函数,也就是我们的主要工作,其原型为:
unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *));
437 return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
438 ip_rcv_finish);
我自己做实验,在nf_register_hooks将自己定义的、包含了hook函数,hook点的nf_hook_ops结构体注册到系统中后,一旦有符合条件的包出现,系统都会打印出相应的语句。
但是在看源码的时候,经常看到有如下的调用方法:
return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
很不理解。难道 ip_rcv_finish 没有用 nf_register_hooks 注册进系统吗?
如果注册了,应该就直接到了hook点,该函数就会来检查一次数据包,不用再用NF_HOOK再调用一次啊?
#else /* !CONFIG_NETFILTER */
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)
这样就相当于直接调用 ip_rcv_finish 了
二 NF_HOOK的实现原理
在网卡收到包之后交由ip层处理的时候,就交给了ip_recv函数
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
在做了基本的头校验等工作后,就到了我们的重点NF_HOOK钩子函数,此时还未作路由等路处理
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
在做完PRE_ROUTING的筛选后,会执行ip_recv_finish函数,继续执行路由等处理,如果是本地的就会交给更高层的协议进行处理,如果不是交由本地的就执行FORWARD
}
通过NF_HOOK的宏定义可以看到,NF_HOOK主要是调用nf_hook_slow,那么,nf_hook_slow主要做了哪些东西呢?
nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh)
{
elem = &nf_hooks[pf][hook];
next_hook:
verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
outdev, &elem, okfn, hook_thresh);
}
nf_interate 的作用就是递归调用注册的钩子函数,
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)
{
list_for_each_continue_rcu(*i, head) {
struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
if (hook_thresh > elem->priority)
continue;
repeat:
verdict = elem->hook(hook, skb, indev, outdev, okfn);
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;
goto repeat;
}
}
return NF_ACCEPT;
}
Nefilter细节实现分析
Netfilter定义了协议栈中的检查点和检查点上引用的数据结构以及对这些数据
结引用的过程。首先看看在检查点上引用的数据结构,
图中ns_hook_ops就是在检查点上引用的结构。每个协议栈预先定义的8个链表数
组用于保存这些结构,这些链表与协议栈中的检查点一一对应。在实际的应用
中,这8个链表并不一定都被使用,比如在IPV4中,只定义了5个检查点,分别对应前5个链表。
nf_hook_ops结构定义在netfilter.h中,如下:
44 struct nf_hook_ops
45 {
46 struct list_head list;
47
48 /* User fills in from here down。 */
49 nf_hookfn hook; /* 函数指针 */
50 int pf; /* 结构对应的协议栈号*/
51 int hooknum; /* 结构对应的检查点号*/
52 /* Hooks are ordered in ascending priority。 */
53 int priority;
54 };
其中有必要先说明一下 其中的一些数据结构,
struct list_head的结构在lish.h中定义,并且在其中定义了一个比较重要的宏:
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)。
在以后的分析中我们将看到其重要性。
18 struct list_head {
19 struct list_head *next, *prev;
20 };
21
22 #define LIST_HEAD_INIT(name) { &(name), &(name) }
23
24 #define LIST_HEAD(name) \
25 struct list_head name = LIST_HEAD_INIT(name)
然后在nf_hook_ops结构中定义了一个指针函数hook其具体说明将在后面的分析中提及。
这个结构由函数nf_register_hook注册到list_head链表上,链表的索引由结构
中hooknum指定。同一链表上的结构按优先值由小到大排列。在检查点上引用这
些结构时,以它们在链表上的先后顺序引用。其实在这里有一个重要的数组结
构:就是在netfilter.c中定义的全局数组,
47 struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];
说的清楚点就是:比如要在IPV4协议栈上的某一个检查点上注册一个
nf_hook_opts的话,那么其pf即为: PF_INET ,hooknum就为检查点,如
NF_IP_PRE_ROUTING,那么它在这个二维数组中的位置就为nf_hooks[PF_INET]
[NF_IP_PRE_ROUTING]。
那么nf_register_hook是如何工作的呢?
60 int nf_register_hook(struct nf_hook_ops *reg)
61 {
62 struct list_head *i;
63
64 br_write_lock_bh(BR_NETPROTO_LOCK);
65 for (i = nf_hooks[reg->pf][reg->hooknum].next;
66 i != &nf_hooks[reg->pf][reg->hooknum];
67 i = i->next) {
68 if (reg->priority < ((struct nf_hook_ops *)i)->priority)
69 break;
70 }
71 list_add(®->list, i->prev);
72 br_write_unlock_bh(BR_NETPROTO_LOCK);
73 return 0;
74 }
用通过把构造好的nf_hook_ops传给函数nf_register_hook,其通过一个循环把这
个结构根据其自身的优先级(priority)插入在nf_hooks[pf][hooknum]链表的相
应的地方。重要的操作就是for循环体中的内容,如果reg->priority的值小于链
表中当前的nf_hook_ops中的优先级时,就立刻跳出循环,执行后来的插入操作.
此处可以看出这个链表上的所有nf_hook_ops结构都是按结构中的priority的值
由小到大的排列,这就为以后的操作提供了方便.插入工作由函数list_add完成,
其实就是一个链表的插入过程。那么当nf_hooks数组链上的结构都已经注册好
了,那下一步该做些什么呢,而且上面提到了检查点,下面看检查点的定义。
我们先看看netfilter_ipv4.h中定义的都有哪些检查点。
39 /* IP Hooks */
40 /* After promisc drops, checksum checks。 */
41 #define NF_IP_PRE_ROUTING 0
42 /* If the packet is destined for this box。 */
43 #define NF_IP_LOCAL_IN 1
44 /* If the packet is destined for another interface。 */
45 #define NF_IP_FORWARD 2
46 /* Packets coming from a local process。 */
47 #define NF_IP_LOCAL_OUT 3
48 /* Packets about to hit the wire。 */
49 #define NF_IP_POST_ROUTING 4
50 #define NF_IP_NUMHOOKS 5 //检查点的数目
实质上检查点的意思就是说当有一个数据包经过此处的话,那么这个数据包先必
须在这些地方做一些检查,如果此检查点觉得没有问题,那么你就继续下面的行
程,如果有问题,将被丢弃,或一些其它的处理。这些检查点是由Netfilter框架加
在协议栈上的,如果在编译内核时,没有定义CONFIG_NETFILTER的话,那么在这些
地方将不对这些数据包做任何处理处理,直接交由后面的部分处理。如果定义了
,那么在这些检查点上就要做一些事情了。Netfilter框架使用了一个巧妙的技
巧,在最大程度上不影响本身的协议。使用NF_HOOK宏来定义这些检查点上的处理
过程,先来看看NF_HOOK宏:
122 #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
123 (list_empty(&nf_hooks[(pf)][(hook)]) \
124 ? (okfn)(skb) \
125 : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))
这里又用到了nf_hooks这个数组,首先看到当在nf_hooks[pf][hook]这个链表为
空时,即没有在这个链上用nf_register_hook函数注册任何一个nf_hook_ops结构
时,这个宏就会直接调用由okfn指向的函数做处理,这里先不管okfn指向了什么
地方,也就相当于在这里没有做任何的检查。另外一种情况就是注册了一些nf_hook_ops在这个链表上,那么就会调用下面的这各函数做一些处理,
128 int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb,
129 struct net_device *indev, struct net_device *outdev,
130 int (*okfn)(struct sk_buff *));
再看其定义:
449 int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb,
450 struct net_device *indev,
451 struct net_device *outdev,
452 int (*okfn)(struct sk_buff *))
453 {
454 struct list_head *elem;
455 unsigned int verdict;
456 int ret = 0;
457
458 /*This stopgap cannot be removed until all the hooks are audited。*/
459 if (skb_is_nonlinear(skb) && skb_linearize(skb, GFP_ATOMIC) != 0) {
460 kfree_skb(skb);
461 return -ENOMEM;
462 }
463 if (skb->ip_summed == CHECKSUM_HW) {
464 if (outdev == NULL) {
465 skb->ip_summed = CHECKSUM_NONE;
466 } else {
467 skb_checksum_help(skb);
468 }
469 }
470
471 /* We may already have this, but read-locks nest anyway */
472 br_read_lock_bh(BR_NETPROTO_LOCK);
473
474 #ifdef CONFIG_NETFILTER_DEBUG
475 if (skb->nf_debug & (1 << hook)) {
476 printk(“nf_hook: hook %i already set。\n”, hook);
477 nf_dump_skb(pf, skb);
478 }
479 skb->nf_debug |= (1 << hook);
480 #endif
481
482 elem = &nf_hooks[pf][hook];
483 verdict = nf_iterate(&nf_hooks[pf][hook], &skb, hook, indev,
484 outdev, &elem, okfn);
485 if (verdict == NF_QUEUE) {
486 NFDEBUG(“nf_hook: Verdict = QUEUE。\n”);
487 nf_queue(skb, elem, pf, hook, indev, outdev, okfn);
488 }
489
490 switch (verdict) {
491 case NF_ACCEPT:
492 ret = okfn(skb);
493 break;
494
495 case NF_DROP:
496 kfree_skb(skb);
497 ret = -EPERM;
498 break;
499 }
500
501 br_read_unlock_bh(BR_NETPROTO_LOCK);
502 return ret;
503 }
在最后明显的地方我们就看到了一些重点,看到了一些关键字:NF_ACCEPT,
NF_DROP,从字面的意思很好理解,并且这些值是由一个叫nf_iterate函数的返回
值,这个函数的执行过程也很简单,如下:
339 static unsigned int nf_iterate(struct list_head *head,
340 struct sk_buff **skb,
341 int hook,
342 const struct net_device *indev,
343 const struct net_device *outdev,
344 struct list_head **i,
345 int (*okfn)(struct sk_buff *))
346 {
347 for (*i = (*i)->next; *i != head; *i = (*i)->next) {
348 struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
349 switch (elem->hook(hook, skb, indev, outdev, okfn)){
350 case NF_QUEUE:
351 return NF_QUEUE;
352
353 case NF_STOLEN:
354 return NF_STOLEN;
355
356 case NF_DROP:
357 return NF_DROP;
358
359 case NF_REPEAT:
360 *i = (*i)->prev;
361 break;
362
363 #ifdef CONFIG_NETFILTER_DEBUG
364 case NF_ACCEPT:
365 break;
366
367 default:
368 NFDEBUG(“Evil return from %p(%u)。\n”,
369 elem->hook, hook);
370 #endif
371 }
372 }
373 return NF_ACCEPT;
374 }
这里看到了一个for循环,其做的就是把这个协议栈上的这个HOOK点
上的所有定义的nf_hook_ops结构遍历一边,并且调用了这个结构中的函数hook指
针,它正是指向我们定义的函数,然后根据这个函数的返回值做进一步的处理,并
且这里有更多的返回值,而在nf_hook_slow函数中只处理了NF_DROP,NF_ACCEPT,
NF_QUEUE。并且看到对不同的返回值,做了一些不同的处理,如果我们定义的函数
返回的是NF_QUEUE,NF_STOLEN,NF_DROP,就立刻返回,如果是NF_REPEAT 只是 改
变一下当前循环中的指针,然后将从新调用我们的注册的处理函数。然而只有所
有这个链表上的注册函数都返回NF_ACCEPT时,这个函数才会返回NF_ACCEPT。
14 /* Responses from hook functions。 */
15 #define NF_DROP 0
16 #define NF_ACCEPT 1
17 #define NF_STOLEN 2
18 #define NF_QUEUE 3
19 #define NF_REPEAT 4
20 #define NF_MAX_VERDICT NF_REPEAT
如果nf_hook_ops结构中我们定义的函数中只要有一个返回NF_DROP或NF_STOLEN
或NF_QUEUE,函数nf_iterate将立即将这个值返回给nf_hook_slow函数,而只有
当我们这个链表上所有nf_hook_ops结构中的hook函数都返回NF_ACCEPT时,
nf_iterate函数才返回一个NF_ACCEPT给nf_hook_slow函数。
这里我们注意到一个问题,调用我们注册的函数时,是按顺序来取的,这里就是体
现nf_hook_ops结构中优先级的重要性的时候了,当在list_head链表中注册我们
的结构时,是根据其中的优先级来确定位置的,优先级高的处在链表的前面。也就
是说当优先级高的函数返回NF_DROP或NF_STOLEN或NF_QUEUE时,那么后面的函数
将不用做任何处理。只有返回NF_ACCEPT时,低优先级的函数才能做进一步的表
决。
在ns_hook_slow中判断nf_iterate的返回值,如果是NF_ACCEPT,则允许数据包
通过,并将数据包传递给协议栈中的下一个函数;如果是NF_DROP,则释放数据
包,协议栈流程中断;如果是NF_STOLEN,同样中断协议栈的流程,但是没有释
放这个数据包;如果是NF_QUEUE,则将这个包发送到用户空间处理,同时中断协
议栈的流程。现在我们来看看检查点在协议栈中的位置,下图是IPV4中的检查
点:
图2 IPV4中的检查点
图中检查点的名称如下:
检查点编号 检查点名称 检查点所在文件名
1 NF_IP_PRE_ROUTING ip_input.c
2 NF_IP_LOCAL_IN ip_input.c
3 NF_IP_FORWARD ip_forward.c
4 NF_IP_POST_ROUTING ip_output.c
5 NF_IP_LOCAL_OUT ip_output.c
图中,ROUTE(1)处对收到的包做路由查找并判断这个包是需要转发的包还是发往
本机上层的包,ROUTE(2)处查找发出包的路由。NF_IP_PRE_ROUTING处对所有传
入IP层的数据包进行检查,在这之前,有关数据包的版本、长度、校验和等正确
性检查已经完成。NF_IP_LOCAL_IN对发往本机上层的数据包进行检查。
NF_IP_FORWARD处检查需要转发的数据包。NF_IP_POST_ROUTING处对所有向链路
层传递的数据包进行检查,注意在此处数据包的路由已经确定。
NF_IP_LOCAL_OUT对本机发出的包进行检查,此处的路由还没有确定,所以可以
做目的地址转换。实现某个网络安全功能可能需要在多个检查点上注册相应的结
构,在后面的分析中我们可以看到具体的例子。
检查点的作用已经明确,那么下面就来看每个检查点的具体位置。正如上表中显
示的一样,各个检查点分别在上面列出的文件中被HOOK。
首先看NF_IP_PRE_ROUTING检查点,它在ip_input.c中出现的位置只有一处:
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt) 函数中:
376 /*
377 * Main IP Receive routine。
378 */
379 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
380 {
381 struct iphdr *iph;
382
383 /* When the interface is in promisc。 mode, drop all the crap
384 * that it receives, do not try to analyse it。
385 */
386 if (skb->pkt_type == PACKET_OTHERHOST)
387 goto drop;
388
389 IP_INC_STATS_BH(IpInReceives);
390
391 if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
392 goto out;
393
394 if (!pskb_may_pull(skb, sizeof(struct iphdr)))
395 goto inhdr_error;
396
397 iph = skb->nh。iph;
398
399 /*
400 * RFC1122: 3。1。2。2 MUST silently discard any IP frame that
? fails the checksum。
401 *
402 * Is the datagram acceptable?
403 *
404 * 1。 Length at least the size of an ip header
405 * 2。 Version of 4
406 * 3。 Checksums correctly。 [Speed optimisation
? for later, skip loopback checksums]
407 * 4。 Doesn’t have a bogus length
408 */
409
410 if (iph->ihl < 5 || iph->version != 4)
411 goto inhdr_error;
412
413 if (!pskb_may_pull(skb, iph->ihl*4))
414 goto inhdr_error;
415
416 iph = skb->nh。iph;
417
418 if (ip_fast_csum((u8 *)iph, iph->ihl) != 0)
419 goto inhdr_error;
420
421 {
422 __u32 len = ntohs(iph->tot_len);
423 if (skb->len < len || len < (iph->ihl<<2))
424 goto inhdr_error;
425
426 /*Our transport medium may have padded the buffer out.Now we
427 * know it is IP we can trim to the true length of the frame。
428 * Note this now means skb->len holds ntohs(iph->tot_len)。
429 */
430 if (skb->len > len) {
431 __pskb_trim(skb, len);
432 if (skb->ip_summed == CHECKSUM_HW)
433 skb->ip_summed = CHECKSUM_NONE;
434 }
435 }
436
437 return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
438 ip_rcv_finish);
439
440 inhdr_error:
441 IP_INC_STATS_BH(IpInHdrErrors);
442 drop:
443 kfree_skb(skb);
444 out:
445 return NET_RX_DROP;
446 }
在437行中我们看到这里调用了前面说明的NF_HOOK宏, 传给它的参数分别是:
PF_INET:IPV4协议栈
NF_IP_PRE_ROUTING: 刚刚讨论的检查点。
skb: sk_buff结构
dev: 输入接口
ip_rcv_finish: okfn指向的后续处理函数。
在调用NF_HOOK之前我们还看到了在这个函数里对这个skb做了一些基本的检查:
如版本,长度,校验和等等。另外的4各HOOK点也在相应的文件里做了相同的处理
,但有一点区别的是,可能一些点上只有输入接口,如: NF_IP_PRE_ROUTING,而有
些只有输出接口,如: NF_IP_POST_ROUTING ,还可能输入输出的接口都有,如:
NF_IP_FORWARD。
总结
从上面的分析中我们可以看出,一个数据包在经过IPV4这个协议栈做处理时,原则
上其流程是不会因为加入Netfilter框架而改变,Netfilter所要做的事情只不过
是在一些必要的地方看一下这些数据包,并根据用户的意愿对这个数据包处理。
为了更清晰的显示Netfilter框架中的几个HOOK点在IPV4中的位置,可以从下图
中清晰的反映出来。图中椭圆中为处理函数,用箭头指出的函数中的点为调用宏
NF_HOOK处,箭头以为数据包的流程方向。(此处为突出重点,只画出了IP层的大部分和Netfilter密切相关的一些函数)