NetworkManager 暴露一个属性,vpn.persistent,而人们却要它同时回答四个不同的问题。它的上游文档很谨慎:持久性只在 VPN 服务支持、且配置文件启用时才生效,随后连接被要求在各种链路变化和中断中存活,直到显式断开。同一份文档还显示该属性的默认值是 false,这并非大多数恢复方案所假定的。请把它读作一次尝试的请求,而不是结果的承诺。

这项设置究竟承诺了什么

保存在配置文件中的值是对管理器的一条指令,而管理器必须把这条指令交给懂得如何处理它的 VPN 插件。如果已安装的插件忽略该属性,配置仍然会激活,日志也依旧沉默,这对后来排查问题的人来说是两头都不讨好。配置文件里的任何东西都无法强迫插件实现它并不具备的行为,因此唯一可见的结果是缺失。

这就是文档里的条件比开关更重要的原因。检查你实际拥有的是哪个插件、哪个版本,并把版本写在连接说明旁边,好让下一个人不必重新发现它。建立在未实现标志上的恢复策略在生产中恰好失败一次,而且总是在最不方便的时刻。

把该属性当作一次尝试,也为测试设定了正确的预期。你要检查的不是标志是否设置,而是链路断开时是否发生了什么。这是两个不同的实验,只有其中一个值得花掉一个下午。

被合并成一个问题的四个问题

激活问的是:究竟是什么让 VPN 起来。NetworkManager 配置文件可以绑定到基础连接,单独管理的 OpenVPN 服务有自己的启动流程,再给这套安排加上第三个启动器,你就会有两个对其权限各有想法的所有者。在再加一个触发器之前,先确定哪个组件拥有启动权。

恢复问的是链路变化或消失时会发生什么,而这正是 vpn.persistent 所处理的狭窄问题。存活问的是客户端能否判断远端已停止响应,而这一点改由 OpenVPN 的 keepalive 或重启行为来回答;这两个机制常被用同一个词描述,却在不同的地方实现。要把它们分开、用不同的工具测试,否则你会把一种行为归到另一种身上。

意图问的是一开始是否就想要重连,而这是状态轮询无法回答的问题。有意停用与一次中断会产生同样的非活动状态,因此任何凭“不在”就重连的做法都在猜测人的决定。猜测本身没问题,直到它越过了某个人。

一份把它们分开的检查清单

按顺序执行这些操作,而不是读配置再推理,并用时间戳记录每一步显示出什么。

第一,激活预期的基层连接,确认 VPN 是通过你预期的触发器起来的,而不是通过你忘了已经安装的机制。第二,在一次获准的中断期间,比较管理器的 VPN 事件、所属客户端的恢复状态,以及对私有资源的一次新请求。第三,有意断开、等待,并确认没有任何东西在你背后重启该配置。

第三步是人们最常跳过的一步,而它最常发现真正的缺陷。一个轮询活动配置、在它不存在时重连的看门狗,会兴高采烈地撤销用户有意的断开。从用户的角度看,这不是韧性,而是一台不肯听话的机器。在加上这种东西之前,先问问他们。

把结果写进与连接说明相同的那份文档。恢复策略的好坏,取决于上一次有人看着它按计划失败时的表现。时间戳让这种差别对之后读笔记的人清晰可见。

认证是恢复的一部分

重连并不纯粹是网络行为。如果获准的流程需要一次新的挑战、一次性验证码,或者 激活上下文够不到的密钥,任何持久性设置都无法凭空造出它。

这解释了一种熟悉的模式:在钥匙环已解锁的机器上连接能恢复,而在同一台机器重启之后却失败。网络层完全照做了要求的事,而凭据层却拿不出任何东西。

去修凭据流程,而不是缩短重试间隔。对着没人能回答的提示更快地重试,只会制造日志噪声,并造成一种“正在尝试”的假象。

持久性不是 kill switch

该属性试图维持或恢复 VPN 关系。它并不决定 VPN 断开时应用可以做什么,也不靠自身拦截任何东西。

如果要求是任何流量都不得离开隧道,那是另一套策略,有自己的机制、自己的测试和自己的失效模式。假定持久性涵盖了它,就把一个安全决策变成了意外。

在恢复笔记里记下两半:VPN 尝试了什么,以及尝试进行期间允许了哪些应用流量。

相关文章