📌 本文亮点:从 DNS 报文字节级拆解到迭代/递归解析全流程,从 dig 高级用法到 DoH/DoT/DoQ 加密方案,从 DNSSEC 信任链到 K8s CoreDNS 服务发现,附 7 个实战排障场景!
前言
每次你在浏览器输入 www.example.com,背后都有一场精密的分布式协作------你的请求从本机出发,经过递归解析器、根域名服务器、TLD 服务器、权威服务器,最终拿到一个 IP 地址。整个过程通常在 几十毫秒内完成。
但当你遇到"域名解析偶尔超时"、"修改 DNS 记录后迟迟不生效"、"SERVFAIL 但不知道为什么"这些问题时,如果不理解 DNS 的底层机制,就只能靠运气排障。
本文从协议报文 出发,系统梳理 DNS 的完整知识体系------报文格式、解析流程、记录类型、加密方案、安全机制,最终落到 dig/tcpdump 实战和 K8s CoreDNS 排障。
一、DNS 核心概念
1.1 DNS 是什么
DNS(Domain Name System)是一个层次化、分布式的命名系统,核心功能是将人类可读的域名转换为机器可用的 IP 地址。
DNS 本质上是一个分布式数据库:
- 没有单点:全球 13 组根服务器 + 数千台 TLD/权威服务器
- 层次化 :根 → TLD(
.com)→ 权威(example.com),逐级授权 - 缓存驱动:每层都有 TTL 缓存,减少重复查询
1.2 DNS 服务器类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 根服务器 | 13 组逻辑实例,知道所有 TLD 的位置 | a.root-servers.net ~ m.root-servers.net |
| TLD 服务器 | 管理顶级域名下的授权信息 | .com、.org、.cn |
| 权威服务器 | 存储域名的实际记录(A、MX 等) | ns1.example.com |
| 递归解析器 | 代替客户端完成整个解析链 | 8.8.8.8、1.1.1.1 |
| 转发解析器 | 将查询转发给上游递归解析器 | 企业内网 DNS |
💡 递归解析器是客户端的"代理",它代替客户端走完整个迭代查询链。客户端只需问递归解析器一次。
二、DNS 解析全流程
2.1 一次完整解析的路径
以解析 www.example.com 的 A 记录为例:
┌──────────┐ ① ┌───────────┐ ② ┌──────────┐ ③ ┌──────────┐ ④ ┌──────────┐
│ 客户端 │────▶│ 递归解析器 │────▶│ 根服务器 │────▶│ TLD 服务器│────▶│ 权威服务器│
│(Stub) │◀────│(Recursive)│◀────│(Root) │◀────│(.com) │◀────│(example) │
└──────────┘ ⑨ └───────────┘ ⑧ └──────────┘ ⑤ └──────────┘ ⑥ └──────────┘
│ │ │ │
│ ⑦ 缓存结果 │ │ 返回授权 │ 返回 A 记录
│ 直接应答 │ │ NS 记录 │ 93.184.216.34
详细步骤:
- 客户端向递归解析器发送查询:
www.example.com A - 递归解析器检查缓存,未命中则向根服务器查询
- 根服务器返回
.comTLD 的 NS 记录(授权) - 递归解析器向
.comTLD 服务器查询 - TLD 服务器返回
example.com的 NS 记录 - 递归解析器向
example.com权威服务器查询 - 权威服务器返回 A 记录:
93.184.216.34 - 递归解析器缓存结果(按 TTL)
- 递归解析器将结果返回客户端
2.2 递归查询 vs 迭代查询
| 对比项 | 递归查询 | 迭代查询 |
|---|---|---|
| 谁负责走完整个链 | 解析器 | 客户端自己 |
| 典型场景 | 客户端 → 递归解析器 | 递归解析器 → 根/TLD/权威 |
| 返回结果 | 最终答案 | 要么答案,要么"去问谁" |
| RD 标志 | RD=1(请求递归) | RD=0 |
| RA 标志 | RA=1(支持递归) | RA=0 |
💡 客户端对递归解析器发的是递归查询 (RD=1),递归解析器对根/TLD/权威发的是迭代查询(RD=0)。
2.3 递归查询 vs 迭代查询详解
生活中的类比
- 递归查询 = 你去图书馆问管理员"帮我找一本关于 DNS 的书",管理员自己跑遍所有书架,最终把书递给你
- 迭代查询 = 你去图书馆问管理员"DNS 的书在哪?",管理员说"去三楼科技区找",你跑到三楼再问,三楼说"去第 7 排",你跑到第 7 排自己找到
逐步过程对照
| 步骤 | 方向 | 查询类型 | 发生了什么 |
|---|---|---|---|
| ① | 客户端 → 递归解析器 | 递归 | "帮我查 www.example.com 的 A 记录" |
| ② | 递归解析器 → 根服务器 | 迭代 | "你知道 www.example.com 吗?" |
| ③ | 根服务器 → 递归解析器 | 迭代响应 | "我不知道,但 .com 的 NS 是这些,你去问它" |
| ④ | 递归解析器 → TLD 服务器 | 迭代 | "你知道 www.example.com 吗?" |
| ⑤ | TLD → 递归解析器 | 迭代响应 | "我不知道,但 example.com 的 NS 是这些,你去问它" |
| ⑥ | 递归解析器 → 权威服务器 | 迭代 | "你知道 www.example.com 吗?" |
| ⑦ | 权威 → 递归解析器 | 迭代响应 | "我知道,A 记录是 93.184.216.34" |
| ⑧ | 递归解析器 → 客户端 | 递归响应 | "你要的答案是 93.184.216.34" |
用 dig 验证两种查询
递归查询(默认行为,RD=1):
bash
dig www.example.com @8.8.8.8
# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
# ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
# ^^ ^^
# RD=1 RA=1 → 递归查询,解析器支持递归
迭代查询跟踪 (+trace 模拟递归解析器的迭代过程):
bash
dig +trace www.example.com
# ;; NS SECTION:
# . 518400 IN NS a.root-servers.net. ← 第1跳:根
# ;; Received 1261 bytes from 198.97.190.53#53
# com. 172800 IN NS a.gtld-servers.net. ← 第2跳:.com TLD
# ;; Received 1221 bytes from 192.5.6.30#53
# example.com. 172800 IN NS ns1.example.com. ← 第3跳:权威
# ;; Received 123 bytes from 192.5.6.30#53
# www.example.com. 3600 IN A 93.184.216.34 ← 最终答案
💡
dig +trace展示的就是递归解析器内部的迭代过程------逐级从根→TLD→权威获取 referral,最终拿到答案。
2.4 生产环境用哪种查询方式
生产环境两种都用,各司其职:
┌────────┐ 递归查询 ┌──────────┐ 迭代查询 ┌────────┐
│ │ (RD=1) │ │ (RD=0) │ │
│ 客户端 │──────────▶│ 递归解析器 │──────────▶│ 权威服务器│
│ │◀──────────│(8.8.8.8) │◀──────────│ │
└────────┘ 最终答案 └──────────┘ referral └────────┘
为什么这样分工:
| 环节 | 方式 | 原因 |
|---|---|---|
| 客户端 → 递归解析器 | 递归 | 客户端(浏览器/应用)不想逐级追问,只要最终答案 |
| 递归解析器 → 根/TLD/权威 | 迭代 | 权威服务器不能替客户端跑全链路,否则全球 13 组根服务器扛不住 |
生产环境的递归解析器部署:
| 方案 | 说明 | 适用场景 |
|---|---|---|
| Unbound | 开源递归解析器,支持 DNSSEC 验证 | 企业内网 DNS、安全要求高 |
| BIND(递归模式) | 既可做权威也可做递归 | 传统企业、混合部署 |
| CoreDNS | K8s 集群内递归 + 服务发现 | K8s 环境 |
| 公共 DNS | 8.8.8.8 / 1.1.1.1 |
小型/无内网 DNS 的环境 |
⚠️ 权威服务器不允许递归查询 。如果你对权威服务器发递归查询(RD=1),它会返回
REFUSED------这是安全策略,防止权威服务器被滥用为开放递归器。
验证:权威服务器拒绝递归:
bash
# 对权威服务器发递归查询 → REFUSED
dig www.example.com @ns1.example.com
# ;; status: REFUSED
# 对权威服务器发迭代查询(+norecurse)→ 正常返回
dig +norecurse www.example.com @ns1.example.com
# ;; status: NOERROR
# ;; AUTHORITY SECTION:
# example.com. 172800 IN NS ns1.example.com. ← 返回授权信息
💡 一句话总结:客户端用递归(省事),解析器用迭代(分散压力)------这就是 DNS 设计的分层协作模型,生产环境没有任何一个角色只做一种查询。
2.5 缓存与 TTL
DNS 的性能核心是缓存,每条记录都带有 TTL(Time To Live):
- 递归解析器缓存:按 TTL 倒计时,过期后重新查询
- 操作系统缓存 :如 Windows
ipconfig /displaydns - 浏览器缓存 :Chrome
chrome://net-internals/#dns - 应用缓存 :JVM 默认缓存成功的 DNS 解析永久 (需配置
networkaddress.cache.ttl)
⚠️ "DNS 传播延迟"不是真的"传播",而是旧缓存还没过期。修改记录前应先降低 TTL,等旧 TTL 过期后再改值。
三、DNS 报文格式
3.1 报文整体结构
一个 DNS 报文由 5 个段组成:
+------------+
| Header | 12 字节,固定长度
+------------+
| Question | 查询的问题
+------------+
| Answer | 应答的资源记录
+------------+
| Authority | 权威服务器的资源记录
+------------+
| Additional | 附加信息(如 Glue 记录)
+------------+
3.2 Header 格式(12 字节)
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 | 事务 ID,响应必须与请求相同 |
| QR | 1 | 0=查询,1=响应 |
| Opcode | 4 | 0=标准查询,5=动态更新 |
| AA | 1 | 权威应答标志 |
| TC | 1 | 截断标志(超过 UDP 512 字节限制) |
| RD | 1 | 期望递归(Recursion Desired) |
| RA | 1 | 支持递归(Recursion Available) |
| Z | 3 | 保留(其中 AD=认证数据,CD=禁用检查) |
| RCODE | 4 | 响应码(0=NOERROR, 2=SERVFAIL, 3=NXDOMAIN, 5=REFUSED) |
| QDCOUNT | 16 | 问题数 |
| ANCOUNT | 16 | 应答记录数 |
| NSCOUNT | 16 | 权威记录数 |
| ARCOUNT | 16 | 附加记录数 |
3.3 域名编码(标签压缩)
DNS 中的域名使用长度前缀标签编码:
text
www.example.com 编码为:
\x03www\x07example\x03com\x00
│ │ │ │
│ │ │ └─ 根标签(0 结尾)
│ │ └─ "com"(长度 3)
│ └─ "example"(长度 7)
└─ "www"(长度 3)
💡 DNS 还支持指针压缩:重复的域名后缀用 2 字节指针替代(高 2 位=11,低 14 位=偏移量),大幅减少报文大小。
3.4 报文实例:抓一个 DNS 查询
bash
# 抓取 DNS 查询
tcpdump -i eth0 -n -vvv -c 2 'udp port 53'
text
14:42:12.989067 IP 10.0.2.15.11008 > 192.168.1.1.53: 17791+ [1au] A? www.example.com. (50)
14:42:12.989655 IP 192.168.1.1.53 > 10.0.2.15.11008: 17791* 1/1/2 A 93.184.216.34 (99)
解读:
| 字段 | 查询行 | 响应行 |
|---|---|---|
| 事务 ID | 17791 |
17791(与请求匹配) |
| 标志 | +(RD=1) |
*(AA=1,权威应答) |
| 记录数 | [1au](1 条 Additional) |
1/1/2(1 Answer / 1 Authority / 2 Additional) |
| 查询类型 | A?(A 记录查询) |
A 93.184.216.34(A 记录应答) |
四、DNS 记录类型
4.1 核心记录类型
| 类型 | 全称 | 说明 | 示例 |
|---|---|---|---|
| A | Address | IPv4 地址 | example.com. IN A 93.184.216.34 |
| AAAA | IPv6 Address | IPv6 地址 | example.com. IN AAAA 2606:2800:220:1:... |
| CNAME | Canonical Name | 域名别名 | blog.example.com. IN CNAME pages.cdn.net. |
| MX | Mail Exchanger | 邮件服务器(带优先级) | example.com. IN MX 10 mail.example.com. |
| NS | Name Server | 域名权威服务器 | example.com. IN NS ns1.example.com. |
| SOA | Start of Authority | 区域授权起始(管理元数据) | example.com. IN SOA ns1 admin 20260101 ... |
| PTR | Pointer | 反向解析(IP → 域名) | 34.216.184.93.in-addr.arpa. IN PTR example.com. |
| TXT | Text | 文本记录(SPF、验证等) | example.com. IN TXT "v=spf1 mx -all" |
| SRV | Service | 服务发现(端口+主机) | _sip._tcp.example.com. IN SRV 10 5 5060 sip.example.com. |
| CAA | Certification Authority | 证书授权限制 | example.com. IN CAA 0 issue "letsencrypt.org" |
4.2 SOA 记录详解
SOA 是每个区域的第一条记录,包含管理元数据:
text
example.com. IN SOA ns1.example.com. hostmaster.example.com. (
2026061401 ; Serial(序列号,YYYYMMDDnn 格式)
3600 ; Refresh(从服务器检查主服务器的时间间隔)
600 ; Retry(刷新失败后重试间隔)
604800 ; Expire(从服务器数据过期时间)
300 ; Minimum / Negative Cache TTL(否定缓存时间)
)
💡 SOA 的
Minimum字段决定了 NXDOMAIN 的缓存时间。当你删除一条记录后,客户端和递归解析器会在 Negative Cache TTL 内继续返回 NXDOMAIN。
4.3 CNAME 的限制
- CNAME 不能与其他记录共存:一个名称如果设置了 CNAME,就不能再有 A、MX、TXT 等记录
- CNAME 不能指向另一个 CNAME:虽然技术上可行,但增加了解析延迟
- MX/NS 不能指向 CNAME:RFC 要求 MX/NS 目标必须是 A/AAAA 记录
4.4 DNSSEC 专用记录
| 类型 | 说明 |
|---|---|
| DNSKEY | 区域公钥(ZSK 签名密钥 + KSK 密钥签名密钥) |
| RRSIG | 资源记录集的数字签名 |
| DS | 委派签名者(父区域持有的子区域 KSK 哈希) |
| NSEC / NSEC3 | 不存在证明(证明"这个名字不存在") |
五、EDNS(0) 扩展机制
传统 DNS 限制 UDP 报文不超过 512 字节,DNSSEC 签名的记录远超此限制。EDNS(0)(RFC 6891)解决了这个问题:
5.1 EDNS 核心功能
| 功能 | 说明 |
|---|---|
| UDP 载荷协商 | 客户端通告"我能接收 4096 字节",超出则回退 TCP |
| DO 标志 | DNSSEC OK,客户端请求 DNSSEC 响应 |
| 扩展 RCODE | 超过 4 位的错误码 |
| 客户端子网 | 传递用户真实子网,提升 CDN 地理路由精度 |
5.2 EDNS 在报文中的位置
EDNS 通过 Additional 段的 OPT 伪记录传递:
text
; 查询中的 OPT 记录
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
; ^^ ^^^^^^^^
; DO=1 UDP 缓冲区 4096 字节
⚠️ 如果中间设备(防火墙/负载均衡器)不识别 OPT 记录而丢弃 EDNS 查询,会导致 DNSSEC 解析失败。这是生产环境 DNS 故障的常见原因。
六、DNS 加密方案
传统 DNS 查询以明文传输,任何人都能看到你查询了哪些域名。现代 DNS 提供了三种加密方案:
6.1 三种加密方案对比
| 方案 | 端口 | 传输层 | 握手开销 | 典型使用场景 |
|---|---|---|---|---|
| DoT | 853 | TLS | 1-RTT | OS 级(Android 私有 DNS、systemd-resolved) |
| DoH | 443 | TLS + HTTP/2 | 1-RTT(或 0-RTT 恢复) | 浏览器级(Chrome、Firefox、Edge) |
| DoQ | 853 | QUIC/TLS 1.3 | 0-RTT | 递归↔权威;新兴客户端支持 |
┌────────────────────────────────────────────────────────────┐
│ 安全性分层 │
│ │
│ DNSSEC → 数据真实性(签名验证,防篡改)不加密 │
│ DoT/DoH → 传输机密性(TLS 加密,防窃听)不验证数据来源 │
│ 两者互补:DNSSEC 防篡改 + DoH 防窃听 = 完整安全链 │
└────────────────────────────────────────────────────────────┘
6.2 DoH 实战:curl 查询
Cloudflare DoH(JSON 格式):
bash
# 查询 A 记录(JSON 响应,人类可读)
curl -s -H "accept: application/dns-json" \
"https://cloudflare-dns.com/dns-query?name=example.com&type=A" | jq
响应示例:
json
{
"Status": 0,
"TC": false,
"RD": true,
"RA": true,
"AD": false,
"CD": false,
"Question": [
{ "name": "example.com.", "type": 1 }
],
"Answer": [
{
"name": "example.com.",
"type": 1,
"TTL": 1726,
"data": "93.184.216.34"
}
]
}
Google DoH(JSON 格式):
bash
# 查询 A 记录
curl -s "https://dns.google/resolve?name=example.com&type=A" | jq
# 带 DNSSEC 数据
curl -s "https://dns.google/resolve?name=example.com&type=A&do=1" | jq
RFC 8484 二进制格式(POST):
bash
# Wire format POST 查询
echo -n "q80BAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB" | base64 -d | \
curl -s --data-binary @- \
-H "content-type: application/dns-message" \
https://cloudflare-dns.com/dns-query | hexdump -C
curl 原生 DoH 支持(7.62+):
bash
# 使用 DoH 解析域名后访问网站
curl --doh-url https://cloudflare-dns.com/dns-query https://www.example.com
6.3 DNS 响应码(RCODE)
| RCODE | 名称 | 含义 | 排障思路 |
|---|---|---|---|
| 0 | NOERROR | 成功 | 正常 |
| 1 | FORMERR | 查询格式错误 | 检查查询工具版本 |
| 2 | SERVFAIL | 服务器失败 | 通常是 DNSSEC 验证失败,dig +cd 绕过 |
| 3 | NXDOMAIN | 域名不存在 | 确认域名拼写、检查是否被删除 |
| 4 | NOTIMP | 未实现 | 服务器不支持此查询类型 |
| 5 | REFUSED | 拒绝查询 | ACL 策略限制,或查询了不允许递归的服务器 |
七、DNSSEC:DNS 安全扩展
7.1 DNSSEC 解决什么问题
DNSSEC 解决的是数据真实性和完整性问题------防止 DNS 响应被篡改(如 DNS 缓存投毒)。
⚠️ DNSSEC 不加密查询。它只保证"你收到的数据确实是权威服务器发布的",但任何人都能看到你查了什么。加密是 DoT/DoH/DoQ 的事。
7.2 DNSSEC 工作原理
┌───────────────────────────────────────────────────────────────┐
│ DNSSEC 信任链 │
│ │
│ 根区域 KSK(信任锚点,预装在解析器中) │
│ │ │
│ ├─ 签名 .com 区域的 DS 记录(父持有子的 KSK 哈希) │
│ │ │
│ ├─ .com 区域 KSK │
│ │ ├─ 签名 .com 区域的 DS 记录 │
│ │ │ │
│ │ ├─ example.com 区域 KSK │
│ │ │ ├─ 签名 example.com 的 ZSK │
│ │ │ │ ├─ 签名 A 记录 → RRSIG │
│ │ │ │ ├─ 签名 MX 记录 → RRSIG │
│ │ │ │ └─ 签名 NS 记录 → RRSIG │
│ │ │ └─ DNSKEY(KSK + ZSK 公钥) │
│ │ └─ DS 记录(父区域持有子区域 KSK 的哈希) │
│ └─ ... │
└───────────────────────────────────────────────────────────────┘
关键角色:
- ZSK(Zone Signing Key):签名区域内的记录,经常轮换
- KSK(Key Signing Key):签名 ZSK,很少轮换,其哈希由父区域持有
- DS(Delegation Signer):父区域存储的子区域 KSK 哈希,链接信任链
- RRSIG:附加在每个 RRset 上的签名
7.3 DNSSEC 验证标志
| 标志 | 方向 | 含义 |
|---|---|---|
| AD(Authenticated Data) | 响应中 | 递归解析器设置,表示 Answer/Authority 段已通过 DNSSEC 验证 |
| CD(Checking Disabled) | 查询中 | 客户端请求解析器跳过验证,返回原始数据(调试用) |
| DO(DNSSEC OK) | 查询 EDNS 中 | 客户端声明"我想要 DNSSEC 响应" |
7.4 NSEC vs NSEC3
两者都用于证明"某个名称不存在":
| 机制 | 原理 | 隐私 | 缺点 |
|---|---|---|---|
| NSEC | 返回"前一个存在名称 → 下一个存在名称" | 差(可枚举所有名称) | 区域遍历攻击 |
| NSEC3 | 返回哈希后的名称范围 | 好(哈希后无法直接枚举) | 计算开销大 |
八、dig 命令高级用法
8.1 常用标志
bash
# 只显示 IP 地址
dig +short www.example.com
# 跟踪完整授权链(从根开始)
dig +trace www.example.com
# 请求 DNSSEC 记录
dig +dnssec www.example.com
# 禁用 DNSSEC 验证(绕过 SERVFAIL)
dig +cd www.example.com
# 只显示 Answer 段
dig +noall +answer www.example.com
# 强制 TCP 传输
dig +tcp example.com
# 设置 EDNS 缓冲区大小
dig +bufsize=1232 example.com
# 显示查询统计
dig +stats example.com
8.2 查询特定记录类型
bash
dig example.com MX # 邮件服务器
dig example.com TXT # 文本记录
dig example.com NS # 权威服务器
dig example.com SOA # 区域管理信息
dig example.com CAA # 证书授权
dig example.com SRV # 服务发现
dig example.com ANY # 所有记录(大多数服务器已禁用)
8.3 区域传送
bash
# 完整区域传送(通常被禁止,除非授权)
dig AXFR example.com @ns1.example.com
# 增量区域传送
dig IXFR=2026010101 example.com @ns1.example.com
8.4 反向解析
bash
# IPv4 反向解析
dig -x 8.8.8.8
# IPv6 反向解析
dig -x 2001:4860:4860::8888
8.5 批量查询与传播检查
bash
# 检查多个递归解析器的解析结果(验证传播)
for resolver in 8.8.8.8 1.1.1.1 9.9.9.9; do
echo "=== $resolver ==="
dig @$resolver +short api.example.com
done
# 查询权威服务器(跳过缓存)
AUTH_NS=$(dig NS example.com +short | head -1)
dig @$AUTH_NS www.example.com
九、DNS 排障实战场景
9.1 SERVFAIL 排查
SERVFAIL 最常见的原因是 DNSSEC 验证失败:
bash
# 第一步:正常查询返回 SERVFAIL
dig www.example.com @8.8.8.8
# ;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL
# 第二步:用 +cd 绕过 DNSSEC 验证
dig +cd www.example.com @8.8.8.8
# 如果 +cd 能返回结果 → DNSSEC 是原因
# 第三步:验证 DNSSEC 信任链
dig +trace +dnssec www.example.com
# 第四步:用 delv 逐步验证
delv +vtrace www.example.com
9.2 NXDOMAIN 排查
bash
# 检查是否是缓存导致的延迟(查看 Authority 段的 SOA)
dig deleted-record.example.com
# 查看否定缓存 TTL(SOA 的 Minimum 字段)
dig example.com SOA +short
# ns1 admin 20260101 3600 900 604800 300
# ^^^ 否定缓存 300 秒
# 直接查询权威服务器(跳过缓存)
AUTH_NS=$(dig NS example.com +short | head -1)
dig @$AUTH_NS target.example.com
9.3 TTL 过期 / 传播延迟
bash
# 查看剩余 TTL(从缓存中倒计时)
dig api.example.com +noall +answer
# api.example.com. 1423 IN A 192.168.1.100
# ^^^^ 剩余缓存秒数
# 变更记录的标准流程:
# 1. 变更前 24-48 小时,将 TTL 降至 300
# 2. 等待旧 TTL 过期
# 3. 修改记录值
# 4. 验证传播
# 5. 恢复 TTL
9.4 Split-Horizon DNS 检测
bash
# 内部解析器返回内网 IP
dig internal-app.example.com
# 10.0.1.50
# 外部解析器返回公网 IP
dig @8.8.8.8 internal-app.example.com
# 203.0.113.10
9.5 Wireshark 过滤器速查
text
dns # 所有 DNS 流量
dns.flags.response == 0 # 仅查询
dns.flags.response == 1 # 仅响应
dns.flags.rcode == 0 # NOERROR
dns.flags.rcode == 2 # SERVFAIL
dns.flags.rcode == 3 # NXDOMAIN
dns.qry.type == 1 # A 记录
dns.qry.type == 28 # AAAA 记录
dns.qry.name contains "example.com"
dns.time > 0.1 # 慢响应(>100ms)
十、DNS Zone 文件格式
10.1 完整示例
bind
$ORIGIN example.com.
$TTL 3600 ; 默认 TTL:1 小时
@ IN SOA ns1.example.com. hostmaster.example.com. (
2026061401 ; Serial(YYYYMMDDnn)
3600 ; Refresh:1 小时
600 ; Retry:10 分钟
604800 ; Expire:1 周
300 ) ; Minimum / Negative Cache:5 分钟
; 域名服务器
@ IN NS ns1.example.com.
@ IN NS ns2.example.net.
; Glue 记录(NS 的 A/AAAA)
ns1 IN A 192.0.2.1
ns1 IN AAAA 2001:db8::1
ns2 IN A 198.51.100.1
; 邮件交换
@ IN MX 10 mail.example.com.
@ IN MX 20 backup-mail.example.net.
; A 和 AAAA 记录
@ IN A 203.0.113.50
@ IN AAAA 2001:db8:1::50
www IN A 203.0.113.50
mail IN A 203.0.113.20
api IN A 203.0.113.100
; CNAME 别名
blog IN CNAME pages.cdn.example.net.
; TXT 记录(SPF、验证)
@ IN TXT "v=spf1 mx -all"
@ IN TXT "google-site-verification=abc123"
; SRV 记录
_sip._tcp IN SRV 10 5 5060 sipserver.example.com.
; CAA 记录
@ IN CAA 0 issue "letsencrypt.org"
@ IN CAA 0 issuewild ";"
10.2 Zone 文件关键规则
| 规则 | 说明 |
|---|---|
| 末尾句点 | ns1.example.com. 是绝对域名;缺少句点则加上 $ORIGIN 后缀 |
| CNAME 限制 | 有 CNAME 的名称不能有其他记录 |
| SOA 邮箱 | hostmaster.example.com. 对应 hostmaster@example.com |
| Serial 格式 | YYYYMMDDnn,每天最多改 99 次 |
| TTL 继承 | 无显式 TTL 的记录使用 $TTL 默认值 |
10.3 验证 Zone 文件
bash
# BIND 验证工具
named-checkzone example.com /var/named/example.com.zone
# NSD 验证工具
nsd-checkzone example.com example.com.zone
十一、DNS 常见攻击与防御
11.1 DNS 放大攻击
原理:攻击者伪造源 IP(受害者的 IP),向开放解析器发送小查询,解析器返回大响应淹没受害者。
bash
# 检测异常 DNS 响应流量
tcpdump -i eth0 -n 'udp port 53 and udp[10] & 0x80 != 0' | wc -l
# ANY 查询产生极大响应(常被滥用)
dig ANY isc.org @open-resolver
防御:关闭开放递归、限速、部署 BCP38 反欺骗过滤。
11.2 DNS 缓存投毒
原理:攻击者猜中事务 ID 和源端口,向递归解析器注入伪造响应。
bash
# 对比多个解析器的结果(不一致则可能被投毒)
for r in 8.8.8.8 1.1.1.1 9.9.9.9; do
echo -n "$r: "; dig @$r victim.com +short
done
# 用 DNSSEC 验证(投毒的响应无法通过签名验证)
dig +dnssec victim.com
防御:启用 DNSSEC、源端口随机化、0x20 大写随机化。
11.3 DNS 隧道
特征:大量随机子域名查询、超长子域、TXT 记录滥用。
bash
# 从 pcap 提取 DNS 查询域名
tshark -r dns.pcap -Y "dns.qry.name" -T fields -e dns.qry.name > queries.txt
# 检测超长子域名(合法域名通常 15-30 字符)
awk '{print length, $0}' queries.txt | sort -rn | head -20
# 检测 Base64/Hex 模式
grep -E '^[A-Za-z0-9+/=]{20,}\.' queries.txt
十二、Kubernetes DNS 服务发现
12.1 CoreDNS 架构
CoreDNS 以 Deployment 运行在 kube-system 命名空间,通过 kube-dns Service 暴露 ClusterIP,所有 Pod 的 /etc/resolv.conf 指向该 IP。
bash
# 查看 CoreDNS Pod
kubectl get pods -n kube-system -l k8s-app=kube-dns
# 查看 Corefile 配置
kubectl get configmap coredns -n kube-system -o yaml
# 查看 Pod 的 resolv.conf
kubectl exec -it my-pod -- cat /etc/resolv.conf
# nameserver 10.96.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5
12.2 默认 Corefile 解读
text
.:53 {
errors ; 错误日志
health { ; 健康检查
lameduck 5s
}
ready ; 就绪探针
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure ; Pod DNS 模式
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153 ; Prometheus 指标
forward . /etc/resolv.conf ; 外部查询转发
cache 30 ; 缓存 30 秒
loop ; 检测转发环路
reload ; 自动重载配置
loadbalance ; 随机化 A/AAAA 记录顺序
}
12.3 K8s DNS 记录格式
| 资源 | DNS 格式 | 记录类型 |
|---|---|---|
| ClusterIP Service | my-svc.my-ns.svc.cluster.local |
A → ClusterIP |
| Headless Service | my-svc.my-ns.svc.cluster.local |
A → Pod IP 列表 |
| ExternalName Service | my-svc.my-ns.svc.cluster.local |
CNAME → 外部域名 |
| SRV(命名端口) | _port._proto.my-svc.my-ns.svc.cluster.local |
SRV |
| Pod(带 hostname) | hostname.subdomain.my-ns.svc.cluster.local |
A |
| Pod(IP 格式) | 10-244-1-5.my-ns.pod.cluster.local |
A |
12.4 K8s DNS 排障
CoreDNS 环路崩溃:
bash
# 原因:节点 /etc/resolv.conf 指向 127.0.0.53(systemd-resolved)
# 修复:kubelet --resolv-conf 指向 /run/systemd/resolve/resolv.conf
ndots:5 性能问题:
bash
# ndots:5 导致 'api.github.com' 触发 4 次 search domain 查询后才查绝对域名
# 修复:对外部流量为主的 Pod 设置 ndots:2
apiVersion: v1
kind: Pod
spec:
dnsConfig:
options:
- name: ndots
value: "2"
启用 CoreDNS 查询日志:
bash
# 编辑 Corefile,添加 log 插件
kubectl -n kube-system edit configmap coredns
# .:53 {
# log ← 添加此行
# errors
# ...
# }
自定义 Stub 域(企业内网 DNS):
text
corp.internal:53 {
errors
cache 30
forward . 10.150.0.1 ; 转发到企业内网 DNS
}
十三、DNS 学习路线
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 入门 │───▶│ 进阶 │───▶│ 高级 │───▶│ 安全 │───▶│ K8s │
│ │ │ │ │ │ │ │ │ │
│ 解析流程 │ │ dig 排障 │ │ 报文格式 │ │ DNSSEC │ │ CoreDNS │
│ 记录类型 │ │ Zone 文件 │ │ EDNS │ │ DoH/DoT │ │ 服务发现 │
│ 缓存 TTL │ │ 反向解析 │ │ 抓包分析 │ │ 攻击防御 │ │ 自定义配置│
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
总结
本文核心要点:
- DNS 是层次化分布式数据库:根 → TLD → 权威,逐级授权,缓存驱动
- 递归 vs 迭代是核心概念:客户端对解析器是递归(RD=1),解析器对权威是迭代
- 报文格式决定排障能力:理解 Header 标志位(QR/AA/TC/RD/RA/RCODE)才能读懂 tcpdump 输出
- DNSSEC 防篡改,DoH/DoT 防窃听:两者互补,缺一不可
- SERVFAIL 先试
dig +cd:90% 的 SERVFAIL 是 DNSSEC 验证失败 - K8s DNS 的 ndots:5 是性能杀手 :外部流量密集的 Pod 应设置
ndots:2
📌 参考文档:
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。