DNS 协议基础

DNS 到底是个啥?一时间似乎没办法答上来,不是不知道,而是因为「DNS」这三个字母背后藏的东西太多了。

它可以一句话讲完:DNS(域名系统,Domain Name System) 是互联网的电话簿,负责把人类好记的域名翻译成机器认识的 IP 地址。 但真把它掰开揉碎,你会看到一套分布式数据库的经典设计、一段被 512 字节逼出来的精巧编码、一个与互联网同龄的故事,还有那个流传了二十多年、至今仍被追问的问题:根服务器为什么偏偏只有 13 台?

一、DNS 是什么:从 HOSTS.TXT 到分布式电话簿

电话簿这个比喻大家都熟:你不需要记住 142.250.66.110 这种数字,只需要记住 google.com;反过来,机器只认 IP,对域名毫无概念。DNS 就夹在中间,干翻译的活儿。

但「电话簿」其实低估了 DNS。它不止查 IP——邮件该投给哪台服务器(MX)、这个域名是谁的别名(CNAME)、服务器上跑着什么服务(SRV),甚至「这域名归我所有」的证明(TXT),全都塞在 DNS 里。严格地说,DNS 是一个分布式、分层的键值数据库:键是域名,值是一堆类型各异的记录。翻译 IP 只是它最出名的一项业务。

为什么非得分布式?这得回到互联网的前身 ARPANET。那个年代,主机名与地址的映射靠一份手工维护的 HOSTS.TXT:由 SRI 国际统一编撰,新主机上线要发邮件申请登记,各节点定期下载同步。几百台主机的时候相安无事,等主机数量开始爆炸,文件越滚越大、同步越来越滞后、名字冲突越来越多——集中式方案被自己的成功压垮了。

1983 年,Paul Mockapetris 发表 RFC 882/883,提出了 DNS 的原始设计;1987 年,RFC 1034/1035 把规范定型,这份标准一直沿用至今。从设计第一天起,它就奔着四个目标去:

  • 高可用:全天候运行,全球任何角落都查得到
  • 可扩展:撑得住数亿域名、百亿台设备
  • 分散管理:每一层各管各的,谁也不用背全库
  • 容错:任何单点故障都不至于让整个系统瘫痪

40 多年过去,DNS 成了互联网上使用最频繁的协议之一。光 .com 一个顶级域名下注册的域名就超过一亿个,全球每天发生万亿次量级的解析请求。而它的报文头部,至今仍能被 1983 年的实现读懂——这种克制,后文讲报文格式的时候你还会再感叹一次。

至于 DNS 现在长什么样:绝大多数查询仍走 UDP 53 明文,但 EDNS0、DNSSEC、DoT/DoH/DoQ 这些补丁和外套已经一件一件套上了身。别急,我们按顺序来。

二、域名空间:从最后那个点说起

问你个问题:www.google.com 的完整写法是什么?

答案是 www.google.com.——注意结尾那个点。它不是笔误,那是根域,整个域名层级树的树根。你在浏览器里从来不敲它,但协议的世界里它一直都在。

域名的层级要从右往左读:根 . → 顶级域名 com → 二级域名 google → 子域 www。每一层都「分治」给不同的机构管理,这正是 DNS 能扩展到全球规模的秘诀:

根域 .netcomcno2x.netgoogle.combaidu.comwww.o2x.netblog.o2x.net

拿我自己的域名 o2x.net 举例:net 是顶级域名,o2x 是我从注册商手里租来的二级域名,www、blog 是我随手起的子域——子域怎么切分由我自己说了算,不用再向谁报备。这就是分治的爽点:你只管你自己的那一亩三分地。

顶级域名大致分这么几类:

类型说明例子
通用顶级域名(gTLD)不限地区,谁都能注册.com、.net、.org
国家顶级域名(ccTLD)按国家/地区分配.cn、.us、.jp
新通用顶级域名2012 年后批量开放.xyz、.top、.io
赞助类/受限类仅限特定组织.gov、.edu、.mil

那「域名是找谁买的」这件事,背后是条什么链子?理清这条链,你就明白用户、域名注册商、注册局、根服务器运营方各自扮演什么角色了:

NS 委派ICANN/IANA根区文件注册局Verisign 运营 .net注册商Cloudflare / 阿里云注册人你:o2x.net你的权威 DNS
  • ICANN/IANA:站在最顶端,维护根区文件——13 台根服务器名单、每个顶级域名委派给谁,都写在里面
  • 注册局(registry):运营某个顶级域名,.net 由 Verisign 运营,.cn 由 CNNIC 运营
  • 注册商(registrar):面向你收钱、替你跑腿提交申请,比如阿里云、Cloudflare、Namecheap
  • 你(注册人):租到域名使用权,并指定「我这个域名由哪台权威 DNS 说了算」

