写在前面
本系列不是介绍How to
配置iptables
的文章。因为网络上已经有很多这类型的教程了,其中一些还不错(比如链接).
本系列也不是一般意义上的Netfilter
源码分析文章。因为大段粘贴代码也会让人心生畏惧和厌烦!
本系列文章的目标是,用尽量少的文字和图片讲明白How Netfilter work
Netfilter 的基本概念
Netfilter
是一套融入在Linux
内核网络协议栈中的报文处理(过滤
或者修改
)框架。它在内核中报文的关键流动路径上定义了5
个HOOK
点(下图蓝色方框),各个协议(如IPv4
、IPv6
、ARP
)可以在这些HOOK
点安装钩子函数,报文流经此地,内核会按照优先级调用这些钩子函数,这些钩子函数最终会决定报文是被NF_ACCEPT
(放行)还是NF_DROP
(丢弃)。
图中红色虚线表示内核最常见的报文流经的路径:本机接收、转发、本机发送。5
个HOOK
点分别是:路由前、本地上送、转发、本地发送、路由后1
链(chain) & 表(table)
初次接触iptables
的同学可能会被四表五链
这个名字吓到,特别是链
这个名字真的很容易令人困惑! 而当你了解了Netfilter
的实现细节后,才会发现:噢,原来链
就是HOOK
点,HOOK
点就是链
,因为有5
个HOOK
点,所以有五链
!
那么,为什么要叫链
呢?
因为一个HOOK
点可以上可以安装多个钩子, 内核用“链条”将这些钩子串起来!
相比之下,四表(table)
就没那么神秘了: 起过滤作用的filter
表、起NAT
作用的nat
表,用于修改报文的mangle
表,用于取消连接跟踪的raw
表。
Netfilter
设计多个表的目的,一方面是方便分类管理,另一方面,更重要的是为了限定各个钩子(或者说用户规则)执行的顺序!
以PREROUTING
这个HOOK
点为例,用户使用iptables
设置的NAT
规则和mangle
会分别挂到nat hook
和mangle hook
,NAT
表的优先级天生比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_IPV6
的hook
;第二个维度是链
,对于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 namespace为net
的hooks[NFPROTO_IPV4][NF_INET_PRE_ROUTING]
链表上的元素,也就是上面图中的第一行的链表。如果三个钩子函数执行的结果(verdict
)都是NF_ACCEPT
,那么NF_HOOK
指定的ip_rcv_finish
就会被执行。