在一台已经在运行 VPN 服务端的机器上再加一份客户端配置,最先受害的是原有服务。新隧道可能根本建立不起来,也可能建立起来之后,服务端原有的客户端悄悄收不到回包。两种冲突造成这两种结果:一是已经在用的隧道地址池,二是新客户端那一半装上的、覆盖整张路由表的默认路由。两者都不会产生 OpenVPN 的错误信息,所以这个故障常被读成服务端的问题。

一台机器、两种角色,以及对回包该往哪里去的两个互不兼容的答案。

同一个地址池,两个服务端:谁赢

每个服务端实例都需要自己的隧道子网。把 server 10.8.0.0 255.255.255.0 这样一行复制进第二份配置,两个实例就拿到同一个前缀,结果要么是启动被拒,要么是一场安静的争夺:一个服务端的客户端拿到的地址,另一个服务端也认为属于自己。换 UDP 端口、换设备名都没用,因为这两者都不碰地址空间。要改就改第二个池子,并且把路由里提到它的地方全部改掉。

动手之前先列清楚哪些是活的:ip -4 addr show 列出隧道设备,ss -lunp 列出监听者,systemctl list-units 给出单元名。看一眼隧道地址,就能判断两个池子是否相撞。

客户端配置往路由表里加了什么

客户端那一半一连上就写路由。redirect-gateway def1 把默认路由拆成 0.0.0.0/1 加 128.0.0.0/1,两条都走新设备,同时还加了一条通往远端服务器的主机路由,并刻意把它钉在物理接口上。这个钉住的动作,是为了让隧道给自己的传输留一条路。其余一切,包括本地服务端欠自己客户端的回包,从此都跟着新的默认路由,从远端链路出去。

所以第二种冲突不是隧道坏了。它是一条工作正常的隧道放错了路由表里的位置。

把两种角色分开的三种办法

侵入最小的修法是把默认路由挡在客户端配置之外:用 route-nopull 加上为远端前缀显式写的 route 行,这样本地路由表只在你要改的地方变动。下一步是一条策略规则,把本地隧道子网排除在外,即使存在默认路由,发给本地客户端的回包也能从它进来的那块网卡出去。最彻底的是给客户端角色单独一个网络命名空间,它把共享的路由表整个拿掉,代价是要多维护一套路由上下文。

无论选哪条路,例外都必须写明本地隧道子网,而不是 LAN。本地服务端发出的回包,目的是一个隧道地址,所以按 LAN 形状写的例外永远匹配不上。

两个实例里到底是哪一个的问题

先把客户端实例停掉,再测原来的客户端。立刻恢复说明是路由问题,不是服务端。把客户端放回来,用 ip route get 同时给出源地址和目的地址去追一条回包,它会显示内核会选哪条路;然后改用 tcpdump -ni any port 1194 观察数据包真正走的是哪块网卡。服务端的状态文件每隔几秒由 status 指令写出,里面有已连接客户端列表和两个方向的字节计数。

把这些计数器对照着读。只出不回的流量是路由给出的答案,不是握手出了问题。

相关文章