HOOOS

iptables TRACE 实战指南:手把手教你跟踪复杂防火墙规则下的数据包

0 41 踩坑无数的老网管 iptablesTRACE防火墙调试NetfilterLinux网络
Apple

搞不定 iptables 规则?数据包莫名其妙被丢弃或者走向了奇怪的方向?当你面对一堆 mangle 标记、DNATSNATfilter 规则交织在一起的复杂场景时,普通的 LOG 目标可能就不够用了。这时候,TRACE 目标就能派上大用场!它就像给数据包装上了一个 GPS 追踪器,让你能清晰地看到它在 Netfilter 框架中的每一步足迹。

这篇指南将通过一个精心设计的、有点“绕”的场景,带你一步步实践如何使用 iptables TRACE 来解开数据包的谜团。

场景设定:一个“有点东西”的网关

想象一下,你正在管理一台 Linux 网关服务器(就叫它 Gateway 吧),它负责连接内部网络和外部互联网。需求是这样的:

  1. 内部网络: 192.168.1.0/24 (接口 eth1)
  2. 外部网络: 公网 IP 203.0.113.10 (接口 eth0)
  3. 内部 Web 服务器: 192.168.1.100,运行在 80 端口。
  4. 策略路由: 来自内部特定 IP 192.168.1.50 的所有出站流量,需要打上标记 0x1,并通过一个特定的路由表(假设该路由表已经配置好,依赖于这个 MARK 值)。
  5. 端口转发 (DNAT): 访问网关公网 IP 203.0.113.10 的 8080 端口的流量,需要转发给内部 Web 服务器 192.168.1.100 的 80 端口。
  6. 源地址转换 (SNAT): 所有来自内部网络 192.168.1.0/24 访问外网的流量,源 IP 都需要伪装成网关的公网 IP 203.0.113.10
  7. 过滤规则:
    • 允许来自内部网络的 SSH (端口 22) 访问网关本身。
    • 允许转发到内部 Web 服务器的 HTTP (端口 80) 流量。
    • 允许来自内部特定 IP 192.168.1.50 的所有转发流量 (配合策略路由)。
    • 允许内部网络访问外部 DNS (端口 53)。
    • 禁止其他所有转发流量。
    • 默认 INPUT 策略为 DROP,FORWARD 策略为 DROP,OUTPUT 策略为 ACCEPT。

这个场景揉合了 mangle (MARK), nat (DNAT, SNAT), 和 filter,足以让新手头疼,也是 TRACE 发挥价值的好地方。

配置 iptables 规则

我们先来把上述场景对应的 iptables 规则配置好。撸起袖子,开干!

# 清空现有规则和链 (危险操作,生产环境慎用!)
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
iptables -Z

# 设置默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# === mangle 表: PREROUTING 链 (用于入站标记) ===
# 暂时不需要在 PREROUTING 做标记,但我们留空占位

# === mangle 表: OUTPUT 链 (用于网关自身出站标记) ===
# 暂时不需要

# === mangle 表: FORWARD 链 (用于转发标记) ===
# 暂时不需要

# === mangle 表: POSTROUTING 链 (用于出站标记) ===
# 暂时不需要

# === mangle 表: PREROUTING 链 (处理来自特定内部IP的流量)
# 注意:标记通常在PREROUTING (入站) 或 OUTPUT (本地产生) 进行
# 对于从内到外的流量,在 PREROUTING 标记更合适
iptables -t mangle -A PREROUTING -i eth1 -s 192.168.1.50 -j MARK --set-mark 0x1
# 为了方便观察,我们给其他内部流量也打个默认标记 (可选)
# iptables -t mangle -A PREROUTING -i eth1 -s 192.168.1.0/24 -j MARK --set-mark 0x2

# === nat 表: PREROUTING 链 (DNAT) ===
# 将访问公网 IP 8080 端口的流量转发给内部 Web 服务器的 80 端口
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80

