写在前面
本系列不是介绍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
就会被执行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。