在 OpenVPN up 上重启服务:钩子胜过 tun0.device
设备单元跟踪的是接口,不是连接。根据服务所需的条件选择 up 或 route-up,然后派发一个非阻塞的重启。
把依赖服务挂到 tun0.device 上,看起来像是在订阅隧道,其实并非如此。设备单元反映的是接口存在,而这一点在连接失败以及随后的重连期间都可能一直为真。于是服务要么在错误的时间动作,要么根本不动作,取决于接口在此期间是否消失。真正描述你所关心事件的钩子,是 OpenVPN 调用的那个,而不是 systemd 推断出来的那个。
先决定服务需要哪个条件,再选择触发器,因为这三个候选并不等价。
设备单元究竟跟踪什么
systemd 依据内核对网络接口的看法创建设备单元,因此接口出现时 tun0.device 变为活动,接口移除时变为不活动。使用 persist-tun 时,软重启会让接口保持原位,单元从不抖动,绑定其上的东西也不会被触发。即使没有持久化,该单元对路由、DNS 以及远端是否应答也一无所知。
因此它并不是“VPN 已就绪”的好替身。只需要本地地址的监听者,也许满足于接口存在;而需要调用远端网络中应用的服务,则需要路由和可达性,这是设备单元无法表达的。
up、route-up 与 up-restart 含义不同
up 在 TUN/TAP 设备打开并配置好接口之后运行,对于绑定所分配地址的监听者来说正是合适的时机。route-up 在路由安装之后运行,受任何已配置的路由延迟约束,适合需要路由阶段完成的工作。两者都不能证明远端应用会应答。在它们之间做选择,问的是服务需要什么,而不是哪个钩子听起来更完整。
使用 persist-tun 时,重启会复用设备,因此除非设置 up-restart,up 不会再次运行。该选项为文档化的重启生命周期启用 up 脚本,而它的环境 比首次启动时有所削减。假定环境完整的脚本在第一次连接时正常工作,在下一次却会失败。
派发动作,但不要等待它
同步调用 systemctl restart 的钩子,可能与被要求等待隧道的单元相互死锁。systemctl --no-block try-restart vpn-dependent.service 把任务排入队列后立即返回,从而避免钩子阻塞它所响应的那条连接。try-restart 只作用于正在运行的单元,而 restart 也会启动不活动的单元;要有意选择,而不是凭习惯。
排队并不等于验证。任务一被接受,钩子就返回成功,所以检查必须落在目标单元的日志里,或它实际的监听状态上。
重启策略在单元里,不在钩子里
带 RemainAfterExit=yes 的 oneshot 单元在命令结束后仍保持活动,第二次启动请求什么也不做,因为 systemd 认为它已经启动。这是需要用设计去应对的性质,而不是靠反复调用启动来绕过的缺陷。如果动作必须按连接执行,单元就需要允许这种做法的形态。
After= 为单元排序,BindsTo= 绑定生命周期,但两者都不意味着“VPN 每次重连就重启它”。绑定到隧道的单元在隧道停止时停止;它不会在隧道回来时自动重启,除非有东西再次启动它。
测三次重连,而不是一次
冷启动、客户端完整重启和软重连各自走不同的路径。软重连才会暴露缺失的 up-restart、被削减的环境,或从未变为不活动的设备单元。三次都要跑,并确认两个方向:隧道升起时服务动作,而无关的链路变化(例如 Wi-Fi 漫游)不会让它重启。
当依赖服务承载你自己的管理访问时,保留一条独立的恢复路径。重启你正赖以连接的东西,是一种可能把你锁在外面的改动。