流程是这样:你在注册商买了 o2x.net,注册商向 Verisign 提交申请,Verisign 就在 .net 的区域文件里写下两条 NS 记录,指向你指定的权威 DNS(比如 Cloudflare 的 ns1.cloudflare.com)。从此以后,全世界的递归解析器想知道 o2x.net 的任何记录,都会顺着这条链一路找到你的权威服务器。

这个机制叫委派(delegation),是整个 DNS 的命门:每一级只知道自己下一级谁说了算,剩下的统统甩给下一级。听起来像踢皮球,其实是分布式系统最优雅的设计——没有谁需要掌握全局,系统却能给出全局一致的答案。

三、四个角色:递归解析器与权威服务器

Cloudflare 的学习中心把 DNS 服务器分成四类,这个分类干净利落,我直接借用:

  • 递归解析器(recursive resolver):你电脑上配置的那个 DNS 地址,可能来自路由器、运营商,也可能是公共 DNS。它干最苦最累的活:替客户端把问题层层追问到底,还要缓存结果、应付海量并发
  • 根服务器(root server):只回答一件事——「.com 的服务器在哪?」13 个名字,Anycast 布点到全球
  • TLD 服务器:管某个顶级域名的委派信息,比如「google.com 的权威服务器是谁」
  • 权威服务器(authoritative server):持有你域名的最终记录。它回答时会置上 AA 标志位,翻译过来就是「这答案我说了算」

日常能见到的公共递归解析器不少,扒拉几个常用的:

提供方地址特点
Cloudflare1.1.1.1响应快,主打隐私
Google8.8.8.8老牌,全球覆盖
阿里223.5.5.5国内延迟低
腾讯 DNSPod119.29.29.29国内延迟低
114DNS114.114.114.114老牌免费

DNS 查询本身也分三种,这是新手最容易绕晕的地方:

  • 递归查询:客户端对解析器说「这事你必须帮我查到底,别给我指路」。请求里 RD 位置 1
  • 迭代查询:服务器说「我不知道,但你可以去问它」,给你一个下一步的地址
  • 非递归查询:解析器缓存里正好有,直接回答,谁都不用麻烦

现实中的分工是:客户端对递归解析器发递归查询,递归解析器对上游的根、TLD、权威走迭代。根服务器几乎从不做递归——它们的职责单一得可怜,也正因如此,才能扛住全球的查询洪流。

顺带埋个伏笔:递归解析器千万别开放在公网。它会被坏人当成 DDoS 反射放大器,一打一个准。细节第八节再算账。

四、一次查询的完整旅程

铺垫够了,现在把一次解析从头到尾走一遍。还是 www.google.com:

  1. 你在浏览器敲下域名,按下回车
  2. 浏览器先翻自己的缓存,没命中,就问你操作系统配置的递归解析器
  3. 递归解析器翻自己的缓存,也没命中——好戏开场。它向一台根服务器发迭代查询:「谁管 .com?」
  4. 根服务器回:「.com 的 TLD 服务器是这几个 IP」
  5. 递归解析器问 TLD 服务器:「谁管 google.com?」
  6. TLD 服务器回:「google.com 的权威服务器是 ns1.google.com 那几个」
  7. 递归解析器问权威服务器:「www.google.com 的 A 记录是啥?」
  8. 权威服务器回 IP,递归解析器把它塞进缓存,再转发给你
  9. 浏览器拿到 IP,向它发起 HTTP 请求——你看到了页面
权威服务器.com TLD 服务器根服务器递归解析器你(浏览器)权威服务器.com TLD 服务器根服务器递归解析器你(浏览器)www.google.com 的 A 记录?(递归)谁管 .com?(迭代).com 的 NS 与 IP谁管 google.com?(迭代)google.com 的 NS 与 IPwww.google.com 的 A 记录?(迭代)142.250.x.x(AA=1)142.250.x.x,缓存 300 秒

口说无凭,用 dig 把这个过程实测一遍。下面是对 time.geekbang.org 做 dig +trace +nodnssec 的真实输出(有删减),我分段解释:

dig-trace.txt
txt
# 第一跳:问本地解析器(192.168.0.1),拿到 13 台根服务器的 NS 列表
. 342811 IN NS a.root-servers.net.
. 342811 IN NS m.root-servers.net.
;; Received 811 bytes from 192.168.0.1#53(192.168.0.1) in 216 ms

