Netfilter 框架

来源:互联网 发布:阿里云对象存储oss 编辑:程序博客网 时间:2024/06/08 00:33

通俗的说,netfilter的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK),而在每个检测点上登记了一些处理函数进行处理(如包过滤,NAT等,还有用户自定义的函数)

1. Netfilter 框架

  • 框架图 image
  • 钩子定义(注册点) kernel/linux/include/linux/netfilter.h
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  };
    • NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测), 目的地址转换在此点进行;
    • NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
    • NF_IP_FORWARD:要转发的包通过此检测点,FORWARD包过滤在此点进行;
    • NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行。
    • NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行;
    • NF_INET_NUMHOOKS: hook点的数目
  • 优先级定义() kernel/linux/include/linux/netfilter_ipv4.h
enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_CONNTRACK_DEFRAG = -400, NF_IP_PRI_RAW = -300, NF_IP_PRI_SELINUX_FIRST = -225, NF_IP_PRI_CONNTRACK = -200,  NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_FILTER = 0,     NF_IP_PRI_SECURITY = 50,    NF_IP_PRI_NAT_SRC = 100,    NF_IP_PRI_SELINUX_LAST = 225,    NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,   NF_IP_PRI_LAST = INT_MAX, };

下图是netfilter内嵌的几个表,mangle,filter,nat等在5个hook点的注册位置和注册顺序 image

2. Netfilter 初始化

内核在启动的时候通过netfilter_init函数完成钩子函数的初始化工作;如果需要在相应的钩子点挂载钩子函数,则需要首先定义一个nf_hook_ops结构,在其中实现所需的钩子函数,再调用nf_register_hook将该钩子函数注册到上述的全局二维数组中。

下面先来看一下netfilter_init初始化函数:

void __init netfilter_init(void){int i, h;/*首先完成全局二维链表的初始化工作*/for (i = 0; i < ARRAY_SIZE(nf_hooks); i++) {        for (h = 0; h < NF_MAX_HOOKS; h++)              INIT_LIST_HEAD(&nf_hooks[i][h]);}#ifdef CONFIG_PROC_FS/*在proc文件系统内注册相应的文件选项*/proc_net_netfilter = proc_mkdir("netfilter", init_net.proc_net);if (!proc_net_netfilter)        panic("cannot create netfilter proc entry");#endif/*完成proc文件夹netfilter下针对nf_queue的初始化工作,完成读、写等功能*/if (netfilter_queue_init() < 0)        panic("cannot initialize nf_queue");/*完成proc文件夹netfilter下日志文件的初始化工作*/if (netfilter_log_init() < 0)        panic("cannot initialize nf_log");}

主要是初始化了下面整个数组

extern struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];

用来存储不同协议过滤点的回调处理函数。如下图所示: 20151221133315696.jpg

我们所说的内核的netfilter,应该包括二层数据的filter操作,以及对三层及三层以上数据的filter等操作。 只不过二层的filter实现与三层及三层也上的filter实现有所不同。其中二层的filter与应用层程序ebtables结合使用,而三层及以上的filter结合iptables使用。但是二层filter与三层filter使用的都是统一的hook机制。

3. Netfilter hook模块注册和注销

3.1 模块注册

  • 注册函数
