【Linux 驱动】netfilter/iptables (二) Netfilter hook 数据结构

Netfilter是Linux 2.4.x 引入的一个子系统,它做为一个通用的、抽象的框架,提供一整套的hook函数的管理机制,使得诸如数据包过滤(filter)、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能。这些功能仅经过使用内核网络代码提供的各式各样的hook函数完成。linux

对于数据在网络协议栈中的传送过程,前面咱们花了较大篇幅,从原理到内核源码剖析了数据包在整个协议栈的传送过程,包括发送和接收,主要是数据包的封装与解包,在用户层是裸数据,而后层层往下逐步添加对应层头部,返回来是逐步去掉头部。web

可是,netfilter对数据包还设置了一些关卡,看下图:编程

这里写图片描述

对于收到的每一个数据包,都是从 PRE_ROUTING 进来,通过路由判决,若是是发送给本机的数据包就通过 LOCAL_IN ,而后往协议栈的上层继续传递,不然,若是该数据包的目的地不是本机,那么就通过 FORWARD,而后顺着 POST_ROUTING 将该包转发出去;api

对于发送的每一个数据包,首先也有一个路由判决,以肯定该包是从哪一个接口出去,而后通过 LOCAL_OUT,最后也是顺着 POST_ROUTING 将该包发送出去。数组

Netfilter 的架构就是在整个网络流程的上面这些位置放置一些检测点(HOOK,或者说回调函数),而在每一个检测点上登记(callback)了一些处理函数进行处理(如包过滤、NAT等,甚至能够是用户自定义的功能)。
这些hook点就是 netfilter hook,在这里,数据包能够被分析而且选择是保留仍是放弃。markdown

Netfilter 中定义了五个关于ipv4的hook类型(这里咱们只讨论ipv4,对于ipv6也是能够对应的的 hook类型,这些符号声明在 linux/netfilter_ipv4.h中。
PS:你们须要知道的是,在高版本中,内核已经作出了修改,下面的ipv4符号均修改成NF_INET形式。因此你们编程的时候要把IP换为INET网络

  1. NF_IP_PRE_ROUTING
    刚刚经过数据链路层解包(从上一篇博文咱们了解到netfilter是在网络层做用),进入网络层的数据包经过此点(刚刚进行完版本号,校验
    和等检测),目的地址转换在此点进行;
  2. NF_IP_LOCAL_IN
    经路由查找后,送往本机的经过此检查点,INPUT包过滤在此点进行;
  3. NF_IP_FORWARD
    目的地是其余主机的数据包,要转发的包经过此检测点,FORWARD包过滤在此点进行 ;
  4. NF_IP_LOCAL_OUT
    本机进程发出的包经过此检测点,OUTPUT包过滤在此点进行;
  5. NF_IP_POST_ROUTING
    全部立刻便要经过网络设备出去的包经过此检测点,内置的源地址转换功能(包括地址假装)在此点进行 ;数据结构

    这里写图片描述

在上面几个关键点,有不少已经按照优先级预先注册了的钩子函数,造成了一条链。对于每一个到来的数据包会依次被这些钩子函数处理一番,而后视状况是将其放行仍是丢弃,因此必须得将处理结果返回给 netfilter。架构

Netfilter 的返回值:框架

  1. NF_ACCEPT:继续正常传输数据报。
    这个返回值告诉Netfilter,到目前为止,该数据包仍是被接受的而且该数据包应当被递交到网络堆栈的下一个阶段
  2. NF_DROP:丢弃该数据报,再也不传输。
    该数据包将被彻底的丢弃,全部为它分配的资源都应当被释放。
  3. NF_STOLEN:忘掉该数据包。
    该hook函数将今后开始对数据包的处理,而且Netfilter应当放弃对该数据包作任何的处理。可是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是hook函数从Netfilter获取了该数据包的全部权。
  4. NF_QUEUE:将该数据包插入到用户空间。
    对该数据报进行排队(一般用于将数据报给用户空间的进程进行处理)。
  5. NF_REPEAT:再次调用该hook函数。慎用,由于很容易形成死循环。

下面咱们学习下 Netfilter hook 的注册和注销

注册和注销Netfilter hook
注册一个 hook 函数是围绕 nf_hook_ops 数据结构的一个很是简单的操做。
为了和编程环境内核源码树版本保持一致,这里咱们分析的源码均是 Linux kernel 3.14
(linux/netfilter.h)

struct nf_hook_ops {
    struct list_head list;//维护Netfilter hook的列表

    /* 如下的值由用户填充 */
    nf_hookfn   *hook;//指向nf_hookfn类型的函数指针
                      //该函数是这个hook被调用时执行的函数
    struct module   *owner;//模块的全部者
    void        *priv;//不知道
    u_int8_t    pf;//用于指定协议族
    unsigned int    hooknum;//用于指定安装的这个函数对应的具体的hook类型
                           //就是前面说到的五个值,钩子函数在哪一个位置设关卡
    /* Hooks are ordered in ascending priority. */
    int     priority;//用于指定在执行的顺序中,这个hook函数应当被放在什么地方
};

//这里只是定义了一个函数指针
struct sk_buff;
struct nf_hook_ops;
typedef unsigned int nf_hookfn(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 *));

对应的hook类型和协议类型以及优先级的定义:
uapi/linux/netfilter.h (linux kernel 3.14)
uapi/linux/netfilter_ipv4.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
};

enum {
 NFPROTO_UNSPEC = 0,
 NFPROTO_INET = 1,
 NFPROTO_IPV4 = 2,
 NFPROTO_ARP = 3,
 NFPROTO_BRIDGE = 7,
 NFPROTO_IPV6 = 10,
 NFPROTO_DECNET = 12,
 NFPROTO_NUMPROTO,
};

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_HELPER = 300,
 NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
 NF_IP_PRI_LAST = INT_MAX,
};

struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];

这里写图片描述
hook函数就保存在上面的二维数组中,经过协议类型和hook类型定位,而且每一个hook函数具有对应的优先级,如上图所示。

**注册一个 Netfilter hook 须要调用 nf_register_hook() ,以及用到一个 nf_hook_ops 数据结构。
注销则是用nf_unregister_hook() ;**

int nf_register_hook(struct nf_hook_ops *reg);//注册
void nf_unregister_hook(struct nf_hook_ops *reg);//注销

ok,有了前面的小小铺垫,下篇咱们将给出一个小小的测试案例。

talk is cheap, show me your code.

参考资料:《深刻Linux网络核心堆栈》