HOOOS

iptables CONNMARK 标记不生效?网络老司机带你一步步排查到底

0 33 防火墙边的老王 iptablesCONNMARK网络故障排查
Apple

兄弟们,搞过 iptables 的,估计不少人都踩过 CONNMARK 的坑。明明规则写上去了,信心满满,结果策略路由、QoS 啥的该不生效还是不生效,连接标记(CONNMARK)就像消失了一样。别急,这玩意儿确实有点绕,但只要思路清晰,一步步排查,总能找到问题所在。今天,我就跟你捋一捋,怎么系统地把 CONNMARK 不生效或标记错误的问题揪出来。

第一步:基础检查 - 规则真的存在且被匹配了吗?

这是最基本也是最容易忽略的一步。别笑,有时候就是个手误或者放错了地方。

核心命令: iptables -t mangle -nvL

你需要关注几个点:

  1. 表和链 (Table & Chain): CONNMARK 操作主要在 mangle 表。设置标记(--set-mark--save-mark)通常发生在数据包刚进入系统(PREROUTING 链,用于转发或本地接收的包)或由本地进程发出(OUTPUT 链)的时候。恢复标记(--restore-mark)也通常在这些链的早期进行,或者在需要使用标记做判断的链(如 FORWARD, INPUT, POSTROUTING)进行。

    • 检查点: 你的 CONNMARK 规则是否放在了 mangle 表的正确链上?比如,你想标记从内网 192.168.1.0/24 出去的流量,规则就应该在 mangle 表的 PREROUTING 链(如果是网关)或 OUTPUT 链(如果是本机发出)。
  2. 规则语法和匹配条件: 仔细核对规则的 -j CONNMARK 部分以及之前的匹配条件(-s, -d, -p, --dport, -m state --state NEW 等)。

    • 检查点: 语法有没有写错? --set-mark 后面跟的值是不是你想要的(通常是十六进制,如 0x1)? --save-mark--restore-mark 是不是用对了场景?匹配条件是否过于严格或宽松,导致目标数据包根本匹配不到这条规则?
  3. 命中计数 (Packets & Bytes): iptables -nvL 输出的头两列 pktsbytes 是关键!

    • 检查点: 你的 CONNMARK --set-mark/--save-mark 规则的 pkts 计数是否大于 0?如果一直是 0,说明根本没有数据包匹配到这条规则。你需要回过头去检查匹配条件和规则所处的链是否正确,或者确认相应的流量是否真的产生了。
    • 检查点: 如果你在其他地方使用了 CONNMARK --restore-mark 或者 -m connmark --mark <value> 来匹配标记,这些规则的计数也需要检查。如果设置标记的规则计数在增加,但使用标记的规则计数为 0,那问题可能出在标记的恢复或匹配逻辑上。

举个栗子:
假设你想给来自 192.168.1.100 访问 8.8.8.8 的新 TCP 连接打上标记 0x1

iptables -t mangle -A PREROUTING -s 192.168.1.100 -d 8.8.8.8 -p tcp -m state --state NEW -j CONNMARK --set-mark 0x1
# 然后可能需要保存标记到连接
iptables -t mangle -A PREROUTING -s 192.168.1.100 -d 8.8.8.8 -p tcp -m state --state NEW -j CONNMARK --save-mark
# 在后续处理或转发时恢复标记
iptables -t mangle -A PREROUTING -m connmark ! --mark 0x0 -j CONNMARK --restore-mark

执行 iptables -t mangle -nvL PREROUTING,看看这几条规则的 pkts 数是不是在增加。

第二步:规则顺序 - 是不是被前面的规则“截胡”了?

iptables 规则是顺序执行的,遇到一个终止性目标 (Terminating Target),比如 ACCEPT, DROP, REJECT, RETURN,数据包处理就会在该链中停止(RETURN 是返回调用链),后面的规则就不会被执行了。

