Switch-Router

Netfilter是如何工作的(一):HOOK点

Published at 2019-06-11 | Last Update 2019-06-11

写在前面

本系列不是介绍How to配置iptables的文章。因为网络上已经有很多这类型的教程了,其中一些还不错(比如链接).

本系列也不是一般意义上的Netfilter源码分析文章。因为大段粘贴代码也会让人心生畏惧和厌烦!

本系列文章的目标是,用尽量少的文字和图片讲明白How Netfilter work

Netfilter 的基本概念

Netfilter是一套融入Linux内核网络协议栈中的报文处理(过滤或者修改)框架。它在内核中报文的关键流动路径上定义了5HOOK点(下图蓝色方框),各个协议(如IPv4IPv6ARP)可以在这些HOOK点安装钩子函数,报文流经此地,内核会按照优先级调用这些钩子函数,这些钩子函数最终会决定报文是被NF_ACCEPT(放行)还是NF_DROP(丢弃)。

图中红色虚线表示内核最常见的报文流经的路径:本机接收转发本机发送5HOOK点分别是:路由前本地上送转发本地发送路由后1

链(chain) & 表(table)

初次接触iptables的同学可能会被四表五链这个名字吓到,特别是这个名字真的很容易令人困惑! 而当你了解了Netfilter的实现细节后,才会发现:噢,原来就是HOOK点,HOOK点就是,因为有5HOOK点,所以有五链

那么,为什么要叫呢?

因为一个HOOK点可以上可以安装多个钩子, 内核用“链条”将这些钩子串起来!

相比之下,四表(table)就没那么神秘了: 起过滤作用的filter表、起NAT作用的nat表,用于修改报文的mangle表,用于取消连接跟踪的raw表。

Netfilter设计多个表的目的,一方面是方便分类管理,另一方面,更重要的是为了限定各个钩子(或者说用户规则)执行的顺序!

PREROUTING这个HOOK点为例,用户使用iptables设置的NAT规则和mangle会分别挂到nat hookmangle hookNAT表的优先级天生比mangle表低,因此报文一定会先执行mangle表的规则。

这就是 四表五链 的概念。我个人认为的比重要多了. 因为就算Netfilter没有表的概念,那么通过小心翼翼地设置各个rule的顺序其实也可以达到相同的效果。但(也就是HOOK点)的作用是独一无二的。换个角度,用户在配置iptables规则时,更多的精力也是放在“应该在哪个HOOK点进行操作”,至于用的是filter表、nat表还是其他表,其实都是顺理成章的事情。

Hook

HOOK 点的位置

用户通过iptables配置的规则最终会记录在HOOK点。HOOK点定义在struct net结构中,即HOOK点是各个net namespace中独立的。所以,在使用容器的场景中,每个容器的防火墙规则是独立的。

struct net {
    /* code omitted */
    struct netns_nf		nf;
    /* code omitted */
}

struct netns_nf {
    /* code omitted */
	struct list_head hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
};

从上面的定义可以看到,HOOK点是一个二维数组,每个元素都是一个链表头。它的第一个维度是协议类型,其中最常用的NFPROTO_IPV4,我们使用的iptables命令都是将这个钩子安装到这个协议的hook,而使用ip6tables就是将钩子安装到NFPROTO_IPV6hook;第二个维度是,对于IPV4来说,它的取值范围如下:

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,
}

HOOK 点的元素

hooks的每个元素都是链表头,链表上挂的元素类型是struct nf_hook_ops,这些元素有两个来源,一类来自于Netfilter初始化时各个表(如filter)的初始化,另一类来自于如连接跟踪这样的内部模块。下图展示了第一类来源的元素的挂接情况,它们按优先级排列(数字越小优先级越高),而.hook就是报文到达对应的路径时会执行的钩子函数。

附:相关内核函数的例子

iptable_filter_init 
  |--xt_hook_link
    |-- nf_register_hooks
       |-- nf_register_hook

HOOK 点的调用

Netfilter框架已经完全融入内核协议栈了,所以在协议栈代码中常常可以看到NF_HOOK宏的调用,这个宏的参数指定了HOOK点。

以本机收到IPv4报文为例

int ip_rcv(struct sk_buff* skb,...)
{
   // code omitted
   return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, net, NULL, skb, dev, NULL, ip_rcv_finish); 
   // code omitted
}

它指定要遍历的钩子函数是net namespacenethooks[NFPROTO_IPV4][NF_INET_PRE_ROUTING]链表上的元素,也就是上面图中的第一行的链表。如果三个钩子函数执行的结果(verdict)都是NF_ACCEPT,那么NF_HOOK指定的ip_rcv_finish就会被执行。