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,似乎不能解析吧,没深入研究。