OpenVPN 客户端被杀死后 DNS 停止解析,而第一反应往往是重写 /etc/resolv.conf。这次改动可能弄坏一个本来正常工作的解析器,而真正的陈旧状态其实在别处。首先要弄清的问题是:隧道写入的 DNS 设置归谁管,以及那次跳过清理的退出是否真的重要。

三个条件决定如何修复:结束进程的信号、接收设置的解析器组件,以及隧道链路是否仍然存在。按这个顺序回答,修复通常无需改动任何全局文件。

判断发生的是哪种退出

在 Community 版本的 OpenVPN 中,SIGINT 和 SIGTERM 都是优雅退出的请求。SIGKILL 无法被捕获:既不能被阻塞,也不能被处理,因此任何关机流程都没有机会执行。前台终端里的普通 Ctrl+C 产生的是 SIGINT,而放弃等待的守护进程管理工具通常会升级为 SIGKILL。这个区别很重要,因为只有第二种情况才能确定钩子被跳过了。

在 screen 或 tmux 中运行并不会改变 Ctrl+C 发送哪个信号。要查看进程实际收到了什么,而不是终端通常做了什么。

当服务或连接管理器在运行隧道时,应通过它的归属者停止那一条连接,而不是直接杀掉 pid。管理器可能把进程重新拉起,留下一条新隧道和混乱的解析器状态。独立进程只需要一个普通的终止请求,清理就会执行。

判断解析器归谁管

systemd-resolved、resolvconf 风格的脚本和直接改写文件是三种不同的归属者。第一种把按链路的设置保存在内存中,接口消失时一并丢弃。第二种保存的记录会一直存在,直到有别的东西把它删除。第三种只写文件,把恢复工作留给写它的人。

在选择修复命令之前先确认归属者。同一个症状有三种不同的正确答案,取决于当前用的是哪一种。

由 NetworkManager 管理的 VPN 可能已经掌管了解析器变更。再叠加第二个辅助程序就会产生两个归属者争夺同一条链路,失败一方的状态会在下次重连时重新出现。如果由管理器负责,就在管理器里改设置,而不是写脚本。

down 钩子能做什么、不能做什么

脚本 down 在 TUN/TAP 设备关闭后执行一次。加上 down-pre 会颠倒这个顺序,在设备仍然存在时运行脚本,这正是需要活动链路的辅助程序所要求的。

这两条指令都不会让钩子在 SIGKILL 之后执行。权限也不会因为 down-pre 而恢复:如果守护进程降权到 user 或 group,或者进入 chroot 环境,钩子就以那样剩下的权限运行。被调用的钩子仍可能无法更新解析器。OpenVPN 参考手册 涵盖了信号与回调生命周期。

把钩子自身的错误和进程的退出状态放在一起看。缺少清理和清理失败在解析器里看起来一样,在日志里却不同。

检查真正残留的状态

在使用 systemd-resolved 的系统上,resolvectl status 会显示哪条链路仍然带着 VPN 的 DNS 服务器或路由域。那条链路可能属于一个已经消失的连接,也可能属于另一条仍在运行的隧道。

接口消失会带走它按链路的配置,因此被跳过的钩子可以和干净的 DNS 共存。如果链路仍在注册,并且只归被停止的连接所有,所属管理器应当把它移除。如果是更早的脚本改写了全局解析器文件,那就转而查找该脚本记录在案的恢复步骤。

在不抢占另一条链路的前提下恢复

当按链路的设置由你管理时,resolvectl revert 会接收链路名并清除该接口的运行时 DNS 配置。这是运行时操作,不是编辑配置档,因此持久化的连接配置不受影响。写错链路名就会清掉一条正常隧道的 DNS,所以名字要取自状态输出。

编辑之前先确认 /etc/resolv.conf 是受管理的符号链接,然后再正常停一次客户端并重新查看 DNS。如果第二次停止就干净了,那么先前的问题是被强制退出,而不是配置。

相关文章