# 第二跳:从名单里挑 a.root-servers.net,问「谁管 org.?」
org. 172800 IN NS d0.org.afilias-nst.org.
org. 172800 IN NS a0.org.afilias-nst.info.
;; Received 448 bytes from 198.41.0.4#53(a.root-servers.net) in 299 ms

# 第三跳:问 org. 的权威服务器,拿 geekbang.org 的 NS 列表
geekbang.org. 86400 IN NS dns9.hichina.com.
geekbang.org. 86400 IN NS dns10.hichina.com.
;; Received 96 bytes from 199.19.57.1#53(d0.org.afilias-nst.org) in 163 ms

# 第四跳:问 geekbang.org 的权威(阿里云万网),拿到最终 A 记录
time.geekbang.org. 600 IN A 39.106.233.176
;; Received 62 bytes from 106.11.141.115#53(dns9.hichina.com) in 4 ms

值得盯着看的地方有三个:

  • 答案越来越小、越来越近:811 字节 → 448 → 96 → 62 字节;从根服务器到权威服务器,逐级收敛
  • TTL 天差地别:根 NS 列表的 TTL 是 342811 秒(约 4 天),geekbang.org 的 NS 是 86400 秒(1 天),而最终的 A 记录只有 600 秒(10 分钟)——越容易变的信息,TTL 越短
  • 没有缓存的世界不可想象:第一跳就已经由本地解析器代答了根服务器列表。如果每台设备每次查询都真去根服务器走一趟,13 组根早就被全球流量淹没了

缓存是 DNS 性能的命根子,它分布在三个层次:

  1. 浏览器缓存:Chrome 自带的 DNS 缓存,量级一分钟
  2. 系统解析器缓存:getaddrinfo 背后的缓存,有些发行版还跑着 systemd-resolved、nscd 之类的前台
  3. 递归解析器缓存:缓存的主力,一切从简、按 TTL 办事

题外话:连「查无此域」的否定答案也要缓存——这叫负缓存(RFC 2308)。不然一条不存在的域名被反复查询,就能把权威服务器打到怀疑人生。

现在你明白开头那个「改了记录怎么不生效」之谜了吧?你改了权威服务器上的 A 记录,但各级缓存手里的旧答案要等 TTL 自然过期才作废。有的运营商还会自作主张,把 TTL 无视掉、缓存得更久——这时候你除了等,只能一边刷新一边骂。

一个实操技巧送给你:改记录前一天,先把 TTL 调短(比如 300 秒),让旧答案尽快过期;改完确认无误,再把 TTL 调回正常值。多少运维老鸟都是被「没提前调 TTL」坑过一遍才记住这条的,包括我。

调试工具三件套

讲到这顺便安利几个排查工具。dig 是功能最全的那个,+trace 能一路展示迭代过程(上面你已经见识了),+short 只输出结果,写脚本时常用;nslookup 是各大系统自带的元老,交互式模式对新手友好;host 更轻量,查一条记录只要 host -t A o2x.net。另外 whois 虽然不走 DNS 协议,但查域名注册信息时总跟它打交道,一并记下。真出问题时,我的排查顺序基本是:dig +trace 看卡在哪一跳,再 whois 看注册状态,最后才是怀疑缓存。

五、根服务器:为什么恰好是 13 台?

「DNS 根服务器只有 13 台,中国一台都没有」——这句话流传了二十多年,前半句对,后半句值得掰扯。先把 13 台的家底亮出来:

主机名IPv4 地址运营机构所属
a.root-servers.net198.41.0.4Verisign美国
b.root-servers.net199.9.14.201USC-ISI美国
c.root-servers.net192.33.4.12Cogent Communications美国
d.root-servers.net199.7.91.13马里兰大学美国
e.root-servers.net192.203.230.10NASA美国
f.root-servers.net192.5.5.241ISC美国
g.root-servers.net192.112.36.4美国国防部 NIC美国
h.root-servers.net198.97.190.53美国陆军研究实验室美国
i.root-servers.net192.36.148.17Netnod瑞典
j.root-servers.net192.58.128.30Verisign美国
k.root-servers.net193.0.14.129RIPE NCC荷兰
l.root-servers.net199.7.83.42ICANN国际
m.root-servers.net202.12.27.33WIDE Project日本