检查点:

  • 在你的 CONNMARK 规则之前,是否有其他规则(尤其是带有终止性目标的规则)可能匹配了同样的数据包?
  • 你是不是把 --restore-mark 放在了 --save-mark 之前的同一个链的处理流程里?对于新连接的第一个包,它还没有标记可恢复,如果先执行 --restore-mark 并且后续没有正确执行 --save-mark,标记就丢失了。
  • 你是不是在设置标记的同一链后续规则里,就期望用 -m connmark 来匹配这个标记?通常不行。CONNMARK --save-mark 是将标记保存到连接跟踪记录里,而不是立即应用到当前这个数据包上(除非也用了--set-mark)。后续的数据包需要通过 --restore-mark 才能从连接记录里把标记“恢复”到数据包上,然后才能被 -m connmark 匹配到。

排查方法:

  • 仔细阅读 iptables -t mangle -nvL <chain_name> 的输出,注意规则的顺序。
  • 尝试将你的 CONNMARK 规则往链的前面移动(使用 iptables -t mangle -I <chain_name> <rule_number> ... 插入到指定位置),看看是否有效果。当然,移动规则要小心,别破坏了原有的逻辑。

第三步:检查连接跟踪 - 连接真的被标记了吗?

CONNMARK 顾名思义,是作用于连接 (Connection) 上的标记,它依赖于 Linux 内核的连接跟踪系统 (conntrack)。

核心命令: conntrack -Lconntrack -L -m <mark_value>

  1. conntrack -L: 这个命令会列出当前系统跟踪的所有连接。先大致看一下,是否有你预期的连接(根据源/目的 IP、端口、协议判断)?如果连连接跟踪记录都没有,那 CONNMARK 自然无从谈起。这可能是 filter 表的规则提前 DROP 了,或者连接跟踪模块没加载,或者连接数满了 (dmesg 里可能会有 nf_conntrack: table full, dropping packet 的报错)。

  2. conntrack -L -m <mark_value>: 这个是重点!它会只列出带有特定标记的连接。注意,这里的 <mark_value> 需要是十进制的!如果你的 iptables 规则里用的是十六进制 0x1,这里就要用 1;如果是 0xa,这里就要用 10

    • 检查点: 执行 conntrack -L -m <你的标记对应的十进制值>,是否有输出?
      • 有输出: 太好了!这说明 CONNMARK --save-mark 至少是成功执行了,标记确实存到连接跟踪记录里了。如果你的应用(如策略路由)还是不生效,问题可能在于后续如何使用这个标记(比如 --restore-mark 没执行,或者 ip rule 配置不对)。
      • 没有输出: 这就意味着,尽管你的 --set-mark--save-mark 规则可能被数据包命中了(iptables -nvL 计数增加),但标记没有成功保存到连接跟踪记录里。这可能是因为:
        • 只用了 --set-mark 而没有用 --save-mark--set-mark 只标记当前这个包(有时也可能影响连接,行为可能依赖内核版本和具体 target),而 --save-mark 才是明确地把标记存到连接记录里。
        • 连接因为某些原因很快就失效或被清除了?
        • 是否存在其他规则清除了标记(例如 CONNMARK --set-mark 0x0 --save-mark)?
        • 内核或 iptables 版本相关的 bug?(可能性相对较低)

示例:
如果你的规则是 iptables -t mangle ... -j CONNMARK --set-mark 0x1 --save-mark,那么你应该用 conntrack -L -m 1 来查找。

# 查找标记为 1 (0x1) 的连接
sudo conntrack -L -m 1

# 如果你的标记是 10 (0xa)
sudo conntrack -L -m 10

观察输出,看是否有符合你预期的连接条目,并且 mark= 字段的值是你设置的。

第四步:终极武器 - NFLOG + tcpdump 深入 Netfilter 内部

如果前面的步骤都查不出问题,或者你想更精细地了解数据包在 Netfilter 钩子点(Hooks)上的标记变化过程,那就得上 NFLOG 了。这相当于在内核处理路径上设置了观察点。

