DNS 域名系统
DNS 域名系统
一次真实的 DNS 抓包
打开终端执行 dig +trace www.example.com,会看到完整解析过程:
; <<>> DiG 9.18.24 <<>> +trace www.example.com
;; global options: +cmd
. 518400 IN NS a.root-servers.net.
. 518400 IN NS b.root-servers.net.
...
a.root-servers.net. 518400 IN A 198.41.0.4
;; Received 811 bytes from 8.8.8.8#53
com. 172800 IN NS a.gtld-servers.net.
com. 172800 IN NS b.gtld-servers.net.
...
example.com. 172800 IN NS ns1.example.com.
ns1.example.com. 172800 IN A 93.184.216.34
;; Received 768 bytes from 192.33.14.30#53
www.example.com. 86400 IN A 93.184.216.34
;; Received 96 bytes from 93.184.216.34#534 段输出对应 4 次 DNS 查询(根、com TLD、example.com 权威、最终 A 记录)。整个过程可能 30-200ms 走完——用户每次访问网站背后,DNS 都在忙。
这一章用抓包和命令把 DNS 协议讲清楚:域名空间结构、查询过程、记录类型、缓存、安全。
问题一:为什么需要 DNS
IPv4 地址是 32 位数字(约 43 亿个),写成点分十进制是 192.168.1.1 这种形式。人类记忆有困难——你能记住 93.184.216.34 吗?记不住 www.example.com 容易得多。
DNS 1983 年由 Paul Mockapetris 设计(RFC 882/883),1987 年 RFC 1034/1035 修订成现代版本。核心目标:把人能记住的域名(example.com)翻译成机器能识别的 IP(93.184.216.34)。
DNS 设计目标不止"翻译":
- 分层命名空间:避免名字冲突。
example.com和example.org是两个不同的域 - 分布式数据库:没有一台服务器存所有域名,全世界 1000 多万个权威 DNS 服务器分担
- 冗余和缓存:每条记录在多个服务器上备份,查询结果可缓存加速
问题二:域名空间的层次
域名从右到左代表层次:
. ← 根域(root,通常省略)
├── com ← 顶级域(TLD,Top-Level Domain)
│ ├── example.com ← 二级域(SLD)
│ │ ├── www.example.com ← 子域(subdomain)
│ │ ├── mail.example.com
│ │ └── api.example.com
│ └── google.com
├── org
├── net
├── io
├── cn ← 国家 TLD
│ ├── com.cn
│ └── edu.cn
└── ...根域:13 组根服务器(A 到 M),用字母命名。物理上通过任播(anycast)部署到全球 1500+ 个站点(截至 2024 年)。中国大陆有 6 个根镜像(F、I、J、L 四个 + HK 的 2 个)。
顶级域(TLD):
- gTLD(通用 TLD):com, org, net, info, biz 等传统 TLD,2014 年后开放了数百个新 TLD(app, dev, blog, ...)
- ccTLD(国家 TLD):cn, uk, jp, de 等,每个国家一个
- arpa:特殊用途域,主要做反向 DNS(IP 反查域名)
二级域:在 TLD 下注册的实际域名。example.com 里的 example 是注册人拥有的,可以任意划分子域。
完全限定域名(FQDN):包含所有层级的域名。www.example.com. 最后那个点表示根域,通常省略。
问题三:DNS 服务器的四种角色
抓包 DNS 时会看到各种 server type,但概念上有四种:
| 角色 | 作用 | 例子 |
|---|---|---|
| 根域名服务器 | 知道所有 TLD 服务器位置 | a.root-servers.net (198.41.0.4) |
| TLD 服务器 | 知道该 TLD 下所有二级域的权威服务器 | a.gtld-servers.net (com TLD) |
| 权威域名服务器 | 真正知道域名到 IP 的映射 | ns1.example.com |
| 递归解析器(Local DNS) | 代用户做递归查询,结果缓存 | 8.8.8.8, 114.114.114.115 |
用户的本机不直接查根服务器。用户配置的 DNS(8.8.8.8 或 ISP 分配的)就是递归解析器,它代替用户去问根、问 TLD、问权威,最后把结果返回用户。
# 看本机配置的 DNS
cat /etc/resolv.conf
# 输出类似:nameserver 8.8.8.8
# 看本机 DNS 缓存(Linux 用 systemd-resolved)
resolvectl statistics问题四:递归查询 vs 迭代查询
递归查询(Recursive Query):客户端发一次查询给递归解析器,然后"等结果"。解析器负责全部查询工作,客户端不参与中间过程。
迭代查询(Iterative Query):客户端(其实是递归解析器)发查询给根服务器,根服务器返回"我不知道这个域名,但 .com 的服务器在 X.X.X.X"。客户端再发查询给 .com 服务器,依次类推。
完整的 dig +trace www.example.com 输出展示了完整迭代过程:
DNS 迭代查询完整流程:
图注:用户只发一次查询给递归解析器("递归"),DNS 服务器之间互相问叫"迭代"。递归解析器缓存结果(TTL 内不再重复查询)。
用户视角是递归("我只想问一次"),DNS 服务器之间是迭代("我告诉你下一步问谁")。
抓包看 DNS 流量:
# 抓 DNS 查询
tcpdump -ni eth0 -X 'udp port 53' -w /tmp/dns.pcap
# 用 tshark 看 DNS 报文结构
tshark -r /tmp/dns.pcap -T fields -e dns.qry.name -e dns.qry.type -e dns.flags.response -e dns.aDNS 默认用 UDP 53 端口(响应大于 512 字节时切换 TCP 53 端口)。每个 DNS 报文格式:
DNS 报文格式:
图注:事务 ID 用于匹配请求和响应。UDP 53 端口,响应超 512 字节改用 TCP 53。
关键标志位:
QR(1 位):0=查询,1=响应RD(1 位):期望递归RA(1 位):递归可用AA(1 位):权威回答(Authoritative Answer,权威服务器返回的查询响应里这个位=1)
问题五:DNS 记录类型
| 类型 | 编号 | 含义 | 例子 |
|---|---|---|---|
| A | 1 | IPv4 地址 | www.example.com A 93.184.216.34 |
| AAAA | 28 | IPv6 地址 | www.example.com AAAA 2606:2800:220:1:248:1893:25c8:1946 |
| CNAME | 5 | 别名,指向另一个域名 | www.example.com CNAME example.com |
| MX | 15 | 邮件服务器,优先级数字小优先 | example.com MX 10 mail.example.com |
| NS | 2 | 该域的权威名称服务器 | example.com NS ns1.example.com |
| TXT | 16 | 任意文本(SPF/DKIM/域验证) | example.com TXT "v=spf1 -all" |
| SOA | 6 | 起始授权记录,区域文件头 | example.com SOA ns1.example.com admin.example.com ... |
| PTR | 12 | 反向 DNS(IP 反查域名) | 34.216.184.93.in-addr.arpa PTR example.com |
| SRV | 33 | 服务位置(端口+主机) | _sip._tcp.example.com SRV 10 5 5060 sip.example.com |
| CAA | 257 | 限定可签发证书的 CA | example.com CAA 0 issue "letsencrypt.org" |
CNAME 与 A 的取舍:
用 A 记录直接指向 IP:www.example.com A 93.184.216.34
用 CNAME 指向另一域名:www.example.com CNAME example.com,再 example.com A 93.184.216.34
CNAME 的好处是改 IP 只需要改 A 记录一处。坏处是增加一次查询开销(先查 CNAME 得到 example.com,再查 A 得到 IP)。CDN 大量用 CNAME 来做调度(CNAME 指向 CDN 域名,CDN 域名 A 记录可以按地理位置变化)。
MX 优先级:
example.com MX 10 mail1.example.comexample.com MX 20 mail2.example.com
数字小的优先,发送方先尝试 mail1。10 vs 20 优先级不同,相同数字则随机选。
问题六:DNS 缓存的工作机制
每一级 DNS 服务器(递归解析器、操作系统、浏览器)都会缓存查询结果。缓存由 TTL(Time To Live) 控制——记录里包含一个 TTL 字段(秒),缓存方最多保留这条记录这么久。
# 看 dig 返回的 TTL
dig www.example.com
# ;; ANSWER SECTION:
# www.example.com. 86400 IN A 93.184.216.34
# 86400 秒 = 1 天TTL 取舍:
- TTL 短(60-300 秒):记录变化能快速传播到全网,但 DNS 服务器查询压力大
- TTL 长(86400 秒 = 1 天):减少查询量,但记录变更后最长要等 1 天才能全网生效
真实运维案例:网站迁移 IP 时,必须先把 TTL 调小到 300 秒,等 24 小时让旧 TTL 过期,然后再改 A 记录。否则部分用户会按老 TTL 缓存访问旧 IP 长达 1 天。
浏览器缓存:
Chrome 自带 DNS 缓存,TTL 60 秒(可通过 chrome://net-internals/#dns 看)。这是为加速重复访问——浏览器知道用户 1 分钟内访问同一网站概率高。
操作系统缓存:
Linux nscd(Name Service Cache Daemon)或 systemd-resolved 维护系统级 DNS 缓存,默认遵守 TTL。
# 强制清除 Chrome DNS 缓存
chrome://net-internals/#dns → "Clear host cache"
# Linux 看 nscd 缓存
nscd -g
# systemd-resolved 统计
resolvectl statistics问题七:DNS 安全威胁
DNS 欺骗/缓存投毒(DNS Spoofing / Cache Poisoning):
2008 年 Dan Kaminsky 公开了一个严重的 DNS 漏洞:攻击者通过预测事务 ID(DNS 报文里 16 位 ID)把自己伪造的 DNS 响应抢先到达受害者,让解析器缓存错误的 IP。
攻击原理:递归解析器发查询到 example.com 的权威服务器。攻击者抢在权威响应前发伪造响应:"example.com 的 IP 是 6.6.6.6",并猜中 16 位事务 ID(65536 种可能,足够时间可以爆破)。如果猜中,解析器把假 IP 缓存 1 天。
修复方案:
- 源端口随机化:把 DNS 端口从 53 改为 53 + 16 位随机源端口,事务 ID 空间从 16 位变 32 位,攻击复杂度从 2^16 变 2^32
- 0x20 编码(DNS 0x20 Bit):把域名里随机字符大小写化作为额外校验位
- DNSSEC:从协议层用数字签名防止伪造
DNSSEC(DNS Security Extensions):
DNSSEC 给每条 DNS 记录加数字签名。根域 2010 年签名为起点,.com 2011 年签名。验证链:
根密钥 → 签 com 域的 DNSKEY
com 密钥 → 签 example.com 的 DS 记录
example.com 密钥 → 签 www.example.com 的 A 记录每一步都验证上一级的签名,确保记录没被中间人篡改。
# 查 DNSSEC 签名
dig +dnssec www.example.com
# 输出会多 RRSIG 记录(数字签名)
# 用 drill 验证 DNSSEC 链
drill -S www.example.comDNSSEC 部署率到 2024 年约 35%,主要障碍是增加 DNS 响应大小(签名 + 几百字节)影响性能,老旧设备不支持 EDNS0。
DoH 和 DoT(DNS over HTTPS/TLS):
传统 DNS 是明文 UDP,运营商和中间人能监听所有查询。DoH 用 HTTPS(443)封装 DNS 查询,DoT 用 TLS(853)。Cloudflare 1.1.1.1、Google 8.8.8.8 都支持。
# 用 DoH 查询
curl -H 'accept: application/dns-json' 'https://1.1.1.1/dns-query?name=www.example.com&type=A'
# 用 DoT 查询
kdig -d @1.1.1.1 +tls-ca +tls-hostname=one.one.one.one www.example.com问题八:DNS 性能优化
Anycast 部署:13 组根服务器通过 BGP Anycast 部署到 1500+ 站点。查询到根服务器时自动选最近的实例,延迟降到几毫秒。
DNS 预取(Prefetch):浏览器在用户点击链接前就预解析链接的域名,节省点击后等待时间。Chrome 默认对 HTTPS 链接做预取。
Happy Eyeballs(双栈快速回退):浏览器同时发起 IPv4 (A) 和 IPv6 (AAAA) 查询,哪个先到用哪个,节省 200ms 等待。
EDNS Client Subnet(ECS):CDN 用来告知权威服务器"用户在哪个 IP 段",返回最近 CDN 节点的 IP,实现地理调度。
# 看 DNS 响应是否带 ECS
dig +subnet=1.2.3.4 www.example.com实战命令
# 基本查询
dig www.example.com
dig www.example.com A # 只查 A 记录
dig www.example.com +short # 只看答案
# 反向 DNS
dig -x 8.8.8.8
# 走完整迭代过程
dig +trace www.example.com
# 指定 DNS 服务器
dig @8.8.8.8 www.example.com
dig @1.1.1.1 www.example.com
# DNS over HTTPS
curl 'https://1.1.1.1/dns-query?name=www.example.com&type=A' -H 'accept: application/dns-json'
# 抓包看 DNS 报文
tcpdump -ni eth0 -X 'udp port 53' -w /tmp/dns.pcap
# 看本机 DNS 缓存
sudo tcpdump -ni lo -X 'udp port 53' # 系统调用层
# 看 DNS 服务端
ss -ulnp 'sport = :53'
# 看浏览器 DNS 缓存
chrome://net-internals/#dns思考题
- 全球根服务器只有 13 组 IP 地址,但实际物理服务器超过 1500 台。Anycast 是怎么实现"用 13 个 IP 撑 1500 站点"的?任何一个根站点被 DDoS 攻击会怎样?
- 抓一次
dig +trace www.example.com,数一下完整查询有多少次 UDP 包往返。如果改用 DoH,多多少 RTT?延迟差异怎么体现? - 假设你负责的网站
yourcompany.com要迁移到新 IP(203.0.113.5→203.0.113.10)。TTL 当前是 86400 秒。请设计迁移流程:提前几天调 TTL?什么时候改 A 记录?什么时候再调高 TTL?如果不调 TTL 直接改会怎样? - 抓 DNS 响应,假设
example.com同时有 A 记录和 CNAME 记录(CNAME 指向别的域名),客户端是怎么处理的?CNAME 链能无限长吗?RFC 1034 规定 CNAME 链最多多少层? - DNS 响应超过 512 字节时切到 TCP 53 端口(RFC 6891 用 EDNS 扩展到 4096 字节)。如果 UDP 53 端口被防火墙挡掉,会出什么问题?DoH 怎么解决这个限制?
- 假设你要给一个全球分布式应用做 DNS 调度。10 万 QPS 的查询量要怎么设计 Anycast 节点?怎么保证华东用户解析到上海 CDN、华南用户解析到广州 CDN?GeoDNS 怎么实现?
延伸阅读
- RFC 1034 / RFC 1035: DNS 标准规范
- RFC 6891: EDNS(0) Extensions
- RFC 4033: DNSSEC 介绍
- RFC 8484: DNS over HTTPS (DoH)
- RFC 7858: DNS over TLS (DoT)
- Paul Albitz & Cricket Liu《DNS and BIND》O'Reilly 经典
- 动手实验:用
unbound或bind搭建一个本地递归解析器,观察缓存和转发过程