一条本该给某个 VPN 客户端池分配独立公网地址的 NAT 规则,计数器一直是零,而同一个池仍从错误的源地址出去。规则通常不是地址写错,而是接口写错。 -o eth0:0 指向一个并不存在的设备,因为 eth0:0 是挂在 eth0地址上的标签。看计数器是确认这一点最快的办法,而修复比排查所暗示的要小。

先看内核把这个设备叫什么

动手之前,把两个视图分开读:

ip link show
ip address show dev eth0
ip route get 203.0.113.20

链路列表显示的是接口。地址列表可能显示第二个 IPv4 地址,它带着标签 eth0:0,而该地址对应的设备仍然是 eth0。iproute2 的地址手册把这个标签当作地址的名字,而不是第二条链路,整个问题的关键正在于此。

历史解释了这种混淆。旧的 ifconfig eth0:0 语法看上去确实像新接口,那个年代的脚本至今仍这样引用。在现在的系统里,别名只是地址上的标签,路由或 netfilter 路径上没有任何东西把它当成独立的出口点。

看看规则实际匹配了什么

对照规则列表读计数器,不要凭记忆:

iptables -t nat -L POSTROUTING -v -n --line-numbers

在匹配的流量经过时,包计数器仍停在零的规则根本没有被评估,而指定了 eth0:0 出口接口条件是很常见的原因。路由选中的出口设备是 eth0,所以去找另一个名字的条件永远不会成立,无论内核选的是哪个源地址。

把条件改成真正的设备,并把标签排除在外。如果需要区分共用这个设备的两个客户端池,就按源前缀区分,而不是用一个不可能存在的接口名。两条匹配范围不重叠的 -s 规则可读也可测;一条带虚构设备的规则两者都不是。给每条规则加上注明池子的注释,因为半年之后,光看前缀没人说得清它为什么在那儿。

检查选中这个设备的路由

postrouting 链上的 NAT 在路由选好出口接口之后才执行,所以 规则里的接口必须是路由选中的那个。直接用 ip route get 向内核查询一个有代表性的客户端地址,并同时读出它报告的设备和源地址。

如果答案是 eth0,规则的接口部分就定了。如果答案是一个隧道设备,数据包根本到不了你正在编辑的规则,排查方向就要转到路由表及其优先级。因为数据包从别处离开而永不匹配的规则,在计数器输出里和写错设备的规则一模一样。改任何东西之前,先确认你属于哪一种,因为第二种需要改路由,而不是改文字。

有意识地选择转换后的源地址

设备对了之后,源地址是另一个决定。这里只是一个示意用的 IPv4 池和一个示例地址:

iptables -t nat -A POSTROUTING -s 10.8.1.0/24 -o eth0 \
  -j SNAT --to-source 203.0.113.20

把地址写进 --to-source 并不会让人把它路由回来给你。上游服务商或地址所有者必须把该地址的回复送到这台主机,而且这个地址必须是你有权使用的。否则,规则照样匹配、照样转换,回复依旧会去别处。

改源地址不等于改上行链路。真正的需求是另一条物理路径时,路由策略必须先选中那条路径;然后 SNAT 再在策略选中的设备上改写源地址。在 eth0 上再加标签,既给不了路由,也给不了授权。

用刚刚新建的连接验证

开一条新的客户端连接,而不是复用已有的。连接跟踪会保留流开始时的转换,所以旧会话可以继续用之前的源地址,让一条正确的新规则看起来是坏的。看着计数器上涨,然后在链路上或远端观察转换后的源地址。

多个池共用一个接口时,给每个池显式的源匹配和各自的转换地址,把它们放在防火墙的受管配置里,并记下它们的评估顺序。然后把验证过的东西记下来: ip route get得到的设备、动过的计数器,以及对端看到的源地址。这份记录可以回答下一份报告,不必再重复标签带来的混淆。

相关文章