只靠一个睡眠钩子并不能让 VPN 恢复
以零退出的钩子只能证明脚本运行过。把它排在网络之后,使用正确的身份,并检查配置是否本来就符合条件。
在睡眠钩子里放一条重连命令,问题看起来就解决了。只有钩子在网络出现之后、以客户端认可的身份、走客户端真正实现的路径运行,才算解决。恢复后的失败大多是这三种之一,而它们都不会在钩子本身显示为错误。无论如何,钩子都会返回成功。
一个以零退出的脚本,并没有告诉你隧道是否回来了。
钩子运行了,然后呢?
#!/bin/sh
case "$1" in
pre)
vpn-client disconnect
;;
post)
vpn-client connect
;;
esac
每个事件会在转换前后各调用一次钩子,并把阶段和事件名传给它。它以 root 身份、在最小环境中运行,因此必须使用绝对路径,也不应指望任何桌面会话变量。两次调用都很快返回,只能说明脚本运行过。配置之后是否真的起来了,是另一回事,需要日志里自己的证据。
把阶段参数当作设计的一部分。pre 阶段的重连尝试发生在机器即将失去一切的时候,它不可能成功。
Root、最小环境、错误的用户
如果客户端按用户保存登录令牌,以 root 运行钩子就会让它无凭可示。要在钩子真正使用的身份下检查状态,而不是你在桌面终端里测试的那个身份。有些构建在断开后还会留下一个占着接口的守护进程,于是重连落在一个陈旧状态里,先清理进程可以恢复,代价是一次不干净的关闭。如果只测顺利路径,这两种行为都看不到。
用绝对路径编写钩子,让它归 root 所有并可执行,并记录它做了什么。没有那份日志,下一次排查就要从零开始。
自动连接不是 VPN 的路径
NetworkManager 提供两种激活机制:配置上的自动连接标志,以及主配置激活时启动的次连接。原生设备类型实现第一种;VPN 插件历来只实现第二种,所以在 OpenVPN 配置上勾选自动连接可能完全没有效果。检查这两个属性,然后改为把 VPN 作为 Wi-Fi 或 Ethernet 配置的次连接挂上去。
[Unit] PartOf=NetworkManager.service Requires=NetworkManager-wait-online.service After=NetworkManager-wait-online.service
由 systemd 单元管理的 WireGuard 接口需要的是那个 drop-in 文件,而不是钩子,并且要排在网络在线之后。
等待网络,但要设上限
timer=1 while ! ping -c1 -n -w1 203.0.113.1 >/dev/null; do sleep 1; timer=$((timer+1)) [ $timer -ge 300 ] && exit 0 done nmcli connection up "work-vpn"
恢复时,Wi-Fi 可能还在重新关联,隧道却已经被请求,这让任何固定睡眠都变成猜测。等到一个可达的地址再拉起配置,并给等待设上限,使钩子不能无限期地占住恢复路径。优先用配置名而不是数字索引;如果正确的配置因网络而异,就让健康的隧道提前把选择写进文件。
也要防重启风暴,靠计数尝试并停止。对每次探测失败都作出反应的看门狗,会把一次中断变成循环。
暂停一次,然后在日志里读钩子自己的输出。这是唯一能证明顺序的测试。