原理:
我们在 mangle 表的关键位置插入 NFLOG 规则,让内核把经过这些点的数据包(以及当时的某些元数据,比如 mark)复制一份,通过 Netlink 套接字发送到用户空间。然后用 tcpdump 监听这个 Netlink 接口,就能看到数据包在不同处理阶段的状态。

步骤:

  1. 加载 NFLOG 模块 (如果需要):

    sudo modprobe nfnetlink_log
    
  2. 创建 NFLOG 虚拟接口 (用于 tcpdump 监听):

    # 创建一个 nflog 组,比如组号 100
    sudo ip link add name nflog100 type nflog group 100
    sudo ip link set nflog100 up
    
  3. 在 iptables 中插入 NFLOG 规则:
    假设你的 CONNMARK --save-mark 规则在 manglePREROUTING 链的第 5 条。我们可以在它之前之后插入 NFLOG 规则,来观察标记的变化。

    # 在第 5 条规则前插入 NFLOG,记录原始状态 (假设只关心特定流量)
    sudo iptables -t mangle -I PREROUTING 5 -s 192.168.1.100 -p tcp --dport 80 -j NFLOG --nflog-group 100 --nflog-prefix "BEFORE_CONNMARK: "
    
    # 你的 CONNMARK 规则现在是第 6 条了
    # iptables -t mangle -A PREROUTING ... -j CONNMARK --save-mark 0x1  (假设这是你原来的第5条)
    
    # 在 CONNMARK 规则后插入 NFLOG (现在是第 7 条)
    sudo iptables -t mangle -I PREROUTING 7 -s 192.168.1.100 -p tcp --dport 80 -j NFLOG --nflog-group 100 --nflog-prefix "AFTER_CONNMARK_SAVE: "
    
    # 如果你还用了 --restore-mark,比如在第 2 条
    # sudo iptables -t mangle -I PREROUTING 2 ... -j CONNMARK --restore-mark
    # 在它后面也加一个 NFLOG
    sudo iptables -t mangle -I PREROUTING 3 -s 192.168.1.100 -p tcp --dport 80 -j NFLOG --nflog-group 100 --nflog-prefix "AFTER_CONNMARK_RESTORE: "
    
    • -I PREROUTING <num>: 插入到指定位置。
    • --nflog-group 100: 将日志发送到我们创建的组 100。
    • --nflog-prefix "...": 给日志加个前缀,方便区分是哪个观察点捕获的。
  4. 使用 tcpdump 监听:

    # 监听 nflog100 接口,-n 不解析域名,-v 显示详细信息,-l 行缓冲
    sudo tcpdump -i nflog100 -n -v -l
    
  5. 触发流量并分析输出:
    现在,从 192.168.1.100 发起访问目标服务的 TCP 80 端口的连接。观察 tcpdump 的输出。

    你会看到类似这样的信息(格式可能略有不同):

    10:30:01.123456 IP (tos 0x0, ttl 64, id 12345, offset 0, flags [DF], proto TCP (6), length 60) 192.168.1.100.54321 > 8.8.8.8.80: Flags [S], cksum 0xabcd (correct), seq 1000, win 64240, options [mss 1460,sackOK,TS val 123 ecr 0,nop,wscale 7], length 0
        NFLOG prefix BEFORE_CONNMARK: 
        mark 0x0 <-------------------- 关注这里的 mark 值
    ...
    10:30:01.123500 IP (tos 0x0, ttl 64, id 12345, offset 0, flags [DF], proto TCP (6), length 60) 192.168.1.100.54321 > 8.8.8.8.80: Flags [S], cksum 0xabcd (correct), seq 1000, win 64240, options [mss 1460,sackOK,TS val 123 ecr 0,nop,wscale 7], length 0
        NFLOG prefix AFTER_CONNMARK_SAVE: 
        mark 0x1 <-------------------- 标记是否在这里被设置了?(如果用了--set-mark)
    ...
    # 对于后续的数据包 (如果设置了 restore 规则)
    10:30:01.123600 IP (tos 0x0, ttl 64, id 12346, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.100.54321 > 8.8.8.8.80: Flags [.], ack 1, win 501, options [nop,nop,TS val 124 ecr 10], length 0
        NFLOG prefix AFTER_CONNMARK_RESTORE: 
        mark 0x1 <-------------------- 标记是否被成功恢复到包上了?
    
    • 分析关键点:
      • 对比 BEFORE_CONNMARKAFTER_CONNMARK_SAVE 时捕获的同一个数据包(可以通过 IP ID, Seq/Ack 号等识别)的 mark 值。如果 CONNMARK --set-mark 规则在这两者之间,mark 值应该发生变化。
      • CONNMARK --save-mark 的效果主要体现在 conntrack -L 中,NFLOG 本身不直接显示保存操作是否成功写入 conntrack entry,但结合 conntrack -L -m <mark> 可以确认。
      • 对于连接的后续数据包,看 AFTER_CONNMARK_RESTORE 时捕获的包,其 mark 值是否变为了你期望的标记?如果变了,说明 --restore-mark 生效了;如果没变,检查 --restore-mark 规则本身(是否命中?条件对不对?)。
  6. 清理 NFLOG 规则和接口:
    排查完毕后,记得删除添加的 NFLOG 规则和虚拟接口。

    # 查看 mangle 表 PREROUTING 链的规则编号
    sudo iptables -t mangle -nvL PREROUTING --line-numbers
    # 根据编号删除 NFLOG 规则 (假设是第 3, 5, 7 条)
    sudo iptables -t mangle -D PREROUTING 7
    sudo iptables -t mangle -D PREROUTING 5
    sudo iptables -t mangle -D PREROUTING 3
    
    # 删除虚拟接口
    sudo ip link set nflog100 down
    sudo ip link delete nflog100
    

第五步:检查内核模块和系统状态

虽然不常见,但偶尔也可能是底层的问题。

  • 内核模块: 确认 nf_conntrack, nf_conntrack_ipv4 (或 ipv6), xt_conntrack, xt_CONNMARK 这些模块都已加载。
    lsmod | grep -E 'conntrack|CONNMARK'
    
  • 连接跟踪表状态: 检查连接跟踪表是否已满?
    # 查看当前连接数
    conntrack -C 
    # 或者更精确的
    cat /proc/sys/net/netfilter/nf_conntrack_count
    
    # 查看最大连接数限制
    cat /proc/sys/net/netfilter/nf_conntrack_max
    
    # 检查 dmesg 是否有报错
    dmesg | grep nf_conntrack
    
    如果表满了,新连接无法跟踪,CONNMARK 自然也无法工作。需要调大 nf_conntrack_max 或者排查为什么连接数这么多。
  • 相关 sysctl 参数: 有些参数可能会影响连接跟踪行为,但一般默认值即可。除非你特意改过,否则不太可能是问题源头。

总结常见的坑

  1. 规则放错表/链: CONNMARK 主要在 mangle 表的 PREROUTINGOUTPUT
  2. 规则顺序错误: 被前面的终止规则挡住,或者 restoresave 前。
  3. 匹配条件不准: 规则压根没被命中 (pkts 为 0)。
  4. 只用 --set-mark 没用 --save-mark: 标记没有持久化到连接跟踪记录。
  5. conntrack 命令查标记时用了十六进制: conntrack -L -m 需要十进制值。
  6. 忘记 --restore-mark: 后续包无法获取标记,导致依赖标记的规则(如 ip rule fwmark)失效。
  7. 连接跟踪表满了: dmesg 报错 table full
  8. 使用了 -m mark 而不是 -m connmark: -m mark 匹配的是数据包本身的 mark (通常由 MARK target 设置),-m connmark 才匹配连接标记。

排查 CONNMARK 问题确实需要耐心和细致。按照这个思路,从基础检查到 iptables 计数,再到 conntrack 状态,最后动用 NFLOG 大杀器,层层递进,总能找到症结所在。祝你好运,少掉点头发!

点评评价

captcha
健康