OpenVPN 3 Linux 的故障以一条 D-Bus 方法错误的形式到来,后面挂着一个陌生的名字,而人的冲动是回滚软件包然后祈祷。客户端并不是一个程序:一个命令行前端通过总线与若干服务对话,而这些服务分别掌管配置文件、会话、VPN 引擎以及需要特权的网络改动。判断失败调用属于这五者中的哪一个,就是诊断的大部分工作,代价只是一条命令,而不是一次回滚。下文就是这个判断,按故障通常出现的顺序排列。

一条命令背后的五个所有者

关键的是五个进程,各自拥有自己的总线名:接受你命令的前端、保存配置文件及其访问策略的配置管理器、掌管会话生命周期的会话管理器、通过 OpenVPN 3 Core Engine 运行连接的后端,以及安装接口、路由与 DNS 设置的网络配置服务。因此,一条点名配置对象的错误不是后端错误,而一个起不来的后端也不是认证失败。把消息归入其中一行必须在任何改动之前完成,因为后面每一步都取决于哪一行才是真的。猜行,正是让一个小修补变成一下午折腾软件包的原因。

先问系统总线上有哪些服务,再问别的事情;然后确认已安装的部件在版本上是一致的。OpenVPN 3 Linux 把这些服务注册为系统单元,所以要查询的是系统总线,版本查询需要管理权限,而用 --user 列出的内容读的是你的登录会话,那里永远不会出现这些单元。服务按需激活,有些在空闲时会退出,所以在你第一次请求之前就缺失的服务,只是还没有被问过任何问题。下面三条命令在不打开 VPN 会话的情况下回答了存在性、版本与单元状态。

sudo busctl list | grep -i openvpn
sudo openvpn3-admin version --services
systemctl list-units 'openvpn3*'

每份列表实际证明了什么

客户端自己的视图是能得到的最便宜的证据,而每一份都证明得比看上去少。配置列表里的一个配置文件,说明某个配置文件被导入了,却完全没有说明是否有人被允许用它连接。会话列表里的一个会话,说明会话对象存在,却完全没有说明远端认证是否完成。

openvpn3 configs-list
openvpn3 sessions-list

这些输出里的对象路径指向总线上被管理的对象,而不是文件,所以它们不能被编辑;先前记录的路径会在该会话断开的那一刻失效。照笔记里抄一个来用,会产生与 VPN 毫无关系的 "no such object" 错误。下面的阶段表把连接的每一步建立了什么、又留下了什么分开,而回滚决定恰恰建立在这个区分上。先读与你证据相符的那一行,再重读错误原文。

StageWhat existsWhat proves itWhat it does not prove
Profile importedA configuration object held by the configuration managerThe profile is listed by the clientThat the profile owner permits a connection
Session requestedA session object held by the session managerThe session appears in the session listThat the remote side accepted the credentials
Connect dispatchedAn asynchronous attempt on the VPN backendStatus changes arrive for that sessionThat an interface was configured
Tunnel configuredAn interface, routes and DNS settingsThe network service reports success and routes appearThat traffic reaches the peer

连接调用在隧道存在之前就返回了

连接调用交给你一个可以观察的会话就返回了,所以一次看似成功的调用之后陷入沉默,是正常的中间状态,而不是矛盾。前端仍然必须跟踪状态变化,并回答任何交互请求。一个需要凭证却收不到回答的配置文件,会停在看起来和卡死后端一模一样的状态里。这个等待状态值得被认出来,因为它把人引向错误的组件。

尝试进行期间,观察会话列表和前端自身的日志,针对那个配置文件。一个从不离开初始状态的会话,就点名了停住的那个组件,而日志通常在前几行就把它点出来了。把配置文件名放在手边,因为日志命令接受的是配置文件而不是会话路径,这也是要重读列表而不是复用路径的又一个理由。

openvpn3 sessions-list
openvpn3 log --config <profile> --level 6

不打开会话也能读一个组件的日志

当前端报不出有用东西时,去 记录下这次失败的组件,而不是再开一次连接。配好 systemd-journald 记录器之后,管理员作用域内的日志视图能在不触碰总线策略、也不打开会话的情况下读取单个组件的事件,这一点很重要,因为会改动被测系统的诊断本身证明不了什么。按单元而不是按 syslog 标签过滤:各个服务单元并不保证共享同一个标识,所以按标签查询可能返回空页,而单元本身其实已经记下了失败。

向任何人求助之前先记下四个字段:错误的完整名字、涉及的组件与对象、时间戳,以及已安装软件包的版本。这些正是一个维护者需要的,其中没有一项要求把配置文件或凭证粘进工单。为了消音而放宽总线访问策略不是诊断,它删掉了那道原本替你点名组件的边界。配置文件的私密内容不进入共享报告,理由和凭证一样。

journalctl -u 'openvpn3-service-*' -n 50 --no-pager
openvpn3-admin journal --service <name>

修正拒绝的那道边界

映射表里每一行都有自己的修正,而行与行之间不能互相替代。针对配置文件的被拒操作是策略与归属问题,而一个不激活的后端是服务与打包问题。拒绝认证的服务器两者都不是,所以反复改密码修不好本地的激活问题。选错修正,就是策略问题变成重装的过程。

更换软件包之后,要验证一套一致且受支持的安装,而不是把一个旧二进制拷回原位。受控回滚是对已确认回归的正当回答,前提是各软件包保持兼容,并且存在回到受支持维护的路径。要在服务停止启动之前把那条路铺好,而不是之后。

别的平台可以共用同一个 VPN 核心库,却不共用 Linux 的总线服务,所以 Windows 上看起来相似的故障说明不了这个故障。D-Bus 概览 记录了为配置与会话暴露的对象路径,而这正是本诊断所依赖的那部分架构。读一遍,下一次错误到来时就已经带着一个组件了。

相关文章