先追本溯源,看看这 13 台是怎么来的:

  • 1984 年,Jon Postel 和 Paul Mockapetris 在南加州大学 ISI 架起第一台根服务器,当时的用途朴素得很:开发和测试 DNS 软件
  • 1985 年扩展到 4 台,SRI 国际、ISI、美军的弹道研究实验室各司其职,跑的都是初代软件 JEEVES 和初代 BIND
  • 1987 年 7 台:NSFNET 兴起,马里兰大学、NASA 埃姆斯研究中心、伦斯勒理工相继入役
  • 1991 年 7 月 28 日,瑞典 KTH/NORDUnet 上线 NIC.NORDU.NET——第一台美国境外的根服务器。同年中国在干嘛?连 TCP/IP 网络都还没有,清华校园网 TUNET 要到 1992 年底才建成
  • 1993~1994 年 9 台:InterNIC 时代,NS.INTERNIC.NET 与 NS.ISC.ORG 加入
  • 1995 年,9 台统一更名为 a~i.root-servers.net
  • 1997 年 1 月,J/K/L/M 四台补位,13 台封顶,从此再没变过

那么问题来了:为什么偏偏是 13?14 不行吗?

答案藏在一个不起眼的数字里:512 字节。

DNS 主要跑在 UDP 上,而早年的 IP 网络最小 MTU 只有 576 字节,减去 IP 头 20 字节、UDP 头 8 字节,剩下 548;RFC 1035 拍板:UDP 报文上限取 512 字节,留出安全余量。

这跟根服务器数量有什么关系?关系大了。递归解析器总得知道根服务器在哪吧?总不能把 13 台地址硬编码进每个客户端——于是有了 Priming 查询:解析器软件自带一份 Root Hints 文件(里面是根服务器的初始名单),启动时从里面挑一台,问它「最新的根服务器名单给我来一份」。这份响应本身也是 DNS 报文,同样被 512 字节的紧箍咒罩着。

根服务器越多,这份名单越长。到 1995 年 9 台根的时候,响应已经快撑破 512 字节了。社区想了两个办法。其一是标签压缩(后面讲报文格式时细说):域名第二次出现时,用两个字节的指针指回第一次出现的偏移。a.root-servers.net 和 b.root-servers.net 共享后缀,后者只需要存 b 加一个指针,20 字节压缩到 5 字节。

其二是改名。压缩的前提是域名有共同后缀,可当时 9 台根服务器的域名是 NS.INTERNIC.NET、NS1.ISI.EDU、TERP.UMD.EDU 这种八竿子打不着的名字——后缀各不相同,压缩无从下手。于是 Bill Manning 和 Paul Vixie 设计了更名方案,Jon Postel 拍板,1995 年 9 台根统一改成 a~i.root-servers.net,这才给压缩腾出了空间。

来算一笔账。Priming 查询的响应里有什么:Header 固定 12 字节;13 条 NS 记录——第一条存完整域名 31 字节,其余 12 条压缩后各 15 字节,共 180 字节;13 个 IP 地址(A 记录)各 16 字节,共 208 字节;查询段里 QTYPE 加 QCLASS 4 字节、根域的 QNAME 1 字节。加起来 12 + 31 + 180 + 208 + 4 + 1 = 436 字节,512 的额度还剩 76 字节。每加一台根服务器需要 15 + 16 = 31 字节,理论上还能塞两台,也就是 15 台上限。

可惜当年根服务器还兼职干着别的事:它们同时是 COM/NET/ORG 的解析服务器。客户端可以直接向根服务器查询某个 .com 域名,响应里就得原样带回查询域名,而一个标签最长 64 字节(63 字符加 1 个长度字节)。76 减 64,只剩 12 字节——连一台根服务器的 31 字节都塞不下了。

13 台,是被 512 字节精确逼出来的数字。 这不是什么神秘的设计决定,就是一条 1987 年的协议限制和 1995 年的一次算术,碰出来的结果。

那后来呢?EDNS0 出来之后,512 字节的限制早就形同虚设,理论上可以加第 14 台了。但这时候 Anycast(任播) 登场了:2002 年,J 根率先部署 Anycast——同一个 IP 地址在全球几十个机房同时广播,BGP 路由协议自动把你带到物理距离最近的那台。从此「加一台根服务器」失去了意义:13 个名字只是 13 组 IP,背后是 12 家运营机构在 2000 多个站点部署的物理实例(root-servers.org 截至 2026 年 8 月的数据是 2004 台)。你今天 ping 一下 f.root-servers.net 的 192.5.5.241,国内延迟几十毫秒——不是那台「服务器」离你近,是它在北京、重庆、杭州的镜像在等着你。

补充:所以「根服务器只有 13 台」严格来说是个修辞。「13 个名字、13 组地址」才是准确的,物理机器早已遍布全球。

