TOC

hosts 文件与 systemd 的域名解析

一、问题现象

在一次常规的 DNS 排查中,发现一个诡异现象:域名 eqr.hosts.d3v.cn 在指定公共 DNS 时可以正常解析 IPv6(AAAA),但依赖系统默认 DNS 时却返回空结果。

-> % dig +noall +answer a eqr.hosts.d3v.cn
eqr.hosts.d3v.cn.   0   IN  CNAME   localhost.
localhost.      0   IN  A   127.0.0.1

-> % dig +noall +answer aaaa eqr.hosts.d3v.cn # 无返回

-> % dig +noall +answer a eqr.hosts.d3v.cn @223.5.5.5
eqr.hosts.d3v.cn.   600 IN  A   192.168.1.37

-> % dig +noall +answer aaaa eqr.hosts.d3v.cn @223.5.5.5
eqr.hosts.d3v.cn.   600 IN  AAAA    2409:8a4d:c36:cc90:49b3:4746:9f09:3f84

清除缓存再重试也还是如此:

-> % sudo resolvectl flush-caches

-> % dig +noall +answer a eqr.hosts.d3v.cn
eqr.hosts.d3v.cn.   0   IN  CNAME   localhost.
localhost.      0   IN  A   127.0.0.1

-> % dig +noall +answer aaaa eqr.hosts.d3v.cn # 无返回

初步怀疑是 DNS 问题或 /etc/hosts 配置所致。

二、排查过程

1. 排除工具与 Hosts 干扰

首先确认 dig 命令的特性:它直接读取 /etc/resolv.conf 并向 DNS 服务器发送数据包,完全绕过 /etc/hosts 和系统解析器(NSS)。因此,早期的空结果看似与 hosts 文件无关。

查看 /etc/resolv.conf

-> % cat /etc/resolv.conf | grep -Ev "^#" | grep -Ev "^$"
nameserver 127.0.0.53
options edns0 trust-ad
search .

这表明系统使用了 systemd-resolved 的 Stub Resolver。

2. 定位上游 DNS

通过 resolvectl status 查看真实上游:

-> % resolvectl status
Global
         Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
  resolv.conf mode: stub

Link 2 (eno1)
    Current Scopes: none
         Protocols: -DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
     Default Route: no

Link 3 (enp4s0)
    Current Scopes: none
         Protocols: -DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
     Default Route: no

Link 4 (wlp2s0)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 192.168.1.1
       DNS Servers: 192.168.1.1 2409:804c:2000:1::1 2409:804c:2000:2::1
     Default Route: yes

Link 5 (docker0)
    Current Scopes: none
         Protocols: -DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
     Default Route: no

系统实际使用的是路由器(192.168.1.1)和移动光猫下发的 IPv6 DNS(2409:...)。

3. 关键验证:直连上游 DNS

为了验证是否是上游 ISP DNS 的问题,直接使用 dig 指定这些地址:

-> % dig +noall +answer @192.168.1.1 aaaa eqr.hosts.d3v.cn         # 正常返回
eqr.hosts.d3v.cn.   138 IN  AAAA    2409:8a4d:c36:cc90:49b3:4746:9f09:3f84

-> % dig +noall +answer @2409:804c:2000:1::1 aaaa eqr.hosts.d3v.cn # 正常返回
eqr.hosts.d3v.cn.   138 IN  AAAA    2409:8a4d:c36:cc90:49b3:4746:9f09:3f84

-> % dig +noall +answer @2409:804c:2000:2::1 aaaa eqr.hosts.d3v.cn # 正常返回
eqr.hosts.d3v.cn.   138 IN  AAAA    2409:8a4d:c36:cc90:49b3:4746:9f09:3f84

结论反转:上游 DNS 和权威服务器均无故障。问题必定出在本机的 systemd-resolved 处理环节。

4. 揭开“合成记录”的面纱

使用 resolvectl query(该命令遵循系统解析逻辑,不同于 dig)进行测试:

-> % resolvectl query eqr.hosts.d3v.cn
eqr.hosts.d3v.cn: 127.0.1.1

-- Information acquired via protocol DNS in 1.0ms.
-- Data is authenticated: yes; Data was acquired via local or encrypted transport: yes
-- Data from: synthetic

标志 synthetic(合成数据)是关键线索。这意味着 systemd-resolved 并未从网络获取结果,而是生成了本地数据。

5. 根因定位

检查 /etc/hosts 文件:

-> % grep eqr /etc/hosts
127.0.0.1 eqr.hosts.d3v.cn eqr eqr6

注释前:

-> % dig +noall +answer aaaa eqr.hosts.d3v.cn # 无输出

-> % resolvectl query eqr.hosts.d3v.cn
eqr.hosts.d3v.cn: 127.0.0.1
                  (localhost)

-- Information acquired via protocol DNS in 1.2ms.
-- Data is authenticated: yes; Data was acquired via local or encrypted transport: yes
-- Data from: synthetic

