Netfilter——Netfilter中的HOOK机制

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

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

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

协议栈中的五条内置链

咱们知道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
+ FORWARDip_forward
+ OUTPUT:raw_send_hdrinc
+ POSTROUTING:ip_mc_outputip_mc_outputmarkdown

2、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 =  0, 
    NFPROTO_IPV4   =  2, 
    NFPROTO_ARP    =  3, 
    NFPROTO_BRIDGE =  7, 
    NFPROTO_IPV6   = 10,
    NFPROTO_DECNET = 12,
    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
};

这里写图片描述
关于全局二维数组的成员,即各个链表的head节点struct nf_hook_ops形式以下:svg

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 < 0)
        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 0;
} 
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, 1)) == 1)\
    __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)) == 1)\
    __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,咱们看下它的实现。ui

/** * 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 1; //直接跳过netfilter处理
#ifndef CONFIG_NETFILTER_DEBUG
    if (list_empty(&nf_hooks[pf][hook]))
        return 1;
#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 = 0;

    /* 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 = 1;
    } 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];,而后遍历调用注册的钩子函数,this

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