最后聊聊「中国没有根服务器」这件事。这个故事其实没有阴谋论里那么悲情,它是一连串错过的窗口:

  • 1991 年欧洲人拿第一台海外根服务器时,中国的互联网还约等于「远程登录收发邮件」:.CN 域名 1990 年就注册了,但解析服务寄存在德国卡尔斯鲁厄大学,直到 1994 年 5 月才迁回国内
  • 1994 年 4 月 20 日,NCFC 通过 Sprint 的 64 kbps 专线全功能接入互联网——中国互联网的元年,比 DNS 诞生整整晚了 11 年
  • 1997 年 J/K/L/M 四台分配,是根服务器最后一次扩编。K 花落英国 Linx(后移交给 RIPE NCC 运营),M 花落日本 WIDE。那一年的中国其实不差:上网用户 62 万、联网计算机 29.9 万台、CN 域名 4066 个——但相关国际会议没有中方代表,名额就这么分了
  • 1998 年 10 月 16 日,Jon Postel 因医疗事故去世。能拍板的人不在了,J 一路变成了 Verisign 的,L 移交给 ICANN,13 台的格局就此固化
  • 再往后,Anycast 普及,社区没有任何动机新增根服务器;而根服务器的运营要求「无差别服务、不过滤查询」,与国内对 DNS 流量的审查现实天然冲突

所以我的看法是:与其说「被卡脖子」,不如说「窗口期错过了,技术路线也换道了」。而且平心而论,「断供」的风险被严重夸大了——根区数据由 IANA 维护、12 家机构独立运营,没有哪一家能单方面改动根区;境内北京、上海、广州等地本来就有 F、I、J、K、L 等根的镜像节点,加上各级缓存的缓冲,即使根服务器集体消失(这几乎不可能发生),现有网络的解析也能撑上一段时间。真正值得警惕的从来不是「断开」,而是根区数据被「篡改」——而后者要靠 DNSSEC 和多方治理,而不是靠把根服务器搬回家。

六、DNS 报文:五段式的信封

好,终于到硬核环节了。DNS 请求和响应共用同一种报文结构,分五段:

dns-message.txt
txt
+---------------------+
| Header              | 头部,固定 12 字节
+---------------------+
| Question            | 问题段:查什么
+---------------------+
| Answer              | 答案段:查到的结果
+---------------------+
| Authority           | 授权段:指向权威服务器的线索
+---------------------+
| Additional          | 附加段:补充信息(如 EDNS0)
+---------------------+

Header 只有 12 字节,信息密度极高:

ID(事务标识)015QR16Opcode1720AA21TC22RD23RA24Z(保留)2527RCODE2831QDCOUNT(问题记录数)3247ANCOUNT(答案记录数)4863NSCOUNT(授权记录数)6479ARCOUNT(附加记录数)8095

逐字段说明:

字段长度 (bit)说明
ID16事务标识,响应必须原样带回,用来匹配请求与应答
QR10 查询,1 响应
Opcode4操作码:0 标准查询、1 反向查询、2 服务器状态请求
AA1权威回答,置 1 表示「答案由该域的权威服务器给出」
TC1截断标志,UDP 应答超过上限时置 1,客户端应改用 TCP 重试
RD1期望递归,客户端置 1 表示「请帮我查到底」
RA1支持递归,服务器置 1 表示「我提供递归服务」
Z3保留位,必须为 0
RCODE4响应码:0 成功、2 服务器故障、3 名字不存在(NXDOMAIN)
QDCOUNT16问题段记录数
ANCOUNT16答案段记录数
NSCOUNT16授权段记录数
ARCOUNT16附加段记录数

问题段一条记录由三部分组成:待查询域名(QNAME)、查询类型(QTYPE)、类别(QCLASS,通常为 1,即 IN 互联网类)。

QNAME 的编码很有意思:域名按 . 切成一个个标签(label),每个标签用一个长度字节开头,最后以 0 结尾。www.google.com 在报文里长这样:

qname.txt
txt
长度字节           3  w  w  w
长度字节           6  g  o  o  g  l  e
长度字节           3  c  o  m
结束标志           0

对应的十六进制就是 03 77 77 77 06 67 6f 6f 67 6c 65 03 63 6f 6d 00。标签最长 63 字节,整个域名最长 253 字符(网络编码 255 字节,含长度字节)。注意「长度前缀」这个设计意味着 63 的最大值二进制是 00111111,高两位恒为 00——这个细节马上要派上大用场。

应答段、授权段、附加段装的是一条条资源记录(RR),结构比问题记录多了三样:

字段说明
NAME域名,编码与 QNAME 相同
TYPE记录类型
CLASS类别,通常 IN
TTL生存时间,32 位秒数
RDLENGTH后续数据长度
RDATA实际数据,随类型而异

