内核netfilter框架简析

以内核版本3.16.44为例。

1、Netfilter简介

Netfilter是嵌入内核IP协议栈的一系列调用入口,设置在报文处理的路径上。网络报文按照来源和去向,可以分为三类:流入的、流经的和流出的,其中流入和流经的报文需要经过路由才能区分,而流经和流出的报文则需要经过投递,此外,流经的报文还有一个FORWARD的过程,即从一个NIC转到另一个NIC。

2、netfilter架构与流程

Netfilter就是根据网络报文的流向,在以下几个点插入处理过程:

  • NF_IP_PRE_ROUTING:接收的数据包刚进来,还没有经过路由选择,即还不知道数据包是要发给本机还是其它机器。在报文作路由以前执行;
  • NF_IP_LOCAL_IN:在已经经过路由选择,并且该数据包的目的IP是本机,进入本地数据包处理流程。;
  • NF_IP_FORWARD:已经经过路由选择,但该数据包的目的IP不是本机,而是其它机器,进入forward流程,在报文转向另一个NIC以前执行;
  • NF_IP_LOCAL_OUT:本地程序要发出去的数据包刚到IP层,还没进行路由选择。在本地报文做流出路由前执行。
  • NF_IP_POST_ROUTING:本地程序发出去的数据包,或者转发(forward)的数据包已经经过了路由选择,即将交由下层发送出去,在报文流出以前执行;

如图所示:

不考虑特殊情况的话,一个数据包只会经过下面三个路径中的一个:

  • 本机收到目的IP是本机的数据包: NF_IP_PRE_ROUTING -> NF_IP_LOCAL_IN
  • 本机收到目的IP不是本机的数据包: NF_IP_PRE_ROUTING -> NF_IP_FORWARD -> NF_IP_POST_ROUTING
  • 本机发出去的数据包: NF_IP_LOCAL_OUT -> NF_IP_POST_ROUTING

3、netfilter 的相关数据结构

Netfilter框架为多种协议提供了一套类似的钩子(HOOK),用一个二维数组结构存储,一维为协议族,二维为上面提到的各个调用入口。在net/netfilter/core.c文件中:

对于这两个维度,NFPROTO_NUMPROTO的定义在 include/uapi/linux/netfilter.h 文件中:

NF_MAX_HOOKS的定义在 include/linux/netfilter.h 文件中:

每个希望嵌入Netfilter中的模块都可以为多个协议族的多个调用点注册多个hook函数,这些钩子函数将形成一条函数指针链,每次协议栈代码执行到NF_HOOK()宏时,都会依次调用挂载到相应钩子点的所有这些函数,处理参数所指定的协议栈内容。

netfilter的hook函数结构struct nf_hook_ops,通过双向链表连接在一起。

struct nf_hook_ops结构在内核的 include/linux/netfilter.h 文件中定义如下:

结构体中最后一个变量 priority 表示当前结构的优先级,可选值定义在 include/linux/netfilter_bridge.h 文件中,定义如下:

4、钩子点的注册与卸载

4.1 注册

向指定钩子点注册单个函数为nf_register_hook(),定义在 net/netfilter/core.c 文件中:

该函数将传入的函数reg插入到全局二维数组nf_hooks[][]中,根据优先级从小到大的顺序插入。

批量向钩子点注册的函数为 nf_register_hooks():

4.2 卸载

钩子点卸载函数 nf_unregister_hook() 的定义如下:

该函数直接将指定钩子点从链表中去掉。

批量卸载钩子点:

5、钩子点的返回值

每个注册的钩子函数经过处理后都将返回下列值之一,位于文件 include/uapi/linux/netfilter.h 中,告知Netfilter核心代码处理结果,以便对报文采取相应的动作:

每个返回值的意义如下:

  • NF_DROP:将报文丢弃;
  • NF_ACCEPT:数据包通过了挂载点的所有规则,继续正常的报文处理;
  • NF_STOLEN:由钩子函数处理了该报文,不要再继续传送;
  • NF_QUEUE:将报文入队,通常交由用户程序处理;
  • NF_REPEAT:为netfilter的一个内部判定结果,需要重复该条规则的判定,再次调用该钩子函数,直至不为NF_REPEAT;
  • NF_STOP:数据包通过了挂载点的所有规则。但与NF_ACCEPT不同的一点时,当某条规则的判定结果为NF_STOP,那么可以直接返回结果NF_STOP,无需进行后面的判定了。而NF_ACCEPT需要所有的规则都为ACCEPT,才能返回NF_ACCEPT。

6、钩子点的调用

Netfilter使用 NF_HOOK 函数在协议栈内部切入到Netfilter框架中。

NF_HOOK宏在内核文件 include/linux/netfilter.h 中定义如下:

NF_HOOK() 函数直接调用了 NF_HOOK_THRESH() 函数,该函数定义如下:

这两个函数的参数变量类型,意义如下:

  • pf: 协议族名,Netfilter架构同样可以用于IP层之外,因此这个变量还可以有诸如PF_INET6等名字。
  • hook: 指定HOOK点,对于IP层,可以取值为 NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_LOCAL_OUT、NF_IP_POST_ROUTING 等五个值;
  • *skb: 表示要处理的数据包。
  • *in: 指出数据包从哪个网络设备进入到系统中,以 struct net_device 结构表示。
  • *out: 指出数据包从哪个网络设备从系统中出去,以 struct net_device 结构表示。
  • *okfn: 该参数是一个函数指针,在所有已注册的钩子点被调用完之后,会调用该函数。
  • thresh: 该参数用来指定遍历调用已注册的钩子点的函数时的优先级,只执行优先级大于该值的hook函数。

可以看到在NF_HOOK() 函数调用 NF_HOOK_THRESH() 函数时,指定了thread的值为宏 INT_MIN。

在 NF_HOOK_THRESH() 函数中直接调用了nf_hook_thresh()函数来执行钩子点上的各个函数,该函数定义如下:

nf_hooks_active() 函数判断 nf_hooks[pf][hook] 指定的项是否为空。
nf_hook_slow() 函数的定义在 net/netfilter/core.c 文件中,该函数会依赖遍历调用nf_hooks[pf][hook]中优先级大于thresh的函数。
————————————————————

原创文章,转载请注明: 转载自孙希栋的博客

本文链接地址: 《内核netfilter框架简析》

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注