Skip to main content

DNS 实战系列(一): 报文解析

本篇博客将会根据 RFC 的内容,实战分析在查询 google.com 时 DNS 报文中的每个字节。

准备工作

我们可以用 nc 命令来获取查询报文和响应报文的数据包

nc -u -l -p 1053 > query_packet.txt
# 新开一个终端,等待dig报错后,ctrl-c 掉 nc
dig +retry=0 -p 1053 @127.0.0.1 +noedns google.com
hexdump -C query_packet.txt
# 执行一两秒后断开
nc -u 208.67.222.123 5353 < query_packet.txt > response_packet.txt
hexdump -C response_packet.txt

你可以在这里下载本篇所用示例 query_packet.txt , response_packet.txt

RFC1035 中, DNS 报文格式如下。

在本篇博客的实例中,查询报文包含 Header 和 Question,响应报文包含 Header、Question 和 Answer。

Query Packet

我们使用 hexdump 来打印刚才生成的查询报文

hexdump -C query_packet.txt
00000000  85 57 01 20 00 01 00 00  00 00 00 00 06 67 6f 6f  |.W. .........goo|
00000010  67 6c 65 03 63 6f 6d 00  00 01 00 01              |gle.com.....|
0000001c

根据DNS 报文格式,我们要分析的查询报文大致可以分为两个区域: Header 和 Question.

Header 的长度为 12 bytes, 剩余部分则全部为 Question 区域, 如下图:

Query Packet: Header Section

RFC1035 中, DNS 报文 Header 区域格式如下

在这里解释一下 headers 的 12 个 bytes:

  • ID: 85 57 一般以 10 进制 id 34135 表示,对于查询和应答使用相同的 id,长度 2 字节(16 bits)
  • QRRCODE 占两个字节大小,他们的十六进制 0x01 20 转换成二进制是 0000 0001 0010 0000
    • QR: 0,0 代表查询,1代表响应,长度 1 bit
    • OPCODE: 0000, 0代表标准查询,长度 4 bits
    • AA: 0, 查询时可忽略,长度 1 bit
    • TC: 0, 查询时可忽略,长度 1 bit
    • RD: 1, 希望 DNS 服务器使用递归解析,长度 1 bit
    • RA: 0, 查询时可忽略,长度 1 bit
    • Z: 010, 本来是保留的3个bits,现在分为 Z/AD/CD 三个 flag,这里知道是 dnssec 相关即可
    • RCODE: 0000, 查询时可忽略,长度 4 bits
  • QDCOUNT : 0x0001 -> 0000 0001 请求查询的问题数,一般为1,长度16 bits (2字节)
  • ANCOUNT: 0x0000 -> 0000 0000 查询时可忽略,长度 16 bits (2字节)
  • NSCOUNT: 0x0000 -> 0000 0000 查询时可忽略,长度 16 bits (2字节)
  • ARCOUNT: 0x0000 -> 0000 0000 查询时可忽略,长度 16 bits (2字节)

我们可以发现,在查询报文的 header 中,ANCOUNT、NSCOUNT、ARCOUNT (第7到12字节) 总是0,这可以作为我们判断一个 DNS 报文是否为查询报文的依据;另一个依据是,查询报文比较短,到 Question 就结束了,没有 Answer 区域。

Query Packet: Question Section

根据 RFC1035, Question 区域格式如下

                    qname                    qtype  qclass
hex		06 67 6f 6f 67 6c 65 03 63 6f 6d 00  00 01  00 01
ascii       g  o  o  g  l  e     c  o  m
dec		6                     3           0      1      1

我们可以看到查询 google.com 并没有用 . 来分割,而是用数字。

  • qname 的表现形式是: 长度 - ascii 码对应的十六进制 - 长度 - ascii 码对应的十六进制 - 0 ascii 码对应的十六进制非常容易分辨,我们都知道域名是由字母组成的,A 的为 41, z 为 7A,在 41 之后十六进制一般就是域名的组成部分,比较小的字节就是代表了域名分割段长度了。当然还会有额外情况,比如 - 符号的十六进制为 2D
  • qtype 0001 1 代表请求A记录
  • qclass 0001 1 代表向网络查询 (IN)

