Skip to content

Commit 81d1843

Browse files
committed
firewall: Add conntrack
1 parent d47ec68 commit 81d1843

File tree

5 files changed

+125
-24
lines changed

5 files changed

+125
-24
lines changed

docs/images/netfilter-kernel-view-tables.svg

Lines changed: 1 addition & 1 deletion
Loading

docs/ops/network/firewall.md

Lines changed: 104 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ icon: material/wall-fire
1212

1313
Linux 内核网络栈中的防火墙模块称为 Netfilter,负责对进出主机的数据包进行过滤和修改。Netfilter 提供了一套强大的工具,用于实现各种防火墙功能,如包过滤、网络地址转换(NAT)和连接跟踪等。
1414

15-
## Netfilter 阶段 {#netfilter-chains}
15+
## Netfilter {#netfilter}
1616

17-
Netfilter 将数据包的处理过程划分为 5 个阶段,并在每个阶段提供 hook 点,允许用户定义规则来控制数据包的流动。
18-
这些阶段包括:
17+
### Netfilter 阶段 {#netfilter-chains}
18+
19+
Netfilter 将数据包的处理过程划分为 5 个阶段,并在每个阶段提供 hook 点,允许用户定义规则来控制数据包的流动。这些阶段包括:
1920

2021
PREROUTING / `NF_INET_PRE_ROUTING`
2122

@@ -47,7 +48,7 @@ POSTROUTING / `NF_INET_POST_ROUTING`
4748

4849
在上图中,ROUTE 指[路由决策](routing.md)
4950

50-
需要指出的是,网上的许多示意图中缺少了由 OUTPUT 阶段经过路由决策后进入 INPUT 阶段的路径,或许是出于图片结果简化的考虑
51+
需要指出的是,网上的许多示意图中缺少了由 OUTPUT 阶段经过路由决策后进入 INPUT 阶段的路径,或许是出于简化图片的考虑
5152
这条路径在实际中是存在的,即所有由本机发往本机(回环接口)的数据包都会依次经过 OUTPUT 和 INPUT 两个阶段,典型的场景是使用 `localhost``127.0.0.1` 访问本机服务。
5253

