按下“断开”确实有效,十秒后隧道却又回来了。这个循环很少是 VPN 客户端本身出了问题;它是一个恢复脚本,永远只问一个问题——“是不是断了?”,然后用连接来回答。一旦你把用户回答的问题与系统报告的问题分开,修复就变成一小份状态,而不是更久的等待。

两个问题,不是一个

这个配置现在应该保持连接吗,系统此刻又在报告什么?这是两个归属不同的问题,恢复策略在行动之前两者都需要。意图是你的工具决定并记住的东西。观测状态是平台告诉你的东西。

IntentWhat you observeWhat to do
WantedDropped unexpectedlyAllow one supported recovery attempt
Paused by the userDisconnectedDo nothing until intent changes
WantedAlready connectingWait instead of starting a rival attempt
WantedAuthentication needs a personPrompt, then stop replaying credentials

把这些行读成一条策略,而不是一串案例。第二行是大多数脚本会漏掉的那一行,也是唯一能把监控程序和“非要对到底”的机器区分开的一行。

观测状态不是偏好

Apple 的 Network Extension 状态模型会报告 connecting、connected、disconnecting、disconnected 和 recovery 五种状态。这五种描述的都是连接。它们都没有说“用户要求这个配置保持暂停”,因为那根本不是一种连接状态。不基于该框架构建的客户端会公开自己支持的控制项和状态调用,它们用自己的词汇划出同一条界线。

结果就是,断开状态永远不能被单独解读。它至少意味着三种情况之一:隧道掉线、用户主动停止,或者系统正处于两次尝试之间。只有你自己的意图记录才能区分它们。把这份记录写下来,如果它必须扛过重启就持久保存,并在用户操作的那一刻更新,而不是事后从日志行里推断。

scutil --nc list
scutil --nc status "Office VPN"

还有谁被允许启动这个配置

你的助手承诺的暂停,强度只等于同一配置的其他所有者。On Demand 规则、受管理的配置或第二个客户端都可能请求连接,而你的助手会一边严格照章行事,一边看起来像坏了。

找出所有能启动该配置的机制 在你承诺尊重暂停之前,写下哪一个会胜出。当受管理的服务刻意不提供个人暂停选项时,那是管理员对策略的决定,而不是脚本该靠与管理层对抗去填补的空缺。

让重试变得无聊

限制尝试次数,并让每一次都针对某个发生了变化的东西:网络恢复了、网关响应了、凭据提示完成了。网关不可用、身份过期、第二因素被拒绝,需要三种不同的反应,而紧密循环给三者同一个反应。

看看快速重试在屏幕上实际产生了什么。用户反复关掉的对话框比一条清晰的失败提示更糟,它们训练人们不读提示就点掉。

暂停和流量策略是两个不同的承诺

用户暂停的隧道往往会故意让流量保持阻断,这正是 kill switch 式策略的全部意义。这意味着“暂停”只应承诺一件事:恢复停止。它不应悄悄改变网络允许的内容。

单独决定流量行为,把它记录在“暂停”控件旁边,并且无论隧道是暂停、失败还是不存在都保持一致。否则用户会学到:按“断开”会发生不可预测的事,而下一张支持工单就是关于一个“有时会毫无征兆地停止工作”的网络。

相关文章