日志里看不出问题:接口是通的,解析器报告的是 VPN 的 DNS 服务器,磁盘上的文件也写着它。可内部主机名仍然经本地路由器解析。这三句话并不矛盾,因为文件、每条链路的状态和查询路径本来就是碰巧同名的不同对象。下面要讲的,就是查询在运行 systemd-resolved 的主机上真正走的路径。

三个都叫 resolv.conf 的对象

在运行 resolved 的主机上,/etc/resolv.conf 通常是指向 /run/systemd/resolve/stub-resolv.conf 的符号链接。那个目标里放着本地 stub 监听器的地址 127.0.0.53,以及 resolved 算出的搜索域。它是状态的视图,而不是状态本身。

状态存放在 resolved 的按链路配置里,所以 resolvectl status 可以和文件不一致却仍然正确。每条链路自带服务器和路由域,查询到来时 stub 会参考这些数据。systemd-resolved 手册 说明了生成的文件和按链路的模型;视图与状态的分家才是值得记住的部分。改符号链接的目标,只会改变传统客户端读到的内容,不会改变解析器掌握的信息。

第三个对象 /run/systemd/resolve/resolv.conf 把每条链路压成一份服务器列表。指向该文件的程序会直接把查询发给列表中的服务器并跳过 stub,因此按链路的策略对它不再生效。所以同一台机器上的两个程序可以对同一个名字给出不同答案,而且谁都没有坏。

顺着 NSS 追一次查询

glibc 读取 /etc/nsswitch.conf 来决定由谁回答名字。包含 resolve 的 hosts 行会把问题交给 nss-resolve,它通过 D-Bus 询问 resolved 并拿到按链路的答案。只含 files dns 的行则把查询发给 /etc/resolv.conf 里保存的地址。

就是这一行解释了浏览器和 dig 之间的大多数分歧。dig 直接读取 resolv.conf,从不碰 NSS,所以它测的是 stub 或上游服务器,而不是应用实际走的路。getent hosts internal.example 走的是 NSS,把它得到的答案和 dig 的答案对照,才是第一个有用的测量。

自带解析器、或对固定端点使用 HTTPS 上 DNS 的应用会绕开这两条路。resolv.conf 和 resolved 的状态都约束不了它们。

先确认文件归谁所有、指向哪里

动手之前先读链接链,因为所有权决定哪个文件说了算。下面每条命令都只读取状态。

stat -c '%N' /etc/resolv.conf
resolvectl status --no-pager
grep '^hosts' /etc/nsswitch.conf
ls -l /run/systemd/resolve/

第一行把链接和目标一起打印出来;若文件是普通文件且归某个 VPN 脚本所有,解析器就绝不会把它当作输入。状态输出会列出按链路的服务器,以及决定每条查询去向的路由域。nsswitch 那一行说明要不要询问 stub,所以文件里有 stub 地址、那里却没有 resolve,就意味着查询能到达本地监听器,却没有任何东西在管理分流。最后那份列表也很重要,因为没有 stub-resolv.conf 的主机根本没在运行这里描述的方案。

如果链接链终止在另一个软件包管理的文件上,VPN 客户端和那个软件包就是需要协调的两位所有者。替换链接会丢掉一方的工作,把另一方的留在原地。

在链接的所有者那里修

VPN 连接应当在自己的链路上注册服务器和域,并在断开时把它们撤回。NetworkManager 的 resolved 集成和客户端自带的助手都是这么做的;只重写 resolv.conf 的脚本则不是。

在已经存在的链路上,运行时设置能看出机制。resolvectl dns tun0 10.40.0.53 会挂上一个服务器,resolvectl domain tun0 '~corp.example' 会挂上一个路由域。波浪号是承重字符:它把这个后缀的查询限制在这条链路上,其余一切不动;而同一个值少了波浪号就变成搜索域,分流随之消失。那些地址只是实验室例子,不是持久配置。用 resolvectl query host.corp.example 确认,并读一读是哪条链路作答的。

注册好的服务器仍然需要路由。查询到了对的链路却超时,问题出在隧道可达性,而不是解析器所有权。

正常工作的状态是什么样

健康的结果在三个视图里都一致:按链路的服务器与 VPN 下发的一致,路由域只覆盖内部后缀,关键应用能解析内部名字,而公共名字仍走本地路径。一次成功的查询,证明力还不如这种一致性。

然后断开连接,把两次查询重做一遍。把域留在注册状态就收工的客户端,会造出 几小时后内部查询失败的机器,隧道没开,原因也不明显。

相关文章