前言
当你输入 www.example.com 按下回车,浏览器在发起 TCP 连接之前,必须先将域名解析为 IP 地址。这个过程看似简单,实际上涉及浏览器缓存、操作系统缓存、本地 DNS 服务器、根域名服务器、顶级域名服务器、权威域名服务器的多级协作。本文完整追踪一个 DNS 查询从诞生到返回的全链路。
DNS 解析全链路概览
一、DNS 报文结构
1.1 DNS 报文整体格式
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Header (12 字节) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Question Section |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Answer Section |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Authority Section |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Additional Section |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+1.2 Header 部分详解
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| ID |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+|QR| Opcode |AA|TC|RD|RA| Z | RCODE |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| QDCOUNT |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| ANCOUNT |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| NSCOUNT |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| ARCOUNT |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+各字段说明:
| 字段 | 长度 | 说明 |
|---|---|---|
| ID | 16 bit | 事务 ID,匹配请求和响应 |
| QR | 1 bit | 0=查询,1=响应 |
| Opcode | 4 bit | 0=标准查询,1=反向查询,4=通知 |
| AA | 1 bit | 权威应答标志 |
| TC | 1 bit | 截断标志(UDP 超过 512 字节时置 1) |
| RD | 1 bit | 期望递归查询 |
| RA | 1 bit | 支持递归查询 |
| Z | 3 bit | 保留 |
| RCODE | 4 bit | 响应码(0=无错误,3=域名不存在) |
| QDCOUNT | 16 bit | Question 条目数 |
| ANCOUNT | 16 bit | Answer 条目数 |
| NSCOUNT | 16 bit | Authority 条目数 |
| ARCOUNT | 16 bit | Additional 条目数 |
1.3 Resource Record 格式
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| |/ NAME // /+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| TYPE |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| CLASS |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| TTL || |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| RDLENGTH |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|/ RDATA // /+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+1.4 DNS 名称编码
// DNS 名称使用长度前缀编码,而非 null 终止// "www.example.com" 编码如下:
const dnsName = Buffer.from([ 0x03, 0x77, 0x77, 0x77, // \x03www 0x07, 0x65, 0x78, 0x61, // \x07exa 0x6d, 0x70, 0x6c, 0x65, // mple 0x03, 0x63, 0x6f, 0x6d, // \x03com 0x00, // 结束标记]);
// 消息压缩:后续出现相同域名可用指针// 指针格式: 11 + 14-bit 偏移量// 例如 0xC00C 指向偏移 12 处的名称二、记录类型详解
2.1 常用记录类型
| 类型 | 值 | 说明 | 示例 |
|---|---|---|---|
| A | 1 | IPv4 地址 | 93.184.216.34 |
| AAAA | 28 | IPv6 地址 | 2606:2800:220:1:... |
| CNAME | 5 | 规范名称(别名) | www.example.com → example.com |
| MX | 15 | 邮件交换 | 10 mail.example.com |
| NS | 2 | 域名服务器 | ns1.example.com |
| TXT | 16 | 文本记录 | SPF, DKIM, DMARC |
| SRV | 33 | 服务定位 | _http._tcp.example.com |
| SOA | 6 | 起始授权 | 主 DNS 管理信息 |
| PTR | 12 | 反向解析(IP→域名) | 34.216.184.93.in-addr.arpa |
| CAA | 257 | 证书颁发机构授权 | 0 issue "letsencrypt.org" |
2.2 A 记录与 AAAA 记录
; A 记录:域名 → IPv4www.example.com. 300 IN A 93.184.216.34
; AAAA 记录:域名 → IPv6www.example.com. 300 IN AAAA 2606:2800:220:1:248:1893:25c8:1946
; 一个域名可以有多条 A 记录(DNS 负载均衡)www.example.com. 300 IN A 93.184.216.34www.example.com. 300 IN A 93.184.216.352.3 CNAME 记录
; CNAME 不能与其他记录共存; 错误示例:blog.example.com. 300 IN CNAME example.github.ioblog.example.com. 300 IN A 1.2.3.4 ; 冲突!
; 正确:用 CNAME 指向另一个域名blog.example.com. 300 IN CNAME example.github.io2.4 MX 记录
; MX 记录带优先级(数值越小优先级越高)example.com. 300 IN MX 10 mail1.example.com.example.com. 300 IN MX 20 mail2.example.com.example.com. 300 IN MX 30 mail3.example.com.
; 发送邮件时优先尝试 mail1; mail1 不可用时尝试 mail2,以此类推2.5 SOA 记录
; SOA(Start of Authority)记录example.com. IN SOA ns1.example.com. admin.example.com. ( 2026032201 ; Serial(序列号,区域传输时递增) 3600 ; Refresh(从服务器检查主服务器的间隔) 900 ; Retry(检查失败后重试间隔) 604800 ; Expire(从服务器数据过期时间) 86400 ; Minimum TTL(否定缓存的 TTL))三、DNS 解析全链路
3.1 浏览器缓存
// Chrome 浏览器 DNS 缓存// 访问 chrome://net-internals/#dns 查看
// 浏览器 DNS 缓存特点:// 1. 默认缓存约 1000 条记录// 2. 缓存时间遵循 DNS 记录的 TTL// 3. 浏览器重启后缓存清除
// 手动清除 Chrome DNS 缓存// chrome://net-internals/#dns → Clear host cache3.2 操作系统缓存
# Linux systemd-resolvedresolvectl status # 查看缓存统计resolvectl flush-caches # 清除缓存resolvectl query example.com # 手动查询
# Linux nscd (Name Service Cache Daemon)nscd -g # 查看缓存统计nscd -i hosts # 清除 hosts 缓存
# macOSdscacheutil -flushcache # 清除 DNS 缓存sudo killall -HUP mDNSResponder
# Windowsipconfig /displaydns # 显示 DNS 缓存ipconfig /flushdns # 清除 DNS 缓存3.3 hosts 文件
# /etc/hosts 文件优先级高于 DNS 查询# 格式: IP 主机名
127.0.0.1 localhost::1 localhost192.168.1.100 myserver.local10.0.0.1 api.internal.example.com
# 解析顺序由 /etc/nsswitch.conf 控制# hosts: files dns → 先查 hosts 文件,再查 DNS3.4 完整解析时序图
四、递归查询与迭代查询
4.1 递归查询
4.2 迭代查询
4.3 查询对比
| 特征 | 递归查询 | 迭代查询 |
|---|---|---|
| 谁做工作 | 被查询者 | 查询发起者 |
| 返回内容 | 最终答案 | 下一步该问谁 |
| 客户端负担 | 轻 | 重(通常不直接使用) |
| 典型场景 | 客户端 → 本地 DNS | 本地 DNS → 各级 DNS |
| 服务器压力 | 递归解析器压力大 | 根/TLD 服务器压力小 |
五、DNS 缓存与 TTL
5.1 多级缓存架构
5.2 TTL 的作用
// TTL(Time To Live)决定缓存存活时间// 单位:秒
// 短 TTL 的利弊const shortTTL = 60; // 1 分钟// 优点: DNS 变更后快速生效// 缺点: 查询量大,增加延迟
// 长 TTL 的利弊const longTTL = 86400; // 1 天// 优点: 减少查询量,低延迟// 缺点: DNS 变更后生效慢
// 实际 TTL 处理function getCachedRecord(record) { const elapsed = Date.now() - record.cachedAt; if (elapsed > record.ttl * 1000) { return null; // 缓存过期,需要重新查询 } return record.value;}5.3 负面缓存
; 当查询的域名不存在时,SOA 的 Minimum TTL 字段; 控制否定缓存的存活时间
; example.com 的 SOA 记录example.com. IN SOA ns1.example.com. admin.example.com. ( 2026032201 ; Serial 3600 ; Refresh 900 ; Retry 604800 ; Expire 86400 ; Minimum TTL(否定缓存 24 小时))
; 查询 nonexistent.example.com 返回 NXDOMAIN; 本地 DNS 会缓存这个否定结果 86400 秒; 期间不再为该域名向上游查询六、DNS 负载均衡
6.1 轮询(Round Robin)
# 一个域名对应多个 A 记录$ dig www.example.com
;; ANSWER SECTION:www.example.com. 300 IN A 93.184.216.34www.example.com. 300 IN A 93.184.216.35
# 第二次查询时顺序可能变化$ dig www.example.com
;; ANSWER SECTION:www.example.com. 300 IN A 93.184.216.35www.example.com. 300 IN A 93.184.216.346.2 地理定位(GeoDNS)
6.3 Anycast
Anycast 原理:多个数据中心广播相同的 IP 地址,BGP 路由协议自动将用户引导到网络拓扑最近的节点。
七、DNSSEC
7.1 DNSSEC 信任链
7.2 DNSSEC 记录类型
| 记录类型 | 说明 |
|---|---|
| DNSKEY | 公钥(KSK 和 ZSK) |
| RRSIG | 资源记录的数字签名 |
| DS | 委派签发者(子区域 KSK 的哈希) |
| NSEC | 下一安全记录(证明域名不存在) |
| NSEC3 | NSEC 的哈希版本(防止域名遍历) |
7.3 KSK 和 ZSK
DNSSEC 使用两把密钥:
KSK(Key Signing Key)├── 只用于签名 ZSK├── 较长(2048-bit RSA 或更大)├── 很少轮换(每年 1-2 次)└── 通过 DS 记录建立父子信任链
ZSK(Zone Signing Key)├── 用于签名区域内所有记录├── 较短(1024-2048-bit RSA)├── 频繁轮换(每月或每季度)└── 由 KSK 签名,不需要父区域参与7.4 签名验证过程
// DNSSEC 验证 www.example.com 的 A 记录
// 1. 获取 A 记录及其 RRSIGconst aRecord = { name: "www.example.com", type: "A", data: "93.184.216.34" };const rrsig = { typeCovered: "A", algorithm: 13, // ECDSA P-256 labels: 3, originalTTL: 300, signatureExpiry: 1711159200, signatureInception: 1711072800, keyTag: 12345, signersName: "example.com", signature: "0xABCDEF...", // 数字签名};
// 2. 获取 example.com 的 DNSKEYconst dnskey = { flags: 256, // ZSK protocol: 3, algorithm: 13, publicKey: "0x123456...", // 公钥};
// 3. 验证签名const signedData = buildSignedData(aRecord, rrsig);const isValid = verifyECDSA(dnskey.publicKey, signedData, rrsig.signature);
if (!isValid) { throw new Error("DNSSEC 验证失败:记录可能被篡改");}7.5 NSEC/NSEC3:证明不存在
; NSEC 记录按字母顺序排列域名; 证明两个有序域名之间没有其他域名
example.com. IN NSEC beta.example.com. A NS SOA; 含义: alpha.example.com 之后是 beta.example.com; 之间没有任何域名; alpha 只存在 A, NS, SOA 类型的记录
; NSEC3 对域名做哈希,防止遍历; 2S9EHANOJVULAP7KQBVDMOJ0M1P1RQCH.example.com.; IN NSEC3 1 0 2 ABCD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR八、DNS over HTTPS 与 DNS over TLS
8.1 传统 DNS 的安全问题
传统 DNS(端口 53)的安全问题:
1. 明文传输 ┌──────┐ UDP/TCP 明文 ┌──────┐ │ 客户端 │ ────────────────→ │ DNS │ └──────┘ └──────┘ 攻击者可窃听查询内容和返回结果
2. 无身份验证 - 任何人都可以伪装 DNS 服务器 - ISP 可以返回篡改的响应
3. UDP 无序 - 响应可能被伪造 - ID 碰撞攻击(16-bit ID 空间有限)8.2 DoH (DNS over HTTPS)
# 使用 curl 测试 DoHcurl -s -H "Accept: application/dns-message" \ "https://dns.google/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE" \ --output - | hexdump -C
# 使用 doh-proxydoh-client --domain example.com --type A --server https://dns.google/dns-query8.3 DoT (DNS over TLS)
DoT (DNS over TLS): 端口: 853 协议: TLS + DNS 用途: 通常用于路由器到 DNS 服务器的加密
DoH (DNS over HTTPS): 端口: 443 协议: HTTPS + DNS 用途: 通常用于浏览器到 DNS 服务器的加密 优势: 与普通 HTTPS 流量混合,更难被检测和封锁8.4 加密 DNS 对比
| 特性 | 传统 DNS | DoT | DoH |
|---|---|---|---|
| 端口 | 53 | 853 | 443 |
| 加密 | 无 | TLS | HTTPS (TLS) |
| 隐蔽性 | 无 | 低 | 高 |
| 性能 | 最快 | 较快 | 中等 |
| 防篡改 | 否 | 是 | 是 |
| 防窃听 | 否 | 是 | 是 |
| 浏览器支持 | 原生 | 否 | 原生 |
九、常见安全问题
9.1 DNS 劫持
9.2 DNS 缓存污染
// DNS 缓存污染攻击原理// 攻击者抢在合法响应之前发送伪造的 DNS 响应
// 攻击条件:// 1. 知道查询的事务 ID(16-bit,暴力猜解)// 2. 知道查询的端口号(固定端口更容易猜)// 3. 在合法响应到达之前发送伪造响应
// Kaminsky 攻击(2008)// 利用源端口随机化不充分的漏洞function kaminskyAttack(targetDomain) { // 1. 向目标 DNS 服务器查询随机子域名 const randomSubdomain = Math.random().toString(36).slice(2); const query = `${randomSubdomain}.${targetDomain}`;
// 2. 同时泛洪伪造的响应 for (let id = 0; id < 65536; id++) { for (let port = 1024; port < 2048; port++) { sendFakeDNSResponse(id, port, query, "evil-ip"); } } // 3. 伪造响应包含额外的权威记录 // 将目标域名的 NS 指向攻击者控制的服务器}9.3 DNS 放大攻击
DNS 放大攻击(DDoS 的一种)
攻击原理:1. 攻击者伪造源 IP(受害者 IP)2. 向开放 DNS 解析器发送查询(使用 EDNS0 扩大响应)3. DNS 解析器将大量响应发送到受害者 IP
放大倍数计算: 查询大小: ~60 字节 响应大小: ~4000 字节(使用 ANY 类型查询) 放大倍数: 4000 / 60 ≈ 66 倍
防御措施:1. DNS 解析器不对外开发(仅服务可信客户端)2. 源地址验证(BCP 38)3. 限制响应大小4. 禁用 ANY 查询十、实战:使用 dig 命令
10.1 基本查询
# 基本查询$ dig www.example.com
; <<>> DiG 9.18.0 <<>> www.example.com;; global options: +cmd;; Got answer:;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54321;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; QUESTION SECTION:;www.example.com. IN A
;; ANSWER SECTION:www.example.com. 300 IN A 93.184.216.34
;; Query time: 28 msec;; SERVER: 8.8.8.8#53(8.8.8.8);; WHEN: Sat Mar 22 08:00:00 UTC 2026;; MSG SIZE rcvd: 5610.2 追踪解析路径
# +trace 参数显示完整解析链路$ dig +trace www.example.com
; 根服务器. 518400 IN NS a.root-servers.net.. 518400 IN NS b.root-servers.net.;; Received 1097 bytes from 198.41.0.4#53(a.root-servers.net)
; .com TLD 服务器com. 172800 IN NS a.gtld-servers.net.com. 172800 IN NS b.gtld-servers.net.;; Received 899 bytes from 192.5.6.30#53(a.gtld-servers.net)
; 权威服务器example.com. 172800 IN NS ns1.example.com.example.com. 172800 IN NS ns2.example.com.;; Received 200 bytes from 199.43.135.53#53(ns1.example.com)
; 最终答案www.example.com. 300 IN A 93.184.216.3410.3 查询特定记录类型
# 查询 MX 记录$ dig example.com MX
# 查询 AAAA 记录$ dig www.example.com AAAA
# 查询 TXT 记录$ dig example.com TXT
# 查询 NS 记录$ dig example.com NS
# 查询 SOA 记录$ dig example.com SOA
# 查询 CNAME 记录$ dig www.example.com CNAME
# 反向查询(IP → 域名)$ dig -x 93.184.216.3410.4 DNSSEC 验证
# 启用 DNSSEC 验证$ dig +dnssec www.example.com
;; ANSWER SECTION:www.example.com. 300 IN A 93.184.216.34www.example.com. 300 IN RRSIG A 13 3 300 ...
;; flags: qr rd ra ad;; 注意 ad 标志(Authenticated Data)= DNSSEC 验证通过
# 查询 DNSKEY$ dig example.com DNSKEY
# 查询 DS 记录(建立信任链)$ dig example.com DS10.5 使用 nslookup
# 简单查询$ nslookup www.example.com
# 指定 DNS 服务器$ nslookup www.example.com 8.8.8.8
# 交互模式$ nslookup> server 8.8.8.8> set type=MX> example.com> set type=ANY> example.com> exitFAQ
Q1: 为什么修改了 DNS 记录但还没生效?
DNS 使用多级缓存,每一级都有 TTL。修改记录后,需要等待所有层级的缓存过期。最坏情况下需要等待整个 TTL 周期(常见的 TTL 为 300 秒到 86400 秒)。如果 TTL 设置为 3600 秒,最多需要 1 小时才能全球生效。
Q2: DNS 使用 UDP 还是 TCP?
DNS 默认使用 UDP 端口 53。当响应数据超过 512 字节时,会切换到 TCP(设置 TC 标志)。DNS 区域传输(zone transfer)始终使用 TCP。DoH 使用 HTTPS(TCP + TLS),DoT 使用 TLS(TCP)。
Q3: 为什么根域名服务器只有 13 组?
DNS 报文在 UDP 模式下限制为 512 字节。13 组根服务器(每组包含多个全球分布的实例)的 NS 记录刚好能在 512 字节内放下。虽然现在有了 EDNS0 扩展,这个限制已经不是硬性约束,但 13 组根服务器的约定保留至今。实际通过 Anycast 技术,全球已有超过 1000 个根服务器实例。
Q4: DNS 缓存被污染了怎么办?
清除各级缓存:浏览器缓存(chrome://net-internals/#dns)、OS 缓存(ipconfig /flushdns 或 resolvectl flush-caches)、路由器缓存(重启路由器)。如果 ISP 级别被污染,可以切换到可信的公共 DNS(如 8.8.8.8、1.1.1.1)或使用 DoH/DoT。
Q5: CNAME 和 A 记录可以同时存在吗?
不可以。根据 DNS 规范,如果一个域名有 CNAME 记录,就不能有其他任何记录(SIG 除外)。这是 CNAME 与其他记录类型的互斥规则。如果需要同时指向别名和拥有自己的记录,应该使用其他方案(如 ALIAS/ANAME 记录,由 DNS 服务商在解析时展开为 A 记录)。
Q6: DNSSEC 能防止 DNS 劫持吗?
DNSSEC 可以检测到 DNS 响应被篡改(因为签名验证会失败),但无法阻止篡改行为本身。如果客户端严格验证 DNSSEC,篡改的响应会被丢弃,客户端得到 SERVFAIL 错误。DNSSEC 解决的是数据完整性和来源验证问题,不是可用性问题。
参考资料
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






