在 nftables 里设置的标记本应把一类流量送进隧道,流量却仍然从物理接口出去。规则集单看是对的。通常发生的是 别的什么东西写了同一个标记,或者路由规则匹配的掩码新值已经不再满足。

检查 1:读内核真正匹配的规则

从策略规则入手,而不是防火墙。内核按优先级顺序检查规则,第一个匹配决定查哪张表。从规则本身读出 fwmark 值和掩码,然后确认带着你所设标记的报文确实匹配它。期望完整 32 位值的规则不会匹配只带一个比特的报文。

然后确认那条规则选中哪张表,以及表里有什么。指向空表的规则会导致查找失败,在很多系统上这意味着报文落到下一条规则,从物理接口出去。

两条命令就能定论:规则列表,以及目标在这张表里的路由查找。

检查 2:报文标记还是连接标记

nftables 可以在报文上设标记,也可以在连接跟踪表项上设标记,二者是不同的对象。报文标记只在报文处理这一次经过期间存在,连接标记则在整个流期间保留,必须刻意复制。设了其中一个却指望另一个,是常见的半吊子修法。

如果路由规则依据的是报文标记,那么在路径后段恢复连接标记对该判定毫无作用,因为判定已经做过了。

先确定路由信号归哪个对象所有,再在做出路由判定的那个点设置它。

检查 3:还有谁在写这个标记

WireGuard 自己的工具会给它的 UDP 报文打标记,以免加密传输又被路由回它所属的隧道。如果你的规则集给同一个字段赋了不相关的值,这个排除就消失了,隧道的外层流量会试图进入自己。症状是隧道建立起来,然后不再转发流量。

其他防火墙工具、容器网络以及用于其他目的的按策略路由也会使用标记,所以两个子系统写整个字段就是冲突,即便各自单独看都没错。

在再加一个之前,先把写入者列出来。

检查 4:掩码决定你保留什么

标记是 32 位字段,掩码是多个所有者共享它的方式。带掩码设值会保留不属于你的比特;设整个字段则会破坏其他所有人的。凡是碰这个字段的规则都要读一遍掩码,包括你没写的那几条。

把比特分配写在规则集旁边。

否则下一个人会重复使用已经有主的值,而这里的冲突看起来像路由缺陷,实际上是地址缺陷。nftables 关于报文元数据的文档 讲的是标记操作本身。

检查 5:用真实流量验证,而不是靠查表

普通的路由查找不带标记,所以它显示的路径可能和真实报文不同。生成你关心的流量,观察计数器、连接跟踪表项,以及它实际从哪个接口出去。被包含的一类和被排除的一类都要测,因为过宽的标记会破坏排除,却看起来像是成功了。如果被包含的一类通了,被排除的一类也进了隧道,那说明标记太宽,而不是太窄。

避免在会话进行中清空连接状态来强制刷新,因为新建和已有的流本来就可以走不同路径。

把标记、掩码和表记在一个地方,下一次修改就可审查了。

相关文章