DNS 反复回到旧状态,VPN 重连抹掉了你的修复,最后团队里有人提议把文件锁住。这一连串现象是症状,不是解法:两个组件都认为 /etc/resolv.conf 归自己管,谁最后写谁生效。锁文件并不能解决归属冲突,只会把它藏起来,同时弄坏你真正想要的那个组件。按顺序做完下面四项检查,在拥有它的层面上修好安排,而不是在文件上动刀。

检查 1:它是不是符号链接,指向哪里

从文件本身查起,因为你正在编辑的路径可能根本不是文件。指向解析器运行时目录的符号链接,与客户端脚本重写的普通文件行为完全不同:

ls -l /etc/resolv.conf
readlink -f /etc/resolv.conf
cat /etc/resolv.conf

链接指向 systemd-resolved 的 stub 路径,意味着文件内容是生成的,手动修改会在下一次事件中被覆盖。带 nameserver 行的普通文件通常由连接时运行的客户端或 DHCP 钩子写入。两者都合法;关键是动手之前先弄清你面对的是哪一种。

检查 2:哪个守护进程在运行

查清实际在运行的是什么,而不是装了什么。现代发行版上可能同时存在多个解析器,在回环接口上占着 53 端口的那个才是负责应答的:

systemctl status systemd-resolved --no-pager
resolvectl status
ss -lunp | grep ':53'

状态输出会列出每条链路的 DNS 服务器和路由域,这正是拆分 DNS 需要的那张地图。如果内部域名必须走内部服务器、其余走公共服务器,这条策略就住在每条链路的域里;把它压平成文件里的静态 nameserver 列表,就会悄悄丢掉这个区别。

NetworkManager 自身的配置决定它用哪个后端。主配置文件里的 dns= 设置在它的内部解析器、dnsmasq、systemd-resolved 和 none 之间做选择,发行版可能已经替你选好了。在决定这台主机上“修 DNS”意味着什么之前,先读一下那个设置。

检查 3:VPN 客户端被允许写什么

冲突通常就住在这里。直接重写解析器文件的 VPN 客户端,会和任何同样维护该文件的服务打架,先跑的那个输。较新的 OpenVPN 可以把 DNS 设置交给 systemd-resolved,而不是重写文件,这正是你想要的协作行为;旧脚本和手写钩子往往调用过时的辅助工具,或者干脆覆盖文件。

WireGuard 的情况在形态上类似:接口配置本身不管 DNS,管它的是周围的工具,通过解析器辅助程序或 resolved 集成来做。如果装了两套这样的辅助程序,两边都会在连接时试图贯彻自己的世界观。

与其猜,不如读客户端的 up 和 down 钩子。/etc/openvpn/up.sh 或同类目录里的几行 shell 能解释原本看似随机的行为,那里也是让客户端合作而不是竞争的地方。

检查 4:在拥有者那里修,而不是在文件里修

知道拥有者之后,就通过该拥有者支持的接口配置 DNS。对 systemd-resolved 来说,是按链路设置和路由域,而不是静态文件;对 NetworkManager 来说是连接配置文件的 DNS 设置和所选后端;对使用 resolvconf 集成的发行版来说,是通过那个辅助程序写入。

不要把文件设成不可变。不可变属性会把合法的 DHCP 续租和 VPN 更新一起停掉,连你正在对抗的那些也一起停,还会让系统处在这样的状态:下一次软件包升级或重连就会产生一个没人能从配置里解释的故障。它掩盖了归属冲突,而那正是你想解决的问题。

如果两个组件确实必须共存,就给它们不同的分工。让网络管理器拥有接口层面的配置,让 VPN 客户端通过同一套集成贡献按链路的 DNS 和域,并删掉那个直接写文件的第三个脚本。systemd-resolved 文档说明了它支持的模式,以及每种模式在 /etc/resolv.conf 中暴露出什么。选一种与你需要保留的策略量相匹配的模式。如果拆分 DNS 重要,带按链路域的 stub 方案能保住它;而把上游服务器复制进文件的模式会悄悄把它抹平。

在重连之后验证,而不是在编辑之后验证

改动刚完成时看起来正确的文件什么也证明不了,因为下一次事件还没发生。触发过去会弄坏它的那套流程:断开再拉起隧道,续租 DHCP 或切换接口,然后重新读取同样的值。

resolvectl query build.corp.example
resolvectl query example.com
resolvectl status | head -40

接着确认真正重要的区别:内部域名通过内部服务器解析,同时公共域名仍然可用。把所有请求都发给内部服务器的配置,在有人试图通过隧道访问互联网之前看起来都没问题;而把所有请求都发给公共服务器的配置会泄露内部域名。

把拥有者、后端和符号链接模式记进部署笔记。就这一段话,能把将来的一次软件包升级从事故变成检查项,也能让下一个人不必再用 chattr 命令重新发现同一个冲突。

相关文章