路由表与实际查找:VPN 流量到底走向何处
从一个目的地址和它的源地址出发,再看究竟哪条规则、哪张表决定了路径。脱离这个语境的全表转储只会误导人。
路由表转储是 VPN 排障中最没用的诊断手段,也是最常被粘贴出来的东西。四十行前缀只能告诉你机器本可以做什么,而不是它为那次失败的请求实际做了什么。你真正需要的机制是查找:给定这个源地址和这个目的地址,哪条规则匹配了、查了哪张表、最终输出了哪个接口和下一跳。从一个目的地址和一个源地址出发,带着这个问题去读路由表,同样的输出就会从噪音变成证据。
路由表是候选集合,不是决定
每个条目描述一个前缀以及匹配它时该怎么做:送到某个接口、交给下一跳,或者直接丢弃。这个列表并不是按意图排序的,看起来对人类最具体的条目也不一定是内核选中的那条,因为选择遵循最长前缀匹配,前缀相同时则看 metric。
所以第一步是去问内核,而不是去读列表。查找命令会针对一个目的地址给出答案;带上源地址参数,它回答的就是一条转发连接或绑定连接真正会用到的那一对地址:
ip route get 198.51.100.10
ip route get 198.51.100.10 from 10.80.0.25
ip -6 route get 2001:db8:10::40
上面每个地址都是文档示例。重点在于答案的形状:接口、选定的源地址、网关,以及这个条目来自哪张表。如果输出和你预期的接口不一致,你已经用一条命令定位了问题,而无论读多少遍全表都不会告诉你哪个候选赢了。
查找先走规则,再走表
在 Linux 上,这个决定分两步。策略规则按优先级顺序求值,第一条匹配的规则会指定要查询的表;然后由那张表完成最长前缀匹配。规则可以按源、目的、防火墙标记、接口或其组合来匹配,所以主表转储可能完全准确,却和你要查的那个数据包毫无关系。
ip rule show
ip route show table all | head -40
ip route show table 100
如果某条规则把你 VPN 地址池的流量送去表 100,那么真正重要的是表 100,而 主表只是诱饵。这也是路由器上基于源的路由背后的机制:同一个目的地址可能有两个不同答案,取决于数据包从哪里来,只有带源地址参数的查找才能揭示哪一个生效。
IPv6 有自己的规则和表。同时运行两套协议族的设计需要两种查找都做,而一个能用的 IPv4 答案对双栈客户端可能偏好的 v6 路径只字未提。
VPN 客户端安装了什么
客户端通常不会替换默认路由,因为那样会破坏隧道自身的外层路径。它们安装的是更具体、靠前缀长度取胜的条目:覆盖 0.0.0.0/1 和 128.0.0.0/1 的一对半默认路由就是经典做法,因为两者的前缀都比 0.0.0.0/0 更长,合起来覆盖整个地址空间。另一些客户端会安装特定的私有前缀再加一条策略规则,还有些会加一个标记,只捕获选定的流量。
这就是为什么“VPN 没有改我的默认路由”并不是失败的证据。要检查它到底加了什么,以及出事的目的地址是否落在那些前缀里:
ip route show | grep -E 'tun|wg|ppp|ipsec'
ip route get 10.20.30.40 from 10.80.0.25
两条半默认路由在转储里很容易被忽略,因为它们看起来不像默认路由。忘记包含某个前缀的拆分隧道配置,会制造出正是你在追的症状:本该走隧道的应用反而在本地解析并连接。要把已安装的前缀和用户真正需要的目的一起比较,而不是和你碰巧测过的地址比较。一份拆分隧道清单覆盖了文件服务器却没有覆盖它的解析器,产生的连接会解析到公网地址、然后在认证时失败;看起来像密码问题,实际是路由问题。
外层路径也必须活着
隧道本身也是流量。承载封装载荷的数据包走物理接口去往对端地址,这条路径绝不能被路由回隧道里。一旦被路由回去,就会出现那种典型故障:隧道建立起来,然后不再传流量,因为隧道的传输路径现在位于隧道内部。
用检查内层目的地址同样的方式检查对端地址,确认它解析到物理接口而不是隧道:
ip route get 203.0.113.10
traceroute -n -m 5 203.0.113.10
递归路由、捕获对端的策略规则,或者被全隧道客户端替换掉的默认路由,是三种常见原因。这三种都能在外层查找中看到,而在事后拍下的主表转储里一种也看不到。
路由输出不能告诉你的事
查找正确只意味着内核会把数据包发往一个合理的地方。它不意味着防火墙放行、IPsec 策略覆盖、NAT 规则完成转换,或者远端存在回程路由。这些每一项都是更靠后的阶段,而 strongSwan 关于转发的说明 描述了一个数据包在两端都必须经过的链条。
它也没有考虑应用层的绑定。一个进程可以绑定到某个接口、设置标记,或者使用自己的解析器、用一个和你测试时解析结果不同的名字去连接。当查找正确而连接仍然失败时,下一个问题就是哪个进程在发起连接,以及那个进程是否看到和你同一张表。
所以让诊断保持有序:先指名目的地址和源地址,运行查找,只读查找点名的那张表和那条规则,再测试下一个阶段。这个顺序能把一次路由排查控制在几分钟内,并留下别人能照着走的记录,而粘出来的表永远做不到这一点。