# === nat 表: POSTROUTING 链 (SNAT/MASQUERADE) ===
# 将来自内部网络的出站流量进行源地址伪装
iptables -t nat -A POSTROUTING -o eth0 -s 192.168.1.0/24 -j SNAT --to-source 203.0.113.10
# 或者使用 MASQUERADE (如果公网 IP 是动态的)
# iptables -t nat -A POSTROUTING -o eth0 -s 192.168.1.0/24 -j MASQUERADE

# === filter 表: INPUT 链 (访问网关本身) ===
# 允许 loopback
iptables -A INPUT -i lo -j ACCEPT
# 允许 ESTABLISHED, RELATED 连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 允许内部 SSH 访问网关
iptables -A INPUT -i eth1 -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT

# === filter 表: FORWARD 链 (转发流量) ===
# 允许 ESTABLISHED, RELATED 连接
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 允许转发到内部 Web 服务器的 HTTP 流量 (DNAT 之后的目标地址)
iptables -A FORWARD -i eth0 -o eth1 -d 192.168.1.100 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT

# 允许来自特定内部 IP 192.168.1.50 的所有转发流量 (配合 MARK)
# 注意:这里我们用 conntrack mark 来匹配,因为 MARK 是连接级别的
# 需要先在 mangle PREROUTING 保存 MARK 到连接跟踪
iptables -t mangle -A PREROUTING -i eth1 -s 192.168.1.50 -j CONNMARK --set-mark 0x1
# 然后在 filter FORWARD 匹配连接标记
iptables -A FORWARD -i eth1 -o eth0 -m connmark --mark 0x1 -j ACCEPT

# 允许内部网络访问外部 DNS
iptables -A FORWARD -i eth1 -o eth0 -p udp --dport 53 -j ACCEPT
iptables -A FORWARD -i eth1 -o eth0 -p tcp --dport 53 -j ACCEPT

# (可选) 明确拒绝其他所有转发流量 (虽然默认策略是 DROP,但明确写出更好)
iptables -A FORWARD -j LOG --log-prefix "FORWARD Denied: " --log-level 6
iptables -A FORWARD -j DROP

# (可选) 明确拒绝其他 INPUT 流量
iptables -A INPUT -j LOG --log-prefix "INPUT Denied: " --log-level 6
iptables -A INPUT -j DROP

# 开启 IP 转发
echo 1 > /proc/sys/net/ipv4/ip_forward

注意: 上面的规则为了演示 MARKCONNMARK 的配合,稍微调整了策略路由的实现方式。我们使用 mangle PREROUTINGMARK 动作结合 CONNMARK --set-mark 将标记保存到连接跟踪条目中,然后在 filter FORWARD 中使用 -m connmark --mark 来匹配这个连接标记。这是处理有状态过滤和策略路由时更常见的做法。

好了,规则已经就位。看起来有点复杂,对吧?如果现在一个从 192.168.1.50 发往外网 80 端口的包,或者一个从外网访问 203.0.113.10:8080 的包,到底经历了什么?TRACE 该上场了!

启用 TRACE 追踪特定数据包

TRACE 目标属于 raw 表。raw 表的优先级最高,在连接跟踪之前处理。在这里插入 TRACE 规则可以确保我们能捕捉到数据包进入 Netfilter 后的第一步。

假设我们要追踪一个从外部主机(例如 IP 198.51.100.2)访问我们网关 203.0.113.108080 端口的 TCP 包。这个包应该被 DNAT 到内部的 192.168.1.100:80

我们需要在 raw 表的 PREROUTINGOUTPUT 链(通常这两个就够了,但为了完整性,有时也会在 FORWARD 等链加入)中添加 TRACE 规则,精确匹配我们关心的数据包。

# 确保 raw 表存在 (通常默认就有)
# modprobe iptable_raw # 如果需要加载模块

# 清空 raw 表规则 (可选,确保干净环境)
iptables -t raw -F

