你把 VPN 切到全隧道,大部分流量遵从,随后有一个地址直接响应——打印机、NAS,或 10.x 上的跳板主机。显而易见的解读是隧道在泄漏,于是你调低默认路由的度量值,结果什么也没变。那次失败的尝试才是线索:默认路由是兜底,不是裁决。路由查找 在比较度量值之前就先取最长匹配前缀,因此 /24 永远赢过 /0,无论你给它绑上什么数字。顺序一旦清楚,泄漏通常就是有人刻意安装的路由,或客户端从未打算覆盖的前缀。

默认路由真正承诺了什么

0.0.0.0/0 以零位特指匹配任意 IPv4 目的地。它存在的意义是让主机能到达其路由表描述得不够精确的一切。通往 192.168.1.0/24 的路由携带 24 个匹配位,所以对 192.168.1.40 而言,它在任何人比较度量值、管理距离或客户端所谓优先级字段之前就已获胜。同一规则适用于 Linux FIB、Windows 路由表和 macOS 背后的 BSD 协议栈。

在普通局域网里,这种行为是特性。本地子网直连,打印机继续工作,而隧道承载你的浏览流量,没有任何配置错误。真正值得问的不是那个地址为什么直连,而是哪张表里的哪个前缀与它匹配,以及是谁把它放在那里的。

半默认路由,或者默认条目为何看起来未被改动

最干净的例子是 redirect-gateway def1 在 OpenVPN 中的做法。它不删除并替换你的默认路由,而是添加 0.0.0.0/1 和 128.0.0.0/1。两者合起来覆盖整个 IPv4,每个前缀都比 /0 长,因此都在击败路由器默认路由的同时把它留在原处。之后运行路由表列表,原始默认路由仍在,仍指向你的网关;它只是永远不会被选中用于互联网目的地。

WireGuard 的 wg-quick 通过基于策略的路由达到同样结果。当 AllowedIPs 覆盖 0.0.0.0/0 时,它创建自己的表,把标记过的数据包发往那里,并添加一条忽略主表零长度前缀的规则。主表保留自己的默认条目,而规则决定:对于接口所拥有的流量,先查询隧道表。

两种设计都需要隧道之外恰好一条路由:经由原网关指向 VPN 服务器自身地址的主机路由。没有它,客户端会试图把构建隧道的数据包送进隧道内部,连接永远无法完成。因此,即使在严格的全隧道配置文件里,表中出现一条非隧道路由也是正常的。真正值得质疑的路由,是那些覆盖你确实在意的目的地的路由。

度量值决定平局,前缀决定胜者

度量值只在同样好地匹配目的地的候选之间比较。在 Linux 上,ip route get 10.20.5.9 会连同其所在表一起打印胜者,ip route show table all 会列出候选,包括任何半默认路由和规则指向的任何表。在 Windows 上,Find-NetRoute -RemoteIPAddress 10.20.5.9 返回被选中的路由和源地址,而 Get-NetRoute 显示原始表——但有效成本是路由度量值加接口度量值,所以慢速适配器可能输掉一场看似它赢的平局。

三种情况覆盖你将看到的大部分现象:

  • 同一目的地,/24 对 /0:无论度量值怎么说,/24 凭前缀长度获胜。
  • 同一目的地,两个 /24 度量值不同:有效度量值更低者获胜。
  • 同一目的地,两张表,其中一张由策略规则选中:规则先选表,前缀比较在其内部进行。

第三种情况正是这类报告大多数终结之处。Linux 的 ip rule 或 Windows 的 VPN 流量过滤器可能把流量送进一张从未持有隧道默认路由的表,而主表全程看起来完美无缺。

谁通常拥有这条绕行路径

从配置文件开始,而不是从表开始。拆分隧道配置和按应用设置的 VPN 会有意把指定前缀留在隧道之外;一个保护某个私有子网、让普通浏览直连的工作配置文件正是按设计行事,覆盖它就是破坏它本该执行的策略。Microsoft 的 VPN 路由指南 在被强制进入隧道的流量与显式例外之间划出同一条线,这使它成为动手修改前有用的交叉检查。

然后寻找残留。指向已不存在接口、或指向前一个网络地址的路由,会在扩展坞、USB 适配器和容器网桥来来去去之后出现。删除一次并不是终点:NetworkManager 连接配置、netplan 文件和 Windows 的持久路由会在下次连接或启动时重新安装它,所以在改动任何东西之前,先找到拥有它的文件或配置。

然后单独检查 IPv6。ip -6 route 和 Get-NetRoute -AddressFamily IPv6 各自保存独立状态、独立默认路由和独立客户端行为。只审计 IPv4 对一台静默偏好原生 IPv6 路径的双栈主机毫无说明,而仅这一点就足以让客户端看起来像拆分隧道。

用应用自身的流量来确认

记录应用实际使用的目的地,然后向系统询问它会从该应用绑定的源地址走哪条路径。ip route get 10.20.5.9 from 192.168.1.40 在 Linux 上回答这个问题;Find-NetRoute -RemoteIPAddress 10.20.5.9 加上本地地址可在 Windows 上做到。把结果与配置文件所声称的对比。浏览器的公网 IP 页面只在一个协议上测试一个目的地,对引发投诉的私有地址毫无说明。

修最窄的那个能解释差异的对象。如果例外已获批准,就写下前缀并保留它;如果没有,就删除该路由,或通过隧道添加一条显式更窄的路由,而不是降低默认路由的度量值。降低度量值无法越过更长的前缀,而在两条 /1 路由拥有互联网路径时改动默认路由同样无济于事。

然后重新连接隧道并重复同一查询。客户端每次连接都会重新安装路由,因此只维持到下一次握手的改动并未被验证。如果路径仍然不一致,剩下的嫌疑是策略规则、第二张表或客户端侧过滤器——而不是默认路由。

  • 说出应用实际使用的目的地和源地址。
  • 用 ip route get 或 Find-NetRoute 打印获胜路由。
  • 检查互联网路径属于 /1 对还是策略表。
  • 列出每个比 /0 长且匹配目的地的前缀。
  • 单独重复 IPv6 检查。
  • 把幸存的那条路由追溯回安装它的文件、配置或过滤器。
  • 在宣称修好之前,重新连接并再次运行查询。

如果这七项都与配置一致,隧道正是按配置执行,修复应落在配置里,而不是落在度量值上。

相关文章