笔记本唤醒后 OpenVPN 为什么会断
休眠可能杀死 OpenVPN 会话却留下 tun0。先验证隧道,再从 systemd 睡眠钩子重启单元,而不是去重启设备。
把机器挂起,再唤醒,然后按这个顺序检查三件事,再动任何配置:tun0 是否还在、指向内网地址的路由是否仍然选中它、服务器是否仍然列出这个对端。设备熬过休眠而路由指向别处,那只是残留,不是隧道。路由正确而流量仍然断掉时,故障在隧道之上,重启客户端不会改变什么。只有当手动测试表明你属于这三种情况中的哪一种,自动化重启才有意义。
ip -br address show tun0
ip route get 10.20.0.10
journalctl -u openvpn-client@office -b --no-pager | tail -30
先把唤醒复现出来,再谈自动化
挂起结束加密会话的次数,比它移除设备的次数更多。维持隧道存活的那条 UDP 流停掉了,服务器可能把这个对端踢掉,客户端要么察觉并重连,要么留下一个背后什么都没有的接口。只看接口并不能证明什么,因为 陈旧设备看起来和正常设备一模一样 直到真的有包要穿过它。跑一次路由查询,再加一次真正穿过隧道的请求,然后读唤醒那一刻前后的日志。
如果客户端自己回来了,钩子只会添乱,而不是增加可靠性。如果它从不恢复,自动化才是诚实的做法。
systemd 睡眠钩子到底放在哪里
许多老教程指向 /etc/pm/sleep.d,那是 pm-utils 的目录,基于 systemd 的挂起从不咨询它。systemd 从 /usr/lib/systemd/system-sleep/ 和 /etc/systemd/system-sleep/ 读取钩子,先传阶段再传操作,所以 pre 和 post 是钩子唯一能分支的值。把本地副本放在 /etc 下,而不是改发行版提供的文件;还要记住脚本在过渡中途以 root 运行,因此必须快速返回。
#!/bin/sh
case "$1" in
post)
systemctl --no-block try-restart [email protected]
;;
esac
exit 0
try-restart 只在单元已经处于活动状态时才动手,所以你有意关掉的隧道在唤醒过程中仍然保持关闭。--no-block 让钩子不必在客户端协商期间等待,这在网络还没起来时很重要。两个标志合起来意味着钩子会安静地失败,而不是拖住整个唤醒序列。单元本来就已停止时什么也不会发生,这正是你在那里想要的结果。
恢复服务,别动 tun0
手动把 tun0 拿掉,守护进程会继续看守一个它已经不再拥有的接口,下一次状态变化就会产生令人困惑的错误。要针对服务或它的连接配置文件,而不是接口;先确认准确的单元名,因为发行版包、OpenVPN 3 和 NetworkManager 各自选的名字都不一样。 当那条连接被重新轮询时,NetworkManager 配置文件会回来,所以那里针对的不是 systemd 单元。无论机制是什么,它都应该和你手工验证过的动作一致。
重启会重新安装客户端连接时推送的路由。多数唤醒都需要这样。而你手工加过路由的机器不需要,所以要重新检查那些路由,而不是假设恢复已经把它们放回来;并把它们写在下次唤醒能找到的地方。
处理你控制不了的时序
无线关联和 DHCP 往往在钩子执行完之后才完成,所以当还没有通往服务器的路径时,重启可能失败。稍等一下那个地址,重试一次,并把结果记进日志。
名字解析是另一场比赛,因为桌面网络栈可能在隧道已经起来之后才恢复 DNS。用 resolvectl query internal.example 单独测试查询,而不是从一条正常路由去推断。路由正确却什么都解析不了的隧道是 DNS 问题,不是钩子问题。
把阶段、单元名和结果写进日志,让钩子的决定可见。在机器笔记里记下你跑的是哪次唤醒测试,这样才能把有效的恢复和侥幸的恢复区分开。选择能把服务带回来、干扰最小的动作,并让能自行恢复的客户端自己去恢复。完整重启会丢掉正在进行的会话,对于一天唤醒好几次的笔记本,这是实实在在的代价。