- 你的系统使用了 systemd-resolved
- 网络由 NetworkManager 或 Netplan 接管
- /etc/resolv.conf 为动态生成的符号链接
- 注意:不同发行版默认配置可能略有差异(如 nsswitch.conf 或 NetworkManager 设置)。
0. DNS 解析的三层结构(核心心智模型)
1. 为什么 resolv.conf 设计成“瞬态报告”?
2. 权力架构:谁才是真正的老板?
3. 核心痛点:DHCP “全家桶”污染
4. 三大对策、各自缺点及 VPN 影响
正统派:经理人覆盖 (Overrides)
# 链式执行:修改并立即激活。注意:&& 不能防御错误的配置,如果配置本身有误,你仍会失联。 nmcli con modify <connection-name> ipv4.ignore-auto-dns yes ipv6.ignore-auto-dns yes && nmcli con up <connection-name>
挂钩派:resolvconf 前置挂钩 (Prepending Hook)
/etc/resolvconf/resolv.conf.d/head 文件,将配置强行拼接到生成的 resolv.conf 最顶部。实施前提:在使用
systemd-resolved的系统上,通常需要将/etc/resolv.conf的控制权平滑移交给resolvconf(如openresolv替代包),必要时需调整服务状态以避免接管冲突。“咖啡店陷阱”:在需要网页认证的公共 Wi-Fi 下,除了传统的 DNS 劫持,部分现代网络已开始通过 DHCP/RA 下发认证 API(RFC 8910/8952)。强行固定 DNS 会破坏网络上下文感知与 Portal 发现流程,极易导致认证页面无法弹出。
VPN 干扰者:极易干扰依赖
resolvconf或systemd-resolved动态注入 DNS 的 VPN(如 WireGuard 的wg-quick或 Tailscale)。写在head里的死配置会固化解析路径,极易压制或破坏 VPN 的 Split-DNS(分流解析)机制。(注:在 NSS 解析链中,收到 NXDOMAIN 会直接阻断查询;若遇到超时或 SERVFAIL,则可能触发不可预期的回退行为。)
破局之法:如果你的挂钩目标是
127.0.0.1(dnscrypt-proxy),这并非死胡同,而是一个适合实现精细化分流的起点。不要解除head的锁死,而是将 OS 层的“配置冲突”转化为代理层的“智能分流”:
在
dnscrypt-proxy.toml中启用:forwarding_rules = 'forwarding-rules.txt'。在该
.txt文件中按<domain> <server address>格式写入规则:corp 10.8.0.1或corp $DHCP。(架构师防坑警告:官方声明
$DHCP转发为实验性功能。面对 WireGuard/Tailscale 等非 DHCP 协议下发的 tun 隧道时,它未必能可靠覆盖此类 VPN 场景。针对动态分配的内网 DNS,在服务端固定网关 IP,或在客户端编写NetworkManager dispatcher.d钩子脚本进行实时同步,是实战中更为稳妥的工程解法。)
暴力派:chattr 焊死 (The Nuclear Option)
- 排障噩梦:NetworkManager 会将其标记为 unmanaged;而 systemd-resolved 会进入“消费者模式”(Consumer Mode),仅仅将其作为向下兼容的参考输入。
- 分歧路径:绕过 libc 直接读文件的程序(如 Go/Node.js),其最终查询目标将不可避免地退化为你锁死的 8.8.8.8,导致 VPN 的 per-link DNS 路由报废。
5. 那个消失的 127.0.0.53
- 流量路径:
- 前半程(程序 → Resolver 解析器):遵循 NSS 解析链的程序(如
curl、ping)若命中nss-resolve路径,走 IPC,lo接口上抓不到包。刻意绕过 NSS、直接发 UDP 裸请求的诊断工具(如dig),走 stub listener,你在lo上能抓到包。 - 后半程 (Resolver → 上游):一旦本地缓存不命中,systemd-resolved 必须向外求助。此时报文会通过路由表从物理网卡(如 wlo1)发出。
6. 真正的入口:nsswitch.conf
- 顺序决定论:glibc 严格从左到右按序调用模块。尽管官方建议将 resolve 排在 files 前以利用缓存,但多数发行版仍优先保障 /etc/hosts 的权威。
- 断路器机制:[!UNAVAIL=return] 意味着只有当 systemd-resolved 进程彻底死亡或不可达时,才会向右回退。如果只是单纯的域名不存在(NXDOMAIN),解析会直接终止,不会触发回退。
7. 短路逻辑与敏感泄露
8. Netplan 的秩序 vs. 恶意的破坏: Netplan 的逻辑秩序与版本敏感性
9. 隐私防护:DoT、DoH 与 DNSSEC
10. DNS 代理的乱入与内核死锁陷阱
当你试图通过 /head 引入本地代理时,必须小心 53 端口的启动死锁。默认情况下,systemd-resolved 牢牢绑定在 127.0.0.53:53。虽然这与 127.0.0.1 是独立的 IP,但部分传统的第三方缓存工具(如 dnsmasq)在出厂配置下会默认尝试全局监听(绑定 0.0.0.0:53)。在 Linux 内核机制下,当具体 IP 被占用时,后续的全局通配符绑定(Wildcard Bind)会被内核直接以 Address already in use 拒绝。这就是为什么许多极客配置了本地代理却莫名启动失败——除非你懂得去深入修改代理工具的底层配置(如在 dnsmasq 中强制开启 bind-interfaces),否则最终往往不得不退回硬编码 8.8.8.8。
架构师的解法 (The Socket Bypass): 像dnscrypt-proxy这类强制显式声明监听地址的工具,本身就能避开0.0.0.0通配符死锁。而更进阶的系统级解法,是直接利用systemd的 Socket Activation(套接字激活)。
无需解除head的锁死,配置.socket单元,将127.0.0.1:53的监听权交由 PID 1 接管。这种基于精确匹配的端点控制能确保代理程序优先拿到端口,彻底消除启动时序的竞争。
(注:此架构生效的底层前提是代理工具必须实现了sd_listen_fds()接口来接收系统传递的描述符。dnscrypt-proxy原生具备此能力,从而完美达成了与systemd-resolved的无缝共存。)
11. 软链接的“狸猫换太子”
- 模式 A(默认):指向 ../run/systemd/resolve/stub-resolv.conf。解析经过管理员过滤,支持 Split-DNS。
- 模式 B(底层):指向 ../run/systemd/resolve/resolv.conf。上游 DNS 直接暴露在文件里,不经过 127.0.0.53 转发。
12. 让理论成为可见的数据
- 查看当前真实使用的 DNS:
resolvectl status - 定位 Split-DNS 链路污染的神器:
resolvectl query google.com
(它会绕过 glibc 告诉你最终选用了哪个网卡接口和哪个具体服务器) - IPC 观测法: resolvectl monitor 。你可以使用此命令实时监听 D-Bus 总线上的解析请求。法证级盲点警告:它只能看到经过 systemd-resolved 处理的查询。如果你的程序完全绕过 systemd-resolved、直接向外部 DNS 发包(例如 resolv.conf 被锁死为外部地址,或程序内置了硬编码 DNS),monitor 的界面上将是一片死寂。这种"看不见",正是底层规则被绕过的最强铁证。
13. 观测与旧时代的遗产:没 nscd 导致的缓存缺失
14. 服务器环境下的生死法则
- 云厂商的 VPC 结界:云架构完全依赖云厂商提供的“内网专属 DNS”(如 AWS 的 169.254.169.253)。在云上,必须优先使用云内网 DNS,否则会导致内网服务解析熔断。
- 代理中转税:在极高并发场景下(如单机数万 QPS),本地 stub 转发路径会引入额外的内核态与用户态切换(Context Switch)及潜在的锁竞争。
15. Docker/K8s 的云原生结界
- Docker 的条件触发回退(Conditional Fallback):在默认的 bridge 网络中,Docker 会参考并继承宿主机的 /etc/resolv.conf(并经过引擎的过滤与重写)。法证级细节:如果 Docker(底层 libnetwork 源码)发现宿主机的配置中包含回环地址(如 127.0.0.53),为了防止容器内发生不可逾越的路由死循环,会强制将其剔除。更暗黑的是,如果剔除后没有留下任何有效的上游 DNS,在部分实现与运行环境中,可能会触发兜底机制,注入预设的公共 DNS 服务器(如 8.8.8.8 和 8.8.4.4)。而在自定义网络(User-defined networks)中,Docker 通常会启用内嵌的 DNS 引擎(固定为 127.0.0.11)(除非被显式 DNS 参数或特殊网络模式如 host 覆盖),负责容器内的服务发现与上游转发。
- K8s 的集群级接管:在 Kubernetes 世界里,DNS 解析被彻底拔高到了集群控制面。kubelet 会根据 dnsPolicy 策略接管并重写容器内的 /etc/resolv.conf 的内容。在默认的 ClusterFirst 策略族(及相关变体)下,这意味着容器的解析路径不再依赖宿主机的 systemd-resolved 守护进程或其宿主级 NSS 解析链,而是通过容器内部的 libc Resolver 指向集群 DNS Service(通常为 CoreDNS 的 ClusterIP,底层经由 kube-proxy 或 IPVS 完成 VIP 流量转发),将解析路径控制权完全收敛至集群控制面(对于集群外部域名,解析请求最终仍可能由 CoreDNS 根据配置转发至外部上游)。(注意:如果你手贱设置了 dnsPolicy: Default,Pod 将直接继承宿主机的解析配置,从而可能再次暴露于宿主机解析机制引发的异常,例如回环地址不可达)。
16. Android 与系统的分化: 移动端的终极形态(DnsResolver APEX)
17. 浏览器的“黑箱解析栈”:OS 解析权力的越狱与反制
- 假阳性(系统瘫痪,浏览器幸存):若将 resolv.conf 锁死为无效地址,终端里的 curl 与 ping 会全线崩溃,但浏览器却能靠内置的 DoH 通道正常打开网页。
- 假阴性(内网通畅,浏览器报非):在企业 VPN 中,系统路由表明确知道 git.corp 应走内网网关,浏览器却直接将其打包发给公网的 Cloudflare 导致 NXDOMAIN。最终表现为终端里能 ping 通内网,网页却打不开。
- Firefox (TRR 机制):在默认的 TRR-first 模式下,DoH 查询失败时允许回退到 OS 的原生解析栈。但若设置为 TRR-only,则形成硬死锁,绝不回退明文。此外,当 Firefox 探测到系统层面的家长控制,或匹配到策略中的排除列表时,会主动跳过 TRR 走 OS 路径。(注:若用户手动开启 DoH,该网络探针信号会被忽略)。
- Chrome (Secure DNS):在默认的 Automatic 模式下,允许探测并回退到 OS。但只要用户显式指定了自定义 DoH 供应商,即刻禁止回退。另外,当 Chrome 检测到自身运行在企业受管环境(Enterprise Policy)中时,会自动降级或彻底关闭 DoH。
- 探针截杀 (Canary Domain):Firefox 遵循标准的探针协议。只要系统级 DNS(如 dnscrypt-proxy)将 use-application-dns.net 拦截并返回 NXDOMAIN,Firefox 会自动关闭内部 DoH。(注:此探针仅对默认开启 DoH 的用户有效;若用户手动强制开启 DoH,该信号将被无视,必须改用策略硬编码)。
- 策略硬编码 (Policy Enforcement):生产环境中应通过 /etc/firefox/policies/policies.json 配置 DNSOverHTTPS 节点。设置 "Locked": true 可彻底锁定设置面板,利用 "ExcludedDomains": ["*.corp.local"] 实现精准的内网分流。为确保极致安全,追加 "Fallback": false 可强制禁止其在 DoH 失败后向系统明文解析器投降。
- Chrome 路径:/etc/opt/chrome/policies/managed/
- Chromium 路径:/etc/chromium/policies/managed/
- Chrome: 访问 chrome://net-internals/#dns,可手动进行 DNS lookup 并清空本地 host cache 以验证策略是否生效。如需完整网络事件日志,请改用 chrome://net-export。
- Firefox: 访问 about:networking#dns。若 TRR 字段显示为 false,代表越狱已被镇压,解析权已牢牢交还给系统。










