天天看点

Netfilter——Netfilter中的HOOK机制一、如何在协议栈中调用钩子函数二、NF_HOOK 钩子函数

  Netfilter 是 Linux网络内核协议栈提供了报文过滤(防火墙)框架,HOOK机制是Netfilter的核心。

一、如何在协议栈中调用钩子函数

  在协议栈中相应位置嵌入Netfilter的函数NF_HOOK,来拦截报文送到Netfilter中进行处理。

 

协议栈中的五条内置链

我们知道Linux网络内核内置了5条链PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING, 其实就是在内核的五个位置嵌入了NF_HOOK函数,然后通过

NF_HOOK

进入Netfilter框架处理。我们以PREROUTING为例,看下插入NF_HOOK的五个位置。

  网卡驱动接收到报文,经过二层处理后,会调用

net_receive_skb

传递给具体的协议处理函数, 对于IPv4来说,这里的协议处理函数指的就是

ip_rcv

了。而我们PREROUTING链的NF_HOOK函数正是在

ip_rcv

中嵌入的。

/*
   *  Main IP Receive routine.
   *  主要功能:对IP头部合法性进行严格检查,然后把具体功能交给ip_rcv_finish。
   */    
  int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
  {           
            ......
        /* 进入Netfilter处理,处理完后如果报文还要继续往下传递,则进入ip_rcv_finish函数处理 */
      return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,
                 ip_rcv_finish);
            ......
 }
           
其他几条链的嵌入位置分别如下:
           

+ PREROUTING:

ip_rcv

+ INPUT:

ip_local_deliver

+ FORWARD

ip_forward

+ OUTPUT:

raw_send_hdrinc

+ POSTROUTING:

ip_mc_output

ip_mc_output

二、NF_HOOK 钩子函数

(一)、nf_hook 钩子函数的存储

  钩子函数存储在全局二维数组

nf_hooks

中。

struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS] __read_mostly;

从nf_hooks的定义我们可以看出,该结构体每一个成员都是一个

struct list_head

对象。

NFPROTO_NUMPROTO有一下取值

enum {
    NFPROTO_UNSPEC =  , 
    NFPROTO_IPV4   =  , 
    NFPROTO_ARP    =  , 
    NFPROTO_BRIDGE =  , 
    NFPROTO_IPV6   = ,
    NFPROTO_DECNET = ,
    NFPROTO_NUMPROTO,
};   
           

NF_MAX_HOOKS有以下取值

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
};  
           
Netfilter——Netfilter中的HOOK机制一、如何在协议栈中调用钩子函数二、NF_HOOK 钩子函数

关于全局二维数组的成员,即各个链表的head节点

struct nf_hook_ops

形式如下:

struct nf_hook_ops
{
    struct list_head list; //该成员将会把这个结构添加在nf_hooks数组引导的队列头中。

    /* User fills in from here down. */
    nf_hookfn *hook;        //钩子处理函数:用于判断报文是否需要经过本钩子到检测过滤等
    struct module *owner;   //可以使用动态加载的内核模块
    u_int8_t pf;            //协议族
    unsigned int hooknum;   //钩子点

    /* Hooks are ordered in ascending priority. */
    int priority;//这里不是一个先来先服务队列,而是一个优先级队列。
};
           

该结构体的第二个成员

nf_hookfn *hook;

其实就是对应一个钩子处理函数。关于nf_hookfn的定义如下:

//Netfilter 钩子函数
//return:   NF_DROP,NF_ACCEPT...
typedef 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 *));
           

(二) 钩子函数的注册

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

//使用互斥锁nf_hook_mutex来保护二维数组下的hook_ops链表
    err = mutex_lock_interruptible(&nf_hook_mutex);
    if (err < )
        return err;
    //比较优先级,注册钩子 按照hook_op的优先级来把hook_op注册到全局二维数组中去
    list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
        if (reg->priority < elem->priority)
            break;
    }
    list_add_rcu(&reg->list, elem->list.prev);
    mutex_unlock(&nf_hook_mutex);
    return ;
} 
EXPORT_SYMBOL(nf_register_hook);
           

(二)、 NF_HOOK调用过程

#define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh)         \
({int __ret;                                       \
if ((__ret=nf_hook_thresh(pf, hook, (skb), indev, outdev, okfn, thresh, )) == )\
    __ret = (okfn)(skb);                               \
__ret;})

#define NF_HOOK_COND(pf, hook, skb, indev, outdev, okfn, cond)             \
({int __ret;                                       \
if ((__ret=nf_hook_thresh(pf, hook, (skb), indev, outdev, okfn, INT_MIN, cond)) == )\
    __ret = (okfn)(skb);                               \
__ret;})

//拦截报文,Netfilter通过在协议栈的相应位置嵌入NF_HOOK来拦截报文
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
    NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)  //*important 
           

  钩子函数最后会调用

(okfn)(skb)

, 即经过Netfilter处理后,如果报文没有被丢弃,应该交付给okfn指向的函数处理。

  NF_HOOK函数的核心在于

nf_hook_thresh

,我们看下它的实现。

/**    
 *  nf_hook_thresh - call a netfilter hook
 *  
 *  Returns 1 if the hook has allowed the packet to pass.  The function
 *  okfn must be invoked by the caller in this case.  Any other return
 *  value indicates the packet has been consumed by the hook.
 */    
static inline int nf_hook_thresh(u_int8_t pf, unsigned int hook, //pf:协议族   hook:协议定义到hook点
                 struct sk_buff *skb,       //报文
                 struct net_device *indev,  //入接口
                 struct net_device *outdev, //出接口
                 int (*okfn)(struct sk_buff *), int thresh, //netfilter处理完成后来处理报文的函数
                 int cond)
{
    if (!cond)
        return ; //直接跳过netfilter处理
#ifndef CONFIG_NETFILTER_DEBUG
    if (list_empty(&nf_hooks[pf][hook]))
        return ;
#endif
    return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh); //netfilter 处理
}  
           
/* Returns 1 if okfn() needs to be executed by the caller,
 * -EPERM for NF_DROP, 0 otherwise. */
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 = ;

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

    elem = &nf_hooks[pf][hook]; //找到hook点的处理函数的链表
next_hook:
    verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
                 outdev, &elem, okfn, hook_thresh); //进入钩子处理
    if (verdict == NF_ACCEPT || verdict == NF_STOP) { //NF_STOP也可以导致一个报文被接收,这就是某些钩子在中间直接放行了报文,之后的钩子将没有机会进行检测。
        ret = ;
    } else if (verdict == NF_DROP) { //drop
        kfree_skb(skb);
        ret = -EPERM;
    } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
        /*这里比较少见,大致看了一下,好像是实现用户态防火墙的一种方法。
         * 可能使用之前说过的netlink机制将这个报文发送到用户态的侦听套接口,
         * 从而让用户态程序对这个报文进行全面检测,如果检测通过,再发送到内核
         * 的netlink套接口中,对应的netlink协议簇为*/
        /* 如果返回值是要把报文存入用户自定义的队列中(TARGET QUEUE和 NFQUEUE
         * 使用),把verdict字段分成了两个部分,前一部分来定义返回值为入队操作,
         * 后半部分给出入队的队列号*/
        if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
                  verdict >> NF_VERDICT_BITS))
            goto next_hook;
    }
    rcu_read_unlock();
    return ret;
}  
EXPORT_SYMBOL(nf_hook_slow);
           

这里会获取对应的个规则链

elem = &nf_hooks[pf][hook];

,然后遍历调用注册的钩子函数,

verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
                 outdev, &elem, okfn, hook_thresh); //进入钩子处理
           

继续阅读