从消息出现的地方开始查。客户端连上、隧道建立,然后日志报告 Linux 添加路由的命令以 File exists 失败。这句话里没有任何内容涉及证书、密钥或配置文件,尽管字面容易让人这么想。内核在描述自己的路由表,而你要安装的前缀已经有一条记录了。

按客户端自身的顺序来:它请求了什么、表里原本有什么、是谁把那条记录放进去的,最后才是该改什么。每一步都排除一个可能的归属者。

客户端实际请求的是什么

失败之前,客户端会记录它打算添加的路由,通常写成目标、子网掩码、网关,有时还有度量值或表。那一行就是请求。随后的错误是内核的答复,两者应当对照着读。

journalctl -u openvpn-client@office -b --no-pager | grep -nE 'route|File exists'
openvpn --config /etc/openvpn/client/office.ovpn --verb 4 --log /tmp/ovpn.log

路由辅助程序的非零退出状态,不如它下面那行文本有信息量,因为不同的内核响应都会产生非零状态。保留精确的报错;是已有记录还是网关不可达,决定了下面哪一节适用。

读现有路由,而不只是地址

在表里搜同一个点分四段地址并不够。路由对象由地址族、表、目标前缀、度量值、协议和出接口共同标识,共享前缀的记录只要这些属性不同,就可以合法共存。真正冲突的是整个对象,不是那段文字。

ip -4 route show table all | grep -F '10.40.'
ip -4 route show match 10.40.0.0/16
ip -4 route get 10.40.0.5

最后一条命令回答的是与前两条不同的问题:不是检查是否存在,而是内核此刻会为这个目标选中哪条记录。两种视角都有用,而且它们经常不一致,值得在得出结论前先比对。两者的不一致才是有意思的情况,而不是舍入误差。

把前缀、表、度量值和设备记在失败请求旁边。如果现有记录属性相同、下一跳也相同,那你面对的是重复指令,而不是路由问题。

前缀可能有的四类归属者

直连路由来自已分配的地址,只要该地址存在,系统就会重建。DHCP 客户端或连接管理器会在自己的生命周期中安装路由。另一个 VPN 客户端可能已经占用了同一个前缀。而本地钩子或脚本也可能加上一条别处不知道的路由。

每类归属者需要不同的修正。直连路由不该由你删除;删掉它可能让本地访问在隧道可用之前就中断,而且内核会把它加回来。由管理器掌管的路由应通过该管理器修改,而不是用一条手动 ip route del,后者会在下次重连时被撤销。

接口被保留会让同样的混乱更容易发生。当隧道设备在一次重连后仍然存在,自定义安装代码设置的路由可能在第二轮运行时依然在,于是假设环境干净的脚本第二次失败、第一次成功。要么有意清掉旧记录,要么让安装代码幂等。

block-local 回答的是另一个问题

当配置把全部流量送进隧道时,客户端必须决定本地网络怎么办。block-local 标志会改变这个决定:它把本地 LAN 也经隧道转发,同时保留文档中所述的 LAN 网关例外——这是一项会直接影响可达性的流量策略选择。

它解释不了为什么添加路由会返回“已存在”的错误;把它当成万能修复,只会让真正的冲突藏在策略改动背后。如果改完配置后本地资源访问不到了,这个标志是最该先检查的几项之一;而如果路由添加失败,它就不是答案。

把它当成“哪些目标仍留在本地”的决定,明确做出并写下来。它不是在日志看起来吓人时拿来试的开关。

闭环:从源头去掉重复

确定是哪一类归属者持有该记录后,就到定义这条指令的地方把重复项删掉。它可能是配置里的 route 行、服务器下发的 push,或一个已不必再运行的钩子。

ip -4 route show match 10.40.0.0/16
ip -4 route get 10.40.0.5
journalctl -u openvpn-client@office -b --no-pager | tail -n 15

然后重连并确认三件事:隧道建立、预期目标解析到你想要的路径、故障消失且没有新的故障顶替它。日志干净但选错了路径,不算修好。

如果这个前缀一边指本地 LAN、另一边指远端网络,那问题是地址重叠而不是重复,删多少次都无法让两种含义同时成立。这种情况需要为其中一个网络另做方案。先决定改哪一边的编号,再动手。

相关文章