# 在 PREROUTING 链追踪目标数据包
# 匹配从 198.51.100.2 发往 203.0.113.10:8080 的 TCP 包
iptables -t raw -A PREROUTING -p tcp -s 198.51.100.2 --dport 8080 -d 203.0.113.10 -j TRACE

# (可选) 如果需要追踪回应包,可以在 OUTPUT 链添加
# 匹配从 192.168.1.100:80 发往 198.51.100.2 的 TCP 包 (DNAT 后的源,SNAT 前的目标)
# 注意:回应包的追踪规则需要根据 NAT 转换后的地址来写,比较复杂,我们先聚焦请求包
# iptables -t raw -A OUTPUT -p tcp -s 192.168.1.100 --sport 80 -d 198.51.100.2 -j TRACE

现在,任何匹配 -s 198.51.100.2 -d 203.0.113.10 --dport 8080 -p tcp 的数据包,在进入 raw 表的 PREROUTING 链时,都会触发 TRACE 动作。

触发数据包并观察日志

TRACE 的输出通常记录在内核日志中。你可以使用 dmesg 或者查看 /var/log/syslog/var/log/messages (取决于你的系统配置)。建议使用 dmesg -wH 实时跟踪输出。

现在,从外部主机 198.51.100.2 发起一个访问请求:

# 在 198.51.100.2 主机上执行
curl http://203.0.113.10:8080

几乎同时,你应该能在 Gateway 服务器上看到类似下面这样的 dmesg 输出 (具体细节可能因内核版本、iptables 版本略有不同):

[ ... ] TRACE: raw:PREROUTING:policy:2 PACKET: IN=eth0 OUT= MAC=... SRC=198.51.100.2 DST=203.0.113.10 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=12345 DF PROTO=TCP SPT=54321 DPT=8080 WINDOW=65535 RES=0x00 SYN URGP=0 OPT (...) 
[ ... ] TRACE: raw:PREROUTING:rule:1 IN=eth0 OUT= MAC=... SRC=198.51.100.2 DST=203.0.113.10 ... -j TRACE 
[ ... ] TRACE: mangle:PREROUTING:policy:1 PACKET: IN=eth0 OUT= MAC=... SRC=198.51.100.2 DST=203.0.113.10 ...
[ ... ] TRACE: mangle:PREROUTING:rule:1 IN=eth0 OUT= MAC=... SRC=198.51.100.2 DST=203.0.113.10 ... -j CONNMARK --set-mark 0x1 # 这条规则不匹配,因为源IP不是192.168.1.50
[ ... ] TRACE: nat:PREROUTING:policy:1 PACKET: IN=eth0 OUT= MAC=... SRC=198.51.100.2 DST=203.0.113.10 ...
[ ... ] TRACE: nat:PREROUTING:rule:1 IN=eth0 OUT= MAC=... SRC=198.51.100.2 DST=203.0.113.10 ... -j DNAT --to-destination 192.168.1.100:80 
[ ... ] TRACE: filter:FORWARD:policy:1 PACKET: IN=eth0 OUT=eth1 MAC=... SRC=198.51.100.2 DST=192.168.1.100 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=12345 DF PROTO=TCP SPT=54321 DPT=80 WINDOW=65535 RES=0x00 SYN URGP=0 OPT (...) 
[ ... ] TRACE: filter:FORWARD:rule:1 PACKET: IN=eth0 OUT=eth1 MAC=... SRC=198.51.100.2 DST=192.168.1.100 ... -m conntrack --ctstate ESTABLISHED,RELATED # 不匹配,这是 SYN 包
[ ... ] TRACE: filter:FORWARD:rule:2 PACKET: IN=eth0 OUT=eth1 MAC=... SRC=198.51.100.2 DST=192.168.1.100 ... -d 192.168.1.100 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT 
[ ... ] TRACE: mangle:POSTROUTING:policy:1 PACKET: IN=eth0 OUT=eth1 MAC=... SRC=198.51.100.2 DST=192.168.1.100 ...
[ ... ] TRACE: nat:POSTROUTING:policy:1 PACKET: IN=eth0 OUT=eth1 MAC=... SRC=198.51.100.2 DST=192.168.1.100 ...
[ ... ] TRACE: nat:POSTROUTING:rule:1 PACKET: IN=eth0 OUT=eth1 MAC=... SRC=198.51.100.2 DST=192.168.1.100 ... -o eth0 -s 192.168.1.0/24 -j SNAT --to-source 203.0.113.10 # 不匹配,因为源 IP 不在 192.168.1.0/24

