
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 能扩展到全球规模的秘诀:
拿我自己的域名 o2x.net 举例:net 是顶级域名,o2x 是我从注册商手里租来的二级域名,www、blog 是我随手起的子域——子域怎么切分由我自己说了算,不用再向谁报备。这就是分治的爽点:你只管你自己的那一亩三分地。
顶级域名大致分这么几类:
| 类型 | 说明 | 例子 |
|---|---|---|
| 通用顶级域名(gTLD) | 不限地区,谁都能注册 | .com、.net、.org |
| 国家顶级域名(ccTLD) | 按国家/地区分配 | .cn、.us、.jp |
| 新通用顶级域名 | 2012 年后批量开放 | .xyz、.top、.io |
| 赞助类/受限类 | 仅限特定组织 | .gov、.edu、.mil |
那「域名是找谁买的」这件事,背后是条什么链子?理清这条链,你就明白用户、域名注册商、注册局、根服务器运营方各自扮演什么角色了:
- 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标志位,翻译过来就是「这答案我说了算」
日常能见到的公共递归解析器不少,扒拉几个常用的:
| 提供方 | 地址 | 特点 |
|---|---|---|
| Cloudflare | 1.1.1.1 | 响应快,主打隐私 |
8.8.8.8 | 老牌,全球覆盖 | |
| 阿里 | 223.5.5.5 | 国内延迟低 |
| 腾讯 DNSPod | 119.29.29.29 | 国内延迟低 |
| 114DNS | 114.114.114.114 | 老牌免费 |
DNS 查询本身也分三种,这是新手最容易绕晕的地方:
- 递归查询:客户端对解析器说「这事你必须帮我查到底,别给我指路」。请求里
RD位置 1 - 迭代查询:服务器说「我不知道,但你可以去问它」,给你一个下一步的地址
- 非递归查询:解析器缓存里正好有,直接回答,谁都不用麻烦
现实中的分工是:客户端对递归解析器发递归查询,递归解析器对上游的根、TLD、权威走迭代。根服务器几乎从不做递归——它们的职责单一得可怜,也正因如此,才能扛住全球的查询洪流。
顺带埋个伏笔:递归解析器千万别开放在公网。它会被坏人当成 DDoS 反射放大器,一打一个准。细节第八节再算账。
四、一次查询的完整旅程
铺垫够了,现在把一次解析从头到尾走一遍。还是 www.google.com:
- 你在浏览器敲下域名,按下回车
- 浏览器先翻自己的缓存,没命中,就问你操作系统配置的递归解析器
- 递归解析器翻自己的缓存,也没命中——好戏开场。它向一台根服务器发迭代查询:「谁管
.com?」 - 根服务器回:「
.com的 TLD 服务器是这几个 IP」 - 递归解析器问 TLD 服务器:「谁管
google.com?」 - TLD 服务器回:「
google.com的权威服务器是ns1.google.com那几个」 - 递归解析器问权威服务器:「
www.google.com的A记录是啥?」 - 权威服务器回 IP,递归解析器把它塞进缓存,再转发给你
- 浏览器拿到 IP,向它发起 HTTP 请求——你看到了页面
口说无凭,用 dig 把这个过程实测一遍。下面是对 time.geekbang.org 做 dig +trace +nodnssec 的真实输出(有删减),我分段解释:
# 第一跳:问本地解析器(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 性能的命根子,它分布在三个层次:
- 浏览器缓存:Chrome 自带的 DNS 缓存,量级一分钟
- 系统解析器缓存:
getaddrinfo背后的缓存,有些发行版还跑着systemd-resolved、nscd之类的前台 - 递归解析器缓存:缓存的主力,一切从简、按 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.net | 198.41.0.4 | Verisign | 美国 |
b.root-servers.net | 199.9.14.201 | USC-ISI | 美国 |
c.root-servers.net | 192.33.4.12 | Cogent Communications | 美国 |
d.root-servers.net | 199.7.91.13 | 马里兰大学 | 美国 |
e.root-servers.net | 192.203.230.10 | NASA | 美国 |
f.root-servers.net | 192.5.5.241 | ISC | 美国 |
g.root-servers.net | 192.112.36.4 | 美国国防部 NIC | 美国 |
h.root-servers.net | 198.97.190.53 | 美国陆军研究实验室 | 美国 |
i.root-servers.net | 192.36.148.17 | Netnod | 瑞典 |
j.root-servers.net | 192.58.128.30 | Verisign | 美国 |
k.root-servers.net | 193.0.14.129 | RIPE NCC | 荷兰 |
l.root-servers.net | 199.7.83.42 | ICANN | 国际 |
m.root-servers.net | 202.12.27.33 | WIDE 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 请求和响应共用同一种报文结构,分五段:
+---------------------+
| Header | 头部,固定 12 字节
+---------------------+
| Question | 问题段:查什么
+---------------------+
| Answer | 答案段:查到的结果
+---------------------+
| Authority | 授权段:指向权威服务器的线索
+---------------------+
| Additional | 附加段:补充信息(如 EDNS0)
+---------------------+Header 只有 12 字节,信息密度极高:
逐字段说明:
| 字段 | 长度 (bit) | 说明 |
|---|---|---|
| ID | 16 | 事务标识,响应必须原样带回,用来匹配请求与应答 |
| QR | 1 | 0 查询,1 响应 |
| Opcode | 4 | 操作码:0 标准查询、1 反向查询、2 服务器状态请求 |
| AA | 1 | 权威回答,置 1 表示「答案由该域的权威服务器给出」 |
| TC | 1 | 截断标志,UDP 应答超过上限时置 1,客户端应改用 TCP 重试 |
| RD | 1 | 期望递归,客户端置 1 表示「请帮我查到底」 |
| RA | 1 | 支持递归,服务器置 1 表示「我提供递归服务」 |
| Z | 3 | 保留位,必须为 0 |
| RCODE | 4 | 响应码:0 成功、2 服务器故障、3 名字不存在(NXDOMAIN) |
| QDCOUNT | 16 | 问题段记录数 |
| ANCOUNT | 16 | 答案段记录数 |
| NSCOUNT | 16 | 授权段记录数 |
| ARCOUNT | 16 | 附加段记录数 |
问题段一条记录由三部分组成:待查询域名(QNAME)、查询类型(QTYPE)、类别(QCLASS,通常为 1,即 IN 互联网类)。
QNAME 的编码很有意思:域名按 . 切成一个个标签(label),每个标签用一个长度字节开头,最后以 0 结尾。www.google.com 在报文里长这样:
长度字节 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 的查询类型有几十种,日常打交道的是这些:
| 类型 | 编号 | 用途 |
|---|---|---|
| A | 1 | 域名 → IPv4 地址 |
| AAAA | 28 | 域名 → IPv6 地址 |
| CNAME | 5 | 别名,指向另一个域名 |
| NS | 2 | 该域名的权威服务器 |
| MX | 15 | 邮件服务器,附带优先级 |
| TXT | 16 | 自由文本 |
| SOA | 6 | 区域文件的「卷首」,管理员邮箱、序列号等 |
| PTR | 12 | 反向解析,IP → 域名 |
| SRV | 33 | 服务定位,_服务._协议.域名 |
| CAA | 257 | 限定哪些 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 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,流量就这么粗糙地摊到了多台机器上:
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
| 协议 | 端口 | 传输 | 伪装性 | 一句话评价 |
|---|---|---|---|---|
| DoT | 853 | TCP + TLS | 差,端口即目标 | 出道最早,目标最大 |
| DoH | 443 | HTTP/2 + TLS | 好,混入网页流量 | 普及度最高,隐私争议也最大 |
| DoQ | 853(UDP) | QUIC | 一般 | 下一代,性能最优 |
| DoH3 | 443(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 多年的演化,浓缩起来还是一句老话:兼容性与效率之间的永恒博弈。它至今没被换掉,本身就是对分布式系统「简单、克制、够用」最好的注脚。
