我有一台实验机,既接受来自远程站点的隧道,又向外拨号连接供应商网络。出站配置原本稳定运行了数周。加入入站服务端之后,出站连接建立后几秒内就不再转发流量,或者干脆以“地址已被占用”的错误拒绝启动。配置文件并没有改动;这台主机只是多了一个 OpenVPN 进程,而它对端口和路由有自己的主张。

这种故障并不随机,也不是认证问题。同一台机器上的两个守护进程争夺同一个本地套接字和同一张路由表,后启动或后重连的那个往往获胜。判断你遇到的是哪一种冲突,决定了先从哪里查。

倒着读症状

先看隧道是什么时候断的。启动时的绑定错误指向监听侧:已经有东西占着那个端口,OpenVPN 宁可拒绝启动也不肯共享。隧道能建立、随后却安静下来,则指向路由,因为另一个实例安装的路由会把外层连接的报文从原路径上拽走。

两种情况产生的是同一句用户抱怨,所以重试循环和看门狗脚本什么也修不好。它们只是把客户端重新接回同一个冲突。更有用的第一个问题是:流量停止时进程还在不在。

每个实例一个套接字、一个设备

监听的服务端会绑定自己的端口;出站客户端也需要一个本地端口来承载封装流量。两者同处一台主机时,客户端的默认行为可能与服务端端口相撞。客户端的本地端口可以固定,而在对端不需要固定源端口的地方可以跳过绑定:

lport 1195
nobind
dev tun1

那些数字只是示例,不是硬性要求。关键在于客户端不再争抢服务端的端口,并且每个实例都拿到独立的隧道设备。两个守护进程共用 tun0 时,即使端口不同也会互相覆盖设备状态,所以每条隧道一个设备并非可选。

nobind 属于客户端一侧。服务端仍然必须绑定并监听,所以不要把这行抄进服务端配置,也不要在对端按固定源端口过滤的客户端上关闭绑定。

默认路由只能有一个主人

只有一条连接能安装默认路由。如果两个配置都重定向全部流量,第二次连接会悄悄替换掉第一条,于是第一条看起来坏了。要么让出站隧道保持分流、只推送供应商的前缀,要么给它一张单独的路由表。

ip rule add from 10.9.0.2 table 120
ip route add default dev tun1 table 120
ip route get 10.9.0.2

上面的地址和表号都是占位值。添加规则后要立刻确认 VPN 服务端自身的地址仍能通过常规路由表解析;一张把外层传输也捕获进去的表,会破坏它本该保护的隧道,随后出现的重连循环看起来就像供应商不可靠。

对于必须经受重连和重启的部署,网络命名空间是更稳固的边界。第二个客户端在自己的命名空间里运行,拥有自己的接口和路由,只暴露必须跨越边界的服务。主机只保留一条默认路由,命名空间内的重连无法改写它。

用路由表验证,而不是看状态行

客户端报告“已连接”只能证明它的控制通道。在每次连接前后抓取路由状态,然后问内核:一个真实目的地址会走哪个设备:

ip route show
ip route get 10.40.0.20
ss -lunp | grep 1194

如果第二条隧道启动后,供应商地址选中的设备变了,那就是路由冲突。如果两个进程出现在同一个本地端口上,那就是套接字冲突。只修前者不修后者,只是把故障推迟到下一次重连。

把端口、设备、路由表和路由归属写进主机的运行手册。否则下一个添加第三条隧道的人会再次发现同样的冲突,而唯一可见的证据,就是一条在别人的守护进程重启前一直能用的连接。

相关文章