解读 TRACE 日志

我们来逐行分析这个日志,看看数据包的旅程:

  1. TRACE: raw:PREROUTING:policy:2 ... SRC=198.51.100.2 DST=203.0.113.10 ... DPT=8080

    • raw:PREROUTING:policy:2: 数据包进入 raw 表的 PREROUTING 链,正在评估第二条规则(通常是隐含的 policy 规则,表示链的默认策略,但这里可能是指规则计数,取决于具体实现)。
    • PACKET: IN=eth0 ...: 显示了数据包的详细信息:入接口 eth0,源 IP 198.51.100.2,目标 IP 203.0.113.10,协议 TCP,目标端口 8080。这是原始数据包。
  2. TRACE: raw:PREROUTING:rule:1 ... -j TRACE

    • raw:PREROUTING:rule:1: 匹配了 rawPREROUTING 链的第一条规则。
    • -j TRACE: 这就是我们添加的 TRACE 规则,它本身不改变数据包,只是触发了日志记录。数据包继续向下传递。
  3. TRACE: mangle:PREROUTING:policy:1 ...

    • 数据包进入 mangle 表的 PREROUTING 链。
  4. TRACE: mangle:PREROUTING:rule:1 ... -j CONNMARK --set-mark 0x1

    • 尝试匹配 manglePREROUTING 链的第一条规则 (给 192.168.1.50 打标记)。
    • 不匹配,因为源 IP 是 198.51.100.2。数据包继续。
  5. TRACE: nat:PREROUTING:policy:1 ...

    • 数据包进入 nat 表的 PREROUTING 链。
  6. TRACE: nat:PREROUTING:rule:1 ... -j DNAT --to-destination 192.168.1.100:80

    • 匹配了 natPREROUTING 链的第一条规则(端口转发规则)。
    • -j DNAT --to-destination 192.168.1.100:80: 触发 DNAT!数据包的目标 IP 被修改为 192.168.1.100,目标端口被修改为 80
    • 关键变化: 从这一步开始,Netfilter 内部看待这个数据包的目标已经是 192.168.1.100:80 了。
  7. TRACE: filter:FORWARD:policy:1 ... DST=192.168.1.100 ... DPT=80

    • 路由决策判定该数据包需要转发 (从 eth0 进,预计从 eth1 出)。
    • 数据包进入 filter 表的 FORWARD 链。
    • 注意看 DSTDPT 已经是 DNAT 之后的值了!这是 TRACE 的强大之处,它显示了 当前步骤 数据包的状态。
    • TTL 从 64 变成了 63,因为经过了一次路由转发的 TTL 递减。
  8. TRACE: filter:FORWARD:rule:1 ... -m conntrack --ctstate ESTABLISHED,RELATED

    • 尝试匹配第一条 FORWARD 规则 (允许已建立连接)。
    • 不匹配,因为这是一个 SYN 包,状态是 NEW
  9. TRACE: filter:FORWARD:rule:2 ... -d 192.168.1.100 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT

    • 匹配了第二条 FORWARD 规则!
    • 规则条件:目标 IP 192.168.1.100,协议 TCP,目标端口 80,连接状态 NEW。完美匹配!
    • -j ACCEPT: 数据包被接受,允许转发。
    • 因为规则匹配并执行了 ACCEPT (这是一个终止动作),所以这个包在 filter:FORWARD 链的处理结束。
  10. TRACE: mangle:POSTROUTING:policy:1 ... DST=192.168.1.100 ...

    • 数据包进入 mangle 表的 POSTROUTING 链。目标地址仍然是 192.168.1.100
    • 没有配置 mangle:POSTROUTING 规则,所以直接通过。
  11. TRACE: nat:POSTROUTING:policy:1 ... DST=192.168.1.100 ...

    • 数据包进入 nat 表的 POSTROUTING 链。
  12. TRACE: nat:POSTROUTING:rule:1 ... -o eth0 -s 192.168.1.0/24 -j SNAT ...

    • 尝试匹配 natPOSTROUTING 链的第一条规则 (SNAT 规则)。
    • 不匹配,因为这条规则是为从内向外 (源 IP 192.168.1.0/24) 的流量准备的,而当前数据包是外到内 (源 IP 198.51.100.2)。

