必须在 VPN 启动后运行的脚本,需要来自拥有该连接的组件的事件。放进旧网络钩子目录的文件,并不是对 NetworkManager、pppd 或独立的 OpenVPN 进程可能创建的每个 VPN 的回调。这些拥有者各自报告不同的事件和不同的环境,因此在选择钩子之前必须先确认拥有者。否则脚本本身正确,却永远不会触发。

对于由 NetworkManager 管理的 VPN,分派器才是正确的位置,vpn-up 才是正确的动作。

用 vpn-up,而不是接口事件

分派器区分 up 与 vpn-up。前者报告已激活的接口,后者报告已激活的 VPN 连接。Wi-Fi 起来只说明底层链路存在;隧道可能仍在协商,也可能根本不会通过认证。

脚本会收到接口和动作,而连接细节通过环境传入,包括配置文件 UUID 和 VPN 的 IP 接口。请读取这些变量,而不是从设备名去推断身份。

把匹配限定在一个连接上

按连接 UUID 或分派器提供的其他身份匹配,而不是按 tun 前缀或 ppp0。接口名并不稳定,同一时间可能存在不止一条隧道,两个配置文件也可能使用同一个显示名。按 tun 前缀匹配意味着脚本最终会为一个人人都不想碰的连接触发。当重连重建设备时,它还可能对同一个配置文件触发两次。

让任务具备幂等性。每次调用都追加一条路由的回调,最终会用重复项填满路由表,而破坏路由的正是第二份。

了解激活并不能证明什么

vpn-pre-up 会在 NetworkManager 报告 VPN 完全激活之前运行,而管理器会等待这些脚本。只有任务确实属于该阶段时才使用阻塞阶段,因为缓慢的检查会拖慢所有其他组件都在等待观察的激活过程。

路由存在并不能证明某个应用正在监听、某个私有名称能解析,或存在用户会话。请用有上限的超时分别检查,并报告是哪一层失败,而不是重试整条链路。

尊重脚本运行所处的上下文

分派器脚本以 root 身份、在任何用户会话之外运行。不要假设登录用户的环境、图形会话或已解锁的密钥环可用。任何人都不应该用让每个凭据对每个进程可读来应对缺少桌面上下文。把用户会话的工作放进用户服务或会话辅助程序中,让回调在不冒充用户的情况下向它发信号。

脚本的所有权和权限也很重要。以 root 执行、任何用户都能替换的文件是提权路径,而且这一发现与 VPN 逻辑是否正确无关。

为永远不会触发的断开做好准备

NetworkManager 的文档指出,对于意外终止或连接丢失这类强制断开,vpn-pre-down 不会被发出。因此,只围绕温和的 pre-down 构建的清理在结构上就是不完整的。脚本改动的任何共享状态都需要另一个拥有者,能在崩溃后恢复它,而这条恢复路径必须经过测试,而不是假设。

用正常连接、重连、主动断开和拔掉网线来检验这个钩子。记录 UUID、动作和触发时的接口,但不要记录机密。这份记录是唯一可靠的证据,证明该钩子为你想要的连接运行过,也正是它让下一次改动变得安全。

相关文章