为什么第二条 VPN 隧道会让第一条失效?
第二个 VPN 客户端会装上半默认路由,第一条隧道随即变黑:看清哪条路由胜出,再按配置文件、路由表或命名空间拆分。
为了接入一个客户网络而安装第二个 VPN 客户端,是这件事最常见的起因;两个客户端都报告成功,第一条隧道却黑了。每个客户端都装了一条默认路由,第二条更具体,内核现在对除第一条隧道自身外层连接之外的一切都走它。没有任何东西崩溃,也没有任何配置文件出错。一条路由替换了另一条,解决办法是决定哪条隧道该拥有默认路由。
症状:第二个客户端悄悄赢了
OpenVPN 的 redirect-gateway def1 不会覆盖 0.0.0.0/0。它添加 0.0.0.0/1 和 128.0.0.0/1:两者合起来覆盖全部 IPv4 地址,而且每个前缀在长度上都胜过普通默认路由。原来的默认路由留在表里,不再用于普通流量;指向 VPN 服务器的主机路由则让隧道不会捕获自己的外层数据包。两个客户端都这么做,第二个只是晚一点装它那一对。
所以第一条隧道看起来像死了,而不是断开了。它的接口仍在,控制通道仍活着,却再也没有应用流量到达。反向的抱怨来自同一个缺失的推送:一台连上却什么都不发的手机,通常是在和从未通告 redirect-gateway 的服务器通话,于是设备保留自己的默认路由,只够到地址池。
在第二个客户端连接前后各取一次路由快照。表里出现的那两条半路由就是全部变化,也解释了两类抱怨。表里其他东西不必移动,症状就会出现。这也是为什么卸掉第二个客户端,第一条会立刻恢复。
看清哪条隧道真正获胜
不要从客户端的启动顺序或托盘图标推断赢家。问内核去往两个私有网络之外的某个目的地时它会用哪条路由,然后读回来的接口。
ip route show
ip route get 198.51.100.24
route print -4
Find-NetRoute -RemoteIPAddress 198.51.100.24
前两条是 Linux 的答案,后两条是 Windows 的等价物。在两条隧道都在时运行,然后断开一条再运行一次。两次输出的差别就是你选定方案前需要的事实,而且比读任何一个客户端的日志都省时间。
把结果记下来,包括接口名和匹配到的前缀。写着“第二个 VPN 弄坏了第一个”的工单只是症状描述,不是任何人都能据以行动的选路结论。这次查证把它变成事实,下一位工程师用一条命令就能复现。
Windows:别让一个配置文件拥有默认路由
Windows 保存已存的配置文件,内置客户端允许一个依赖另一个,于是第二个拨号时第一个会自动起来。最近连上的配置文件往往握着默认路由,所以实际的拥有者是最后一次拨号,而不是第一次。给每个配置文件各自的推送路由,并对不该拥有默认路由的那个打开拆分隧道。
Set-VpnConnection -Name "Contoso" -SplitTunneling $true
rasdial Contoso
拆分隧道会改变这一点:只有服务器推送的前缀进入路由表,默认路由就留在第一条隧道手里。如果两台服务器都推送默认路由,设计仍然会失败,因为这时最后一次连接获胜,客户端侧的任何设置都改不了。要么修正服务器侧的推送,要么接受只有一个拥有者。
由应用触发的配置文件是另一项功能,值得了解:Windows 配置文件可以在某个指定可执行文件启动时开始,由流量过滤器决定它承载什么。这是配置文件设置,不是防火墙规则,解决的是与默认路由冲突不同的问题。
Linux:把第二条隧道留在主表之外
在一台机器上跑两个 openvpn --config 实例,会把两个客户端放进同一张表,三种做法以递增的隔离度把它们分开。最轻的一种是干脆不让第二个客户端改表:用 route-nopull 加显式路由,或用一个只安装你意图的 route-up 脚本。这样第二条隧道只承载你点名的前缀,别的什么都不带。这样你就是自己决定前缀往哪走,而不是事后才发现。
策略路由是中间选项。一条规则为某个源地址或防火墙标记选择单独的表,那张表承载第二条隧道的路由。这里的陷阱是反向路径过滤:到达接口与表会选择的接口不同的回包会被丢弃,所以在断定隧道坏了之前先检查 rp_filter 设置。
ip rule add from 10.44.7.0/24 table 100
ip route add default dev tun1 table 100
sysctl net.ipv4.conf.all.rp_filter
网络命名空间是最重也最干净的做法。在 ip netns 里启动第二个客户端,给它一对 veth,进入那个命名空间去用它。干扰消失了,因为每个命名空间拥有自己的表而不是共用一张;当两条隧道都要连着好几周时,这点额外配置值得。
按应用来用,以及把串接当成另一个问题
有时只该让一个程序用第二条隧道。在 Linux 上,按用户、cgroup 或端口做策略路由就能做到,而且可以在完全不碰默认路由的情况下把单个目的地钉在某个接口上。把套接字钉在某个接口的应用根本不会去查路由表,所以启动器可以继续用物理网卡,直到它自己的设置里选定地址为止。
串接和并排跑两条隧道是两回事。把 A 串在 B 之上,意味着内层服务器的地址必须能从外层隧道内部到达。外层客户端也不能把内层客户端的加密包当成泄漏。虚拟机里放一个客户端是笨但可靠的做法;两个都放在同一台主机上,就需要上面那套路由工作,再加一条永不进入内层隧道、通往内层服务器的外层路径。
把默认路由的拥有者、每条隧道承载的前缀清单和 DNS 路径写下来。这三件事能把下一次事故变成一次查阅,而不是一场调查。