DNS
是域名解析协议,服务端实现很多,不过很多时候我需要的客户端程序。用dns来解析IP在很多地方都会,他很重要,但经常被刻意的忽略。这里记录一些我自
己实现dns客户端的一些资料和过程。
1、
rfc版本
我先附上主要参考的rfc版本。
2、研究过程
使用wireshark跟踪port 53端口,然后ping
记录下发送和接收的报文。
3、解码代码
$(wireshark_src)\epan\dissectors\packet-dns.c
中dns_get_name函数
所有的
DNS,不论是发送和接收到,都遵循相同的消息格式,如
/* MESSAGE FORMAT*/
/*
+---------------------+
| Header |
+---------------------+
| Question | the question for the name server
+---------------------+
| Answer | RRs answering the question
+---------------------+
| Authority | RRs pointing toward an authority
+---------------------+
| Additional | RRs holding additional information
+---------------------+
*/
其中消息头部的格式比较固定,如下:
/* HEADER FORMAT*/
/*
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 |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
在填充和解析报文的时候,必须注意,这里报文所有的数据
格式都是大端格式,而且协议描述的flag部分非常容易出错,必须予以强调。
假设QR=1,其他为0,那么这个字节的值为0x80;如果RD=1,而其他为0,那么这个字节的值为0x01。我们假
设QR字段到RCODE字段的这两个字节中,QR=1,RD=1,RC=1,其他部分都是0,那么这2个字节的值为0x81 , 0x80。
16bit的标志字段 如下:
QR:0
表示查询报文,1表示响应报文
Opcode:通常值为0(标准查询),其他值为1(反向查询)和2(服务器状态请求)。
AA:表示授权回
答(authoritative answer).
TC:表示可截断的(truncated)
RD:表示期望递归
RA:表示可用
递归
随后3bit必须为0
Rcode:返回码,通常为0(没有差错)和3(名字差错)
后面4个16bit字段说明最后4个变长字
段中包含的条目数。
仔细理解
上面这段描述,才能理解各个标志位的含义。
HEADER
中的QDCOUNT只是question节的数量,ANCOUNT为answer节的数量,NSCOUNT为auth节的数量,ARCOUNT为
addition节的数量。这几个2字节构成的整型也是大端格式。
question主要是由客户端请求来填的,他的格式为
/* QUESTION FORMAT */
/*
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| |
/ QNAME /
/ /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QTYPE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QCLASS |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
一般来说QTYPE如果是域名解析的话,为T_A =1
,当然也有T_MX(mail exchange)。在所以名称字段,比较特殊,比如这里的QNAME,其他以字符串形式出现的都是一样的规则。
比如,他会编成这样的形式:
0x03 0x77 0x77 0x77 0x04
0x63 0x73 0x64 0x6e 0x03 0x6e 0x65 0x74 0x00
和其他字符串一样,这个格式仍然以0x00结尾,不过中间确实不同,他是len +
data来混合编码,每个字符串都是以长度开始,然后接着内容。比如"www",他的字符值为0x77 ,
在前头是0x03开始,表示后面紧跟着3个字符。
还有一种格式,是使用指针,准确地说应该是偏移量,如下:
0xc0 0x0c 0x00 0x01 0x00 0x01 0x00 0x00 0x02 0x1a 0x00
0x04
这个是DNS服务端返回报文中地一
段。0xc0开头,表示这个是指针,后面字节跟着地是偏移量。这个偏移量应该是这样计算的:0xc0 &
(~0xc0),这样得到偏移量的高字节,然后和0x0c拼接成一个大端格式短整型。这偏移量是相对于报文起点的偏移量。
我打个比方,比如偏移量超过256,是300
,他的小端格式为0x012c,那么他在内存中的印象应该是这样的:
0xc1 0x2c。
ANSWER/AUTH/ADDS的几个报文格式都是一样:
/* ANSWER/AUTH/ADDI FORMAT */
/*
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| |
/ /
/ NAME /
| |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TYPE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| CLASS |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TTL |
| |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| RDLENGTH |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/ RDATA /
/ /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
只需要关注的是,每个TYPE都会有自己格式填充在
RDATA中,RDATA实际上存放的是一个缓冲区。根据具体TYPE,来解析他的含义,一般说来,我们比较关注的A/CNAME/MX等格式,其他都可
以忽略。
linux下提供
res_query可以发送请求报文,但返回内容却需要自己解析,没发意思,跟自己写个库都差不多了。解析部分可以参看wireshark,做的很全。
win32也有个函数,不过也差不多。按道理ping应
该会有相应的代码,可惜我没有找到相关。最紧密的是gethostbyname,似乎不能解析吧,没深入研究。