至此,这个数据包的 Netfilter 之旅(对于请求包)就结束了。它成功地被 DNAT 修改了目标地址和端口,并通过了 filter:FORWARD 链的检查,最终会被发送到内部网络 eth1 接口,目标是 192.168.1.100 的 80 端口。

思考一下: 如果 filter:FORWARD 的第二条规则写错了,比如写成了 --dport 8080,那么 TRACE 日志就会显示该规则不匹配,然后数据包可能会匹配到后面的 LOG --log-prefix "FORWARD Denied: "DROP 规则,我们就能立刻定位到错误点!

追踪另一个场景:内部特定 IP 出站

我们再来追踪一个从内部特定 IP 192.168.1.50 发往外部任意地址(比如 8.8.8.8 的 80 端口)的包,看看 MARKSNAT 是如何工作的。

# 清理旧的 TRACE 规则
iptables -t raw -F

# 添加新的 TRACE 规则,追踪从 192.168.1.50 发出的包
# 我们需要在 PREROUTING (包进入网关时) 和 OUTPUT (如果包是网关自己产生的,虽然这里不是) 添加
iptables -t raw -A PREROUTING -s 192.168.1.50 -j TRACE
# 也可以更精确地匹配目标
# iptables -t raw -A PREROUTING -s 192.168.1.50 -d 8.8.8.8 -p tcp --dport 80 -j TRACE

现在,在 192.168.1.50 这台机器上执行:

# 在 192.168.1.50 主机上执行
curl http://8.8.8.8

查看 Gateway 上的 dmesg -wH 输出,你会看到类似:

[ ... ] TRACE: raw:PREROUTING:policy:1 PACKET: IN=eth1 OUT= MAC=... SRC=192.168.1.50 DST=8.8.8.8 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=54321 DF PROTO=TCP SPT=12345 DPT=80 ...
[ ... ] TRACE: raw:PREROUTING:rule:1 IN=eth1 OUT= MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... -j TRACE 
[ ... ] TRACE: mangle:PREROUTING:policy:1 PACKET: IN=eth1 OUT= MAC=... SRC=192.168.1.50 DST=8.8.8.8 ...
[ ... ] TRACE: mangle:PREROUTING:rule:1 IN=eth1 OUT= MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... -j CONNMARK --set-mark 0x1 
[ ... ] TRACE: nat:PREROUTING:policy:1 PACKET: IN=eth1 OUT= MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... MARK=0x1 # 注意 MARK=0x1 已经被设置
[ ... ] TRACE: nat:PREROUTING:rule:1 IN=eth1 OUT= MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... -i eth0 -p tcp --dport 8080 -j DNAT ... # 不匹配,接口和端口都不对
[ ... ] TRACE: filter:FORWARD:policy:1 PACKET: IN=eth1 OUT=eth0 MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... MARK=0x1
[ ... ] TRACE: filter:FORWARD:rule:1 PACKET: IN=eth1 OUT=eth0 MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... -m conntrack --ctstate ESTABLISHED,RELATED # 不匹配
[ ... ] TRACE: filter:FORWARD:rule:2 PACKET: IN=eth1 OUT=eth0 MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... -d 192.168.1.100 ... # 不匹配
[ ... ] TRACE: filter:FORWARD:rule:3 PACKET: IN=eth1 OUT=eth0 MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... -m connmark --mark 0x1 -j ACCEPT 
[ ... ] TRACE: mangle:POSTROUTING:policy:1 PACKET: IN=eth1 OUT=eth0 MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... MARK=0x1
[ ... ] TRACE: nat:POSTROUTING:policy:1 PACKET: IN=eth1 OUT=eth0 MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... MARK=0x1
[ ... ] TRACE: nat:POSTROUTING:rule:1 PACKET: IN=eth1 OUT=eth0 MAC=... SRC=192.168.1.50 DST=8.8.8.8 ... -o eth0 -s 192.168.1.0/24 -j SNAT --to-source 203.0.113.10 
[ ... ] TRACE: mangle:OUTPUT:policy:1 PACKET: OUT=eth0 SRC=203.0.113.10 DST=8.8.8.8 ... # 这是 SNAT 之后的数据包,准备从 OUTPUT 发出

