·约 10 分钟阅读·DeepSeek

DNS 究竟是怎么解析的:一步步拆解(写给工程师)

一篇从数据包层面拆解 DNS 解析的文章:存根解析器、递归解析器、根服务器与顶级域服务器、权威应答,缓存与 TTL,日常真正用得上的记录类型(A/AAAA/CNAME/TXT/MX/NS/SOA/CAA),CNAME 的规则,glue 记录以及为什么裸域不能用 CNAME(ALIAS/ANAME),权威角色与递归角色的区别,以及如何用 dig +trace、kdig、DoH 和 DoT 来排障。

networkinglinux

你把 api.example.com 指向了一台新机器,可两小时后,半个互联网还在访问旧的那台。什么都没坏;你只是撞上了 how DNS works 里从来不会画进示意图的那一部分——缓存。DNS 是一个分布式的、层级化的、被极度激进的缓存的数据库,而绝大多数所谓的“生效慢”问题,本质都是缓存过期问题。这篇就是我给新工程师做 onboarding 时用的走查:一次 DNS resolution 对某个域名究竟做了什么,从你进程里的存根解析器(stub resolver),一路到拥有答案的**权威(authoritative)**服务器;以及当答案不对时,怎么用 dig +trace 把它揪出来。

我先完整走一遍单次查询,再讲你日常真正会碰到的记录类型,最后讲那些真会让你被叫起来处理(page)的故障模式。

How DNS works:服务器阵容

每一次查询都会经过一组固定的角色。把它们混为一谈,就是大多数 DNS 争论的根源。

  • 存根解析器(stub resolver)——内嵌在操作系统或 libc 里的小客户端(getaddrinfo)。它什么都不懂,只是把查询转发给它被配置的那个递归解析器,然后等回复。
  • 递归解析器(recursive resolver)——干活的(systemd-resolved、unbound、你的路由器、1.1.1.1)。它负责走完整棵树,把它学到的一切缓存下来,再答复你的进程。它是唯一真正在做“解析”的组件。
  • 根服务器(root servers)——十三个具名身份(.),由数百个 anycast 节点提供服务。它们并不知道你的域名,只会指向顶级域。
  • 顶级域服务器(TLD servers)——它们拥有 .com、.io、.dev,并指向其下那个域名的权威服务器。
  • 权威服务器(authoritative servers)——它们持有 example.com 的真实记录,并以“权威”的方式作答。它们从不问别人,它们就是链条的终点。

关键分工在于:根、顶级域、权威服务器都是权威的——它们只为自己拥有的 zone 作答,并且拒绝递归。只有递归解析器才会递归。你的机器几乎从不直接和根服务器说话。

DNS resolution:一步步走完

假设你笔记本上的某个进程要查 www.example.com。下面是完整路径。

stub            recursive               root      TLD (.com)     authoritative
 |                 |                      |           |               |
 |-- query www.example.com -------------->|           |               |
 |                 |-- miss cache         |           |               |
 |                 |-- query . ---------------------->|               |
 |                 |<- referral: .com NS + glue --------|               |
 |                 |-- query .com ----------------------------->       |
 |                 |<- referral: example.com NS + glue -----------------|
 |                 |-- query www.example.com ----------------------->  |
 |                 |<- authoritative A 203.0.113.10, TTL 300 ----------|
 |<- answer 203.0.113.10 (cached for 300s) |           |               |

按顺序读:

  1. 存根解析器把一个递归查询(RD=1)发给递归解析器。
  2. 递归解析器先查自己的缓存。命中,故事到此结束。
  3. 未命中时,它从根开始。根回一个转介(referral):“.com 去问这些顶级域服务器”,并附带 glue 地址,这样就不用先去解析顶级域服务器自身的域名。
  4. 它去问某个 .com 顶级域服务器,后者把它转介给 example.com 的权威域名服务器——同样带 glue。
  5. 它去问 example.com 的权威服务器,拿回真正的 A 记录以及一个 TTL。
  6. 递归解析器按 TTL 缓存每一段答案,再把最终地址返回给你的进程。

你可以不信任任何解析器、直接观察这一整套序列,因为 dig +trace 会自己从根开始、沿着转介一步步走:

