bigywater.tech
网络协议 · 67

递归与迭代:一次域名解析要问几个服务器

解析一个域名,其实是沿着 根 → 顶级域 → 权威服务器 这条链一路问下去。这篇自己扮演递归解析器,不借助任何缓存,从根服务器开始一问一问地迭代,看每一层给的是什么,再和直接问 1.1.1.1 对比。过程中我意外发现,这台机器所在网络里有些域名的应答被抢先伪造了。

3 问 / 783 ms
迭代解析 example.com:根、com.、权威各一问
7 ms ~ 280 ms
13 个根服务器(任播)里 j 与 e 的往返时间
+6 ms
问 a 根 www.bbc.co.uk 时伪造应答到达的时刻(正常往返约 242 ms)

迭代解析

实测 · 本机执行输出
    第 1 问:a.root-servers.net          242.6 ms  转介到 com.:授权区 13 个 NS,附加区带 6 个 IPv4 胶水地址
    第 2 问:l.gtld-servers.net.         251.6 ms  转介到 example.com.:授权区 2 个 NS,附加区带 6 个 IPv4 胶水地址
    第 3 问:hera.ns.cloudflare.com.     287.5 ms  回答:1 条 A(AA)
    共 3 问,总耗时 783 ms;最终 ['172.66.147.243', '104.20.23.154']
  每一问只回答“我知道的最近一层”:根服务器不知道 example.com,但知道谁管 com.;com. 的服务器不知道地址,但知道谁管 example.com;最后一问才是权威服务器的 AA 回答

根给 com. 的转介,com. 的服务器给 example.com 的转介,权威服务器给答案。每一问都是 RD=0。

转介与胶水

实测 · 本机执行输出
  RCODE NOERROR;标志 QR TC;问题 1,回答 0,授权 13,附加 11 条;489 字节
  授权区(前 3 条):com. NS l.gtld-servers.net.;com. NS j.gtld-servers.net.;com. NS h.gtld-servers.net. …共 13 条
  附加区(胶水记录):A 6 条、AAAA 5 条——有了它们才能不绕回头去查这些 NS 名字自己的地址
  标志里没有 AA(根服务器对 example.com 不权威),也没有 RA(根服务器不做递归);回答区是空的

转介报文的授权区放 NS,附加区放这些 NS 的地址(胶水)。不用 EDNS0 时 UDP 响应上限 512 字节,会被截断(TC)。

问几次

实测 · 本机执行输出
  example.com:
    第 1 问:a.root-servers.net          234.3 ms  转介到 com.:授权区 13 个 NS,附加区带 6 个 IPv4 胶水地址
    第 2 问:l.gtld-servers.net.         232.7 ms  转介到 example.com.:授权区 2 个 NS,附加区带 6 个 IPv4 胶水地址
    第 3 问:hera.ns.cloudflare.com.     289.4 ms  回答:1 条 A(AA)
    共 3 问,总耗时 757 ms;最终 ['172.66.147.243', '104.20.23.154']
  iana.org:
    第 1 问:a.root-servers.net          303.0 ms  转介到 org.:授权区 6 个 NS,附加区带 6 个 IPv4 胶水地址
    第 2 问:a2.org.afilias-nst.info.    210.6 ms  转介到 iana.org.:授权区 4 个 NS,附加区带 1 个 IPv4 胶水地址
    第 3 问:a.iana-servers.net.         193.9 ms  回答:1 条 A(AA)
    共 3 问,总耗时 708 ms;最终 ['192.0.43.8']
  www.bbc.co.uk:
    第 1 问:a.root-servers.net            7.6 ms  回答:1 条 A(AA)
    共 1 问,总耗时 8 ms;最终 ['199.59.148.206']
  www.github.com:
    第 1 问:a.root-servers.net          220.9 ms  转介到 com.:授权区 13 个 NS,附加区带 6 个 IPv4 胶水地址
    第 2 问:l.gtld-servers.net.         268.4 ms  转介到 github.com.:授权区 8 个 NS,附加区带 1 个 IPv4 胶水地址
    第 3 问:ns-520.awsdns-01.net.       178.2 ms  回答:1 条 A,1 条 CNAME(AA)
    共 3 问,总耗时 668 ms;最终 ['20.205.243.166']
  python.org:
    第 1 问:a.root-servers.net          220.2 ms  转介到 org.:授权区 6 个 NS,附加区带 6 个 IPv4 胶水地址
    第 2 问:a2.org.afilias-nst.info.    208.7 ms  转介到 python.org.:授权区 4 个 NS,附加区带 1 个 IPv4 胶水地址
    第 3 问:ns-484.awsdns-60.com.       191.0 ms  回答:1 条 A(AA)
    共 3 问,总耗时 620 ms;最终 ['151.101.192.223', '151.101.64.223', '151.101.0.223', '151.101.128.223']
  汇总:example.com 3 问 757 ms;iana.org 3 问 708 ms;www.bbc.co.uk 1 问 8 ms;www.github.com 3 问 668 ms;python.org 3 问 620 ms

