3_iptables进阶与连接跟踪
1. Conntrack 连接跟踪:iptables 的 “ 记忆力 “
连接跟踪(Connection Tracking,简称 conntrack)是 netfilter 最核心的子系统之一,也是理解有状态防火墙的关键。
1.1 什么是连接跟踪?
类比酒店前台登记簿:
没有登记簿的酒店(无状态防火墙):每个人每次进出都要重新验证身份证、重新登记,效率极低。有登记簿的酒店(有状态防火墙):第一次入住登记后,凭房卡自由进出,前台翻登记簿就知道 “ 此人是合法住客 “。退房后从登记簿中删除。
conntrack 就是这个 “ 登记簿 “——它记录每个网络连接的状态信息(源 IP、目标 IP、源端口、目标端口、协议、状态)。有了它,iptables 能区分 “ 这是已建立连接的回包 “ 还是 “ 一个全新的、未授权的请求 “。
1.2 五种报文状态
conntrack 将经过的每个数据包标记为以下 5 种状态之一:
| 状态 | 含义 | 类比 |
|---|---|---|
| NEW | 连接的第一个包(如 TCP SYN) | 新客人来办入住 |
| ESTABLISHED | 已建立连接的后续包(双向都已有流量) | 住客刷房卡进出 |
| RELATED | 与已有连接相关的新连接(如 FTP 数据通道) | 住客家属凭住客信息关联入住 |
| INVALID | 无法识别或不合法的包 | 拿着假房卡的人 |
| UNTRACKED | 被 raw 表标记为不追踪的包 | VIP 免登记通道 |
下图展示 TCP 三次握手和四次挥手过程中,conntrack 如何标记每个报文的状态:
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3B82F6', 'actorTextColor': '#1E3A5F', 'actorBorder': '#2563EB', 'signalColor': '#60A5FA', 'activationBkgColor': '#DBEAFE', 'activationBorderColor': '#3B82F6'}}}%%
sequenceDiagram
autonumber
participant C as "客户端"
participant S as "服务端"
Note over C,S: "三次握手"
C->>S: "SYN"
Note over C,S: "conntrack 标记为 NEW"
S->>C: "SYN+ACK"
Note over C,S: "conntrack 看到双向流量, 标记为 ESTABLISHED"
C->>S: "ACK"
Note over C,S: "保持 ESTABLISHED"
C->>S: "数据传输..."
Note over C,S: "保持 ESTABLISHED"
Note over C,S: "四次挥手"
C->>S: "FIN"
S->>C: "ACK"
S->>C: "FIN"
C->>S: "ACK"
Note over C,S: "conntrack 条目进入 TIME_WAIT, 超时后删除"conntrack 的 “ 连接 “ 概念比 TCP 更广。即使无连接的 UDP 和 ICMP,conntrack 也会追踪——只要收到一对 “ 请求 - 响应 “,即视为 ESTABLISHED。
1.3 查看和管理 Conntrack 表
1 | # 安装 conntrack 工具 |
示例输出:
1 | tcp 6 431999 ESTABLISHED src=10.0.0.5 dst=203.0.113.10 sport=49234 dport=443 |
每条记录包含:协议、超时时间、状态、正向四元组、反向四元组(用于 NAT 回包)。
1.4 Conntrack 表满——生产中的常见故障
conntrack 在高并发场景下有一个典型故障:表被撑满后新连接无法建立。
症状:服务器突然无法建立新连接,dmesg 出现:
1 | nf_conntrack: table full, dropping packet |
原因:conntrack 表有大小限制(默认通常为 65536 或 262144),高并发场景下可能被撑满。
下面是排查和处理流程:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart TD
A["服务器新连接失败"] --> B{"dmesg 是否出现 nf_conntrack: table full?"}
B -->|"是"| C["确认 conntrack 表满"]
B -->|"否"| D["排查其他原因"]
C --> E["查看 conntrack_count vs conntrack_max"]
E --> F{"接近上限?"}
F -->|"是"| G["应急: 调大 conntrack_max"]
F -->|"否"| D
G --> H["优化: 缩短超时时间"]
H --> I["写入 sysctl.conf 永久生效"]
classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
classDef success fill:#10B981,stroke:#059669,color:#fff
classDef warning fill:#F59E0B,stroke:#D97706,color:#fff
classDef danger fill:#EF4444,stroke:#DC2626,color:#fff
class A danger
class C,E primary
class G,H,I success
class D warning排查命令:
1 | # 查看是否接近上限 |
应急处理:
1 | # 临时调大(立即生效,重启失效) |
理解了 conntrack 的运行机制和常见故障,接下来看如何在规则中利用连接状态来简化防火墙配置。
2. State 扩展模块:连接跟踪的实战应用
2.1 有状态防火墙 Vs 无状态防火墙
无状态防火墙:每个包独立判断,不关心这个包属于哪个连接。配置繁琐——必须为每个网络服务(Web、SSH、MySQL 等)分别写 “ 放行请求 “ 和 “ 放行响应 “ 两条规则。
有状态防火墙:利用 conntrack 追踪连接状态。新连接仍然由具体端口规则放行,但一旦连接建立(进入 ESTABLISHED 状态),后续所有往返流量由一条 ESTABLISHED 规则统一放行,不再需要为每个服务单独写响应规则。
1 | # 无状态做法:每个网络服务都要写两条——一条管请求进来,一条管响应出去 |
刚开机时 conntrack 表是空的,第一条 ESTABLISHED 规则匹配不到任何包,相当于不存在。新连接(状态为 NEW)由后面的端口规则放行。一旦客户端和服务器完成握手,conntrack 将该连接标记为 ESTABLISHED,后续的数据包就走第一条 “ 快速通道 “ 直接放行,不再逐条遍历后面的规则。
两种模式的差异可以这样理解:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart LR
subgraph stateless [无状态防火墙]
direction TB
A1["入站请求 :80"] -->|"规则1: ACCEPT"| B1["放行"]
A2["出站响应 :80"] -->|"规则2: ACCEPT"| B2["放行"]
A3["入站请求 :22"] -->|"规则3: ACCEPT"| B3["放行"]
A4["出站响应 :22"] -->|"规则4: ACCEPT"| B4["放行"]
end
subgraph stateful [有状态防火墙]
direction TB
C1["所有已建立连接的回包"] -->|"规则1: ACCEPT"| D1["放行"]
C2["入站请求 :80"] -->|"规则2: ACCEPT"| D2["放行"]
C3["入站请求 :22"] -->|"规则3: ACCEPT"| D3["放行"]
end
classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
classDef success fill:#10B981,stroke:#059669,color:#fff
class B1,B2,B3,B4,D1,D2,D3 success2.2 State 匹配的典型用法
1 | # 放行已建立和相关连接(几乎每台服务器都应该加的规则) |
-m state --state 在较新内核中已标记为 deprecated,推荐统一使用 -m conntrack --ctstate:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
State 模块解决了 “ 连接级别 “ 的匹配问题。但实际运维中,还需要更灵活的匹配条件——按 IP 范围、按并发数、按速率等。下面逐一介绍这些扩展模块。
3. 常用扩展模块
3.1 iprange——连续 IP 范围
基础的 -s 选项只能指定单个 IP 或 CIDR 网段。要匹配不按 CIDR 对齐的连续 IP,用 iprange:
1 | # 拒绝 192.168.1.127 ~ 192.168.1.146 范围内的源地址 |
3.2 connlimit——并发连接数限制
限制每个 IP 的并发连接数,防止单个 IP 耗尽服务器资源。
1 | # 每个 IP 最多 2 个 SSH 并发连接 |
类比餐厅限流:每位客人(IP)最多带 2 位朋友(连接),超出的在门口排队(REJECT)。
3.3 limit——速率限制
控制单位时间内匹配的包数量,基于令牌桶算法实现。
1 | # 限制 ping 速率:令牌桶容量 3 个,每分钟补充 10 个令牌 |
令牌桶算法的工作方式:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart TD
A["数据包到达"] --> B{"令牌桶中有令牌?"}
B -->|"有"| C["消耗 1 个令牌"]
C --> D["匹配规则, ACCEPT"]
B -->|"没有"| E["不匹配该规则"]
E --> F["继续匹配下一条, 被兜底规则 REJECT"]
G["系统定时补充令牌, 10/minute = 每 6 秒 1 个"] --> H["令牌桶容量上限: 3"]
H --> B
classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
classDef success fill:#10B981,stroke:#059669,color:#fff
classDef danger fill:#EF4444,stroke:#DC2626,color:#fff
classDef warning fill:#F59E0B,stroke:#D97706,color:#fff
class A primary
class D success
class F danger
class G,H warning这两条规则必须配合使用:第一条限速放行,第二条兜底拒绝。如果只写第一条,超限的包不匹配后会继续往下走,可能被默认策略放行。
3.4 其他扩展模块
string 模块可按报文内容匹配字符串,time 模块可按时间段限制访问。两者生产中使用场景有限(string 性能开销大,time 需求通常在应用层解决),了解即可:
1 | # string: 拒绝包含指定字符串的 HTTP 报文(--algo 必须指定 bm 或 kmp) |
扩展模块解决了 “ 怎么匹配 “ 的问题,但在网络安全场景中,还需要更细粒度的控制。TCP 标志位就是这个精细工具。
4. TCP 标志位匹配
4.1 –tcp-flags 的工作原理
TCP 报文头有 6 个标志位:SYN、ACK、FIN、RST、URG、PSH。--tcp-flags 可以按这些标志位过滤报文。
语法:--tcp-flags 要检查的标志位 必须为1的标志位
1 | # 匹配 TCP 第一次握手(SYN=1,ACK=0,FIN=0,RST=0) |
4.2 –syn 快捷方式
--syn 等价于 --tcp-flags SYN,RST,ACK,FIN SYN,匹配 TCP 新连接请求(第一次握手):
1 | iptables -I INPUT -p tcp --dport 22 --syn -j REJECT |
4.3 防 SYN Flood 基础配置
SYN Flood 攻击通过发送大量 SYN 包、不完成三次握手来耗尽服务器资源。
内核层面,首先启用 SYN Cookies(比 iptables 限速更基础的防护手段):
1 | # 启用 SYN Cookies(临时) |
在此基础上,结合 iptables limit 模块做进一步限速:
1 | # 限制每秒最多 20 个新 SYN 连接,突发上限 50 |
这两层都只是基础防护。大规模 DDoS 需要在更上层解决(CDN、云防护、专用硬件)。
5. ICMP 类型匹配
ICMP 有多种类型,ping 涉及两种:
| 类型 | 编号 | 方向 |
|---|---|---|
| echo-request(请求) | 8 | 别人 → 本机 |
| echo-reply(回复) | 0 | 本机 → 别人 |
1 | # 禁止别人 ping 我(拒绝入站的 echo-request) |
生产环境一般不禁止 ping,因为它是排障的基础工具。如果要禁,只禁入站的 echo-request 即可。
到这里已经覆盖了常规的匹配手段。但在高流量场景下,conntrack 本身可能成为性能瓶颈——下一节介绍如何绕过它。
6. Raw 表与 NOTRACK
conntrack 虽然强大,但有代价:每个连接都要在 conntrack 表中创建和维护一条记录。对于高流量场景(如 DNS 服务器、负载均衡器),conntrack 表可能成为瓶颈。
raw 表提供 NOTRACK 动作(新版本改名为 CT --notrack),让特定流量跳过连接跟踪:
1 | # 对 DNS 流量(UDP 53)跳过连接跟踪 |
跳过 conntrack 后,这些包的状态会变成 UNTRACKED,
-m conntrack --ctstate ESTABLISHED不再能匹配它们的回包。需要单独为这些流量写无状态规则。
7. Mangle 表与 MARK
mangle 表最常用的场景是给数据包打 MARK 标记,配合策略路由使用。
1 | # 给来自 192.168.1.0/24 的包打上标记 1 |
类比:给不同快递贴不同颜色的标签(MARK),分拣员按颜色送到不同的配送线路(路由表)。
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart LR
A["数据包到达"] --> B["mangle PREROUTING"]
B --> C{"来源网段?"}
C -->|"192.168.1.0/24"| D["MARK 1"]
C -->|"192.168.2.0/24"| E["MARK 2"]
C -->|"其他"| F["无标记"]
D --> G["路由表 100, via 10.0.0.1"]
E --> H["路由表 200, via 10.0.0.2"]
F --> I["默认路由表"]
classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
classDef success fill:#10B981,stroke:#059669,color:#fff
classDef warning fill:#F59E0B,stroke:#D97706,color:#fff
class A,B primary
class D,E warning
class G,H,I success8. 自定义链
8.1 为什么需要自定义链?
当 INPUT 链有几十上百条规则时,全部堆在一起难以管理。自定义链可以把相关规则分组。
类比文件夹整理:把所有文件堆在桌面上 vs 按项目分文件夹存放。自定义链就是 “ 文件夹 “。
8.2 完整生命周期
1 | # 1. 创建自定义链 |
自定义链的引用与数据包流向如下:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart TD
A["数据包进入 INPUT 链"] --> B{"匹配 tcp :80,:443?"}
B -->|"是"| C["跳转到 WEB_RULES"]
B -->|"否"| D["继续 INPUT 后续规则"]
C --> E{"src=10.0.0.0/8?"}
E -->|"是"| F["ACCEPT"]
E -->|"否"| G{"src=172.16.0.0/12?"}
G -->|"是"| F
G -->|"否"| H["REJECT"]
classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
classDef success fill:#10B981,stroke:#059669,color:#fff
classDef danger fill:#EF4444,stroke:#DC2626,color:#fff
class A,C primary
class F success
class H danger删除自定义链时如果报
Too many links,说明还有默认链在引用它;如果报Directory not empty,说明链中还有规则。
9. LOG 动作:调试规则的利器
LOG 动作不会阻止数据包,只记录日志后让包继续匹配后续规则。
1 | # 在 DROP 之前加 LOG,记录被丢弃的包 |
日志输出到 /var/log/messages 或 /var/log/kern.log:
1 | [IPTABLES-DROP] IN=eth0 OUT= SRC=1.1.1.1 DST=10.0.0.5 PROTO=TCP SPT=54321 DPT=22 |
--log-prefix 加前缀便于 grep 过滤。--log-level 对应 syslog 级别(4 = warning)。
配合 rsyslog 可以将 iptables 日志写到独立文件:
1 | # /etc/rsyslog.d/iptables.conf |
10. 本篇小结
| 知识点 | 核心要义 |
|---|---|
| conntrack | iptables 的 “ 记忆力 “,追踪每个连接的状态 |
| 5 种状态 | NEW → ESTABLISHED / RELATED / INVALID / UNTRACKED |
| state 模块 | 一条规则放行所有已建立连接的回包 |
| conntrack 表满 | 高并发场景常见故障,调大 nf_conntrack_max 解决 |
| limit | 令牌桶算法限速 |
| connlimit | 限制单 IP 并发连接数 |
| TCP 标志位 | --syn 匹配第一次握手,可做 SYN Flood 基础防护 |
| raw + NOTRACK | 高流量场景跳过 conntrack 提升性能 |
| mangle + MARK | 给包打标记,配合策略路由 |
| 自定义链 | 规则按功能分组,提升可维护性 |
| LOG | 不阻断包,只记录日志,调试利器 |
下一篇:NAT 与网络防火墙 —— 理解 NAT 原理,掌握 SNAT/DNAT/MASQUERADE,配置 FORWARD 链实现网络防火墙。