dig +trace www.example.com

# 复现整个层级:root -> .com -> example.com -> 答案。
# 不使用任何缓存,所以你能看到真实的委派链。

注意角色上的区别:第 1–2 步是递归的(解析器替你干活);第 3–5 步是迭代的(每台服务器只指向下一台)。

缓存与 TTL

每条记录都带一个以秒为单位的 TTL。解析器可以在这段时间内缓存答案,之后必须重新查询。有两个数字值得记住:

  • 权威服务器公布的——记录自身携带的 TTL。
  • 某个解析器实际选择保留的——它只会遵守记录 TTL 与自身下限中较小的那个,而且它压根没有缓存的义务。

这就是为什么“我改了记录但它生效很慢”不是 bug。如果旧记录的 TTL 是 24 小时,那么全世界的解析器都有权在 24 小时内继续返回旧值。实用的法则是:迁移前一天把 TTL 调低,做切换,然后再调回来。这是让 DNS 变更真正快速生效的唯一可靠办法。

日常真正用得上的记录类型

你不需要背下每一种 RR 类型。下面这八种几乎覆盖了所有实际工作。

类型含义典型用途
AIPv4 地址example.com. 300 IN A 203.0.113.10
AAAAIPv6 地址example.com. 300 IN AAAA 2001:db8::10
CNAME指向另一个名字的别名www → example.com
TXT任意文本SPF、DKIM、域名验证
MX邮件交换器(含优先级)该域名的邮件去向
NSzone 的域名服务器委派本身
SOAzone 的权威记录serial、refresh、否定 TTL
CAA哪些 CA 可以签证书允许 letsencrypt.org

其中两个的影响远超其体量。SOA 携带否定 TTL(minimum 字段)——即解析器可以把“这个名字不存在”缓存多久——这就是为什么一个 NXDOMAIN 会莫名其妙地赖着不走。CAA 则可能悄悄拦住证书续期:如果你已经不再使用的某个 CA 是列表里唯一被允许的,续期就会失败。

CNAME 的规则:一跳可以,链条不行

CNAME 的意思是“这个名字是别名,去问那个名字”。有两条规则会反复绊倒人:

  • **CNAME 不能与其他任何数据共存。**一个拥有 CNAME 的名字不能再有 A、TXT 或 MX。CNAME 会取代一切。
  • 只有被别名的那一侧生效,不是整个 zone。www.example.com CNAME example.com 并不会让 mail.example.com 跟着走。

很长的 CNAME 链在技术上能工作,但会带来延迟(每一跳都是一次新查询),也多了几个可能断掉的地方。能压到一跳就压到一跳。

裸域 CNAME、glue,以及 ALIAS/ANAME 为何存在

一个 zone 的裸域(apex)(也就是 example.com 本身)必须携带 SOA 和 NS 记录,而任何数据都不能与 CNAME 共存。所以你无法在裸域放一条普通的 CNAME——example.com CNAME example.com 根本就是非法的 DNS。这正是 ALIAS 与 ANAME 要解决的问题:它是一种非标准记录,由权威服务器在内部解析,然后当作 A/AAAA 作答,于是裸域可以指向一个负载均衡器而不需要 CNAME。它之所以能工作,仅仅是因为该厂商的服务器做了特殊处理;客户端永远看不到 ALIAS。

Glue 则是反方向的同类技巧。当顶级域把 example.com 委派给 ns1.example.com 时,解析 ns1.example.com 却需要先找到 example.com——形成循环依赖。于是顶级域的 zone 里会为同 zone 内的域名服务器保留 glue A/AAAA 记录,打破这个环。错误的 glue(域名服务器指向了一个过期 IP)是让整个域名变得不可达的经典原因。

权威 vs 递归:两份不同的工作

把这两个角色理清楚,一半的 DNS 困惑就消散了。

  • 递归解析器为任意名字作答、做缓存,并愿意去走整棵树。它可能错,而且是“缓存式的错”。
  • 权威服务器只为自己持有的 zone 作答,从不缓存别人的数据,是事实的唯一来源。它要是错了,这个域名在全世界就都错了。

