---
title: DNS 协议与解析
url: https://doc.liz6.com/networking/04-DNS/01-dns-protocol-and-resolution
locale: zh
area: networking
tags:
- networking
- DNS
date: 2026-06-30
modified: 2026-07-11
description: 互联网的"电话簿"——从根服务器到递归解析器到权威服务器,一次 DNS 查询可能触发数十次委派追踪。edns0、anycast、负缓存,这些机制让 DNS 在日均万亿次查询下仍然可用。
---

# DNS 协议与解析

> 互联网的"电话簿"——从根服务器到递归解析器到权威服务器,一次 DNS 查询可能触发数十次委派追踪。edns0、anycast、负缓存,这些机制让 DNS 在日均万亿次查询下仍然可用。

## 概述

DNS 是互联网的"电话簿"——将人类可读的域名翻译为 IP 地址。设计于 1983 年（RFC 882/883），分布式和层次化的架构使其可以扩展到互联网规模：从 root 到 TLD 到 authoritative，每一层只负责自己的命名空间。递归解析器做"繁重工作"——替 stub client 完成从 root 到 answer 的全过程。DNS 最初是无安全设计的协议（UDP 明文），后来的 DNSSEC 和加密传输（DoT/DoH/DoQ）都在修补这个缺憾。

## DNS 查询流程

<svg viewBox="0 0 720 420" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="DNS 递归查询完整链路:从 stub resolver 到 authoritative 服务器">
  <defs><marker id="dnsq" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="420" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">DNS 查询流程:从 stub resolver 到 authoritative</text>

  <rect x="60" y="46" width="600" height="44" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="64" font-size="12" font-weight="700" fill="#3730a3">① stub resolver(libc / systemd-resolved)</text>
  <text x="76" y="82" font-size="11" fill="#4f46e5">读 /etc/resolv.conf → nameserver(如 127.0.0.1:53, AdGuard)</text>
  <line x1="360" y1="90" x2="360" y2="100" stroke="#475569" stroke-width="1.7" marker-end="url(#dnsq)"/>

  <rect x="60" y="100" width="600" height="44" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="76" y="118" font-size="12" font-weight="700" fill="#115e59">② recursive resolver(如 AdGuard → upstream DoH/DoT)</text>
  <text x="76" y="136" font-size="11" fill="#0f766e">查 local cache → miss → 从 Root hints 开始</text>
  <line x1="360" y1="144" x2="360" y2="154" stroke="#475569" stroke-width="1.7" marker-end="url(#dnsq)"/>

  <rect x="60" y="154" width="600" height="44" rx="8" fill="#e2e8f0" stroke="#cbd5e1"/>
  <text x="76" y="172" font-size="12" font-weight="700" fill="#334155">③ Root(a–m.root-servers.net)</text>
  <text x="76" y="190" font-size="11" fill="#475569">问 "com. NS?" → + glue: a.gtld-servers.net A 192.5.6.30</text>
  <line x1="360" y1="198" x2="360" y2="208" stroke="#475569" stroke-width="1.7" marker-end="url(#dnsq)"/>

  <rect x="60" y="208" width="600" height="44" rx="8" fill="#e2e8f0" stroke="#cbd5e1"/>
  <text x="76" y="226" font-size="12" font-weight="700" fill="#334155">④ TLD(com)</text>
  <text x="76" y="244" font-size="11" fill="#475569">问 "example.com NS?" → + glue: ns1.example.com A ...</text>
  <line x1="360" y1="252" x2="360" y2="262" stroke="#475569" stroke-width="1.7" marker-end="url(#dnsq)"/>

  <rect x="60" y="262" width="600" height="44" rx="8" fill="#dcfce7" stroke="#4ade80"/>
  <text x="76" y="280" font-size="12" font-weight="700" fill="#166534">⑤ Authoritative(ns1.example.com)</text>
  <text x="76" y="298" font-size="11" fill="#15803d">问 "www.example.com A?" → answer: 93.184.216.34(+ 可选 RRSIG/NSEC)</text>
  <line x1="360" y1="306" x2="360" y2="316" stroke="#475569" stroke-width="1.7" marker-end="url(#dnsq)"/>

  <rect x="60" y="316" width="600" height="40" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="340" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">⑥ stub resolver → 返回给客户端</text>

  <rect x="60" y="368" width="600" height="44" rx="7" fill="#eef2ff"/>
  <text x="360" y="386" text-anchor="middle" font-size="12" font-weight="600" fill="#3730a3">recursive resolver 对 stub 是一次代劳(recursive):自己逐跳发起 iterative 查询</text>
  <text x="360" y="402" text-anchor="middle" font-size="12" fill="#3730a3">(root → TLD → authoritative),拼出最终答案再返回。</text>
</svg>

三种查询类型:
- Recursive (RD=1): resolver 替客户端全程递归 (stub → recursive)
- Iterative: 一次返回 referral 或 answer (recursive → authoritative)
- Inverse: PTR 查询 (IP → name, 极少用)

## DNS Wire Format

