命令返回了,服务显示失败,终端里却没有收到任何像解释的东西。这种沉默就是人们所说的“OpenVPN 起不来”,它几乎从不意味着程序没吭一声就死了。它意味着消息去了你没有在看的通道,或者你启动的东西并不是你配置的东西。一旦不再把配置文件当嫌疑人,这两种情况都能在一分钟内诊断出来。

输出会流向四个地方:unit 的 journal、拥有该连接的插件、隧道的另一端,以及一个和你以为在运行的不一样的二进制文件。每个地方都有自己的检查方法。

沉默意味着你看错了通道

在前台调用会把内容打印到你的终端。unit 则写进 journal,而失败的 unit 可能在什么都没写之前就退出——如果失败出在 它是怎么被启动的,而不是它被告知了什么。这个区别很重要:journal 里没有任何记录,说明进程根本没有真正运行;而有一条记录随后退出,说明它运行了并拒绝了什么。

systemctl status openvpn-client@office
journalctl -u openvpn-client@office -b --no-pager

加上 systemctl show -p Result -p ExecMainStatus openvpn-client@office,把干净退出和信号区分开。状态 1 配 Result=exit-code 是程序的决定;Result=signal 或超时则指向环境,再怎么改配置文件也不会改变这一点。重启任何东西之前先把两个值记下来,因为重启会清掉产生它们的临时状态。

实例名是路径的一部分

打包的模板 unit 不接受文件路径。它接受一个实例名,这个名字会被拼到某个目录后面去找配置文件:office 在 Debian 和 Ubuntu 的打包里解析为 /etc/openvpn/client/office.conf。名为 office-v2.conf 的文件以 @office 启动时永远不会被读取,unit 会悄悄退出,因为从它的角度看无事可做。查找目录在客户端模式和服务端模式下不同,所以切换模板时同一个错误会指向完全不同的地方。

这种不一致是静默失败最常见的原因,而且因为配置文件本身没问题,它在配置文件里是看不见的。列出实际存在的东西,并逐字符比较两个名字,包括打包所期望的任何扩展名。

ls -l /etc/openvpn/client/
systemctl list-units 'openvpn*' --all

如果 unit 已启用却没有列出任何实例,启用本身可能指向了一个后来被改名的名字。重新启用正确的实例通常就够了,而留下来的旧 unit 文件值得删掉,免得下一个人困惑。一个遗留的 unit 文件是件小事,却会让某人后来多花一小时。

当插件拥有隧道时,终端什么也看不到

从桌面小程序启动的连接归 NetworkManager 所有,它的 OpenVPN 输出会进入那个服务的 journal,而不是你打开的某个 shell。手动运行客户端却什么也看不到,在这种安排下是正常的,因为手动运行的那个进程并不是承载连接的进程。

在 journal 里搜索拥有它的服务,然后用你能读懂的详细程度复现解析过程。下面两条命令看的是同一次失败的不同所有者。

journalctl -b -u NetworkManager --no-pager | grep -i -A3 openvpn
openvpn --config /etc/openvpn/client/office.conf --verb 4 --log /tmp/ovpn.log

独立运行是为了读消息,不是为了取代受管的连接,而且之后两者并不相同:DNS 集成、路由和生命周期属于插件。修好独立运行里的解析错误,并不会在桌面小程序接管后自动修好名字解析,所以要把两种观察分开。

两个二进制,一条 PATH

手工装在 /usr/local 下的构建会在 shell 调用中胜出,因为 /usr/local/sbin 在默认 Ubuntu PATH 里排在 /usr/sbin 前面。unit 不用你的 PATH;它执行的是写进 ExecStart 的绝对路径。所以终端里能用的版本和作为服务失败的版本,可能是真正不同的程序。

重建任何东西之前先比较两者。第三条命令才是决定性的那条。

command -v openvpn
whereis openvpn
systemctl cat openvpn-client@office | grep ExecStart

还有一个环境差异藏在同一处。打包的 unit 依赖 /run 下由 systemd 为它创建的运行时目录;手写替代 unit 若漏掉这个设置,可能在一个文件都没打开前就失败,而且失败看起来像没有任何 OpenVPN 消息的笼统拒绝。

客户端无法报告的失败

有些结果是在远端决定的。一个用 HTTP 状态应答并关闭连接的网关已经告诉了你它自己的状态,下一步有用的做法是看它的日志或凭据背后的账户,而不是你的本地文件。那种情况下本地没有任何东西坏掉,为了补偿去改配置文件只会让下一次诊断更难。碰客户端配置之前先读那份日志。

另一种安静的情况是客户端其实工作正常。客户端模式的隧道从不在端口上监听,所以期待开放监听端口的检查什么也报不出来,看起来像失败。从空的端口列表下结论之前,先确认你启动的是哪种模式。

按顺序处理各层:unit 是否存在、实例名是否正确,有没有东西到达 journal 或 --log 文件,到底是哪个二进制和哪份配置在运行,失败是本地还是远端。每个答案都会去掉一层。从配置文件开始,等于重写一个可能从未被打开过的文件。

相关文章