一、netfilter 项目
netfilter 项目是 Linux 内核中用于数据包过滤的框架。
netfilter 项目有两个常用的子项目:iptables 和 nftables。
iptables 项目中包含了一系列用于配置 Linux 内核中包过滤规则集命令行工具:iptables,ip6tables,arptables,ebtables。
nftables 项目则是 iptables 项目的继承者,它允许更灵活、可扩展和性能更高的数据包分类。
本文只介绍使用 iptables 命令行工具管理 ip 协议时所需的相关内容。
二、iptables 中的基本概念
netfilter 框架的核心是在 Linux 内核处理数据包过程中的几个关键地方调用 NF_HOOK
函数,拦截数据包来进行处理。
图片来源:Netfilter hooks
利用 netfilter 框架可实现的功能包括检查、修改、转发、重定向和/或丢弃数据包。
- 根据用途的不同,将内核中处理数据包的代码组织成了几个不同的表(table)。
- 表由一组的链(chain)组成。
- 链中包含一系列规则(rule)。
- 规则由匹配条件和相应的执行动作组成(如果条件匹配成功,则执行相应的动作)。
- 链中的规则会按顺序的逐一执行,直到某个匹配的动作处理的结果是终止处理或预定义的链内的规则都执行完。
2.1 预定义的链
链的本质其实是 Linux 内核处理数据包的过程中,netfilter 框架嵌入 HOOK 函数的地方。
netfilter 框架在 Linux 内核处理很多协议(比如:ip,ip6,arp等)的过程中都嵌入了 HOOK 函数,本文只关注 iptables 处理 ip 协议用到的。
iptables 在每一个嵌入了 HOOK 函数的地方都预定义了一些链:
- PREROUTING:处理由网络设备进入的报文,包括由本机外部通过网络设备如
eth0
进入本机的报文,或者本机访问环回网络设备lo
后再次进入的报文。 - INPUT:报文经过 PREROUTING 链处理后,经过路由判断,判断出目标为本机时进入 INPUT 链,经过 INPUT 链处理后报文将被发往本机内的进程。
- FORWARD:报文经过 PREROUTING 链处理后,经过路由判断,判断出目标不为本机时进入 FORWARD 链,经过 FORWARD 链处理后将通过 POSTROUTING 链向外转发。
- OUTPUT:报文由本机内进程发出,经过路由处理后首先进入 OUTPUT 链,如果在此链上包含目标重定向规则,则会再次计算路由,在 OUTPUT 链完成处理后将进入 POSTROUTING 链进行处理。
- POSTROUTING:报文经过 FORWARD 链或 OUTPUT 链处理后,都将进入 POSTROUTING 链,在进入此链前已经完成了目标路由的计算,因此在本链中不能使用目标重定向规则,但可以使用 SNAT 修改报文的源地址,最后报文经过网络设备如
eth0
被发送到其他主机,或经过环回网络设备lo
再次回到 PREROUTING 链。
预定义的链支持设置默认的数据包处理策略。
# 将 INPUT 链的默认数据包处理策略设置为 ACCEPT(允许包通过)
sudo iptables -P INPUT ACCEPT
# 将 FORWARD 链的默认数据包处理策略设置为 DROP(丢弃所有的包)
sudo iptables -P FORWARD DROP
2.2 表
根据表的功能和链所在的位置,并不是每个表都需要所有的链。
Raw 表
在 iptables 1.2.9 版本之后新增的,优先级高于内置表 conntrack 及其他表,可用于决定是否继续执行本链上其他 iptables 表上配置的规则。比如 Raw 表在处理完成时,可跳过 conntrack 表内报文连接跟踪或 Nat 表内地址转换 DNAT/SNAT 处理。
包含的链:
- PREROUTING
- OUTPUT
Mangle 表
主要用于修改报文的 TOS、TTL,以及为报文设置标记(Mark),以实现 QoS 调整及策略路由等(Tproxy模式会用到)。在设计上,Mangle 表将处理进入当前所在链的每个报文,因此需要考虑比 Nat 表更复杂的情形。而在经过 Nat 表处理及连接建立后,报文收发路径将固定,因为不用考虑如何正确发送和接收连接内的后续报文。相较之下,Mangle 表除了需要处理连接建立时的握手报文 SYN、ACK 如何重定向,还需要考虑连接建立后如何正确收发此连接上的后续报文。
包含的链:
- PREROUTING
- INPUT
- FORWARD
- OUTPUT
- POSTROUTING。
Nat 表
主要用于修改报文的 IP 地址、端口号等信息,在经过 Nat 表处理后,报文的原始地址将被保存到 Socket 连接内,可以在应用中通过 getsockopt 获取。另外在设计上,Nat 表在开始处理前都会判断当前 conntrack 表内的连接记录状态,如果连接已经建立成功,则不再执行 Nat 规则。这样就保证了请求报文在连接建立阶段与后续报文的收发路径一直,因此逻辑比较清晰。
包含的链:
- PREROUTING:处理到达本机的报文,典型的如 DNAT 在路由转发前修改报文的目标地址及端口。
- INPUT:处理发往本机进程的报文,典型的如 SNAT 在发送到本机进程前修改报文的源地址及源端口。
- OUTPUT:处理由本机进程发出的报文,典型的如 MASQUERADE 修改发完二层网络设备的报文的源地址及源端口。注意,在进行 DNAT 后,内核将判断目标地址是否比进入 INPUT 链发生变化,如果有变化,则将重新计算报文路由。
- POSTROUTING:处理离开本机的报文,典型的如 SNAT 修改报文的源地址及源端口。
Filter 表
是 iptables 规则的默认表,如果在使用 iptables 命名行创建规则时未指定表,那么默认使用 Filter 表。 Filter 表主要用于过滤报文,根据具体规则决定是否放行该报文或进行日志记录(DROP、ACCEPT、REJECT、LOG)。
包含的链:
- INPUT:过滤流向本机进程的报文。
- FORWARD:过滤经过本机但目标地址不是本机的所有报文。
- OUTPUT:过滤本机进程产生的报文。
Security 表
用于完成与安全相关的标记功能。
- INPUT
- FORWARD
- OUTPUT
接下里根据 iptables 规则链处理报文的时机,来解析 5 种规则链的作用方式。如上图所示,网络设备接收的报文在进入内核协议栈后被 PREROUTING 链处理,可以在这里决定是否对报文进行目标地址转换。之后进行路由判断,如果报文的目标地址是本机,则内核协议栈会将其传给 INPUT 链处理。INPUT 链在允许通过后,报文有内核空间进入用户空间,被主机进程处理。如果 PREROUTING 链处理后的报文的目标地址不是本机地址,则将其传给 FORWARD 链处理,最后交给 POSTROUTING 链处理。
本机进程发出的报文首先经过 路由计算 来选择发送的目标网络设备,并且根据目标网络计算结果对源地址进行填充。需要说明的是,在填充时如果没有指定源地址,则将根据路由表找一个合适的源 IP 地址,保证通过此源 IP 地址对应的网络设备可以将报文发送到目标地址。如果已经手工配置(绑定)了源 IP 地址,但根据路由计算得知无法从此源 IP 地址对应的网络设备将报文发送到目标地址,则将此报文丢弃。一种特别情况是在 Socket 设置了 IP_TRANSPARENT
选项后将不做此检查,而是依赖配置的 策略路由 将报文发送到目标网络设备,其中策略路由指不基于报文目标网络地址(如报文标记等)进行转发的判断机制。经过路由计算后报文进入 OUTPUT 链,此时也可以进行报文目标地址转换,如果判断报文处理后的目标地址发生变化,则需要再次进行路由计算。完成处理的报文需要再次进行路由计算,检查并确保报文有对应的可以发送的网络设备。经过处理后的报文最终到达 POSTROUTING 链,在此处可以进行源地址转换。
在使用 iptables 规则时要注意一下事项:
iptables 属于三层网络处理机制,与协议栈内的二层网络设备属于同一网络空间。从上图可以看出,不论报文是进入协议栈还是从协议栈发出,从报文的处理角度来说,只会经过一次路由判断,路由计算是当报文发出时根据请求的目标地址所对应的发送网络设备所在的子网信息填写报文源地址等信息的处理过程,而路由判断则为判断已经填写完整的报文是否可以继续被 iptables 链处理或采用哪条 iptables 链处理。注意,报文从 FORWARD 链出来后不再进行路由判断,在路由判断中如果发现目标不可达,则将被丢弃。
在 POSTROUTING 链中, Nat 表只能添加 SNat 规则,因为在之前进行路由判断时,已经确定目标所适配的二层设备了,如果这里能够允许修改 DNat 规则,则还需要再次进行路由处理,这显然是不符合 iptables 处理逻辑的。
各个表在内核中的优先级定义如下(数值越小、优先级越高):
// include/uapi/linux/netfilter_ipv4.h
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_RAW_BEFORE_DEFRAG = -450,
NF_IP_PRI_CONNTRACK_DEFRAG = -400,
NF_IP_PRI_RAW = -300,
NF_IP_PRI_SELINUX_FIRST = -225,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_SECURITY = 50,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_SELINUX_LAST = 225,
NF_IP_PRI_CONNTRACK_HELPER = 300,
NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
NF_IP_PRI_LAST = INT_MAX,
};
即各个表、NAT 和 connection tracking 之间的优先从高到低为:
- Raw
- Create connection tracking
- Mangle
- DNAT
- Filter
- Security
- SNAT
- Confirm connection tracking
Tables↓/Chains→ | PREROUTING | INPUT | FORWARD | OUTPUT | POSTROUTING |
---|---|---|---|---|---|
(routing decision) | v | ||||
raw | v | v | |||
(create connection tracking) | v | v | |||
mangle | v | v | v | v | v |
nat (DNAT) | v | v | |||
(routing decision) | v | v | |||
filter | v | v | v | ||
security | v | v | v | ||
nat (SNAT) | v | v | |||
(confirm connection tracking) | v | v |
2.3 自定义的链
除了使用预定的链外,iptables还支持自定义链。数据包在处理的过程中能够通过 jump 动作跳转到同一个表的另一个链(这个链必须是自定义的,不能是预定于的)上继续处理。
同时,自定义的链不支持设置默认数据包处理策略,其默认的处理策略是返回父链中继续进行处理。
在下面的图中,在链 1 中的 规则 3 时匹配成功,跳到链 2 从头开始执行,最后通过链 2 返回到链 1 的规则 4 继续执行。
2.4 连接状态
Linux 内核中一个名为 conntrack
的模块提供了连接状态的跟踪功能,可以跟踪各种协议的连接并标记状态。需要注意的是,连接跟踪中的连接与 TCP/IP 中 TCP 的连接不是同一个概念。
连接主要有五种状态:
NEW
此状态表示数据包是系统看到的某个连接上的第一个数据包。比如,对于 TCP 来说,三次握手收到的一个包的状态就是这个状态。
不管是收到的第一个数据包,还是发出的第一个数据包都都算是第一个。
不管数据包是否是正常的,只要是第一个数据包都算。比如,对于 TCP 的三次握手来说,正常握手的第一个包中仅包括 SYN 标记,但是如果第一个包中包含了错误的标记(如:SYN,RST),也会算是第一个数据包。
ESTABLISHED
此状态表示系统在通信的双向都看到了流量通信,并且后续的数据包都会是这个状态。只需要一个机器发送一个数据包,并收到想要的应答数据包后,连接就会进入此状态(即使收到的是一个 ICMP 的错误报告)。
RELATED
此状态表明一个连接和另一个已经处于 ESTABLISHED 状态的连接有关时,就会被认为是这个状态。
连接进入此状态的前提是已有一个处于 ESTABLISHED 状态,并且 Linux 内核能够理解这个连接,同时这个连接另外再产生了一个新的连接,这个新的连接的状态就是 RELATED 。比如 FTP 的 Data 连接就是根据 FTP 的 Control 连接产生的,所以 FTP 的 Control 连接是 ESTABLISHED 状态,FTP 的 Data 连接是 RELATED 状态。
INVALID
此状态表明数据包不能被识别或者它没有任何状态。导致的原因可能是系统内存不足或是 ICMP 的错误报告不能匹配上本地的任何连接。一般而言,推荐丢弃此状态的数据包。
UNTRACKED
此状态表明数据包被标记为了不跟踪,同时与此连接相关的原本是 RELATED 状态的连接也会是此状态。
内核对于每一种网络包,都设置了两个记录点:
- 对于发给本机的包,在 PREROUTING 调用
ipv4_conntrack_in
,创建连接跟踪记录;在 INPUT 调用ipv4_confirm
,将这个连接跟踪记录挂在内核的连接跟踪表里面。 - 对于本地发出的包,在 OUTPUT 调用
ipv4_conntrack_local
,创建连接跟踪记录;在 POSTROUTING 调用ipv4_confirm
,将这个连接跟踪记录挂在内核的连接跟踪表里面。 对于经过本机的包,在 PREROUTING 调用
ipv4_conntrack_in
,创建连接跟踪记录;在 POSTROUTING 调用ipv4_confirm
,将这个连接跟踪记录挂在内核的连接跟踪表里面。对于经过本机的 NAT 包,在 PREROUTING 调用
ipv4_conntrack_in
,创建连接跟踪记录,并调用nf_nat_ipv4_in
进行地址转换;在 POSTROUTING 调用nf_nat_ipv4_out
进行地址转换,并调用ipv4_confirm
,将这个连接跟踪记录挂在内核的连接跟踪表里面。
因为有 filter 表可能会把包过滤掉,也就是丢弃了,所以才需要两个记录点。
三、iptables 规则的匹配条件
参考:Iptables matches。
四、iptables 规则的执行动作
参考:Iptables targets and jumps。
五、常见问题
1. NAT 能建立多少连接
DNAT 即外网访问内网的某个服务,是没有端口数目限制的。
SNAT 即内网访问外网的场景,鉴于 conntrack 是由 {源 IP,源端口,目标 IP,目标端口},hash 后确定的。所以如果内网机器很多,但是访问的是不同的外网,也即目标 IP 和目标端口很多,这样内网可承载的数量就非常大,可不止 65535 个。
但是如果内网所有的机器,都一定要访问同一个目标 IP 和目标端口,这样源 IP 如果只有一个,这样的情况下,才受 65535 的端口数目限制,根据原理,一种方法就是多个源 IP,另外的方法就是多个 NAT 网关,来分摊不同的内网机器访问。
2. iptables 的规则对 Unix Domain Socket 无效
3. nfmark 和 ctmark
nfmark
标记为网络协议栈的三层报文本身所携带,可配合 策略路由 影响路由计算结果。
ctmark
标记为 conntrack
表中对每个 Socket 连接维护的记录项,由 conntrack
表自身维护且与 TCP 状态不同,可以理解为帮助 iptables 规则判断 TCP 状态而存在,可以用 Mangle
表中的 iptables 规则,比如将当前连接上的 ctmark
标记复制到报文的 nfmark
标记上,保证响应报文继续采用 策略路由 进行处理等,同时 Nat
表依赖 conntrack
表内的连接记录状态决定是否跳过 Nat
表上的规则。
一些例子
下面的例子都应该在
Mangle
表中。
匹配规则 | 功能描述 |
---|---|
-A PREROUTING -p tcp -m mark --mark 0x539 -j CONNMARK --save-mark --nfmask 0xffffffff --ctmask 0xffffffff |
对进入 PREROUTING 链且携带 nfmark=0x539 标记的 TCP 报文,调用 CONNMARK 规则将 nfmark=0x539 标记保存到连接标记 ctmask 上 |
-A OUTPUT -o lo -p tcp -m mark --mark 0x539 -j RETURN |
对进入 OUTPUT 链、目标地址为本地且已经携带 nfmark=0x539 标记的 TCP 报文,不再做特殊处理 |
-A OUTPUT ! -d 127.0.0.1/32 -o lo -p tcp -m owner --uid-owner 1337 -j MARK --set-xmark 0x53a/0xffffffff |
对进入 OUTPUT 链且目标地址为非 127.0.0.1 但目标网络设备为本地,即目标为本机地址的由 UID 为 1337 进程发出的 TCP 报文,添加 nfmark=0x53a 标记 |
-A OUTPUT -p tcp -m connmark --mark 0x539 -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff |
对进入 OUTPUT 连接且四层连接已经携带 ctmark=0x539 标记的 TCP 报文,经过 CONNMARK 规则恢复到三层报文的 nfmark 标记上 |
-A ISTIO_TPROXY ! -d 127.0.0.1/32 -p tcp -j TPROXY --on-port 15006 --on-ip 0.0.0.0 --tproxy-mark 0x539/0xffffffff |
对进入 ISTIO_TPROXY 链的目标地址为非 127.0.0.1 的 TCP 报文,经过 TPROXY 规则的处理,直接发送到本地网络监听端口 0.0.0.0:15006,并为其设置 nfmark=0x539 标记 |