分析这次追踪:

  1. 包进入 raw:PREROUTING,匹配 TRACE 规则。
  2. 进入 mangle:PREROUTING,匹配第一条规则,CONNMARK --set-mark 0x1 被执行。连接被标记。
  3. 进入 nat:PREROUTING,没有匹配的 DNAT 规则。
  4. 路由决策(可能基于 MARK=0x1 选择了特定路由表),判定需要转发到 eth0
  5. 进入 filter:FORWARD,不匹配前两条规则,但匹配了第三条 -m connmark --mark 0x1 -j ACCEPT!数据包被允许转发。
  6. 进入 mangle:POSTROUTING,无操作。
  7. 进入 nat:POSTROUTING,匹配了第一条 SNAT 规则!源 IP 192.168.1.50 被修改为 203.0.113.10
  8. 最后,经过 SNAT 后的数据包(源 IP 203.0.113.10)准备从 eth0 发出。

看到了吗?TRACE 清晰地展示了 MARK -> CONNMARK -> filter 匹配 connmark -> SNAT 的整个流程。

清理 TRACE 规则

调试完成后,记得删除 TRACE 规则,因为它会产生大量日志并可能影响性能。

# 清空 raw 表所有规则
iptables -t raw -F
# 或者精确删除
# iptables -t raw -D PREROUTING <rule_number>
# iptables -t raw -D OUTPUT <rule_number>

总结与注意事项

  • iptables TRACE 是调试复杂 Netfilter 规则集的强大武器,它可以逐一显示数据包在各个表、链中被规则处理的过程和状态变化。
  • TRACE 规则通常添加到 raw 表的 PREROUTINGOUTPUT 链中,因为 raw 表优先级最高,能捕捉到最早的状态。
  • 务必精确匹配你想要追踪的数据包(使用 -s, -d, -p, --dport, --sport 等),避免产生不必要的日志。
  • TRACE 日志记录在内核日志 (dmesg, syslog) 中,注意观察。
  • 仔细分析 TRACE 日志中的 table:chain:type:num (类型如 rule, policy;num 是规则编号或策略编号)、PACKET 详细信息(特别是 IP、端口、MARK 等变化)以及执行的 target (-j ACTION)。
  • TRACE 会带来性能开销和大量的日志,调试结束后 一定记得删除 TRACE 规则。
  • 对于非常繁忙的系统,即使精确匹配,TRACE 也可能产生过多日志。可以考虑结合 LOG 目标进行分段调试,或者使用更高级的工具如 bpftrace 进行观测。

希望这个实战指南能帮助你掌握 iptables TRACE,让你在面对复杂的防火墙规则时更有信心!下次再遇到“灵异事件”,试试给数据包上个 TRACE 吧!

点评评价

captcha
健康