常见名字 3 问;最后一问的权威服务器可能顺带把 CNAME 指向的 A 一起带回。

递归的好处

实测 · 本机执行输出
  example.com:5 次往返(ms):[286, 284, 235, 294, 284];中位 284 ms
  www.bbc.co.uk:5 次往返(ms):[36, 42, 38, 48, 44];中位 42 ms
  www.github.com:5 次往返(ms):[286, 292, 301, 306, 230];中位 292 ms
  对比自己迭代:example.com 757 ms,www.bbc.co.uk 8 ms,www.github.com 668 ms(本机到各服务器的往返都是公网,没有缓存)

交给 1.1.1.1 只发一个查询,耗时就是到它的往返。

任播根服务器

实测 · 本机执行输出
  a 252;b 227;c 171;d 240;e 280;f 7;g 248;h 111;i 122;j 7;l 8;m 126(单位 ms,各问 3 次取中位)
  根服务器名字共 13 个;最快 j 7 ms,最慢 e 280 ms

13 个名字背后是很多台机器,本机到各个根的往返差了近 40 倍。

被伪造的应答

实测 · 本机执行输出
  向 192.0.2.1 问 example.com:0 个响应
  向 192.0.2.1 问 www.bbc.co.uk:0 个响应
  向 192.0.2.1 问 www.facebook.com:0 个响应
  向 198.41.0.4 问 www.bbc.co.uk:2 个响应;+6 ms 来自 198.41.0.4,47 字节,答案 ['157.240.11.40'],TTL [122],标志 QR RD RA;+6 ms 来自 198.41.0.4,47 字节,答案 ['199.59.148.209'],TTL [75],标志 QR RD RA
  192.0.2.1 是预留给文档的地址,不会有任何真实的 DNS 服务;如果它“回答”了,说明响应是路径上的某个设备伪造的。结论只限于“这台机器所在的网络路径”,不涉及别的网络

向根服务器问 www.bbc.co.uk,6 ms 内收到两个互相矛盾、带 RA 的答案——不是根能给出的。明文 DNS 无法分辨真假。

分步演示

递归与迭代
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。向根服务器、com. / org. 的服务器和若干权威服务器发了几十个普通 DNS 查询(RD=0);DNS 伪造的结论只限于这台机器所在的网络路径,没有查来源和原因;往返时间随网络状况每次不同
    • 没有验证:IPv6 传输的迭代;DNSSEC 验证(下面有专门一篇);根服务器 k 为什么没有响应;其它域名是否也被伪造;1.1.1.1 的缓存策略(只是看到往返接近网络往返);循环 CNAME、深层委派、无胶水委派(NS 在自己的区内外)的处理;迭代时的 NS 选择与重试策略(我只取了第一个 NS)
    京ICP备2026067502号-1京公网安备11010502063487号