到此为止,查询报文就看完了。

Response Packet

同样使用 hexdump 来打印响应报文

hexdump -C response_packet.txt
00000000  85 57 81 80 00 01 00 01  00 00 00 00 06 67 6f 6f  |.W...........goo|
00000010  67 6c 65 03 63 6f 6d 00  00 01 00 01 c0 0c 00 01  |gle.com.........|
00000020  00 01 00 00 01 2c 00 04  8e fb 2b 0e              |.....,....+.|
0000002c

Response Packet: Header Section

响应报文的 header 部分格式与查询报文相同

查询报文的 header 部分为 85 57 01 20 00 01 00 00 00 00 00 00

响应报文的 header 部分为 85 57 81 80 00 01 00 01 00 00 00 00

只有第 3、4字节 (QR到RCODE) 和第8个字节(ANCOUNT)不同,我们可以详细对比一下他们的区别

query packet answer packet
QR 到 RCODE 两个字节 0x0120 -> 0000 0001 0010 0000 0x8180 -> 1000 0001 1000 0000
QR query/response 0代表查询 1代表响应
OPCODE 0000 代表标准查询 0000 代表标准查询
AA (Authoritative Answer) 0 查询时可忽略 0 代表不是权威服务器返回的结果
TC (TrunCation) 0 查询时可忽略 0表示没有超出UDP报文长度
RD (Recursion Desired) 1 希望 DNS 服务器使用递归解析 由查询报文设置,与其相同
RA (Recursion Available) 0 查询时可忽略 1DNS服务器使用了递归查询
Z 010 dnssec 相关 000 DNS服务器没有响应 dnssec
RCODE 0000 查询时可忽略,长度 4 bits 0000 代表了没有错误
ANCOUNT (answer resource records) 0000 0000查询时可忽略 0000 0001 有一个响应结果

Z 本来是保留的,总是置为0,现在分为 Z/AD/CD 三个 flag,这里知道是 dnssec 相关即可,不需要关心。如果你很在意,可以看 rfc2535 https://datatracker.ietf.org/doc/html/rfc2535#section-6.1

Response Packet: Question Section

question 部分和请求报文中完全相同,这里不再重复说明。

Response Packet: Answer Section

Answer 区域也叫资源记录(RR)区域,我们同样可以对照 RFC1035 来看

       NAME    TYPE   CLASS         TTL      RDLENGTH      RDATA
      ------  ------  ------  --------------  ------  --------------
HEX   c0  0c  00  01  00  01  00  00  01  2c  00  04  8e   fb  2b  0e
DEC   192 12       1       1             293       4  142 251  43  14

NAME: 总是以 c00c开头,我们可以看一下具体是怎么计算出来的:

c00c 的二进制表现形式为 1100000000001100

  • 11 最高位总是11
  • 00000000001100 转为十六进制是 0x12,这代表了一个偏移量,或者说指针。当响应报文中重复出现一些域名,我们可以利用已经存在的数据来减少数据包大小。我们已经知道了 header 区域的指针是 0x0 ~ 0x11,因此可以从 0x12 开始计算,这正好是 question 区域的初始指针。

接下里看剩余的部分:

  • TYPE: 1 代表了 A类型
  • CLASS: 1 代表了 IN (Internet)
  • TTL: 0000012c 通常以十进制 293表示,单位是秒
  • RDLENGTH: 0004 ip 占用的资源的长度,这里是 4字节
  • RDATA: 8e fb 2b 0e 响应的数据,ipv4 一般是以十进制142 251 43 14的形式表示

至此,请求报文和响应报文我们都已经手撕完成。

对你可能有帮助的资料