连接现有 VPN 的小程序需要的是控制器,而不是再写一套 VPN 实现。受支持的引擎应当负责协商和隧道流量;你的程序应当表达用户的请求,并如实解释结果。在 Linux 上,NetworkManager 的 D-Bus 接口为这个控制器提供了可遵循的具体模型。

四项检查让控制器不至于把已保存的配置文件、激活请求和正在运行的会话混为一谈。

检查一:选择的是哪个身份

把配置文件的 UUID 作为持久身份,需要对象路径时再通过管理器解析。显示名称可能被两个配置文件共用,而运行时的总线路径并不是值得保存的合理用户偏好。把选择写进你自己的配置,而不是写在注释里说列表里哪一条排在第二。

在把它称为匹配之前,先核实管理器返回了什么。已不存在的 UUID 会产生查找失败,这与存在但拒绝激活的配置文件是两条不同的代码路径。

检查二:激活调用返回什么

有文档的激活调用接受现有连接的对象路径、设备路径和一个特定对象。它返回的是活动连接的路径,而该响应是句柄,不是就绪结果。不要用返回值去设置应用的就绪标志;而应订阅活动连接的状态和原因。

对 VPN 而言,特定对象选定的是活动的基础连接。传入根路径会把设备选择交给 NetworkManager,由它挑选当前默认的设备。这是路由上下文的决定,而不是密码字段,并且会改变隧道叠在哪台设备上。

不要照搬通用的“添加并激活”示例,还指望它创建 VPN 配置文件。文档里的辅助函数排除了 VPN 连接,所以准备或导入配置文件是一个单独的、经过评审的步骤。这很重要,因为准备需要授权和评审,而连接按钮并不提供这些。把导入当作管理操作,让它有自己的日志行和自己的审批人。

检查三:状态如何表示

活动连接会经过激活中、已激活、停用中和已停用,原因字段会说明为什么。把这一切压成一个名为 loggedIn 的布尔值,就会丢掉用户需要的信息,以及支持人员会追问的信息。至少显示出连接是待定、已建立还是已消失,并为失败情形保留原因字符串。

把异步回复与产生它的请求关联起来。较早尝试的迟到完成不得覆盖较新的用户操作;重复按下 Connect 应当被忽略,或者按可见的策略当作取消并重试。

检查四:重启之后会发生什么

当管理器重启或控制器重新连上总线时,从所有者那里重建所观察到的状态。不要因为内存里还留着旧的对象路径,就继续把缓存的会话显示为活动。状态变化信号存在的意义,正是让控制器可以重新同步,而不是靠猜。

选择关闭控制器意味着什么。让属于机器且已存在的连接保持开启可能是正确的,拆掉一个刻意短暂保留的会话也可能是正确的。让属于机器且已存在的连接保持开启可能是正确的;拆掉一个刻意短暂保留的会话也可能是正确的。把这份所有权约定写进界面,而不是假定进程退出就会执行网络动作。用户不应被迫猜测关闭窗口会不会断掉他们的隧道。把答案放在界面文字里,而不只是代码注释里。

把权限和秘密留在它们的归属方

权限会以 yes、auth 或 no 返回,界面应当体现允许、认证后允许和拒绝之间的区别。以 root 运行控制器并没有实现权限策略;它只是取消了这个问题。

凭据属于所配置认证方式对应的、受支持的密钥代理或客户端集成。源代码里、参数里或日志里的密码都算问题,而本地管理授权与网关认证并不是同一件事。

最后,把连接状态与用户想执行的任务分开报告。隧道可能处于活动状态,而某个私有名称或应用仍不可用,第二个失败值得有它自己的消息和状态。能准确说出哪一层失败的控制器,比只说 VPN 断了的控制器更有用。

相关文章