两个 VPN 客户端可以并排运行,但它们的 kill switch 不会合并成一套策略。每个客户端只为自己的隧道安装规则,谁都不知道另一个存在。我遇到的情况是工作客户端先连上:第二个应用完成不了握手,在“Connecting”上卡了十分钟,而第一条隧道悄悄接管了全部流量。第二个应用是否受到保护,取决于三个条件,而不是两个图标是否都显示已连接。

kill switch 的实际作用范围

操作系统里并不存在单一的 kill switch 开关。宣称支持它的客户端会安装一套规则:丢弃所有不经由我的隧道发出的流量,并且常常在隧道消失时采取 fail closed。在 Linux 上通常是一条 nftables 或 iptables 链,在 Windows 上是一组 Windows Filtering Platform 过滤器,在 Android 和 iOS 上则是隧道服务自身的路由加上阻止无 VPN 连接的系统设置。Android VPN 指南描述了这种服务模型,如果你想了解平台视角。

作用范围才是关键。像“只允许从 wg0 出口接口发出的数据包”这样的规则,并不关心是否存在第二条隧道。如果另一个客户端把 wg1 拉起来,第一个客户端的规则会把这些数据包当作普通流量:通常直接丢弃,偶尔放行。无论如何都是第一个客户端在做决定,而它完全不知道还有第二个应用在等。

决定谁被覆盖的三个条件

你不需要枚举所有组合。三个问题几分钟内就能定下来。

  1. 平台是否允许第二条隧道?大多数 Android 手机和 iPhone 每个用户配置只接受一个活动的 VPN 服务,所以第二个应用永远拿不到隧道,也永远不执行任何策略。你只得到一套策略,来自抢到位置的那个应用。
  2. 哪个进程拥有默认路由?Windows、macOS 和 Linux 都能轻松承载两条隧道。默认路由属于最后配置它或者度量值更好的那个客户端,另一个客户端虽然连着,却处于闲置状态。在 Linux 上,wg-quick 的默认路由模式使用带私有表和 fwmark 规则的政策路由,因此第二条 wg-quick 隧道会新增一条竞争规则,而不是替换第一条。
  3. 第二个客户端能到达它自己的服务器吗?如果第一个 kill switch 是默认拒绝,第二个应用的外层端点就只是一个被丢弃的目标。它会悄悄重试,从不报错。

给支持帖用的简版:

SetupWhat you actually get
Second client cannot hold a tunnelOne active policy; the other app only reports that it is waiting
Both tunnels up, second took the default routeThe first kill switch blocks traffic it never meant to touch; the connection dies, or leaks if its rules are broad
First kill switch allows only its own endpoint and interfaceSecond client never connects; first tunnel unaffected
Both up, DNS written by whichever client ran lastName resolution breaks for one side when either tunnel drops

其中两行从托盘图标看完全一样,所以图标在这里是最差的证据。

先坏掉的是什么,以及它长什么样

另一个客户端健康、这个客户端却卡在“Connecting”上,几乎总是端点饥饿:正在运行的 kill switch 把第二个客户端发往自己服务器的握手指令丢掉了。便宜的验证方法是把第一个客户端的拦截规则暂停一次。如果第二个立刻连上,问题就有答案了。

DNS 是第二个故障。客户端会为自己的接口设置解析器配置,有些则干脆直接覆盖全局解析器列表。两条隧道都起来时,查询可能从一侧出去,而解析器却属于另一侧。断开任意一个应用,活下来的那个会保留一个不可达的域名服务器:页面停顿一分钟然后重新解析,而 VPN 仍显示已连接。

第三个是路由抖动。重连会重新排列默认路由,而绑定在接口上的 kill switch 不会跟着走。刚刚还在隧道里的流量,现在匹配到一条丢弃规则,用户侧看到的就是 VPN 时不时突然失灵。

验证到底哪套策略在生效

用无害的流量来做,别用你会在意的东西。

在 Linux 上问内核谁说了算、防火墙绑定到什么。ip -4 route show default 会说出拥有出口的那条隧道。ip rule show 显示是否启用了政策路由。sudo nft list ruleset | grep -n oifname 显示丢弃规则绑定了接口名、标记,还是整个输出链。

在 Windows 上同样的故事在路由度量和过滤器列表里:Get-NetRoute -DestinationPrefix "0.0.0.0/0" | Sort-Object RouteMetric 和 Get-NetFirewallRule -Enabled True | Where-Object DisplayName -match "VPN"。在 macOS 上则是 netstat -rn -f inet | head -5 和 scutil --dns | head -20。

然后做大多数人跳过的那一步:故意停掉隧道,确认你预期会失败的流量真的失败了。已连接的图标是界面状态,不是策略执行。

什么时候两个客户端是错误的架构

如果第二个应用背后的唯一需求是更多保护,那就把它去掉。两套互相作用的拦截策略比一套更不可预测,而每次重连都有可能落进两家厂商都没测过的状态。

如果你确实需要链路串联,比如通过商业跳点抵达企业网关,那就选只有一个执行点的方案。这通常意味着一个路由器,或者一个支持该链路的客户端,而不是两个桌面应用去抢同一条默认路由。在受管设备上,保留被批准的客户端,把其他一切都当作范围之外,不管它在托盘里显示什么。

同时保留两者之前的检查清单

按顺序往下走;第一个失败的项目就是要修的那个。

  • 确认只有一个客户端同时拥有默认路由和解析器。
  • 如果你想让第二个客户端保持在线,就显式放行它的服务器端点,并在每次客户端更新后重新检查。
  • 写下任一条隧道掉线时的预期结果,然后专门测那个场景。
  • 重启之后检查失败路径,此时启动顺序不同。
  • 在受管配置里,先停下来并在添加任何东西之前询问所有者。

如果第三步失败,诚实的结论是只有一个客户端在给你策略。决定你信任哪一个,然后放掉另一个。

相关文章