int nf_register_hook(struct nf_hook_ops *reg){    struct nf_hook_ops *elem;    int err;    //互斥锁加锁(可中断)    err = mutex_lock_interruptible(&nf_hook_mutex);    if (err < 0)        return err;    //内核链表中的一个函数,常用于遍历链表搜索元素    //这里是插入新对象,下面的循环遍历就是根据优先级找到合适的插入位置,这样在进行规则匹配的时候就是按照优先级来的    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);//释放锁    #if defined(CONFIG_JUMP_LABEL)        static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]);//忽略    #endif    return 0;}

nf_hook_ops数据结构在linux/netfilter.h中定义,该数据结构的定义如下:

struct nf_hook_ops {                  struct list_head list;//双向链表                                    nf_hookfn *hook;//回调函数                  int pf;//协议族                  int hooknum;//hook点                                    int priority;//优先级          };

从上面的代码就可以看出,所有的 struct nf_hook_ops 都以指针的形式记录在一个列表中,其保存的顺序就是按照优先级的顺序。这个表就是前面图片中的链表。

  • 回调处理的hook函数
typedef unsigned int nf_hookfn(unsigned int hooknum,//hook点                                         struct sk_buff **skb,                                         const struct net_device *in,//入口                                         const struct net_device *out,//出口                                         int (*okfn)(struct sk_buff *));//okfn函数是当数据包被允许通过时所进行的处理
#ifdef CONFIG_NETFILTER...#ifdef CONFIG_NETFILTER_DEBUG#define NF_HOOK nf_hook_slow#else#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)            \(list_empty(&nf_hooks[(pf)][(hook)])                    \? (okfn)(skb)                                \: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))#endif...#else#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)#endif

可见okfn函数是必不可少的,当Netfilter被启用时,它用于完成接收的数据包后的后续操作,如果不启用Netfilter做数据包过滤,则所有的数据包都被接受,直接调用该函数做后续操作

  • 内核的Netfilter钩子函数调用:

20140224201714093.png

在网卡收到包之后交由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);
NF_HOOK    |--> NF_HOOK_THRESH               |--> nf_hook_thresh                         |--> nf_hook_slow                                   |--> nf_iterate

nf_hook_slow() /net/netfilter/core.c:

int nf_hook_slow(int pf, unsigned int hook, struct sk_buff **pskb,     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(); #ifdef CONFIG_NETFILTER_DEBUG if (unlikely((*pskb)->nf_debug & (1 << hook))) { printk("nf_hook: hook %i already set.\n", hook); nf_dump_skb(pf, *pskb); } (*pskb)->nf_debug |= (1 << hook); #endif /*取得对应的链表首部*/ elem = &nf_hooks[pf][hook]; next_hook: /*调用对应的钩子函数*/ verdict = nf_iterate(&nf_hooks[pf][hook], pskb, hook, indev,       outdev, &elem, okfn, hook_thresh); /*判断返回值,做相应的处理*/ if (verdict == NF_ACCEPT || verdict == NF_STOP) { ret = 1; /*前面提到过,返回1,则表示装继续调用okfn函数指针*/ goto unlock; } else if (verdict == NF_DROP) { kfree_skb(*pskb); /*删除数据包,需要释放skb*/ ret = -EPERM; } else if (verdict == NF_QUEUE) { NFDEBUG("nf_hook: Verdict = QUEUE.\n"); if (!nf_queue(*pskb, elem, pf, hook, indev, outdev, okfn)) goto next_hook; } unlock: rcu_read_unlock(); return ret; }

内核的数据转发函数,调用宏NF_HOOK时,会告诉它协议簇和Hook类型,nf_hook_slow函数根据这两个要素,可以很轻易地从nf_hooks 中取得对应的前面注册好的Hook链表的首部: elem = &nf_hooks[pf][hook]; 然后,就调用函数nf_iterate ,遍历Hook链表,调用链用上所有的Hook函数。

在nf_iterate中会有以下几个target来处理数据包:

#define NF_DROP 0#define NF_ACCEPT 1#define NF_STOLEN 2#define NF_QUEUE 3#define NF_REPEAT 4#define NF_STOP 5

NF_DROP: 表示丢弃掉报文;

NF_ACCEPT: 表示勾子函数允许报文继续向下处理,此时应该继续执行队列上的下一个勾子函数。

NF_STOLEN: 表示报文不再往上传递,与NF_DROP不同的是,它没有调用kfree_skb()释放掉skb;

NF_QUEUE: 将该数据包插入到用户空间。

NF_REPEAT: 表示要重复执行勾子函数一次;所以勾子函数要编写得当,否则报文会一直执行一个返回。

NF_STOP : 表示停止执行队列上的勾子函数,直接返回;

  • 小结

    hook注册函数nf_register_hook 功能: 将一个新的hook 结构添加nf_hooks数组的相应的成员链表中

    • A 根据协议号、hook点,确定链表
    • B 根据优先级将该hook结构添加到链表的合适位置
    • C.注册一个hook函数是围绕nf_hook_ops数据结构的一个操作。

3.2 模块注销

void nf_unregister_hook(struct nf_hook_ops *reg){    mutex_lock(&nf_hook_mutex);//获取互斥锁,不可中断    list_del_rcu(&reg->list);//删除指定对象    mutex_unlock(&nf_hook_mutex);//释放锁#if defined(CONFIG_JUMP_LABEL)    static_key_slow_dec(&nf_hooks_needed[reg->pf][reg->hooknum]);#endif    /**     *  synchronize_net -  Synchronize with packet receive processing     *     *  Wait for packets currently being received to be done.     *  Does not block later packets from starting.     */    synchronize_net();}

注销一个 Netfilter hook 其实就是把我们之前注册插入的 struct nf_hook_ops 指针对象从链表中删除,从代码可以看出,它并没有销毁这个对象。 在后面还有个 synchronize_net(); 这个函数可能会引起睡眠,其目的是等待处理完数据包接收过程。

3.3 示例

#include <linux/module.h>#include <linux/kernel.h>#include <linux/netfilter.h>#include <linux/netfilter_ipv4.h>MODULE_LICENSE("Dual BSD/GPL");static struct nf_hook_ops nfho;//钩子函数,按照各自的需求实现unsigned int hook_func(const struct nf_hook_ops *ops,         struct sk_buff *skb,        const struct net_device *in,        const struct net_device *out,        int (*okfn)(struct sk_buff *)){    return NF_DROP;//丢弃所有的数据包}static int __init hook_init(void){    nfho.hook = hook_func;//关联对应处理函数    nfho.hooknum = NF_INET_PRE_ROUTING;//2.6.22及以后的内核中,内核态编程需要使用这个    nfho.pf = PF_INET;//ipv4,所以用这个    nfho.priority = NF_IP_PRI_FIRST;//优先级,第一顺位,首先执行我们定义的函数    nf_register_hook(&nfho);//注册    return 0;}static void __exit hook_exit(void){    nf_unregister_hook(&nfho);//注销}module_init(hook_init);module_exit(hook_exit);

上面的例子就是一个简单的netfilter的hook函数的实现过程,实例化一个nf_hook_ops的结构体对象,把对象插入到指定位置,执行相应的回调函数,实际应用中可以替换其中的回调函数实现相应的功能。

原创粉丝点击