还记得上一节说的标签压缩吗?现在能讲透了:既然标签长度的高两位只能是 00,社区就约定,遇到高两位为 11 的「长度字节」时,它不是长度,而是指针——低 14 位表示从报文头部算起的偏移量,指向真正的域名。b.root-servers.net 于是可以编码成「b + 指向 a.root-servers.net 中 root-servers.net 的指针」,20 字节缩到 5 字节。一条报文里同一个域名出现得越多次,省得越多。

这个精妙的小技巧,也成了后来无数 DNS 解析器安全漏洞的温床——指针可以指向任何偏移,包括指针自己。解码时不设防,一条报文就能让解析器死循环。第九节细说。

历史注脚:512 字节的限制在今天已经被 EDNS0(RFC 6891) 解除。现代客户端在附加段塞一条 OPT 伪记录,通告自己能接受的最大 UDP 载荷(常见 1232 或 4096 字节)。真正超限时,服务器置 TC 位,客户端改用 TCP 重试。TCP 53 端口还承担另一项职责:主从权威服务器之间的区域传输(AXFR)。

然后是记录类型。DNS 的查询类型有几十种,日常打交道的是这些:

类型编号用途
A1域名 → IPv4 地址
AAAA28域名 → IPv6 地址
CNAME5别名,指向另一个域名
NS2该域名的权威服务器
MX15邮件服务器,附带优先级
TXT16自由文本
SOA6区域文件的「卷首」,管理员邮箱、序列号等
PTR12反向解析,IP → 域名
SRV33服务定位,_服务._协议.域名
CAA257限定哪些 CA 可以给该域签证书

