nf_register_hooks NF_HOOK

来源:互联网 发布:淘宝品控怎么处理 编辑:程序博客网 时间:2024/05/28 23:11

如果我们想加入自己的代码,便要用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(&reg->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密切相关的一些函数)



0 0
原创粉丝点击