```
DNS Message (TCP or UDP):

Header (12 bytes):
  Transaction ID (2B): 随机, 匹配 request ⇔ response
  Flags (2B):
    QR (bit 0): 0=Query, 1=Response
    OPCODE (bits 1-4): 0=Standard, 1=Inverse, 2=Status, 4=Notify, 5=Update
    AA (bit 5): Authoritative Answer
    TC (bit 6): Truncated (use TCP)
    RD (bit 7): Recursion Desired
    RA (bit 8): Recursion Available
    Z (bits 9-11): 0 (reserved)
    RCODE (bits 12-15): 0=NoError, 1=FormErr, 2=SrvFail, 3=NXDomain, 5=Refused
  QDCOUNT (2B): Questions count
  ANCOUNT (2B): Answers count
  NSCOUNT (2B): Authority records count
  ARCOUNT (2B): Additional records count

Question Section (variable):
  QNAME: label format (见下)
  QTYPE (2B): 1=A, 28=AAAA, 5=CNAME, 15=MX, 2=NS, 6=SOA, 33=SRV, ...
  QCLASS (2B): 1=IN (Internet)

Answer/Authority/Additional Sections (variable):
  每个 RR:
    NAME: label format (通常 compressed — pointer to previous name)
    TYPE (2B)
    CLASS (2B): 1=IN
    TTL (4B): cache 有效期 (秒)
    RDLENGTH (2B): RDATA 长度
    RDATA: type-specific
```

### Label Format 与 Compression

```
Label encoding:
  [length 1B][label data ...] → 长度 + 数据, 每 label 独立
  最后一个 label: length=0 (root, 空 label)

  例: www.example.com → 0x03 'w' 'w' 'w' 0x07 'e' 'x' 'a' 'm' 'p' 'l' 'e' 0x03 'c' 'o' 'm' 0x00

DNS Compression (节省空间):
  高 2 bits of length byte = 11 → 这是 pointer (不是 label!)
  pointer = 低 14 bits of offset (from DNS message start)
  例: 0xC0 0x0C → 指向 offset 12 (第一个 question 的 QNAME)

  这允许在同一消息中重复引用之前出现的 domain name
  → "example.com" 出现在 QNAME, A record 的 NAME, NS record 的 RDATA → 三次引用 → 省 ~12+12+12 = 36 bytes
```

## 关键 Record Types

```
A (type 1):  RDATA = 4-byte IPv4 address
AAAA (type 28): RDATA = 16-byte IPv6 address
CNAME (type 5): RDATA = canonical name (label format) — 别名解析
  → 限制: CNAME 不能与其他类型共存 (除了 RRSIG/NSEC)
    即: 如果 www.example.com 是 CNAME, 它不能同时有 A record
MX (type 15): RDATA = preference (2B) + exchange (label format)
  → 优先级: 低 preference = 高优先级
SOA (type 6): RDATA = mname + rname + serial + refresh + retry + expire + minimum TTL
  → zone 的权威信息: serial 用于 zone transfer 版本比较
NS (type 2): RDATA = nameserver (label format) — 授权此 zone 的 nameserver
SRV (type 33): RDATA = priority + weight + port + target
  → _service._proto.name → 目标 host:port
  例: _sip._tcp.example.com SRV 10 60 5060 sipserver.example.com
PTR (type 12): RDATA = name (label format) — reverse lookup (IP → name)
  → in-addr.arpa for IPv4, ip6.arpa for IPv6
TXT (type 16): RDATA = one or more text strings (length-prefixed)
  → SPF, DKIM, DMARC, verification tokens
CAA (type 257): RDATA = flags + tag + value
  → 限制哪些 CA 可以为此 domain 签发证书
  → 例: 0 issue "letsencrypt.org" → 只有 Let's Encrypt 能签
```

## EDNS0 (RFC 6891)

```
传统 DNS 限制: UDP payload 最大 512B → 大型应答 (DNSSEC RRSIG) 需要 TCP fallback

EDNS0: 在 Additional section 加 OPT pseudo-RR:
  NAME=0 (root), TYPE=41 (OPT), CLASS=UDP payload size, TTL=flags+RCODE

  UDP payload size: 通告接收方能处理的最大 UDP 响应 (通常 1232 or 4096)
  → 减少 TCP fallback 次数
  DNSSEC OK (DO bit): client 支持 DNSSEC → server 应该附带 RRSIG

  flags: DNSSEC OK (DO bit 15)
```

## AFXR (Zone Transfer)

```
AXFR (full zone transfer):
  常用于: primary → secondary nameserver 同步

  TCP connection (zone transfer 可能 >512B):
    secondary: AXFR request (type 252, class IN) → primary
    primary: 返回 zone 中所有 RRs (SOA ... SOA, 头尾两个 SOA)

IXFR (incremental zone transfer, RFC 1995):
  只传输变化 (基于 SOA serial)
```

## 参考

- **RFC**: 1034, 1035, 6891, 3596 (AAAA), 2782 (SRV), 6844 (CAA)
- **工具**: `dig +trace`, `drill`, `kdig`, `delv`, `tcpdump -nn port 53`
- **源码**: glibc `resolv/`, `systemd-resolved`

*Keywords: DNS, A/AAAA/MX/NS/SOA/SRV/CNAME/TXT/CAA, recursive resolver, EDNS0, label compression, zone transfer*
