VPN 的 DNS 抢走每一次解析:修正 NetworkManager 的 dns-priority
VPN 连接默认优先级为 0,并认领所有域名。为顺序设置负优先级,再用路由域把其余部分交还。
连上办公 VPN 后,每一次名称解析都突然转向公司解析器。公网名字仍能解析,所以直到本地打印机名字失败,或者 split-horizon 记录返回办公地址,才看得出问题。在某些 Fedora 配置里恰好相反:隧道建立后,运营商解析器仍然在应答。两者其实是同一个问题——这次查询归哪个解析器所有——而在 NetworkManager 系统上,答案就在每条连接的 DNS 属性里。
两条连接,一个胜出的解析器
每条连接都会声明 DNS 优先级。普通连接默认是 50,VPN 连接默认是 0,数值更小者胜出。正是这个默认值,让 VPN 连接在它所认领的每个域名上悄悄压过有线连接:没人选过,零却赢了五十。
优先级决定候选顺序。它不限制向胜出者询问什么。没有搜索域的 VPN 连接会认领一切,所以优先用它也会把公网查询送进隧道;在分割隧道设计里,还会送往公司解析器转发的位置。
优先级决定顺序,搜索列表决定归属
nmcli -f connection.id,ipv4.dns,ipv4.dns-search,ipv4.dns-priority con show "Corp VPN"
resolvectl status
readlink -f /etc/resolv.conf
搜索列表就是归属声明。像 corp.example 这样的裸域名会成为搜索后缀和路由域。只用于路由的形式 ~corp.example 会把该后缀下的解析交给这条连接,而不把域名追加到短名之后。那个波浪号正是「问这台服务器」和「问这台服务器并替我把输入补全」的区别。
当用户输入短主机名、而记录同时也有全限定名时,两种形式都用。若只想分割、不改变解析器补全裸名的方式,就只用路由形式。
然后把偏好显式化,并重新激活连接:
nmcli con modify "Corp VPN" ipv4.dns-priority -100
nmcli con modify "Corp VPN" ipv6.dns-priority -100
nmcli con modify "Corp VPN" ipv4.dns-search "~corp.example,corp.example"
nmcli con up "Corp VPN"
负优先级是文档给出的「偏好这条连接」写法;同时设置两个地址族,可以让行为在具备 IPv6 连通性的主机上保持一致。只改属性而不重新激活连接,当前运行的什么都不变,所以这一串命令以显式的 up 收尾。
把分割交给 systemd-resolved
默认的 dnsmasq 插件把查询灌进单个本地解析器,所以按链路分离仍然很粗。systemd-resolved 按链路展示分割,而 resolvectl query 会指出是哪条链路作答。
systemctl enable --now systemd-resolved
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
systemctl restart systemd-resolved NetworkManager
resolvectl query build.corp.example
此后公司链路承载 corp.example,公网解析器负责其余部分。当查询落到错误的链路上时,命令会指出真正作答的链路,知道这一点胜过只知道地址。
让 DNS 设计匹配隧道类型
把所有流量都送进隧道的隐私设计不该带分割列表,因为那会把公网名字交回本地解析器,违背本意。分割隧道设计则需要一份:没有它,公司解析器会看到每一次公网查询,隧道一断配置就崩。
接着是封锁隧道外 DNS 的 kill switch。隧道内的区域能解析,其他地方全部失败,而连接本身完全健康。可行的答案是明确放行局域网解析器,或者只转发公司区域的本地缓存;两者都是有归属的例外,而不是隧道的缺陷。
在接口启动时重写解析器配置的配置文件也差不多:一旦本地解析器不可达,就只有隧道认识的名字能解析。无论适用哪种机制,都要写下每种状态下哪些名字应当可用,并把这份清单交给处理报告的人。
逐链路验证,重连之后再来一次
分别问一个内部名字和一个公网名字,并观察查询到底去了哪里。泄漏检测页面告诉你哪个解析器作答,而不是系统选了哪条链路,所以它对你刚配置的顺序什么也没说。
断开并重连之后,再跑一次这两个查询。连接激活时优先级会重新应用,而旧的符号链接可能熬过一次没人记得的升级。最后要写下来的是:哪个解析器拥有哪个后缀,以及一项明天仍然成立的检查。