Chinaunix首页 | 论坛 | 博客
  • 博客访问: 539615
  • 博文数量: 120
  • 博客积分: 3030
  • 博客等级: 中校
  • 技术积分: 1445
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-05 01:00
文章存档

2011年(1)

2009年(2)

2008年(32)

2007年(33)

2006年(52)

我的朋友

分类: LINUX

2008-03-26 09:53:25

Nefilter细节实现分析
Netfilter定义了协议栈中的检查点和检查点上引用的数据结构以及对这些数据
结引用的过程。首先看看在检查点上引用的数据结构,如图所示:


              图1 nf_hoo_ops数据结构的组织

图中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密切相关的一些函数)
 
阅读(1391) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~