Kill switch 与禁止重叠这两条规则指向相反的方向,这也解释了为什么普通 VPN 设置从不表达后者。Kill switch 针对的是已经断开的隧道,而这条要求针对的是仍在运行的隧道,并让某个应用始终不出现在画面中。一个设计决策就能定下大部分:究竟是把应用停掉、阻止它启动,还是只剥夺它的网络访问。先选定这一层,再去考虑防火墙规则或杀进程的循环,因为靠观察来执行的规则在每次启动时都会漏出一个窗口。

误区:kill switch 会阻止程序启动

Kill switch 是流量控制,而且它们对此很坦率:根据隧道状态放行或拒绝数据包。它们没有一个会拒绝启动程序,也没有一个会检查是哪个可执行文件打开了套接字。桌面快捷方式、计划任务和后台助手都能抵达网络,却不会经过任何知道应用名字的东西。这两种机制之间的距离,正是大部分混乱所在。

真正决定程序能否启动的安排是应用控制。在 Windows 上这意味着 AppLocker 或 WDAC;在受管理的 macOS 上,意味着由设备配置文件强制执行的允许列表。两者都与隧道无关,所以这两条要求会被合并进同一张工单,然后哪一条都没有被满足。

先给模式命名,再给工具命名

把规则写成三态状态机:日常使用、VPN 工作会话和离开。被禁止的重叠属于中间状态,而不属于客户端软件。

改变状态的人按顺序承担三个动作:结束该应用的工作、请求它正常关闭,并在隧道升起之前确认实例已经消失。另一端的顺序也很重要,因为先连接、后停止程序的 VPN 已经允许了重叠。如果要求涵盖连接建立,那么等到已连接指示出现就已经太晚。

双向冲突的实际效果

对于两个确实由 systemd 管理的服务,Conflicts= 是对称的:启动其中一个会停止另一个。unit 参考记录了这一行为,并另外要求:当其中一个必须先完成停止时,要有明确顺序。没有这些顺序条目,停止和启动就会竞争,结果取决于时机。

这不是拒绝列表。把依赖接反,启动应用反而会停掉 VPN,这正是隧道在可预测时刻自行断开时应当怀疑的故障。桌面应用不是 systemd unit,NetworkManager 连接也不是,所以这种模式只适用于你自己包装的服务。

检查各个转换,而不只是第一次启动

四个转换决定要求是否成立:在应用运行时进入会话、在会话中途再次启动它、VPN 启动失败,以及有意断开。逐一测试,并把结果放在 unit 文件旁边。下面的命令读取当前关系,然后演练最重要的那个转换。

systemctl show work-vpn.service -p Conflicts -p After
systemctl start work-vpn.service
systemctl is-active office-sync.service
nmcli connection show --active

第三条命令应当让应用保持非活动,第四条命令在会话运行时应当只列出隧道连接。若结果不同,说明这个转换根本没有被强制执行。

会话中的再次启动,正是暴露基于包装的设计的情形,因为从桌面启动的快捷方式从不咨询启动脚本。如果这种绕过很重要,强制就应该属于应用控制,而不是 VPN 客户端。也要记录会话结束,并优先选择有意的继续,而不是把进入时停掉的每个进程都重新打开。把每个转换的结果记在保存 unit 文件的同一份笔记里。

相关文章