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

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

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

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

【Linux 驱动】netfilter/iptables (二) Netfilter hook 数据结构_第1张图片

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

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

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

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
    所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行 ;

    【Linux 驱动】netfilter/iptables (二) Netfilter hook 数据结构_第2张图片

在上面几个关键点,有很多已经按照优先级预先注册了的钩子函数,形成了一条链。对于每个到来的数据包会依次被这些钩子函数处理一番,然后视情况是将其放行还是丢弃,所以必须得将处理结果返回给 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];

【Linux 驱动】netfilter/iptables (二) Netfilter hook 数据结构_第3张图片
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网络核心堆栈》

你可能感兴趣的:(Linux,Driver)