-> % resolvectl query -4 eqr.hosts.d3v.cn
eqr.hosts.d3v.cn: 127.0.0.1
                  (localhost)

-- Information acquired via protocol DNS in 1.4ms.
-- Data is authenticated: yes; Data was acquired via local or encrypted transport: yes
-- Data from: synthetic

-> % resolvectl query -6 eqr.hosts.d3v.cn
eqr.hosts.d3v.cn: resolve call failed: 'eqr.hosts.d3v.cn' does not have any RR of the requested type

-> % getent hosts eqr.hosts.d3v.cn
127.0.0.1       eqr.hosts.d3v.cn eqr eqr6
-> % getent ahosts eqr.hosts.d3v.cn
127.0.0.1       STREAM eqr.hosts.d3v.cn
127.0.0.1       DGRAM
127.0.0.1       RAW
-> % getent ahostsv6 eqr.hosts.d3v.cn
::ffff:127.0.0.1 STREAM eqr.hosts.d3v.cn
::ffff:127.0.0.1 DGRAM
::ffff:127.0.0.1 RAW

注释这一行之后再次解析就正常了:

-> % dig +noall +answer aaaa eqr.hosts.d3v.cn
eqr.hosts.d3v.cn.   600 IN  AAAA    2409:8a4d:c36:cc90:49b3:4746:9f09:3f84

-> % resolvectl query eqr.hosts.d3v.cn
eqr.hosts.d3v.cn: 2409:8a4d:c36:cc90:49b3:4746:9f09:3f84 -- link: wlp2s0
                  192.168.1.37                 -- link: wlp2s0

-- Information acquired via protocol DNS in 37.3ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network

-> % resolvectl query -4 eqr.hosts.d3v.cn
eqr.hosts.d3v.cn: 192.168.1.37                 -- link: wlp2s0

-- Information acquired via protocol DNS in 6.0ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network

-> % resolvectl query -6 eqr.hosts.d3v.cn
eqr.hosts.d3v.cn: 2409:8a4d:c36:cc90:49b3:4746:9f09:3f84 -- link: wlp2s0

-- Information acquired via protocol DNS in 5.0ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network

-> % getent hosts eqr.hosts.d3v.cn
2409:8a4d:c36:cc90:49b3:4746:9f09:3f84 eqr.hosts.d3v.cn

-> % getent ahosts eqr.hosts.d3v.cn
2409:8a4d:c36:cc90:49b3:4746:9f09:3f84 STREAM eqr.hosts.d3v.cn
2409:8a4d:c36:cc90:49b3:4746:9f09:3f84 DGRAM
2409:8a4d:c36:cc90:49b3:4746:9f09:3f84 RAW
192.168.1.37    STREAM
192.168.1.37    DGRAM
192.168.1.37    RAW

-> % getent ahostsv6 eqr.hosts.d3v.cn
2409:8a4d:c36:cc90:49b3:4746:9f09:3f84 STREAM eqr.hosts.d3v.cn
2409:8a4d:c36:cc90:49b3:4746:9f09:3f84 DGRAM
2409:8a4d:c36:cc90:49b3:4746:9f09:3f84 RAW

结论

systemd-resolved 将 /etc/hosts 中定义的 eqr.hosts.d3v.cn 纳入本地 Synthetic Database。
当收到该名称的解析请求时,它优先使用本地数据作为该名称的权威来源,而不会继续向上游 DNS 查询同名记录。
由于本地仅定义了 IPv4(127.0.0.1),没有对应的 IPv6 地址,因此 AAAA 查询直接返回“该名称不存在 AAAA 记录(NXRRSET)”,最终表现为 AAAA 解析失败。
注释 /etc/hosts 中的对应记录后,查询重新落到网络 DNS,A 与 AAAA 均恢复正常。

验证

添加一个 TXT 类型记录,然后再次 dig 查询,来验证 /etc/hosts 会阻拦所有记录类型的解析。

# 注释之后可以解析
-> % dig +noall +answer txt eqr.hosts.d3v.cn
eqr.hosts.d3v.cn.   600 IN  TXT "hello world"

# 取消注释之后没有结果
-> % dig +noall +answer txt eqr.hosts.d3v.cn

果然如此。

三、处理办法

最好的办法:不通过 hosts 文件定义真实域名,直接走 DNS。

如果有特殊需求不能修改 DNS 外网解析,又要求本地走 127.0.0.1,那就在本地把 IPv6 也定义上:

::1       eqr.hosts.d3v.cn
-> % dig +noall +answer aaaa eqr.hosts.d3v.cn
eqr.hosts.d3v.cn.   0   IN  AAAA    ::1

我的排查过程中发现了一个大 BUG(疑似):

/etc/hosts 本质上只提供 A / AAAA 记录,但是 systemd-resolved 却把它提升为了整个 Owner Name 的权威数据源(authoritative source),这个不合理。

如果你有魔法,你可以看到一个评论框~