两个 VPN 池的源地址路由:你的规则漏掉了什么
from 形式的规则匹配的是 SNAT 改写之前的客户端地址,而从网关发起的测试两个都匹配不上。按池执行查找,改为读取优先级。
两个客户端池、一台网关,计划看上去很简单:一条规则让第一个池走快速上行链路,另一条让第二个池走合作方隧道。规则配好之后,第二个池却仍然从默认路由出去。规则没有坏。在查找发生的那一刻,报文的源地址并不是作者以为的那个,因为地址转换和查找发生在同一个报文旅程的不同位置,而顺序与大多数人脑中画的图正好相反。
查找发生在源地址被改写之前
对从 VPN 客户端转发出去的报文,内核按固定顺序执行。报文到达隧道接口,经过 conntrack 与 DNAT 所在的 prerouting,随后做出路由决策,再转发,最后在 postrouting 应用 SNAT。from 形式的规则是在路由决策阶段被查询的,也就是说它看到的是客户端的原始地址,而不是转换后的地址。
因此,加一条 masquerade 规则并不改变哪条策略匹配出向流。改变的是对端看到什么,从而决定存在哪条回程路径。这是两件不同的后果,而变更评审经常把它们混为一谈。把两者分开,设计往往在一分钟内就清楚了。
回程方向上,转换后的地址才会出现在规则里。回到网关的报文以转换后的地址为目的,如果你的策略规则基于目的地址或报文进入的接口,匹配到的就是那个值。同一条连接,两个方向各自的参照点不同。
| Stage | Source the kernel sees | What it means for your rule |
|---|---|---|
| Prerouting | Client address, 172.31.7.42 | Where marks and DNAT are applied |
| Routing decision | Still 172.31.7.42 | This is where from rules match |
| Postrouting | Becomes 198.51.100.9 | What the far end and its return route see |
所以来源不同的两个池确实需要不同的路由表,设计本身没有问题。上表排除掉的,是靠观察远端收到了什么去反推规则这条捷径。
从网关发起的测试不是池的测试
网关自己生成的流量从不经过 prerouting,因为它不是从某个接口进来的。它走的是 output 路径,源地址是网关自己的地址,而以客户端网段为条件的 from 规则自然不会命中。这个领域里每一张机器上能通、客户端不通的工单,都是这条事实的复述。
与其近似,不如模拟客户端。路由查找可以直接查询,只要给出源地址和入口接口:
ip route get 198.51.100.9 from 172.31.7.42 iif tun0
ip route get 198.51.100.9 from 172.31.8.42 iif tun0
ip route show table 201
每个池各跑一次。如果两条命令返回同一个接口,说明规则没有把两个池分开,原因要么是缺规则,要么是有一条优先级更高、对两者都命中的规则。如果返回的接口不同但客户端仍然失败,故障就在查找之后,可以不再看规则了。
优先级,而不是文件中的位置
规则按优先级从小到大的顺序求值,起作用的是数字,而不是你输入的先后。local 表位于优先级 0,main 表位于 32766,默认规则位于 32767,所以用默认优先级添加的规则会排在列表末尾附近,几乎输给所有规则。
ip rule show
ip rule add from 172.31.8.0/24 lookup 201 priority 1000
ip -4 route show table 201
显式写明优先级,可以让预期顺序在文件里和评审中都看得见。它还能防止后加的一条规则悄悄插到已有规则前面,这正是原本可用的策略在一次无关变更之后失效的最常见原因。
命中规则却指向一张不完整的表,是另一个坑。如果 201 表里有默认路由,却没有到目的地的路由,报文会被丢弃或送到错误的地方,而规则单独看完全正确。要看表,不能只看规则。规则是路标,空表则指向空无。
回程路径仍然必须存在
源策略只选择出口。它不创造回程路径。如果第二个池经合作方隧道出去,合作方必须知道怎样到达 172.31.8.0/24:要么有一条回网关的路由,要么接收它已经认识的转换后地址。
在这两者之间取舍,是有可见后果的设计决定。把客户端网段回程路由过去,可以在合作方那里保留每个客户端的身份,代价是要求对方接受你的前缀。做地址转换则把池藏在一个地址背后,只要求对方接受这一个地址,代价是双方的日志不再一致,无法再对上是谁连上来的。
无论选哪种,都写在池定义旁边。只存在于某人记忆里的回程路由,恰恰会一直活到合作方改防火墙的那一天,然后再花一天重建。
不要在策略里抓到网关自己的隧道
你为客户端写的规则同样作用于网关自己生成的流量,除非显式排除。一条基于标记或出接口的宽泛规则可能抓到网关到合作方的那条连接,于是承载策略的隧道被策略本身路由了。这会产生环路或中断,看起来像运营商故障。
把每条规则收窄到设计允许的程度:源前缀、入口接口,以及在支持的情况下只由客户端报文携带的标记。然后每次改规则之后都测一遍网关自身的连通性,因为这个故障是自指的,在客户端测试里根本不会出现。
ip-rule 手册页记录了选择器与优先级的语义。把它和按池的查找测试结合起来,设计就变得可检查,这正是能撑一年的策略路由和没人敢碰的策略路由之间的区别。