OpenVPN 推送了 DNS 服务器,Linux 却视而不见
推送的 DNS 服务器出现在日志里,隧道也已建立,内部域名却仍然解析失败。检查解析器的链路,并为这条连接选定一个归属方。
推送的 DNS 服务器出现在日志里,隧道也已建立,内部域名却仍然解析失败。推送本身没有坏,设置到达客户端后就停在那里。推送的 DNS 选项是发给客户端的一条消息,而不是给操作系统解析器的指令,二者之间的交接正是多数此类问题的终点。按顺序做五项检查,就能找出没有发生的那次交接。
检查一:连接是如何启动的
连接的归属方决定了谁能写入解析器设置。从 shell 直接运行的 openvpn 命令只拥有自己的进程,而 systemd 单元、NetworkManager 或厂商客户端各有发布 DNS 的方式。先读进程列表和单元名,再下判断。
命令行进程并不自动等于 NetworkManager 连接,NetworkManager 连接也不自动等于写入 resolv.conf 的那一方。这两点误解解释了本次排查中大部分浪费的时间。要问:对于你真正看到的那个接口,谁会发布 DNS。
继续之前先把归属方写下来。后面每一项检查都依赖它。
检查二:解析器链路里到底有什么
在 systemd-resolved 主机上,resolvectl status 会列出每条链路及其 DNS 服务器和域名。找到隧道的链路并读它的条目;不要假定接口一定叫 tun0,因为编号按创建顺序走。若推送的地址不在链路上,失败的是交接而不是查询。
地址存在时,下一个问题是这条链路声明了哪些域名。没有域名列表的链路只回答被直接询问的名字,这通常不是 split-DNS 部署想要的。只有地址是必要条件,还不够。
下结论之前,先把解析器输出里的链路名与 VPN 日志里的接口名对照。那里对不上,通常意味着日志行来自另一个进程。
检查三:命名空间是否选中了这条链路
解析器可能持有正确的服务器,却始终不被问询。按链路划分的域名决定哪些查询走哪条链路;没有路由域名时,私有命名空间会退回到默认链路的说法。这就是为什么 resolvectl query 对内网名能成功,而应用程序仍然失败。应用程序可能走的是完全另一条解析器路径。
要检查域名归属,而不只是服务器地址。写成 ~corp.example 的域名是路由域名,波浪号会改变名字的匹配方式。
如果应用程序自带解析器,就用它自己的那套机制测试,而不是系统工具。绕过系统解析器的查询工具,无论链路状态如何都会成功。
检查四:只有一个归属方,而不是两个
如果 NetworkManager 已经在管理 VPN 和解析器状态,再额外挂一个无关的 hook 脚本就会产生互相竞争的写入方。软件包为 systemd-resolved 提供的 hook 是受支持的集成方式,它要求特定的生命周期:script-security 2、up 路径、up-restart、down 路径和 down-pre。这些选项让注册与移除发生在正确的时机,而不只是在首次连接时。
使用软件包安装的路径,而不是从教程里抄来的示例路径。权限同样重要,因为 hook 在降权之后才运行。
为了改变输出目标去改发行版的解析器脚本,只是记录了一台主机的绕行办法。这并不是通用修复。
检查五:重连之后结果是否相同
重新连接并再次读取链路。只出现一次、在下一次切换时消失的注册,正是这份清单要抓的故障。在挂起后、强制重连后以及重启解析器服务后各测一次。每次切换都会考验一种不同的顺序假设。
只有在重连时五项检查全部通过,才能说推送的设置真的在用。更早的任何结论都只是某个幸运瞬间的快照。