当有人说“DNS 服务器挂了”,先问是哪一种。如果递归挂了,只有你的客户端受影响。如果权威挂了,等缓存过期后,整个互联网都会丢掉这个 zone。

用 dig、kdig 和 dig +trace 排障

dig 是你应该长期住进去的工具。要点如下:

# 默认:通过你配置的解析器查 A 记录,带完整头部
dig example.com

# 只要答案,适合写脚本
dig +short example.com

# 指定某台服务器,绕过你的配置
dig @1.1.1.1 example.com A
dig @ns1.example.com example.com SOA

# 自己从根开始沿着委派走一遍
dig +trace example.com

# 查询 DNSSEC 记录并查看校验结果
dig +dnssec example.com

几个省时间的习惯:

  • dig 不使用 /etc/hosts,也不走 NSS 栈——它直接查 DNS。如果 dig 正常但你的应用失败,问题在 nsswitch.conf 或卡住的本地存根,而不在上游解析器。
  • 读 status: 那一行。NOERROR 但 answer 段为空,说明名字存在但没有该类型的记录;NXDOMAIN 说明名字根本不存在。这是两种不同的 bug。
  • +trace 能证明链条断在哪一跳:根答了、顶级域答了,但权威服务器沉默 → 问题出在委派或那台服务器上。

kdig(来自 Knot DNS)在同样的语法之上加了加密传输:

# DNS over TLS
kdig +tls @1.1.1.1 example.com

# DNS over HTTPS
kdig +https @https://cloudflare-dns.com/dns-query example.com

DoH 与 DoT:加密的 DNS

经典 DNS 是明文 UDP 53 端口,这意味着链路上任何人都能读取并改写你的查询。DoT 把 DNS 包进 853 端口的 TLS;DoH 把它包进 443 端口的 HTTPS,使其与普通网页流量无从区分。DoH 能在 53 端口被封的地方穿行——包括强制门户(captive portal)在你登录前搭起的那个围墙花园,这正是为什么某些门户在启用了 DoH 的浏览器里能通、在终端里却失败。

但要留个心眼:加密保护的是链路,不是解析器。你只是把信任从你的网络转移到了 DoH 提供商身上,而后者从此看得到你的全部查询。

你真正会撞上的故障模式

  • **“生效传播”。**它并不存在。这里没有推送。每个解析器都各自持有一份拷贝,直到它的 TTL 过期。两个用户看到不同答案,是因为他们的解析器在不同时间缓存了结果。解决办法是规划 TTL,而不是干等。
  • **TTL 的意外。**解析器可能把你的记录钉得比你预期久得多,或者 CDN 可能用一个很短的 TTL 强制重新解析。永远检查你实际收到的 TTL,而不是你设置的那个。
  • **Split-horizon DNS(分域解析)。**同一个名字在网络内外的答案不同——内部客户端拿到私有地址,外部客户端拿到公网地址。排查“在我这儿能通”时,通常意味着你查的是内部视图。dig @8.8.8.8 看到的是全世界;dig 看到的是你的局域网。
  • **DNSSEC 校验失败。**一个会做校验的解析器(dig +dnssec 报 SERVFAIL)会拒绝一个签名过期、密钥在父级缺失 DS、或者父级声明应签名而响应却未签名的 zone。签名链必须从根一路完整——你忘了发布的那个轮换密钥,会让该域名对所有校验型解析器全部变黑,哪怕记录本身看起来完全正常。
  • **非对称可达性。**如果你的递归解析器本身处在 NAT 或状态防火墙之后,某些上游服务器可能不可达,查询会悄悄卡住。你在处理入站连接时跟同样的 NAT 穿透问题较劲,在这里则会表现为解析器超时。

一段话总结

存根解析器把一个问题转给递归解析器,后者以迭代方式走完整棵树——根、顶级域、权威——把每段答案按其 TTL 缓存,再返回地址。权威那一侧拥有事实,递归那一侧拥有缓存,而缓存正是“什么都不传播”的原因。一旦你懂了 CNAME 的共存规则、以及裸域为何特殊,记录本身其实很简单。出问题时,dig、dig +trace 和 kdig +tls/+https 会准确地告诉你哪一跳不再作答——以及问题到底是缓存、委派,还是 DNSSEC 签名链。