29 January 2015
这几天分析了一下DNS协议的内容,用Go语言。

一直有一个愿望,能把知道的东西的原理搞明白:计算机网络、操作系统等等等等。今天好好研究了研究DNS协议。

DNS协议是应用层协议,一般是基于UDP协议,不过我看了Golangnet 包里相关源码用的是TCP协议传输。端口是53,这次写代码实现了一边DNS客户端,估计短时间内忘不了这个端口号了。

了解DNS这个协议,从书本上我基本没看懂过,书里我基本就记着DNS是递归查询的,如果查询服务器上没有找到相应的记录,则递归得去查询上一级服务器。还有就是根域名服务器在美国这些,反正我就是从来没搞懂过。

昨天在网上找到一篇文章[2],上面提到说学习协议还是借助WireShark比较好,Linux之下就是通过tcpdump和WireShark结合进行抓取数据包。具体的方法可以参考我之前的文章。而模拟DNS请求,Linux下是host www.cyeam.com,Windows下是nslookup www.cyeam.com

1	0.000000	10.0.1.23	10.0.1.1	DNS	73	Standard query 0x11ac  A www.cyeam.com
2	0.061714	10.0.1.1	10.0.1.23	DNS	123	Standard query response 0x11ac  CNAME vm68h.x.incapdns.net A 149.126.77.152

我的电脑IP是10.0.1.23,路由器IP是10.0.1.1,查询网站www.cyeam.com的DNS记录。

抓到的请求内容如下:

Domain Name System (query)
    [Response In: 2]
    Transaction ID: 0x11ac
    Flags: 0x0100 Standard query
	0... .... .... .... = Response: Message is a query
	.000 0... .... .... = Opcode: Standard query (0)
	.... ..0. .... .... = Truncated: Message is not truncated
	.... ...1 .... .... = Recursion desired: Do query recursively
	.... .... .0.. .... = Z: reserved (0)
	.... .... ...0 .... = Non-authenticated data: Unacceptable
    Questions: 1
    Answer RRs: 0
    Authority RRs: 0
    Additional RRs: 0
    Queries
	www.cyeam.com: type A, class IN
	    Name: www.cyeam.com
	    Type: A (Host address)
	    Class: IN (0x0001)

DNS协议头是12个字节,共两项内容,每个都是两个字节。

  • Transaction ID。是客户端随机生成的一个无符号整数,范围是0~2^16。在响应头里面会返回这个值用于校验。如果值不相等,丢弃响应内容。
  • Flags。具体每个位的含义这里就不复述了,上面抓到请来看,是标记了递归调用这一项。
  • 查询数Questions。一次DNS请求能查询多个域名,这里只查了一个域名,所以是1。
  • Answer是响应数,请求里面响应数肯定是0。剩下两个目前没有用到,还没有研究具体含义。

协议头之后,就是消息体了。在请求里面,消息体是请求查询的内容,而在响应里面,则是查询到的内容。消息体由三方面组成:域名、类别、分类:

  • Name。我们要查询www.cyeam.com这个域名,分别用.将域名进行了切割,而DNS协议设计的时候,用了一个巧妙的方法,将点去掉,而是在每一部分之前加上这个部分占的位数。还有,就是在最后补上一个字节的0,表示Name的结束。那么域名就会变成3www5cyeam3com0这样。
  • Type。查询类型,有A、CNAME等。
  • 查询分类,好像见到的都是IN。这两个值都是两个字节。

头是12个字节,消息体用了15+2+2=19个字节,一个是31字节。下面的表格是16进制计数的,一行是16个,一共也是15个。大家也可以根据协议头和消息体的内容计算看看。

0000   11 ac 01 00 00 01 00 00 00 00 00 00 03 77 77 77
0010   05 63 79 65 61 6d 03 63 6f 6d 00 00 01 00 01

响应的头如下:

Domain Name System (response)
    [Request In: 1]
    [Time: 0.061714000 seconds]
    Transaction ID: 0x11ac
    Flags: 0x8180 Standard query response, No error
	1... .... .... .... = Response: Message is a response
	.000 0... .... .... = Opcode: Standard query (0)
	.... .0.. .... .... = Authoritative: Server is not an authority for domain
	.... ..0. .... .... = Truncated: Message is not truncated
	.... ...1 .... .... = Recursion desired: Do query recursively
	.... .... 1... .... = Recursion available: Server can do recursive queries
	.... .... .0.. .... = Z: reserved (0)
	.... .... ..0. .... = Answer authenticated: Answer/authority portion was not authenticated by the server
	.... .... ...0 .... = Non-authenticated data: Unacceptable
	.... .... .... 0000 = Reply code: No error (0)
    Questions: 1
    Answer RRs: 2
    Authority RRs: 0
    Additional RRs: 0
    Queries
	www.cyeam.com: type A, class IN
	    Name: www.cyeam.com
	    Type: A (Host address)
	    Class: IN (0x0001)
    Answers
	www.cyeam.com: type CNAME, class IN, cname vm68h.x.incapdns.net
	    Name: www.cyeam.com
	    Type: CNAME (Canonical name for an alias)
	    Class: IN (0x0001)
	    Time to live: 10 seconds
	    Data length: 22
	    Primaryname: vm68h.x.incapdns.net
	vm68h.x.incapdns.net: type A, class IN, addr 149.126.77.152
	    Name: vm68h.x.incapdns.net
	    Type: A (Host address)
	    Class: IN (0x0001)
	    Time to live: 3 minutes, 47 seconds
	    Data length: 4
	    Addr: 149.126.77.152 (149.126.77.152)

协议头的含义和请求的一致。需要注意的是,Answer变成了2,因为找到了两条记录。消息体内也包含了请求的Queries的内容,含义和请求里的是一样的。多出来的就是响应的内容。

查询的结果由5部分组成:

  • Name,也就是查询的域名,两个字节。
  • Type,记录类型,第一条是CNAME类型。对应的值是5。两个字节。
  • Class,分类,两个字节,值是1(IN)。
  • Data length,数据长度。两个字节。
  • Primaryname,这是重点,主要就是在查这个东西。如果上面Type是CNAME类型,那么解析的方式要按照域名的方式解析,编码方式和上面请求里发送域名的方式一样。如果是A,那么解要按照IP解析,Data length也对应的是4(IPv4)。一个字节是一个IP里的块,然后用点连起来。16进制分别是95、7e、4d、98,那么换成能看懂的就是149.126.77.152。

    0000 11 ac 81 80 00 01 00 02 00 00 00 00 03 77 77 77 0010 05 63 79 65 61 6d 03 63 6f 6d 00 00 01 00 01 c0 0020 0c 00 05 00 01 00 00 00 0a 00 16 05 76 6d 36 38 0030 68 01 78 08 69 6e 63 61 70 64 6e 73 03 6e 65 74 0040 00 c0 2b 00 01 00 01 00 00 00 e3 00 04 95 7e 4d 0050 98

上面抓到的数据包,可以从这里访问到。本来是想写Golang语言实现的,太晚了,明天再写。


参考文献
  1. 《计算机网络(第五版)》谢希仁
  2. What does a DNS request look like? - serverfault

原文链接:DNS协议分析,转载请注明来源!

EOF