Netfilter——Netfilter中的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
+ FORWARDip_forward
+ OUTPUT:raw_send_hdrinc
+ POSTROUTING:ip_mc_outputip_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 =  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形式如下:

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,我们看下它的实现。

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

verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
                 outdev, &elem, okfn, hook_thresh); //进入钩子处理
  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux Netfilter是Linux内核的一个框架,用于对数据包进行过滤、转发、修改等操作。Netfilter可以在内核通过一个叫做iptables的工具进行配置和管理。 Netfilter提供了很多钩子函数,可以让我们在数据包经过某个网络层协议时对其进行处理。当数据包经过某个协议时,内核会调用相应的钩子函数,我们可以在这些钩子函数编写代码,来对数据包进行处理。 如果你想要编写一个Netfilter模块,可以按照以下步骤进行: 1. 编写模块代码 模块代码需要定义一个结构体,其包含钩子函数和相关的数据结构。这个结构体需要注册到Netfilter框架,以便内核在需要时调用。 2. 编译模块 使用编译器将代码编译成模块格式,然后使用insmod命令将模块插入内核。 3. 配置iptables规则 使用iptables工具配置规则,将数据包传递给我们的模块进行处理。 4. 测试模块 使用网络工具测试模块是否正常工作,以及是否符合预期。 下面是一个简单的Netfilter模块示例: ``` #include <linux/module.h> #include <linux/kernel.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> static struct nf_hook_ops nfho; // Netfilter 钩子结构体 // 钩子函数 unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { // 在这里编写处理代码 return NF_ACCEPT; // 返回值表示下一步操作 } int init_module() { nfho.hook = hook_func; // 设置钩子函数 nfho.hooknum = NF_INET_PRE_ROUTING; // 设置钩子位置 nfho.pf = PF_INET; // 设置协议族 nfho.priority = NF_IP_PRI_FIRST; // 设置优先级 nf_register_hook(&nfho); // 注册钩子 return 0; } void cleanup_module() { nf_unregister_hook(&nfho); // 注销钩子 } MODULE_LICENSE("GPL"); ``` 在这个示例,我们定义了一个钩子函数hook_func,它会在数据包经过PRE_ROUTING钩子时被调用。在钩子函数,我们可以对数据包进行处理,并返回一个值表示下一步操作。 我们将钩子函数注册到nfho结构体,并使用nf_register_hook函数将其注册到Netfilter框架。在模块加载时,init_module函数会被调用,模块卸载时,cleanup_module函数会被调用。 这只是一个简单的示例,实际的Netfilter模块可能需要更多的功能和处理逻辑。如果你想深入了解Netfilter的编程,可以参考Linux内核源码的相关代码和文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值