改 resolv.conf 修不好 VPN 断开后的 DNS
列表顶端的死隧道解析器会让每次查询多等一个超时。先弄清文件归谁管,重置链路状态,别再编辑生成出来的输出。
断开 VPN 后,ping 1.1.1.1 照样通,可每次域名查询都要卡到页面最终放弃为止。随后 cat /etc/resolv.conf 里出现一个只在隧道内存在过的域名服务器,通常还排在第一位。删掉那行看起来能好上十分钟,直到一次休眠、一次 Wi-Fi 漫游或一次重启又把它写回来。你编辑的文件不是设置本身;它只是另一个组件所拥有的链路状态的渲染结果。
为什么一条过期记录会让每次查询多花几秒
glibc 解析器按顺序遍历域名服务器列表,先等满一个超时才会去试下一个,而 resolv.conf(5) 把这份列表限制在三项以内。隧道消失后,把 10.8.0.1 这类隧道侧解析器放在首位,每次查询都要先付掉超时,才能落到真正应答的服务器上。这就是为什么症状看起来像连接断了而不是 DNS 出错:浏览器卡住、超时,有时还会返回后来已经失效的缓存地址。
这也解释了为什么抱怨总是「网坏了」。能 ping 通 IP 就说明链路正常,于是故障看起来像应用的问题。解析器列表是第一件要读的东西,也是最后一件事要改的东西。
误区:resolv.conf 是设置文件
动手改之前,先问清这个路径到底是什么:
ls -l /etc/resolv.conf
readlink -f /etc/resolv.conf
在 systemd-resolved 主机上,它是指向 stub 文件的符号链接,而 stub 指向 127.0.0.53,真正的服务器活在 resolvectl 打印的按链路状态里。在 NetworkManager 主机上,它可能是 NetworkManager 从活动连接写出的文件。在某些厂商客户端下,它是连接时原地重写、不走运时断开后永不恢复的普通文件。这是三个不同的所有者,手工改前两个要么毫无作用,要么撑到下一次网络事件。
resolvectl status 才是说真话的视图:按链路的 DNS 服务器、路由域,以及隧道链路是否还在。resolvectl query example.com 更进一步,会显示哪个接口应答了。动文件之前先用这两个,并且记住:自带安全 DNS 的浏览器会把这些全忽略。
改任何东西之前先找到所有者
通常有三个组件抢同一项设置:systemd-resolved、NetworkManager,以及 VPN 客户端装的那套。先认出还活着的那个:
systemctl is-active systemd-resolved
nmcli connection show --active
ps aux | grep -E "openvpn|wg-quick|vpn"
如果 resolved 在运行,且符号链接指向它的 stub,那么全局文件就是生成出来的输出,你的修改只是表面功夫。如果连接归 NetworkManager 管,服务器属于连接配置,修法是改 nmcli 属性,而不是改文件。如果厂商守护进程在跑,只有它自己的恢复路径能撑过下一次连接。
清掉客户端留下的链路状态
被杀掉的客户端跑不了自己的清理钩子。SIGKILL 根本捕获不到,SIGINT 和 SIGTERM 则能捕获,所以强制杀掉后「就这么停了」的隧道,往往把接口注册和解析器条目都留在原地。找到具体的 PID,只给那一个发信号:
ps aux | grep -E "openvpn|wg-quick"
sudo kill <pid>
resolvectl revert tun0
读完整份匹配列表之前,别急着敲 pkill -f vpn 或 killall vpn。-f 匹配整条命令行,可能顺手结束一个包装脚本、一个编辑器会话,或某个路径里含这个字符串的无关进程。在运行中途杀错进程,正是解析器问题变成文件丢失的常见路径。
resolvectl revert 会重置那条链路的运行时 DNS 配置,范围比重启解析器窄,更比改全局文件窄得多。如果隧道链路已经彻底没了,systemctl restart NetworkManager 会重建连接状态,这也是各种帖子里反复回到的一步。要留意带 Restart=always 的服务管理器,它可能把被杀掉的客户端连同 DNS 设置一起原样拉回来。
另一个方向:推下去的 DNS 根本没到
过期条目的镜像,是隧道里根本没有任何可用解析器。配置里的 DNS 选项只是一个请求;真正管连接的组件必须把它交给解析器。如果日志显示服务器已经收到,但隧道链路没带上任何 DNS 设置,那缺的是集成,不是服务器。
手动做一次交接来验证,这能把「服务器不对」和「没人应用它」分开:
resolvectl dns tun0 10.10.10.10
resolvectl domain tun0 ~.
全捕获路由域会把所有名字都送到那条链路,全隧道客户端要的就是这个,而分离 DNS 绝不能用它。这些设置下次连接就会消失。要在 NetworkManager 里做成持久的,就关闭自动 DNS、在连接上设置服务器,再加上域:
nmcli connection modify office ipv4.ignore-auto-dns yes
nmcli connection modify office ipv4.dns 10.10.10.10
nmcli connection modify office ipv4.dns-search "~."
对公司隧道只应回答内部名字的场景,把这些区域用前导波浪号列出来,而不是用全捕获,这样公网名字继续走本地解析器。
会变成下一次故障的临时办法
连接前备份 resolv.conf、断开后再还原,在 runbook 里是合法的一行,但它是绕行而不是修复。它恰恰在最需要的时候失效,因为崩溃或强制重启会跳过还原。把它写成临时办法,别让下一个人在上面继续搭建。
关掉 systemd-resolved 再硬写公网服务器,是另一个流行做法。它让连接和断开变得可预测,同时把其他所有网络的按链路 DNS 都抹掉,既破坏了内部名字的分离 DNS,也让「把查询留在隧道内」这个目的落空。装 resolvconf 再固定公网服务器,成本一样,还多一层。把它们中任何一个当作诊断手段:如果它恢复了上网,你学到的是解析器集成有问题,而不是公网服务器正确。
能长期维持的修法是每层只有一个所有者:确定哪一层组件拥有隧道链路的 DNS、哪一层拥有全局默认值,写下来,再把其他组件从路径上移走。然后在断开后用只有 VPN 才能解析的名字验证。如果私有名字仍然能解析,说明注册没被移除;如果什么也解析不了,说明文件指向了一个早已不存在的服务器。这两点在打开浏览器之前都能在 resolvectl 里看到。