用 systemd ExecCondition 跳过家庭 VPN
有意的跳过与损坏的准备工作命令是不同的结果。先定义辅助脚本的退出码,再测试网络之间的切换。
当笔记本已经坐在家庭网络上时,家庭网络 VPN 无事可做。诱人的捷径是让准备命令以非零码退出;这会让单元失败,并让每个仪表板看起来都坏了。systemd 有一条专门针对这类决策的指令,它把有意的跳过与真实故障区分开来。做对这件事,主要是写清每个退出码的含义。
为什么失败的前置条件是不正确的形态
ExecStartPre 把非零退出视为启动失败,这对准备服务的命令是正确的。把它当作条件,会使策略决定与损坏的依赖无法区分。ExecCondition 的存在,是为了让单元可以拒绝启动而不被标记为失败。
这一区分在凌晨三点很重要,那时没人愿意去分诊一个按设计行事的红色单元。失败单元还会把真正的故障藏在有意为之的失败之中。
先写退出码契约
在写脚本之前先写契约。退出零表示继续,退出一表示跳过剩余的启动命令,退出 255 表示条件本身无法运行。二到 254 之间的码通常也表示跳过,当辅助脚本返回随机错误码时这一点很容易被忘记。下面的 drop-in 把辅助脚本挂到真实的单元名上。
[Service]
ExecCondition=/usr/local/libexec/should-start-home-vpn在假定默认语义之前,检查生效单元是否有继承的 SuccessExitStatus 或 stop-post 设置。然后有意测试三种结果中的每一种。
网络线索不是位置证明
可到达的路由器地址或已知的 MAC 可以暗示正确的网络,但两者都不能证明它。另一个网络可能复用同一地址,ARP 数据可能已过时,标识符也可能被仿冒。把探测视为与策略一致的优化,而不是认证结果。
事先决定不确定意味着什么。在不确定时启动 VPN 对只会连接失败的客户端是安全的;在不确定时跳过它则不安全。让探测有界,并读取活动接口,而不是假定列表中的第一个。挂起的探测比回答“否”的探测更糟,因为单元要等它。
把这些假设记录在单元文件旁边,让下一个人知道辅助脚本的信念。脚本顶部的注释是保存它们最便宜的地方。
处理网络之间的迁移
条件在服务启动时运行,之后不会重新评估。笔记本搬到咖啡馆后,被跳过的单元仍然被跳过;笔记本回到家后,正在运行的隧道仍然保持开启。用单独的机制把网络变更生命周期与启动和停止决策连接起来。
systemctl cat home-vpn.service测试四种情况:在家启动、在外启动、无法运行的探测,以及单元活动期间在网络之间迁移。把跳过原因保持在日志中可见,让有意的决定永远不像认证失败。服务参考记录了退出语义和最低版本。