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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 安装 conntrack 工具
yum install conntrack-tools # CentOS
apt install conntrack # Ubuntu

# 查看当前所有连接跟踪条目
conntrack -L

# 统计当前连接数
conntrack -C

# 查看 conntrack 表大小限制
cat /proc/sys/net/netfilter/nf_conntrack_max

# 查看当前已用条目数
cat /proc/sys/net/netfilter/nf_conntrack_count

示例输出:

1
2
tcp  6 431999 ESTABLISHED src=10.0.0.5 dst=203.0.113.10 sport=49234 dport=443
src=203.0.113.10 dst=10.0.0.5 sport=443 dport=49234 [ASSURED] mark=0 use=1

每条记录包含:协议、超时时间、状态、正向四元组、反向四元组(用于 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
2
3
4
5
# 查看是否接近上限
echo "当前: $(cat /proc/sys/net/netfilter/nf_conntrack_count) / 上限: $(cat /proc/sys/net/netfilter/nf_conntrack_max)"

# 查看内核日志
dmesg | grep conntrack

应急处理:

1
2
3
4
5
6
7
8
9
# 临时调大(立即生效,重启失效)
echo 524288 > /proc/sys/net/netfilter/nf_conntrack_max

# 永久生效
echo "net.netfilter.nf_conntrack_max = 524288" >> /etc/sysctl.conf
sysctl -p

# 缩短已建立连接的超时时间(默认 432000 秒 = 5 天)
echo 3600 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established

理解了 conntrack 的运行机制和常见故障,接下来看如何在规则中利用连接状态来简化防火墙配置。

2. State 扩展模块:连接跟踪的实战应用

2.1 有状态防火墙 Vs 无状态防火墙

无状态防火墙:每个包独立判断,不关心这个包属于哪个连接。配置繁琐——必须为每个网络服务(Web、SSH、MySQL 等)分别写 “ 放行请求 “ 和 “ 放行响应 “ 两条规则。

有状态防火墙:利用 conntrack 追踪连接状态。新连接仍然由具体端口规则放行,但一旦连接建立(进入 ESTABLISHED 状态),后续所有往返流量由一条 ESTABLISHED 规则统一放行,不再需要为每个服务单独写响应规则。

1
2
3
4
5
6
7
8
9
10
11
12
# 无状态做法:每个网络服务都要写两条——一条管请求进来,一条管响应出去
iptables -A INPUT -p tcp --dport 80 -j ACCEPT # 放行 Web 请求
iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT # 放行 Web 响应
iptables -A INPUT -p tcp --dport 22 -j ACCEPT # 放行 SSH 请求
iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT # 放行 SSH 响应
# ... 10 个服务就是 20 条规则

# 有状态做法
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 快速通道:放行已建立连接的后续流量
iptables -A INPUT -p tcp --dport 80 -j ACCEPT # 新连接靠这条进来(状态为 NEW)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT # 新连接靠这条进来(状态为 NEW)
# 不需要再写 OUTPUT 的响应规则——响应包的状态是 ESTABLISHED,被第一条规则放行

刚开机时 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 success

2.2 State 匹配的典型用法

1
2
3
4
5
6
7
8
9
10
# 放行已建立和相关连接(几乎每台服务器都应该加的规则)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 丢弃无效包
iptables -A INPUT -m state --state INVALID -j DROP

# 只允许本机主动发起的连接收到回包,拒绝外部主动发起的新连接
# 适用于只需要"主动访问外部"但不提供服务的场景
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -j DROP

-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
2
3
4
5
6
7
8
# 拒绝 192.168.1.127 ~ 192.168.1.146 范围内的源地址
iptables -I INPUT -m iprange --src-range 192.168.1.127-192.168.1.146 -j DROP

# 也支持目标地址范围
iptables -I OUTPUT -m iprange --dst-range 192.168.1.127-192.168.1.146 -j DROP

# 取反
iptables -I INPUT -m iprange ! --src-range 192.168.1.127-192.168.1.146 -j DROP

3.2 connlimit——并发连接数限制

限制每个 IP 的并发连接数,防止单个 IP 耗尽服务器资源。

1
2
3
4
5
# 每个 IP 最多 2 个 SSH 并发连接
iptables -I INPUT -p tcp --dport 22 -m connlimit --connlimit-above 2 -j REJECT

# 每个 /24 网段最多 20 个 SSH 并发连接
iptables -I INPUT -p tcp --dport 22 -m connlimit --connlimit-above 20 --connlimit-mask 24 -j REJECT

类比餐厅限流:每位客人(IP)最多带 2 位朋友(连接),超出的在门口排队(REJECT)。

3.3 limit——速率限制

控制单位时间内匹配的包数量,基于令牌桶算法实现。

1
2
3
# 限制 ping 速率:令牌桶容量 3 个,每分钟补充 10 个令牌
iptables -I INPUT -p icmp -m limit --limit-burst 3 --limit 10/minute -j ACCEPT
iptables -A INPUT -p icmp -j REJECT

令牌桶算法的工作方式:

%%{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
2
3
4
5
# string: 拒绝包含指定字符串的 HTTP 报文(--algo 必须指定 bm 或 kmp)
iptables -I INPUT -p tcp --sport 80 -m string --algo bm --string "OOXX" -j REJECT

# time: 工作时间禁止访问 80 端口
iptables -I OUTPUT -p tcp --dport 80 -m time --timestart 09:00:00 --timestop 18:00:00 -j REJECT

扩展模块解决了 “ 怎么匹配 “ 的问题,但在网络安全场景中,还需要更细粒度的控制。TCP 标志位就是这个精细工具。

4. TCP 标志位匹配

4.1 –tcp-flags 的工作原理

TCP 报文头有 6 个标志位:SYN、ACK、FIN、RST、URG、PSH。--tcp-flags 可以按这些标志位过滤报文。

语法:--tcp-flags 要检查的标志位 必须为1的标志位

1
2
3
4
5
6
7
8
# 匹配 TCP 第一次握手(SYN=1,ACK=0,FIN=0,RST=0)
iptables -I INPUT -p tcp --dport 22 --tcp-flags SYN,ACK,FIN,RST SYN -j REJECT

# 用 ALL 代替列出所有标志
iptables -I INPUT -p tcp --dport 22 --tcp-flags ALL SYN -j REJECT

# 匹配第二次握手(SYN=1 且 ACK=1)
iptables -I OUTPUT -p tcp --sport 22 --tcp-flags ALL SYN,ACK -j REJECT

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
2
3
4
5
6
# 启用 SYN Cookies(临时)
echo 1 > /proc/sys/net/ipv4/tcp_syncookies

# 永久生效
echo "net.ipv4.tcp_syncookies = 1" >> /etc/sysctl.conf
sysctl -p

在此基础上,结合 iptables limit 模块做进一步限速:

1
2
3
# 限制每秒最多 20 个新 SYN 连接,突发上限 50
iptables -A INPUT -p tcp --syn -m limit --limit 20/s --limit-burst 50 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP

这两层都只是基础防护。大规模 DDoS 需要在更上层解决(CDN、云防护、专用硬件)。

5. ICMP 类型匹配

ICMP 有多种类型,ping 涉及两种:

类型编号方向
echo-request(请求)8别人 → 本机
echo-reply(回复)0本机 → 别人
1
2
3
4
5
6
7
8
9
10
# 禁止别人 ping 我(拒绝入站的 echo-request)
iptables -I INPUT -p icmp --icmp-type 8 -j REJECT
# 或用名字
iptables -I INPUT -p icmp --icmp-type echo-request -j REJECT

# 禁止我 ping 别人(拒绝出站的 echo-request)
iptables -I OUTPUT -p icmp --icmp-type 8 -j REJECT

# 更精确的写法:type/code
iptables -I INPUT -p icmp -m icmp --icmp-type 8/0 -j REJECT

生产环境一般不禁止 ping,因为它是排障的基础工具。如果要禁,只禁入站的 echo-request 即可。

到这里已经覆盖了常规的匹配手段。但在高流量场景下,conntrack 本身可能成为性能瓶颈——下一节介绍如何绕过它。

6. Raw 表与 NOTRACK

conntrack 虽然强大,但有代价:每个连接都要在 conntrack 表中创建和维护一条记录。对于高流量场景(如 DNS 服务器、负载均衡器),conntrack 表可能成为瓶颈。

raw 表提供 NOTRACK 动作(新版本改名为 CT --notrack),让特定流量跳过连接跟踪:

1
2
3
# 对 DNS 流量(UDP 53)跳过连接跟踪
iptables -t raw -A PREROUTING -p udp --dport 53 -j NOTRACK
iptables -t raw -A OUTPUT -p udp --sport 53 -j NOTRACK

跳过 conntrack 后,这些包的状态会变成 UNTRACKED-m conntrack --ctstate ESTABLISHED 不再能匹配它们的回包。需要单独为这些流量写无状态规则。

7. Mangle 表与 MARK

mangle 表最常用的场景是给数据包打 MARK 标记,配合策略路由使用。

1
2
3
4
5
6
# 给来自 192.168.1.0/24 的包打上标记 1
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j MARK --set-mark 1

# 然后配合 ip rule 实现策略路由
ip rule add fwmark 1 table 100
ip route add default via 10.0.0.1 table 100

类比:给不同快递贴不同颜色的标签(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 success

8. 自定义链

8.1 为什么需要自定义链?

当 INPUT 链有几十上百条规则时,全部堆在一起难以管理。自定义链可以把相关规则分组。

类比文件夹整理:把所有文件堆在桌面上 vs 按项目分文件夹存放。自定义链就是 “ 文件夹 “。

8.2 完整生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 创建自定义链
iptables -N WEB_RULES

# 2. 往自定义链中添加规则
iptables -A WEB_RULES -s 10.0.0.0/8 -j ACCEPT
iptables -A WEB_RULES -s 172.16.0.0/12 -j ACCEPT
iptables -A WEB_RULES -j REJECT

# 3. 从默认链中引用(不引用则不生效)
iptables -I INPUT -p tcp -m multiport --dports 80,443 -j WEB_RULES

# 4. 查看引用关系
iptables -nvL INPUT # target 列显示 WEB_RULES
iptables -nvL WEB_RULES # 查看自定义链内的规则

# 5. 修改链名
iptables -E WEB_RULES HTTP_RULES

# 6. 删除自定义链(必须按顺序:先删引用 → 再清空规则 → 最后删链)
iptables -D INPUT -p tcp -m multiport --dports 80,443 -j HTTP_RULES # 删引用
iptables -F HTTP_RULES # 清空规则
iptables -X HTTP_RULES # 删除链

自定义链的引用与数据包流向如下:

%%{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
2
3
# 在 DROP 之前加 LOG,记录被丢弃的包
iptables -I INPUT -s 1.1.1.1 -j LOG --log-prefix "[IPTABLES-DROP] " --log-level 4
iptables -A INPUT -s 1.1.1.1 -j DROP

日志输出到 /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
2
3
4
5
# /etc/rsyslog.d/iptables.conf
# 如果日志消息包含 "[IPTABLES-" 字符串,就写到独立文件
:msg, contains, "[IPTABLES-" /var/log/iptables.log
# 写完后停止处理,不再重复写入 /var/log/messages
& stop

10. 本篇小结

知识点核心要义
conntrackiptables 的 “ 记忆力 “,追踪每个连接的状态
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 链实现网络防火墙。