DNS(域名系统)所有的查询(Query)和响应(Response)都采用统一的报文格式。这一格式分为首部(Header)和数据区两大部分。

以下是详细的字段解析,这对于网络流量分析(如使用 Wireshark)或编写网络工具非常重要。

1. 报文整体结构

DNS 报文由 5 个部分组成。其中首部(Header)是固定的 12 字节,其余部分长度可变。

区域名称

描述

长度

Header (首部)

报文的核心控制信息(ID、标志位、计数器)。

12 字节

Question (问题)

正在查询的域名和类型。

可变

Answer (回答)

服务器回复的资源记录(RR)。

可变

Authority (授权)

指向权威名称服务器的记录。

可变

Additional (附加)

相关的额外信息(如权威服务器的 IP)。

可变

2. 首部 (Header) 详解

这是 DNS 报文最关键的部分,包含了事务 ID 和标志位。

A. 事务 ID (Transaction ID) - 16 bit

用于匹配请求和响应。客户端发送查询时生成一个 ID,服务器回复时必须复制该 ID。

安全注记: 这是防止 DNS 欺骗(Spoofing)的第一道防线。

B. 标志位 (Flags) - 16 bit

这 16 位控制了查询的行为和响应的状态:

QR (Query/Response, 1 bit):

0: 查询报文

1: 响应报文

Opcode (4 bits): 操作码。

0: 标准查询 (Standard Query)

1: 反向查询 (Inverse Query)

2: 服务器状态请求 (Status Request)

AA (Authoritative Answer, 1 bit): 授权回答。仅在响应中有效,1 表示回复该域名的服务器是权威服务器。

TC (Truncated, 1 bit): 截断标志。1 表示报文长度超过限制(UDP 通常为 512 字节),被截断了。

RD (Recursion Desired, 1 bit): 期望递归。客户端希望服务器进行递归查询。

RA (Recursion Available, 1 bit): 支持递归。服务器告知客户端它是否支持递归。

Z (3 bits): 保留位,必须为 0。

RCODE (Response Code, 4 bits): 响应码。

0: 无错误 (No Error)

3: 名字错误 (NXDomain) - 域名不存在

2: 服务器失败 (ServFail)

C. 计数器 (Counts) - 各 16 bit

QDCOUNT: 问题部分(Question)的记录数。

ANCOUNT: 回答部分(Answer)的资源记录数。

NSCOUNT: 授权部分(Authority)的资源记录数。

ARCOUNT: 附加部分(Additional)的资源记录数。

3. 数据区格式

A. 问题部分 (Question Section)

包含查询的具体内容。

QNAME (查询名): 变长。域名被编码为一系列标签(Label)。例如 www.google.com 会被编码为 3www6google3com0(数字代表长度,0 代表结束)。

QTYPE (查询类型): 16 bit。例如 A (1), NS (2), CNAME (5), MX (15), AAAA (28)。

QCLASS (查询类): 16 bit。通常为 IN (Internet, 值 1)。

B. 资源记录部分 (Answer, Authority, Additional)

这三个部分都采用统一的资源记录 (Resource Record, RR) 格式:

字段

长度

描述

NAME

变长

域名(通常使用压缩指针)。

TYPE

16 bit

记录类型(如 A, MX)。

CLASS

16 bit

类(通常为 IN)。

TTL

32 bit

Time To Live,生存时间(秒),用于缓存控制。

RDLENGTH

16 bit

数据长度。

RDATA

变长

具体数据(如 A 记录就是 4 字节的 IP 地址)。

4. 特殊机制:域名压缩 (Name Compression)

为了减小报文体积,DNS 报文使用了指针机制。

如果一个字节的前两位是 11 (二进制),即 0xC0,则表示这是一个指针。

接下来的 14 位表示相对于 DNS 报文起始处的偏移量 (Offset)。

这在解析 DNS 报文时非常关键,不处理压缩会导致解析错误。

以下是一个 DNS 响应报文的完整十六进制解析示例。

假设我们发起了一个查询:example.com 的 A 记录(IPv4 地址),服务器返回了结果 93.184.216.34。

1. 原始报文 (十六进制 Hex Dump)

这是你在 Wireshark 或网络抓包工具中看到的原始数据流:

AA AA 81 80 00 01 00 01 00 00 00 00 07 65 78 61

6d 70 6c 65 03 63 6f 6d 00 00 01 00 01 C0 0C 00

01 00 01 00 00 0E 10 00 04 5D B8 D8 22

2. 逐字节详细解析

我们将这段十六进制数据拆解为 Header、Question 和 Answer 三部分。

第一部分:首部 (Header) - 固定 12 字节

AA AA 81 80 00 01 00 01 00 00 00 00

十六进制

字段名

值/解释

AA AA

Transaction ID

0xAAAA (43690)。用于匹配请求和响应。

81 80

Flags

二进制 1000 0001 1000 0000。

• QR=1 (响应)

• Opcode=0 (标准查询)

• RD=1 (希望递归)

• RA=1 (支持递归)

• RCODE=0 (无错误) |

| 00 01 | Questions | 1 个问题。 |

| 00 01 | Answer RRs | 1 个回答。 |

| 00 00 | Authority RRs | 0 个授权记录。 |

| 00 00 | Additional RRs | 0 个附加记录。 |

第二部分:问题区 (Question Section)

07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01 00 01

这里存储了我们查询的域名 example.com。

十六进制

ASCII / 值

解释

07

(长度)

Label Length: 接下来 7 个字节是标签。

65 78 ... 65

example

Label: 域名的第一部分。

03

(长度)

Label Length: 接下来 3 个字节是标签。

63 6f 6d

com

Label: 域名的后缀。

00

(结束符)

Zero byte: 域名结束(根域)。

00 01

1

QTYPE: A 记录 (Host Address)。

00 01

1

QCLASS: IN (Internet)。

第三部分:回答区 (Answer Section)

C0 0C 00 01 00 01 00 00 0E 10 00 04 5D B8 D8 22

这里包含了服务器返回的 IP 地址。注意开头的指针压缩。

十六进制

字段

详细解析

C0 0C

Name (Pointer)

关键点!

C0 (二进制 11000000) 标志这是一个指针。

0C (十进制 12) 是偏移量。

它告诉程序:“去报文第 12 个字节处找域名”。

第 12 字节正好是 Question 部分的 07 example...,从而避免重复传输域名。 |

| 00 01 | Type | A 记录 (IPv4 地址)。 |

| 00 01 | Class | IN (Internet)。 |

| 00 00 0E 10 | TTL | 0x0E10 = 十进制 3600 秒 (1 小时)。

告诉客户端可以将此结果缓存 1 小时。 |

| 00 04 | Data Length | 数据长度 4 字节 (IPv4 地址的标准长度)。 |

| 5D B8 D8 22 | Address | IP 地址数据。

5D = 93

B8 = 184

D8 = 216

22 = 34

结果:93.184.216.34 |

总结:如何手动分析?

看开头 12 字节:确认状态(是查询还是响应?有几个回答?)。

找域名边界:域名不是以点分隔的,而是 长度+内容,并以 00 结尾。

注意 C0**:如果在 Name 字段看到 C0 开头,千万不要把它当字符解析,它是跳转指针**。

IP 地址转换:Answer 部分最后的十六进制直接转十进制就是 IP。