5354
!!! question "路由决策与 Reroute check 是什么关系?"
@@ -58,18 +59,109 @@ POSTROUTING / `NF_INET_POST_ROUTING`
5859
{#wikipedia-netfilter-packet-flow}
5960

6061
它与本文的图示有一处微妙的区别:路由决策位于 OUTPUT 阶段之前,而 OUTPUT 阶段后另有一个 Reroute check。
61-
事实上 Wikipedia 的图是更加准确的,但在大多数情况下,将路由决策视作位于 OUTPUT 之后更容易理解,尤其是这样能保持 OUTPUT 阶段与 PREROUTING 阶段的相似性(均在路由决策之前)。
62+
事实上此图是更加准确的,但在大多数情况下,将路由决策视作位于 OUTPUT 之后更容易理解,尤其是这样能保持 OUTPUT 阶段与 PREROUTING 阶段的相似性(均在路由决策之前)以及路由结果的准确性
6263
本文在介绍 iptables 的表时绘制了 [Netfilter 视角的阶段图](#netfilter-kernel-view-tables),能够更直观地反映出此「相似性」。
6364

65+
??? info "Reroute check 的细节(待补充)"
66+
6467
### Hook 的优先级 {#netfilter-hook-priorities}
6568

6669
Netfilter 为 hook 定义了一系列优先级,优先级越高的 hook 越早执行。特别地,iptables 的各个表是注册在对应的优先级上的,因此不同表的处理顺序也由 hook 的优先级决定。
6770

6871
在同一个阶段中,不同 hook 的处理顺序为 raw → (conntrack) → mangle → nat (DNAT) → filter → security → nat (SNAT)。
69-
该顺序定义在 [`enum nf_ip_hook_priorities`](https://elixir.bootlin.com/linux/v6.17.8/source/include/uapi/linux/netfilter_ipv4.h#L30) 中。
72+
该顺序定义在 [`enum nf_ip_hook_priorities`](https://elixir.bootlin.com/linux/v6.17.8/source/include/uapi/linux/netfilter_ipv4.h#L30),数值越小则优先级越高
7073

7174
### conntrack {#conntrack}
7275

76+
连接跟踪器(**Conn**ection **Track**er,conntrack,也经常简称为 CT)是 Netfilter 的一个核心组件,用于跟踪网络连接的状态。
77+
78+
#### 连接跟踪 {#connection-tracking}
79+
80+
Conntrack 表的一个重要作用是支持有状态防火墙,允许 Netfilter 组件获取连接状态,并据此做出过滤决策。
81+
一个典型的例子是,允许已建立连接的数据包通过防火墙,而仅过滤新连接请求。
82+
由于 iptables 和 nftables 的规则链都是按顺序线性执行的,若在规则链开头插入「允许 conntrack 状态为已建立(ESTABLISHED)」的规则,就能减少大量数据包的匹配开销。例如:
83+
84+
=== "iptables / ip6tables"
85+
86+
```shell
87+
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
88+
```
89+
90+
=== "nftables"
91+
92+
```shell
93+
nft add rule ip filter input ct state established,related accept
94+
```
95+
96+
#### 连接标记 {#conntrack-mark}
97+
98+
Conntrack 除了记录连接的五元组(四层协议、源地址、目的地址、源端口、目的端口)外,还可以为连接记录一个「标记」(conntrack mark,`CONNMARK`)。
99+
该标记可以在 iptables 或 nftables 规则中从数据包保存或恢复到数据包上,实现「数据包标记」与「连接标记」的双向互动,例如:
100+
101+
=== "iptables / ip6tables"
102+
103+
```shell
104+
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
105+
iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark
106+
```
107+
108+
=== "nftables"
109+
110+
```shell
111+
nft add rule ip mangle prerouting meta mark set ct mark
112+
nft add rule ip mangle postrouting ct mark set mark
113+
```
114+
115+
#### NAT 支持 {#conntrack-nat}
116+
117+
在 Netfilter 中,用户定义的 NAT 规则只会对**新连接**生效,而已建立的连接的后续数据包则由 conntrack 负责处理 NAT,确保同一连接内的所有数据包都能被正确处理。
118+
119+
例如,在一个典型的家用路由器上,当内网主机向外网发起连接时,路由器上的 NAT 规则会将数据包的源地址改写为路由器的 WAN 口地址,整个流程如下:
120+
121+
- 内网主机对外建立一个新的连接,发送第一个数据包到路由器
122+
- 第一个数据包经过 POSTROUTING 阶段,被 MASQUERADE 改写源地址
123+
- 此时 conntrack 记录该连接**双向**的五元组(共 9 个字段,其中协议号只需记录一次),包括正向(original)的四元组和反向(reply)的四元组
124+
- 后续的数据包(不论方向)经过该路由器时,在 conntrack 阶段(`NF_IP_PRI_CONNTRACK`)匹配到某一方向的四元组,由 conntrack 改写为另一方向的四元组的反向地址[^nf_nat_manip_pkt],使对端主机能够正确接收数据包。
125+
126+
[^nf_nat_manip_pkt]: 参见 [`nf_nat_manip_pkt`](https://elixir.bootlin.com/linux/v6.17.8/source/net/netfilter/nf_nat_proto.c#L383) 函数。
127+
128+
#### conntrack 命令 {#conntrack-command}
129+
130+
[`conntrack(8)`][conntrack.8] 可以查看和管理内核中的 conntrack 表,其记录了所有经过主机的数据包的连接状态信息。
131+
132+
最常用的命令是列出当前的连接跟踪条目:
133+
134+
```shell
135+
conntrack -L
136+
```
137+
138+
!!! example "conntrack 输出示例"
139+
140+
```text
141+
udp 17 91 src=192.0.2.2 dst=8.8.8.8 sport=39043 dport=53 src=8.8.8.8 dst=198.51.100.1 sport=53 dport=39043 [ASSURED] mark=1 use=1
142+
```
143+
144+
- 协议:`udp`(协议号 17)
145+
- 剩余超时时间:91 秒
146+
- 正向四元组 src, dst, sport, dport
147+
- 反向四元组 src, dst, sport, dport
148+
149+
本例中,当前主机为负责进行 NAT 的出口路由器,所以反向的 dst 地址为路由器的 WAN 口地址。
150+
151+
- 连接状态标记,如 `[ASSURED]`
152+
- 连接标记 `mark`
153+
- 引用计数器 `use`
154+
155+
`conntrack` 命令支持一些与 iptables 语法相同的匹配条件,可以用来过滤输出的连接条目,例如:
156+
157+
```shell
158+
conntrack -L -p udp --dport 53
159+
```
160+
161+
`conntrack` 命令也可以对 conntrack 表进行修改操作,但相比于查询类操作较为不常用,因此具体用法可以参考 [conntrack(8)][conntrack.8]
162+
163+
另外,`conntrack -E` 命令可以实时监控 conntrack 表的变化,适合用于调试和分析网络连接的动向。`-E` 操作同样支持匹配条件,可以过滤出特定的连接事件,方便查找和分析。
164+
73165
## iptables {#iptables}
74166

75167
iptables 是 Netfilter 的用户空间工具,用于管理防火墙规则。
@@ -120,7 +212,7 @@ iptables -P FORWARD ACCEPT
120212
iptables -P OUTPUT ACCEPT
121213
```
122214

123-
注意到 `-L`, `-S`, `-F`, `-X`, `-Z` 这些命令都可以省略链名参数,表示对所有链进行操作。
215+
注意到 `-L`, `-S`, `-F`, `-X`, `-Z` 这些操作都可以省略链名参数,表示对所有链进行操作。
124216

125217
完整的 iptables 规则语法和选项可以参考 [iptables(8)][iptables.8][iptables-extensions(8)][iptables-extensions.8] 手册页。
126218

@@ -186,7 +278,7 @@ iptables -P OUTPUT ACCEPT
186278
- MARK:为数据包打上防火墙标记(firewall mark),通常与路由策略结合使用。
187279
- CONNMARK:为数据包对应的 conntrack 连接打上标记,或从连接中恢复标记。
188280

189-
每条内置链都有一个**默认策略**`-P` / `--policy`),当数据包经过该链但未匹配到任何规则时,会由该默认策略处理。默认策略可以只能是 ACCEPT、DROPREJECT
281+
每条内置链都有一个**默认策略**`-P` / `--policy`),当数据包经过该链但未匹配到任何规则时,会由该默认策略处理。默认策略只能是 ACCEPT 或 DROP
190282

191283
!!! example "例:科大镜像站上限制 80 / 443 端口并发连接数"
192284

@@ -226,8 +318,10 @@ nat
226318
需要注意的是,nat 表的 PREROUTING 和 OUTPUT 链只能使用 DNAT 目标,而 INPUT 和 POSTROUTING 链只能使用 SNAT(或 MASQUERADE)目标。
227319
这是因为 iptables 将 Netfilter 的两种 hook 优先级(`NAT_DST` 和 `NAT_SRC`)都放进了 nat 表,因此尽管四个链都在 nat 表中,它们实际上是分属于两种不同的 Netfilter hook。
228320

229-
特别的是,仅有建立新连接的数据包会经过 nat 表,而已经建立连接的数据包不会经过 nat 表,而是由 conntrack 模块处理。
230-
对于用户而言,可以理解为「nat 表自带 `--ctstate NEW` 约束,之后的数据包都使用已经转换后的地址进行通信」。
321+
特别的是,仅有建立新连接的数据包会经过 nat 表,而已经建立连接的数据包不会经过 nat 表,而是由 [conntrack 模块](#conntrack)处理。
322+
对于用户而言,可以理解为「nat 表自带 `--ctstate NEW` 约束[^nf_nat_inet_fn],之后的数据包都使用已经转换后的地址进行通信」。
323+
324+
[^nf_nat_inet_fn]: 该约束事实上是「NEW 或 RELATED」,具体可参考 [`nf_nat_inet_fn`](https://elixir.bootlin.com/linux/v6.17.8/source/net/netfilter/nf_nat_core.c#L936) 函数。
231325

232326
mangle
233327

docs/ops/network/routing.md

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ local 192.0.2.1 dev eth0 proto kernel scope host src 192.0.2.1
102102
broadcast 192.0.2.255 dev eth0 proto kernel scope link src 192.0.2.1
103103
```
104104

105-
### 自定义路由表 {#more-routing-tables}
105+
### 自定义路由表 {#custom-routing-tables}
106106

107107
内核中有多张路由表,以编号区别,默认的路由表有三张:
108108

@@ -112,7 +112,8 @@ broadcast 192.0.2.255 dev eth0 proto kernel scope link src 192.0.2.1
112112
| main | 254 |
113113
| default | 253 |
114114

115-
其中 local 表和 main 表会由内核自动维护一些规则(如上一段所述),如本地路由等。其余路由表则需要用户手动管理。
115+
路由表的编号仅用于区分,不影响路由表的功能和优先级(优先级由路由规则决定)。
116+
其中 local 表和 main 表会由内核自动维护一些规则(如上一段所述),如本地路由等。其余的路由表则需要用户手动管理。
116117

117118
在下文的「策略路由」部分,由于单一的路由表功能非常有限,自定义更多的路由表是实现复杂路由策略的基础。
118119

@@ -184,16 +185,6 @@ $ ip -6 rule
184185

185186
1. IPv6 的默认路由规则不包括 `default` 路由表,尽管你可以手动添加该规则。
186187

187-
在内核中,每个路由表都有一个编号,默认的路由表编号如下:
188-
189-
| 路由表 | 编号 |
190-
| :-----: | :---: |
191-
| local | 255 |
192-
| main | 254 |
193-
| default | 253 |
194-
195-
路由表的编号仅用于区分,不影响路由表的功能和优先级(优先级由路由规则决定)。
196-
197188
### `ip rule` 命令 {#ip-rule}
198189

199190
`ip rule` 命令用于管理路由规则,其默认操作是显示当前的路由规则列表(等价于 `ip rule list`)。
@@ -208,6 +199,17 @@ ip rule add from 192.0.2.0/24 table 100 pref 1000
208199

209200
由于内核不保证相同优先级的规则之间的顺序,因此我们建议仅为逻辑上完全互斥的规则使用相同的优先级,避免出现非预期的行为。
210201

202+
此时你可以在路由规则列表中看到新添加的规则:
203+
204+
```console
205+
$ ip rule
206+
[...]
207+
1000: from 192.0.2.0/24 lookup eth0
208+
[...]
209+
```
210+
211+
如果你在[前文](#custom-routing-tables)中为路由表 100 定义了名称映射(如 `eth0`),`ip rule` 命令就会显示该名称,便于识别。
212+
211213
### 路由策略匹配规则 {#routing-policy-matching}
212214

213215
路由规则的匹配条件可以包含多个字段,常用的匹配条件包括:
@@ -229,7 +231,7 @@ ip rule add from 192.0.2.0/24 table 100 pref 1000
229231

230232
- from 条件既适用于本机发出的数据包(需要 socket 已经 `bind()` 到一个 IP 地址上),也适用于经过本机转发的数据包(此时数据包的源地址是已知的)。
231233

232-
如果要匹配没有 `bind()` 的数据包,可以使用 `from 0.0.0.0``from ::`。特别注意这里的 `from 0.0.0.0` 等价于 `from 0.0.0.0/32`,而非 `from all``from 0.0.0.0/0`
234+
如果要匹配没有 `bind()` 的数据包,可以使用 `from 0.0.0.0``from ::`(即零地址,注意不是前缀为零的 CIDR)
233235

234236
- fwmark 条件匹配防火墙的数据包标记,通常与 Netfilter 的 `MARK` 目标结合使用,实现更复杂的路由策略。
235237
- iif 条件匹配数据包的输入接口,其中由本机发出的数据包可以用 `iif lo` 匹配。
@@ -243,6 +245,8 @@ ip rule add from 192.0.2.0/24 table 100 pref 1000
243245
[^oif-why]: 否则,你觉得在路由决策阶段,内核怎么可能知道数据包将要从哪个接口发出呢?
244246
[^port-protocol]: 其实还有 DCCP 和 SCTP 协议也支持端口号,但这两种协议较少使用,因此本文在此不做赘述。
245247

248+
## 路由案例 {#routing-examples}
249+
246250
### 源进源出 {#source-based-routing}
247251

248252
一种常见的做法是,新增一条优先级非常高的规则指向 main 表,然后为每个接口指定一张专属路由表,实现本机流量的「源进源出」:

includes/man.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
<!-- Note: Debian man pages for useradd(8)/userdel(8) mentions the "low-level" feature and recommends adduser(8)/deluser(8) instead.
7171
Do not link to a "generic" man page for these commands -->
7272
[adduser.8]: https://manpages.debian.org/stable/adduser/adduser.8.en.html
73+
<!-- None of Debian, man7.org or linux.die.net provides conntrack(8), weird -->
74+
[conntrack.8]: https://man.archlinux.org/man/conntrack.8.en
7375
[deluser.8]: https://manpages.debian.org/stable/adduser/deluser.8.en.html
7476
[iptables.8]: https://www.man7.org/linux/man-pages/man8/iptables.8.html
7577
[iptables-extensions.8]: https://www.man7.org/linux/man-pages/man8/iptables-extensions.8.html

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ theme:
3232
- content.code.annotate
3333
- content.code.copy
3434
- content.footnote.tooltips
35+
- content.tabs.link
3536
- navigation.indexes
3637
- navigation.path
3738
- navigation.sections

0 commit comments

Comments
 (0)