内核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文件中:
1 2 | struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS] __read_mostly; EXPORT_SYMBOL(nf_hooks); |
对于这两个维度,NFPROTO_NUMPROTO的定义在 include/uapi/linux/netfilter.h 文件中:
1 2 3 4 5 6 7 8 9 10 | enum { NFPROTO_UNSPEC = 0, NFPROTO_INET = 1, NFPROTO_IPV4 = 2, NFPROTO_ARP = 3, NFPROTO_BRIDGE = 7, NFPROTO_IPV6 = 10, NFPROTO_DECNET = 12, NFPROTO_NUMPROTO, }; |
NF_MAX_HOOKS的定义在 include/linux/netfilter.h 文件中:
1 2 | /* Largest hook number + 1 */ #define NF_MAX_HOOKS 8 |
每个希望嵌入Netfilter中的模块都可以为多个协议族的多个调用点注册多个hook函数,这些钩子函数将形成一条函数指针链,每次协议栈代码执行到NF_HOOK()宏时,都会依次调用挂载到相应钩子点的所有这些函数,处理参数所指定的协议栈内容。
netfilter的hook函数结构struct nf_hook_ops,通过双向链表连接在一起。
struct nf_hook_ops结构在内核的 include/linux/netfilter.h 文件中定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 | struct nf_hook_ops { struct list_head list; /* User fills in from here down. */ nf_hookfn *hook; struct module *owner; void *priv; u_int8_t pf; unsigned int hooknum; /* Hooks are ordered in ascending priority. */ int priority; }; |
结构体中最后一个变量 priority 表示当前结构的优先级,可选值定义在 include/linux/netfilter_bridge.h 文件中,定义如下:
1 2 3 4 5 6 7 8 9 10 | enum nf_br_hook_priorities { NF_BR_PRI_FIRST = INT_MIN, NF_BR_PRI_NAT_DST_BRIDGED = -300, NF_BR_PRI_FILTER_BRIDGED = -200, NF_BR_PRI_BRNF = 0, NF_BR_PRI_NAT_DST_OTHER = 100, NF_BR_PRI_FILTER_OTHER = 200, NF_BR_PRI_NAT_SRC = 300, NF_BR_PRI_LAST = INT_MAX, }; |
4、钩子点的注册与卸载
4.1 注册
向指定钩子点注册单个函数为nf_register_hook(),定义在 net/netfilter/core.c 文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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(®->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; } EXPORT_SYMBOL(nf_register_hook); |
该函数将传入的函数reg插入到全局二维数组nf_hooks[][]中,根据优先级从小到大的顺序插入。
批量向钩子点注册的函数为 nf_register_hooks():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n) { unsigned int i; int err = 0; for (i = 0; i < n; i++) { err = nf_register_hook(®[i]); if (err) goto err; } return err; err: if (i > 0) nf_unregister_hooks(reg, i); return err; } EXPORT_SYMBOL(nf_register_hooks); |
4.2 卸载
钩子点卸载函数 nf_unregister_hook() 的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 | void nf_unregister_hook(struct nf_hook_ops *reg) { mutex_lock(&nf_hook_mutex); list_del_rcu(®->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(); nf_queue_nf_hook_drop(reg); } EXPORT_SYMBOL(nf_unregister_hook); |
该函数直接将指定钩子点从链表中去掉。
批量卸载钩子点:
1 2 3 4 5 6 | void nf_unregister_hooks(struct nf_hook_ops *reg, unsigned int n) { while (n-- > 0) nf_unregister_hook(®[n]); } EXPORT_SYMBOL(nf_unregister_hooks); |
5、钩子点的返回值
每个注册的钩子函数经过处理后都将返回下列值之一,位于文件 include/uapi/linux/netfilter.h 中,告知Netfilter核心代码处理结果,以便对报文采取相应的动作:
1 2 3 4 5 6 7 8 | /* Responses from hook functions. */ #define NF_DROP 0 #define NF_ACCEPT 1 #define NF_STOLEN 2 #define NF_QUEUE 3 #define NF_REPEAT 4 #define NF_STOP 5 #define NF_MAX_VERDICT NF_STOP |
每个返回值的意义如下:
- 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 中定义如下:
1 2 3 4 5 6 7 | static inline int NF_HOOK(uint8_t pf, unsigned int hook, struct sk_buff *skb, struct net_device *in, struct net_device *out, int (*okfn)(struct sk_buff *)) { return NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN); } |
NF_HOOK() 函数直接调用了 NF_HOOK_THRESH() 函数,该函数定义如下:
1 2 3 4 5 6 7 8 9 | NF_HOOK_THRESH(uint8_t pf, unsigned int hook, struct sk_buff *skb, struct net_device *in, struct net_device *out, int (*okfn)(struct sk_buff *), int thresh) { int ret = nf_hook_thresh(pf, hook, skb, in, out, okfn, thresh); if (ret == 1) ret = okfn(skb); return ret; } |
这两个函数的参数变量类型,意义如下:
- 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()函数来执行钩子点上的各个函数,该函数定义如下:
1 2 3 4 5 6 7 8 9 10 | static inline int nf_hook_thresh(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 thresh) { if (nf_hooks_active(pf, hook)) return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh); return 1; } |
nf_hooks_active() 函数判断 nf_hooks[pf][hook] 指定的项是否为空。
nf_hook_slow() 函数的定义在 net/netfilter/core.c 文件中,该函数会依赖遍历调用nf_hooks[pf][hook]中优先级大于thresh的函数。
————————————————————
原创文章,转载请注明: 转载自孙希栋的博客
本文链接地址: 《内核netfilter框架简析》