挑几个有坑的展开说:

  • CNAME 的坑:RFC 规定,一个域名如果配了 CNAME,就不能再有其他类型的数据。所以 example.com 配了 CNAME,就配不了 MX——收不到邮件。很多域名服务商用「CNAME 扁平化」之类的技术绕过限制,但那是厂商的魔改,不是协议的原意。另外 CNAME 意味着多一轮查询:解析器拿到别名后要重新发起解析,链路又长一截
  • MX 的优先级:数字越小越优先。两台邮件服务器一个 10 一个 20,都活着时邮件进 10 那台;10 挂了邮件进 20。邮件不丢的秘诀就在这
  • TXT 的妙用:SPF 发信人验证、DKIM 公钥、域名所有权验证(Google Search Console、Let's Encrypt 的 DNS-01 挑战)都往这里塞。所谓「TXT 是 DNS 的万能口袋」,一点不夸张
  • SOA:一个区域的权威服务器必须有一条 SOA,里面 serial 序列号尤其重要——从服务器靠它判断主服务器有没有更新,该不该触发区域传输
  • PTR 与反向解析:把 IP 转回域名。查 1.2.3.4 时,实际是查 4.3.2.1.in-addr.arpa 的 PTR 记录。最典型的使用场景是邮件服务器反垃圾:对方收信前会查你发信 IP 有没有 PTR、PTR 指向的域名能不能再解析回这个 IP,对不上就大概率进垃圾箱

再贴一段真实的 dig 应答,对照着看结构:

dig-answer.txt
txt
$ dig time.geekbang.org A +noall +answer

time.geekbang.org. 600 IN A 39.106.233.176

翻译成人话:time.geekbang.org 的 A 记录是 39.106.233.176,TTL 600 秒(10 分钟内放心用),IN 表示互联网类。就这么一行,浓缩了 RR 的全部六个字段——NAME、TYPE、CLASS、TTL、RDLENGTH(被 dig 隐藏了)、RDATA。

七、记录的更多玩法:负载均衡、CDN 与 DDNS

DNS 记录写得花一点,能干的事情远超「域名指 IP」。挑三个最实用的说说。

轮询负载均衡

给同一个域名配多条 A 记录,权威服务器会轮流(或者随机)返回不同 IP,流量就这么粗糙地摊到了多台机器上:

round-robin.txt
txt
o2x.net. 300 IN A 1.2.3.4
o2x.net. 300 IN A 5.6.7.8

便宜、零依赖,是它的全部优点。缺点也明摆着:DNS 不感知服务器死活,一台挂了,解析照样把流量发过去;而且各级缓存会把结果固定住,轮询的粒度完全失控。它适合做「聊胜于无」的兜底,真正的流量调度还得看下一节。

智能解析与 CDN

更高级的玩法是按查询来源返回不同答案:北京的用户查同一个域名,得到北京的服务器 IP;广州的用户得到广州的。国内 DNS 服务商管这叫「线路解析」,学术点叫基于地理位置的分区解析(GeoDNS)。CDN 的「就近访问」就是这么实现的——你把 www 用 CNAME 指到 CDN 给你的接入域名,CDN 的权威服务器根据来源 IP 返回离你最近的边缘节点。这也是为什么同一个域名,你 dig 出来的 IP 一天能变好几次:不是它不稳定,是它在跟你捉迷藏。

动态 DNS(DDNS)

家庭宽带的公网 IP 是动态的,隔三差五就换。想在域名上稳定访问家里的 NAS,就得用 DDNS:路由器或者客户端检测到 IP 变化后,调用 DNS 服务商的 API 把 A 记录改成新 IP,配合短 TTL(几十秒到几分钟),实现「域名永远跟着 IP 走」。各家路由器和群晖之类的 NAS 都内置了这功能,国内用户用得尤其多。

内部网络与服务发现

DNS 不止服务公网。公司内网的私有域名、Kubernetes 里的 Service 名、微服务之间的寻址,全都靠内部 DNS。SRV 记录在这里大放异彩:_http._tcp.服务名 这种格式,把「服务在哪台机器的哪个端口」一次说清楚,Consul、etcd 这类服务发现组件背后站着的都是它。

八、安全与隐私:明文裸奔与加密 DNS 的救赎

聊到这儿,得泼盆冷水:传统 DNS 是明文的。你的查询跑在 UDP 53 上,一路裸奔,链路上的每一跳——咖啡店的 WiFi、运营商、任何中间设备——都能完整看到你查了哪些域名。

这比想象的更严重。网页正文有 HTTPS 加密,但域名查询是明的。几点查了银行、几点查了医院、几点查了招聘网站——查询日志拼出来的画像,比浏览历史还精准。

顺带数数明文时代的三大威胁:

  • DNS 劫持:链路中间伪造响应,把你导去钓鱼站,或者弹运营商的广告页
  • 缓存投毒:把伪造的答案塞进递归解析器的缓存(2008 年 Kaminsky 把这事做到了极致,下文会说它的连锁反应)
  • 反射放大攻击:攻击者伪造受害者 IP 发一个 60 字节的查询,服务器回一个几千字节的响应,公网上敞开的解析器全成了放大器

针对「伪造」,社区祭出了 DNSSEC(RFC 4033-4035):从根到叶子逐级签名,权威服务器的答案带着签名链,解析器可以验证「这确实是权威签过的」。它靠一整套专用记录干活:RRSIG 存签名、DNSKEY 存公钥、DS 在上一级做委派签名、NSEC/NSEC3 用来证明「某域名不存在」——简单说,就是给每条答案附上一张可验证的防伪签。它防篡改,但不防偷看——加密的事它管不了。而且说实话,DNSSEC 部署率低迷了很多年,国内 CDN 场景更是麻烦缠身。我自己的域名就没开 DNSSEC——它防的是「我的 DNS 服务商被人冒充」,而我信任我的服务商。等它哪天成了默认选项,我再来补票。这是取舍,不是摆烂。

针对「偷看」,加密 DNS 出场了,三兄弟排排坐:

  • DoT(DNS over TLS,RFC 7858):DNS 跑在 TLS 上,专用端口 853。优点是端口专物专用、生态成熟;缺点同样致命——防火墙一看 853 就知道你在加密 DNS,想封就封、一封一个准,典型的此地无银三百两
  • DoH(DNS over HTTPS,RFC 8484):把 DNS 塞进 443 端口的 HTTPS 里,和普通网页流量长得一模一样,按端口封锁的招数彻底失效。代价有二:查询流量从运营商手里转移到了 DoH 提供商手里(有人讥讽这叫「隐私大搬家」,从 ISP 搬到 Cloudflare,换个地方裸奔);二是 DoH 通常不传 EDNS Client Subnet,CDN 拿不到你的大致位置,调度精度会下降——Cloudflare 的 1.1.1.1 就明确不支持 ECS
  • DoQ(DNS over QUIC,RFC 9250):基于 QUIC,专用端口 853 走 UDP,0-RTT 建连、没有队头阻塞,被设计成 DoT 的下一代;同一份 RFC 还定义了 DoH3,即 HTTP/3 版的 DoH,走 443
协议端口传输伪装性一句话评价
DoT853TCP + TLS差,端口即目标出道最早,目标最大
DoH443HTTP/2 + TLS好,混入网页流量普及度最高,隐私争议也最大
DoQ853(UDP)QUIC一般下一代,性能最优
DoH3443(UDP)HTTP/3好DoH 的性能补完版

加密 DNS 早已不是极客玩具:Firefox 在美国默认开启 DoH,Chrome 会尝试自动升级到系统配置的 DoH,Windows 11 和 macOS 也都原生支持。我的选择:家里路由器上跑 dnsmasq + cloudflared,全家统一走 DoH,家人无感,链路劫持弹广告这类破事从此绝迹。自己搭 DoT 服务端也试过,证书、运维、监控一套下来,维护成本不值当,最终回到了公共 DoH。折腾和简单之间,我选简单——但简单不等于裸奔,这个底线得守住。

至于国内环境,加密 DNS 至少能挡住「运营商劫持弹广告」这类链路伪造;更深的话题不在这篇文章的射程内,就此打住。

九、如果要自己实现 DNS:踩坑清单

如果你哪天动了念头,想自己写一个 DNS 服务器——恭喜,你进入了一个「表面简单、暗坑无数」的领域。

说它表面简单:五段报文、一问一答,协议核心两天就能写通。说它暗坑无数:下面这些,每一个都够你修一周。

性能侧的坑:

  • 缓存是命根子。递归解析器的命中率做到 90% 以上是及格线;没有缓存的 DNS 服务器不如不写
  • TTL 是唯一的时间标尺,别自作聪明「优化」它。预取是个正经思路:TTL 将尽时后台提前刷新,用户永远等不到重新解析的那一拍(Unbound 的 serve-expired 加 prefetch 就是这个玩法)
  • UDP 优先、TCP 兜底。超过协商上限就截断转 TCP;EDNS0 通告 1232 字节已经是现代默认,别傻乎乎死守 512

安全侧的坑:

  • 事务 ID 和源端口都要随机。Kaminsky 攻击之后,只随机 ID 等于裸奔:16 位 ID 空间只有 65536 种可能,伪造者可以在缓存刷新窗口内暴力试完;ID 加源端口一起随机,空间涨到 40 亿量级,爆破成本翻了几个数量级
  • 0x20 编码:把查询域名的大小写随机化(WwW.ExAmPlE.cOm),DNS 协议大小写不敏感但必须原样回显,权威服务器回的大小写对不上就丢弃——给伪造响应再上一道锁
  • 查询名最小化(RFC 7816):向根只问 .com 的 NS,别把完整域名喂给每一级。每级服务器只该看到它需要看到的部分,这是隐私最小化的正解
  • RRL 与来源限制:响应速率限制,防止你被坏人当反射放大器;递归解析器必须限制来源网段

警告:把递归解析器开放在公网,等于把一台放大器拱手送给 DDoS 攻击者。

  • 负缓存:NXDOMAIN 也要缓存(RFC 2308),不然一条不存在的域名就能把你的上游权威打爆
  • 指针炸弹:第六节埋的雷。压缩指针可以指向报文任何偏移,包括指针自身。解码时必须限制指针跳转次数与深度,不然一条精心构造的报文就能让你的解析器死循环——过去二十年里,这几乎是最稳定的漏洞来源

正确性侧的坑:

  • CNAME 链成环要检测(a 指向 b,b 又指回 a)
  • 标签长度 ≤63、域名总长 ≤253、字符集白名单,三条都要在解析入口卡死
  • TC 位的处理、ANY 查询的降级、AA 位语义,别搞混

选型上给个实在的建议:别从零手写协议。Go 用 github.com/miekg/dns,Rust 用 hickory-dns,都是被捶打了十年以上的老库;只是想要功能的话直接上 CoreDNS,写个插件就能解决的问题,别去重写一台服务器。

真要是一腔热血想手写——协议两天能写通,后面两周都在修上面这些坑。这话我说的,欢迎打脸。

结语

回到开头那个问题:DNS 到底是个啥?

电话簿是它最贴切的比喻,但「电话簿」三个字装不下它。它是互联网分治思想的活标本,是被 512 字节逼出精巧编码的化石,是每天万亿次查询、却让你几乎感觉不到存在的基建——而「感觉不到」,恰恰是它设计得好的证明。只有它挂掉的那天,你才会发现整个世界突然安静了。

从 HOSTS.TXT 到 DNSSEC 与 DoH,DNS 这 40 多年的演化,浓缩起来还是一句老话:兼容性与效率之间的永恒博弈。它至今没被换掉,本身就是对分布式系统「简单、克制、够用」最好的注脚。

作者Aoang
发布于2025-12-26
更新于2025-12-26
许可协议CC BY-NC-SA 4.0
转载或引用本文时请遵守许可协议,并注明出处。