[嵌入式开发模块]DNS(Domain Name System)模块 附报文格式和请求过程浅析

前言

现代人在网上冲浪时不停地在和DNS系统打着交道。

你在浏览器的地址栏中输入一个网站名比如www.baidu.com并回车时,浏览器第一个要干的事情就是通过DNS系统把这个文本的域名解析为对应的IP地址,因为IP报文在网络上传递时使用IP地址来确定接收方和发送方,而不是这一长串文本。

这么设计有几个方面考虑:从用户的角度来说,一串有意义的文本总比那4个字节的数字好记得多,相信没人会愿意像背电话号码一样把各个网站的IP地址都直接背下来;从网络通信的角度来说,为了节省流量、降低延迟,要尽可能减少每个报文的额外信息,4个字节的IPv4地址自然比可能多达两百多字节的域名要省的多;从服务器的角度来说,使用域名系统后,可以实现IP地址与服务器地址的解耦,自己的IP地址可以随时变化,反正变化后只要通知下DNS服务器,这样下次用户要连接服务器时就会解析同一个域名到新的IP地址上。另外还可以实现一个域名对应多个IP地址,这样通过一些技巧,不同的用户就可以均匀连接到几个不同的IP去,其实就是负载均衡的一种实现。还有其他用处就不详说了。

这篇博文给出了我写的一个 DNS报文解析/构造 模块。对于大的网络协议栈来说肯定是自带DNS这么基础的功能的,但是谁让我们这是嵌入式呢,各种轮子都要自己造,用个轮子也得各种知其所以然。于是为了大家能够上手使用这个模块,我在这里把DNS的相关知识也直接附上,希望讲的足够清晰,帮助更多的人。

当然要是你连一点基础知识也来不及补的话,也可以直接荡下模块源码,然后就直接翻到使用示例那看怎么用就好了。
更方便地,如果你用的W5500的io库,你可以直接用我封装好的DNSClient模块的接口:
https://blog.csdn.net/lin_strong/article/details/101936463

DNS速成

这部分内容大量参考https://jocent.me/2017/06/18/dns-protocol-principle.html
相当于是其上的一个精简,如想更深入一步了解DNS系统,可以点进去看。

域名结构

先明确一点,我们平常在浏览器上方看到的那一行比如:
https://baike.baidu.com/item/dns/427444
这个是URL,统一资源定位符,其中包含很多信息。

https 指这个资源要通过https协议访问
baike.baidu.com 才是服务器的域名
而后面那一长串则是需要获取的具体资源。可以想象服务器是一个文件系统,上面这一个URL的意义就是:
用HTTPS协议访问baike.baidu.com这个服务器,访问其上的item文件夹下的dns文件夹下的427444这个文件。

当然,URL不止由这些部分构成,但一般也就知道这些就够了,其他不是这篇文章的重点。

因特网在命名的时候采用了层次结构的命名方法。每一个域名(本文只讨论英文域名)都是一个label,用字母(A-Z,a-z,不分大小写)、数字(0-9)和连接符(-)组成,域名总长度不能超过255个字符,它由点号分割成一个个的label,每个label应该在63个字符之内,每个label都可以看成一个层次的域名。级别最低的域名写在左边,级别最高的域名写在右边。

所以baike.baidu.com被点号分层了3级域名,顶级域名com,二级域名baidu,三级域名baike。

整个DNS系统实际上是由分布全球的无数DNS服务器分层次组成的,上级服务器知道下级服务器的域名以及对应地址。根域名服务器有着固定分配的IP地址,根域名服务器知道各个顶级域名服务器的地址,顶级知道二级,以此类推。在请求baike.baidu.com这个域名的地址时,完整的,会先去根域名服务器要到com这个域名服务器的地址,然后要到baidu的,然后最后从baidu域名服务器上知道baike这个子域名的IP地址完成解析。

解析过程

当客户端想要解析一个域名时,它需要先构造一个DNS请求报文(有些词看不懂没事,看完下一章报文结构后再回来看就看懂了),QR位自然要设为0,ID随便设一个,然后对于最普通的这种域名转IP地址的DNS请求,我们选择标准查询(实际上DNS系统不只能解析域名,它还有很多其他功能,但我们这里只关心解析域名);另外,一般我们会选择递归查询,这样这个DNS服务器就会帮你完成上述那个递归的一级一级查询的整个过程,最后直接把查询结果返回给你,如果是非递归的话,你就得按上述过程自己一个个去问;最后在Queries区域带上要查询的域名,查询类IN,类型A(即IPv4地址),这样就构造好了。

DNS默认是基于UDP通信,所以继续往下看之前起码你要先学会怎么收发UDP报文,这个自己学去。。。

默认的,DNS服务器会监听在UDP 53端口。为了完成解析,我们要把构造好的报文发送给服务器的这个UDP端口,然后如果正常运作的话,DNS服务器应该会答复你一个答复报文。这个答复报文的QR位自然是1,ID与你的请求报文一致;然后在Answers区域应该会有对应答复的IPv4地址。

注意:因为之前说的那些服务器端的考虑,答复中可能有多个地址记录。而且这些地址记录对应的直接名字可能并不是你请求的那一个,不过这种情况下会有CNAME类型的记录告诉你之前那个询问的域名有个别名。所以答复也许是这样的 - baike.baidu.com CNAME aaaaa.com , baike.baidu.com CNAME bbbbb.com, aaaaa.com A XXX.XXX.XXX.XXX,bbbbb.com A XXX.XXX.XXX.XXX。意思就是说这个域名有两个别名,然后它们分别的IP地址。

报文格式

头部

  • 会话标识(2字节):是DNS报文的ID标识,对于请求报文和其对应的应答报文,这个字段是相同的,通过它可以区分DNS应答报文是哪个请求的响应
  • 标志(2字节):

    QR(1bit) 查询/响应标志,0为查询,1为响应
    opcode(4bit) 0表示标准查询,1表示反向查询,2表示服务器状态请求
    AA(1bit) 表示授权回答
    TC(1bit) 表示可截断的
    RD(1bit) 表示期望递归
    RA(1bit) 表示可用递归
    rcode(4bit) 表示返回码,0表示没有差错,3表示名字差错,2表示服务器错误(Server Failure)
  • 数量字段(总共8字节):Questions、Answer RRs、Authority RRs、Additional RRs 各自表示后面的四个区域的数目。Questions表示查询问题区域节的数量,Answers表示回答区域的数量,Authoritative namesversers表示授权区域的数量,Additional recoreds表示附加区域的数量

Queries区域

  • 查询名:长度不固定,且不使用填充字节,一般该字段表示的就是需要查询的域名(如果是反向查询,则为IP,反向查询即由IP地址反查域名)
  • 查询类型:列出一些常用的
类型 助记符 说明
1 A 由域名获得IPv4地址
2 NS 查询域名服务器
5 CNAME 查询规范名称
12 PTR 域名指针(别名)
15 MX 邮件交换
16 TXT 文本字符串
28 AAAA 由域名获得IPv6地址
33 SRV 服务定位
47 NSEC
255 ANY 对所有记录的请求

· 查询类:通常为1(IN),表明是Internet数据

资源记录(RR)区域

包括回答区域,授权区域和附加区域,格式都是一样的。

  • 域名:格式和Queries区域的查询名字字段是一样的。
  • 查询类型:表明资源纪录的类型,见上表。
  • 查询类:对于Internet信息,总是IN
  • 生存时间(TTL):以秒为单位,表示的是资源记录的生命周期,一般用于当地址解析程序取出资源记录后决定保存及使用缓存数据的时间,它同时也可以表明该资源记录的稳定程度,极为稳定的信息会被分配一个很大的值(比如86400,这是一天的秒数)。
  • 资源数据:该字段是一个可变长字段,表示按照查询段的要求返回的相关资源记录的数据。可以是Address(表明查询报文想要的回应是一个IP地址)或者CNAME(表明查询报文想要的回应是一个规范主机名)等。

Name格式

报文中Name字段的格式如下。
每个label前面都用一个数字表示这个label的长度,对于最后一个label,其后跟一个0作为结尾,表明Name字段的结束。

因此,对于baike.baidu.com
其在报文中的格式为

| 5 | b | a | i | k | e | 5 | b | a | i | d | u | 3 | c | o | m | 0 |

其中,字母代表这个字节的ASCII对应的字符,数字代表这个字节的值。

另外,因为同一个报文的不同记录间经常会有重复的域名,所以为了对域名进行压缩,DNS报文采用了指针机制。指针由两个字节组成,其特征是最高两个bit为11,其余的14bit构成的无符号整型值代表从DNS报文的开始处开始的偏移量。所以,每当在本该表示label长度的那个字节处读到最高两bit是11时,就代表这是一个指针,接下来就应该跳转到指针指向的那个地址继续读取剩下的域名。

我们看看rfc文档上的例子:

   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
20 |           1           |           F           |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
22 |           3           |           I           |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
24 |           S           |           I           |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
26 |           4           |           A           |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
28 |           R           |           P           |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
30 |           A           |           0           |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
40 |           3           |           F           |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
42 |           O           |           O           |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
44 | 1 1|                20                        |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
64 | 1 1|                26                        |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
92 |           0           |                       |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 

假设20处开始Name字段,那这个域名就是 “F.ISI.ARPA”,这个应该很好理解,就不详细解释了。
我们看40字节起的Name字段。先读了一个3字节的label,是FOO,然后读到一个指针,指针指示跳转到地址为20那里去,然后我们接着读出了上一个域名,因此拼起来就是“FOO.F.ISI.ARPA”。
还有可能像64那里那样上来就是指针的,这样这个域名就是“ARPA”。

模块源码

DNS.h

/*
******************************************************************************************
*
*                                  DOMAIN NAME SYSTEM MODULE
*                                         DNS模块
*
* File : DNS.h
* By   : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date:  2019/10/15
* Version: V1.1
* History:    2019/05/07  beta
*             2019/07/22  V1.0  fix that Domain name should be case-insensitive.
*             2019/10/15  V1.1  add print interfaces of DNS-SD.
*                               remove debug messages in DNSMsg_getDomainNameLen
* Note(s): 1. the module is to provide basic definition and functions for Domain Name System.
* References: https://github.com/mwarning/SimpleDNS
*             https://jocent.me/2017/06/18/dns-protocol-principle.html
*             rfc1035
*             rfc2782
******************************************************************************************
*/

#ifndef _DNS_H
#define _DNS_H

/*
*******************************************************************************************
*                                       INCLUDES
*******************************************************************************************
*/

#include <stdint.h>
#include "common.h"

/*
*******************************************************************************************
*                                    CONFIGURATION
*******************************************************************************************
*/
// malloc define _DNS_DEBUG to enable debug message(useless in the version)
// #define _DNS_DEBUG

// malloc define _DNS_SCAN_TXT_MALLOC to ask scanRR to allocate new space for hold RDATA
// for TXT type.
// Default is pointer to the buffer, so you can't touch buffer before use up the returned RR.
// #define _DNS_SCAN_TXT_MALLOC

/*
*******************************************************************************************
*                                     CONSTANTS
*******************************************************************************************
*/
// <domain-name> is a domain name represented as a series of labels, and terminated by a 
//   label with zero length. 
// <character-string> is a single length octet followed by that number of characters.  
// <character-string> is treated as binary information, and can be up to 256 characters in
//   length (including the length octet).

// Port of DNS Server
#define DNSS_PORT     53 
// max size of <label> in DNS message
#define DNS_MAXSIZE_LABELS    63
// max size of <domain name> in DNS message
#define DNS_MAXSIZE_NAMES     255

#define DNS_MAXSIZE_TTL       2147483647L

#define DNS_MAXSIZE_UDPMSG    512

// ResponseType of DNS Message(rcode)

// No error condition
#define DNSRC_OK                  0
// The name server was unable to interpret the query.
#define DNSRC_FormatError         1
// The name server was unable to process this query due to a problem with the name server.
#define DNSRC_ServerFailure       2
// Meaningful only for responses from an authoritative name server, this code signifies 
// that the domain name referenced in the query does not exist.
#define DNSRC_NameError           3
// The name server does not support the requested kind of query.
#define DNSRC_NotImplemented      4
// The name server refuses to perform the specified operation for policy reasons.
#define DNSRC_Refused             5

// opcode of DNS Message
#define DNSOC_Query         0  // a standard query
#define DNSOC_IQuery        1  // an inverse query
#define DNSOC_Status        2  // a server status request
#define DNSOC_Notify        4  // request zone transfer
#define DNSOC_Update        5  // change resource records

// Query Type
#define DNSQT_A       1       // a host address   i.e. IPv4
#define DNSQT_NS      2       // an authoritative name server
#define DNSQT_MD      3       // a mail destination (Obsolete - use MX)
#define DNSQT_MF      4       // a mail forwarder (Obsolete - use MX)
#define DNSQT_CNAME   5       // the canonical name for an alias  查询规范名称
#define DNSQT_SOA     6       // marks the start of a zone of authority 开始授权
#define DNSQT_MB      7       // a mailbox domain name (EXPERIMENTAL)
#define DNSQT_MG      8       // a mail group member (EXPERIMENTAL)
#define DNSQT_MR      9       // a mail rename domain name (EXPERIMENTAL)
#define DNSQT_NULL    10      // a null RR (EXPERIMENTAL)
#define DNSQT_WKS     11      // a well known service description 熟知服务
#define DNSQT_PTR     12      // a domain name pointer   域名指针
#define DNSQT_HINFO   13      // host information        主机信息
#define DNSQT_MINFO   14      // mailbox or mail list information
#define DNSQT_MX      15      // mail exchange 邮件交换
#define DNSQT_TXT     16      // text strings  文本字符串
#define DNSQT_AAAA    28      // 由域名获得IPv6地址
#define DNSQT_SRV     33      // the location of services 
#define DNSQT_NSEC    47
// below are QTYPE, superset of TYPE
#define DNSQT_IXFR    251     // aa
#define DNSQT_AXFR    252     // A request for a transfer of an entire zone
#define DNSQT_MAILB   253     // A request for mailbox-related records (MB, MG or MR)
#define DNSQT_MAILA   254     // A request for mail agent RRs (Obsolete - see MX)
#define DNSQT_ANY     255     // 对所有记录的请求

// Query Class  
#define DNSQC_IN      1      // the Internet 
#define DNSQC_CS      2      // the CSNET class
#define DNSQC_CH      3      // the CHAOS class
#define DNSQC_Hesiod  4      // Hesiod [Dyer 87]
// QCLASS
#define DNSQC_ANY     255    // any class

/*
*******************************************************************************************
*                                     TYPE DEFINITION
*******************************************************************************************
*/

// Header of DNS message
typedef struct DNSHDR_STRUCT{
  // A 16 bit identifier assigned by the program that generates any kind of query. 
  // This identifier is copied the corresponding reply and can be used by the requester
  // to match up replies to outstanding queries. 会话ID
  uint16_t TransID;

  // Flags

  // Recursion Desired - this bit may be set in a query and is copied into the response.  
  // If RD is set, it directs the name server to pursue the query recursively. Recursive 
  // query support is optional.
  // 是否期望递归查询
  unsigned int RD : 1;
  // TrunCation - specifies that this message was truncated due to length greater than that 
  // permitted on the transmission channel.
  // 帧是否截断
  unsigned int TC : 1;
  // Authoritative Answer - this bit is valid in responses, and specifies that the responding
  // name server is an authority for the domain name in question section. Note that the contents
  // of the answer section may have multiple owner names because of aliases.  The AA bit corresponds
  // to the name which matches the query name, or the first owner name in the answer section.
  // 是否是授权的回答
  unsigned int AA : 1;
  // A four bit field that specifies kind of query in this message.  This value is set by the
  // originator of a query and copied into the response.(DNSOC_XX)
  unsigned int opcode : 4;
  // A one bit field that specifies whether this message is a query (0), or a response (1).
  // 查询/响应标志位,0为查询,1为响应,1bit
  unsigned int QR : 1;
  // Response code - this 4 bit field is set as part of responses. (DNSRT_XX)
  // 答复码(见DNSRC_XX)
  unsigned int rcode : 4;
  // Reserved for future use.  Must be zero in all queries and responses.
  // unsigned int Z: 3;

  // Recursion Available - this be is set or cleared in a response, and denotes whether 
  // recursive query support is available in the name server.
  // 在答复中用于表示递归有效
  unsigned int RA : 1;

  // count fields

  // an unsigned 16 bit integer specifying the number of entries in the question section.
  // 请求数
  uint16_t Questions;
  // an unsigned 16 bit integer specifying the number of resource records in the answer section.
  // 回答资源记录数
  uint16_t AnswerRRs;
  // an unsigned 16 bit integer specifying the number of name server resource records in the
  // authority records section.
  // 授权资源记录数
  uint16_t AuthorityRRs;
  // an unsigned 16 bit integer specifying the number of resource records in the additional records section.
  // 附加资源记录数
  uint16_t AdditionalRRs;
} DNSHdr;

// Queries Section
typedef struct DNSQUE_STRUCT {
  const char * Name;  // Query Name (ASCII format)
  uint16_t Type;      // Query Type (DNSQT_XX)
  uint16_t Class;     // Query Class(DNSQC_XX)
} DNSQue;

// Data part of a Resource Record
typedef union DNSRD_STRUCT{
  struct {
    const char *target;
    uint16_t port;
    uint16_t priority;
    uint16_t weight;
  } SRV;
  // the module don't resolve the content of the TXT RData, because it highly depends on the usage.
  struct {
    // data in RData
    const uint8_t *data;
    // the length of RData.
    uint16_t len;
  } TXT;
  struct {
    const uint8_t* addr;   // A for Ipv4 address uint8_t[4], AAAA for IPv6 address uint8_t[16]
  } A, AAAA;
  struct {
    const char *dname;  // <domain-name>, but in ascii string format here.
  } MB, MD, MF, MG, MR, NS, PTR, CNAME;
  struct {
    // The <domain-name> of the name server that was the original or primary source of data for this zone.
    // In ascii string format.
    const char * MName;
    // A <domain-name> which specifies the mailbox of the person responsible for this zone.In ascii string
    // format.
    const char* RName;
    // The unsigned 32 bit version number of the original copy of the zone.  Zone transfers preserve this
    // value.  This value wraps and should be compared using sequence space arithmetic.
    uint32_t serial;
    // A 32 bit time interval before the zone should be refreshed.
    uint32_t refresh;
    // A 32 bit time interval that should elapse before a failed refresh should be retried.
    uint32_t retry;
    // A 32 bit time value that specifies the upper limit on the time interval that can elapse before the
    // zone is no longer authoritative.
    uint32_t expire;
    // The unsigned 32 bit minimum TTL field that should be exported with any RR from this zone.
    uint32_t minimum;
  } SOA;
//   struct {
//     uint16_t preference;
//     const char *exchange;
//   } MX;
  struct {
    uint8_t useless;
  } Null;
  struct {
    // the next owner name (in the canonical ordering of the zone) that has authoritative data or
    // contains a delegation point NS RRset, <domain-name>, but in ascii string format here.
    const char *NextDName;
    // the bytes count of TypeBitMaps. Currently, not support > 32.
    uint16_t TypeBitMapsLength; 
    // bitmaps array bit 0 for TYPE 0, bit 1 for TYPE 1(A), and so on. in network order.
    uint8_t * TypeBitMaps; 
  } NSEC;
} DNSRD;

// Resource Record Section
typedef struct DNSRR_STRUCT {
  const char *Name;
  uint16_t Type;     // Query Type (DNSQT_XX)
  uint16_t Class;    // Query Class(DNSQC_XX)
  uint32_t TTL;      // 
  // uint16_t RDLength; according to Query Type and RData
  DNSRD    RData;    // according to Query Type
} DNSRR;

/*
*******************************************************************************************
*                                     INTERFACES
*******************************************************************************************
*/

// description: print the header to a DNS message stream.
// parameter  : buf      the buffer for DNS message.
//              maxLen   max length of buffer
//              header   the header message.
// return     : pointer to the end of current message stream.
//              NULL   if any error
// note       : neither of buf and header can't be NULL
uint8_t * DNSMsg_printHeader(uint8_t *buf, uint16_t maxLen, const DNSHdr * header);
// description: print a query to a DNS message stream.
// parameter  : buf      the buffer for DNS message.
//              maxLen   max length of buffer
//              p        pointer to the current position of the message stream
//              que      the query.
// return     : pointer to the end of current message stream.
//              NULL    if any error
// note       : p, que and que->Name can't be NULL
//              if buf == NULL, it means you don't need overflow check.
//              the current version can't correctly process the situation when Name looks like
//              "XXX..XXX" or "XXXXXXX.", just make sure it's a normal Domain Name.
uint8_t * DNSMsg_printQuery(uint8_t *buf, uint16_t maxLen, uint8_t *p, const DNSQue * que);
// description: print a resource record to a DNS message stream.
// parameter  : buf      the buffer for DNS message.
//              maxLen   max length of buffer
//              p        pointer to the current position of the message stream
//              rr       the resource record.
// return     : pointer to the end of current message stream.
//              NULL    if any error
// note       : p, que and que->Name can't be NULL
//              if Type in rr is unsupported, NULL will be returned.
uint8_t * DNSMsg_printRR(uint8_t *buf, uint16_t maxLen, uint8_t *p, const DNSRR * rr);
uint8_t * DNSMsg_printRRwithAnotherTTL(uint8_t *buf, uint16_t maxLen, uint8_t *p, const DNSRR * rr, uint32_t ttl);
// description: calculate the lenth of a domian name when converted to ascii format.
// parameter  : msg     point to the start of the DNS message.
//              msgLen  total length of message.
//              p       point to the start of the <domain name>.
// return     : >=0    the lenth of a domian name when converted to ascii format
//              <0     if any error.
// note       : 
int16_t DNSMsg_getDomainNameLen(const uint8_t *msg, uint16_t msgLen, const uint8_t *p);
// description: get the domian name converted to ascii format.
// parameter  : msg     point to the start of the DNS message.
//              msgLen  total length of message.
//              p       point to the start of the <domain name>.
// return     : the domian name converted to ascii format
//              NULL     if any error.
// note       : user are responsible for call free to the return string.
const char *DNSMsg_getDomainName(const uint8_t *msg, uint16_t msgLen, const uint8_t *p);

// description: scan DNS message for resolving DNS Header.
// parameter  : msg     point to the DNS message(start of the header)
//              msgLen  total length of message.
//              ret     return the resolved header message.
// return     : pointer to the position after the header.
//              NULL   if any error
// note       : 
const uint8_t * DNSMsg_scanHeader(const uint8_t *msg, uint16_t msgLen, DNSHdr * ret);

// description: scan DNS message stream for resolving a DNS Query.
// parameter  : msg     point to the start of the DNS message.
//              msgLen  total length of message.
//              p       point to the current position for resolving query
//              ret     return the resolved query.
// return     : pointer to the position after the Query.
//              NULL   if any error
// note       : if return value is not NULL. don't forget to use DNSMsg_freeQueReturnByScan
//              to free DNSQue.
const uint8_t * DNSMsg_scanQuery(const uint8_t *msg, uint16_t msgLen, const uint8_t *p, DNSQue * ret);

// description: scan DNS message stream for resolving a DNS Resource Record.
// parameter  : msg     point to the start of the DNS message.
//              msgLen  total length of message.
//              p       point to the current position for resolving Resource Record
//              ret     return the resolved Resource Record.
// return     : pointer to the position after the Resource Record.
//              NULL    if any error
// note       : if return value is not NULL. don't forget to use DNSMsg_freeRRReturnByScan
//              to free DNSRR.
//              do check the pointers in the returned RR, may be NULL if memory run out.
const uint8_t * DNSMsg_scanRR(const uint8_t *msg, uint16_t msgLen, const uint8_t *p, DNSRR * ret);
// description: whether support this Type of DNS
// parameter  : Type    the type to ask
// return     : not 0    if support
//              0        if not
// note       : 
int DNSMsg_supportType(uint16_t Type);

// description: free the allocated memory used by DNSQue returned by DNSMsg_scanQuery
// parameter  : que    the DNSQue to be free memory.
// return     : 
// note       : the que must the one returned by DNSMsg_scanQuery!!!
//              call this function after successfully call DNSMsg_scanQuery
void DNSMsg_freeQueReturnByScan(DNSQue * que);

// description: free the allocated memory used by DNSRR returned by DNSMsg_scanRR
// parameter  : rr    the DNSRR to be free memory.
// return     : 
// note       : the rr must the one returned by DNSMsg_scanRR!!!
//              call this function after successfully call DNSMsg_scanRR
void DNSMsg_freeRRReturnByScan(DNSRR * rr);

// description: print a standard query DNS message for Ipv4 addr(A)
// parameter  : buf     pointer to the buffer for DNS message.
//              maxLen  the MAX. size of buffer.
//              ID      ID used in this query
//              name    pointer to the domain name.
// return     : the length of the DNS message.
//              -1      if any error
// note       : 
int16_t DnsMsg_printStandardQueryIPv4Message(uint8_t * buf, uint16_t maxLen, uint16_t ID, char const * name);

// description: print a standard query DNS message for service(PTR)
// parameter  : buf      pointer to the buffer for DNS message.
//              maxLen   the MAX. size of buffer.
//              ID       ID used in this query
//              svcName  pointer to the service name("_service._tcp/_udp.local").
// return     : the length of the DNS message.
//              -1      if any error
// note       : 
int16_t DnsMsg_printStandardQueryServiceMessage(uint8_t * buf, uint16_t maxLen, uint16_t ID, char const * svcName);

// description: print a standard query DNS message for service instance record(SRV)
// parameter  : buf         pointer to the buffer for DNS message.
//              maxLen      the MAX. size of buffer.
//              ID          ID used in this query
//              svcInsName  pointer to the service instance name("instance._service._tcp/_udp.local").
// return     : the length of the DNS message.
//              -1      if any error
// note       : 
int16_t DnsMsg_printStandardQueryServiceInstanceMessage(uint8_t * buf, uint16_t maxLen, uint16_t ID, char const * svcInsName);

// description: print the content of a DNS message to standard output
// parameter  : msg     pointer to the DNS message.
//              msgLen  the of DNS message.
// return     : 
// note       : 
void DnsMsg_resolveMessage(const uint8_t * msg, uint16_t msgLen);

// description: judgment whether two DNSQue are equal.
// parameter  : rr1  the Question 1 to be compared.
//              rr2  the Question 2 to be compared.
// return     : TRUE   if not equal
//              FALSE  if not equal
// note       : no parameters NULL check.
BOOL DNSQue_isEqual(const DNSQue *que1, const DNSQue *que2);
// description: judgment whether two DNSRR are equal.
// parameter  : rr1  the Resource record 1 to be compared.
//              rr2  the Resource record 2 to be compared.
// return     : TRUE   if not equal
//              FALSE  if not equal
//              < 0    if error or unsupport
// note       : no parameters NULL check.
int DNSRR_isEqual(const DNSRR *rr1, const DNSRR *rr2);
int DNSRR_isEqualIgnoreTTL(const DNSRR *rr1, const DNSRR *rr2);
BOOL DNSDName_isEqual(const char *dn1, const char *dn2);
#endif

DNS.c

/*
******************************************************************************************
*
*                                  DOMAIN NAME SYSTEM MODULE
*                                         DNS模块
*
* File : DNS.c
* By   : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date:  2019/10/15
* Version: V1.1
* History: 
* NOTE(s): 
******************************************************************************************
*/

/*
*******************************************************************************************
*                                       INCLUDES
*******************************************************************************************
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "DNS.h"
#include "NetworkLib.h"
#include "lib_lite.h"   // for strcasecmp

#ifdef _DNS_DEBUG
#define _DEBUG
#else
#undef _DEBUG
#endif

#include "DebugMsg.h"
/*
*******************************************************************************************
*                                       CONSTANT
*******************************************************************************************
*/
// Mask and offset of flags byte 1
#define MASK_QR       0x80
#define MASK_opcode   0x78
#define MASK_AA       0x04
#define MASK_TC       0x02
#define MASK_RD       0x01
#define OFST_QR       7
#define OFST_opcode   3
#define OFST_AA       2
#define OFST_TC       1
#define OFST_RD       0

// Mask and offset of flags byte 2
#define MASK_RA       0x80
#define MASK_zero     0x70
#define MASK_rcode    0x0F
#define OFST_RA       7
#define OFST_zero     4
#define OFST_rcode    0

/*
*******************************************************************************************
*                                       LOCAL VARIABLE
*******************************************************************************************
*/

typedef struct {
  uint16_t num;
  const char * s;
} NameList;

static const NameList NameList_QueryType[] = {
  {DNSQT_A,     "A(IPv4 Address)"   },
  {DNSQT_AAAA,  "AAAA(IPv6 Address)"},
  {DNSQT_CNAME, "CNAME(alias name)" },
  {DNSQT_NSEC,  "NSEC" },
  {DNSQT_MB,    "MB"   },
  {DNSQT_MD,    "MD"   },
  {DNSQT_MF,    "MF"   },
  {DNSQT_MG,    "MG"   },
  {DNSQT_MR,    "MR"   },
  {DNSQT_NS,    "NS"   },
  {DNSQT_PTR,   "PTR"  },
  {DNSQT_NULL,  "NULL" },
  {DNSQT_SOA,   "SOA"  },
  {DNSQT_SRV,   "SRV"  },
  {DNSQT_TXT,   "TXT"  },
  {DNSQT_IXFR,  "IXFR" },
  {DNSQT_AXFR,  "AXFR" },
  {DNSQT_MAILB, "MAILB"},
  {DNSQT_MAILA, "MAILA"},
  {DNSQT_ANY,   "ANY"  },
};

static const NameList NameList_QueryClass[] = {
  {DNSQC_IN,    "IN(the internet)"},
  {DNSQC_CS,    "CS"    },
  {DNSQC_CH,    "CH"    },
  {DNSQC_Hesiod,"Hesiod"},
  {DNSQC_ANY,   "ANY"   },
};

static const char _emptyStr[] = "";

/*
*******************************************************************************************
*                                 LOCAL FUNCTION DECLARATIONS
*******************************************************************************************
*/
// return the name string of query type, NULL if not found.
static const char * _getNameOfQueryType(uint16_t qt);
// return the name string of query type, NULL if not found.
static const char * _getNameOfQueryClass(uint16_t qc);

#define _isDomainNamePtr(labelLenByte)         ((labelLenByte) >= 0xC0)

// <domain-name> is a domain name represented as a series of labels, and terminated by a label
// with zero length. 

// skip the <domain-name> in DNS message
static const uint8_t * _skipDomainName(const uint8_t *p);
// print a domain name in ascii to <domain-name>
// if s == NULL, will output an empty <domain-name>
static uint8_t * _putDomainName(uint8_t * buf, const char *s);
// print <domain-name> 's', Type field 't' and Class field 'c'
static uint8_t * _printDomainNameTypeClass(uint8_t *buf, const char * s, uint16_t t, uint16_t c);

static void _resolveHeader(const DNSHdr *hdr);
static void _resolveQuery(const DNSQue *que);
static void _resolveRR(const DNSRR *rr);
/*
*******************************************************************************************
*                             PUBLIC INTERFACES IMPLEMENTATIONS
*******************************************************************************************
*/
int16_t DnsMsg_printStandardQueryIPv4Message(uint8_t * buf, uint16_t maxLen, uint16_t ID,char const * name){
  uint8_t *p = buf;
  DNSQue que;
  DNSHdr hdr;

  memset(&hdr, 0, sizeof(DNSHdr));
  // hdr.opcode = DNSOC_Query;
  hdr.TransID = ID;
  // Recursion desired
  hdr.RD = 1;
  hdr.Questions = 1;

  p = DNSMsg_printHeader(buf, maxLen, &hdr);

  que.Name = name;
  que.Type = DNSQT_A;
  que.Class = DNSQC_IN;
  p = DNSMsg_printQuery(buf, maxLen, p, &que);
  if(p)
    return p - buf;
  else
    return -1;
}

int16_t DnsMsg_printStandardQueryServiceMessage(uint8_t * buf, uint16_t maxLen, uint16_t ID, char const * svcName){
  uint8_t *p = buf;
  DNSQue que;
  DNSHdr hdr;

  memset(&hdr, 0, sizeof(DNSHdr));
  // hdr.opcode = DNSOC_Query;
  hdr.TransID = ID;
  // Recursion desired
  hdr.RD = 1;
  hdr.Questions = 1;

  p = DNSMsg_printHeader(buf, maxLen, &hdr);

  que.Name = svcName;
  que.Type = DNSQT_PTR;
  que.Class = DNSQC_IN;
  p = DNSMsg_printQuery(buf, maxLen, p, &que);
  if(p)
    return p - buf;
  else
    return -1;
}

int16_t DnsMsg_printStandardQueryServiceInstanceMessage(uint8_t * buf, uint16_t maxLen, uint16_t ID, char const * svcInsName){
  uint8_t *p = buf;
  DNSQue que;
  DNSHdr hdr;

  memset(&hdr, 0, sizeof(DNSHdr));
  // hdr.opcode = DNSOC_Query;
  hdr.TransID = ID;
  // Recursion desired
  hdr.RD = 1;
  hdr.Questions = 1;

  p = DNSMsg_printHeader(buf, maxLen, &hdr);

  que.Name = svcInsName;
  que.Type = DNSQT_SRV;
  que.Class = DNSQC_IN;
  p = DNSMsg_printQuery(buf, maxLen, p, &que);
  if(p)
    return p - buf;
  else
    return -1;
}


uint8_t * DNSMsg_printHeader(uint8_t *buf, uint16_t maxLen, const DNSHdr * header){
  if(buf == NULL || maxLen < 12 || header == NULL)
    return NULL;
  buf = NetworkStream_putUint16(buf,header->TransID);
  *((uint16_t *)buf) = 0;  // clear flag field
  *buf++ = (header->QR << OFST_QR) | (header->opcode << OFST_opcode) | 
           (header->AA << OFST_AA) | (header->TC >> OFST_TC) | (header->RD >> OFST_RD);
  *buf++ = (header->RA << OFST_RA) | (header->rcode << OFST_rcode);
  buf = NetworkStream_putUint16(buf,header->Questions);
  buf = NetworkStream_putUint16(buf,header->AnswerRRs);
  buf = NetworkStream_putUint16(buf,header->AuthorityRRs);
  buf = NetworkStream_putUint16(buf,header->AdditionalRRs);
  return buf;
}

uint8_t * DNSMsg_printQuery(uint8_t *buf, uint16_t maxLen, uint8_t *p, const DNSQue * que){
  const char * s;
  uint16_t len;
  if(p == NULL || que == NULL)
    return NULL;
  s = que->Name;
  if(s)
    len = strlen(s);
  else
    len = 0;
  if(buf != NULL){      // 溢出检查
    if((p + len + 2 + 4 - buf) > maxLen)
      return NULL;
  }
  p = _printDomainNameTypeClass(p, s, que->Type, que->Class);
  return p;
}
uint8_t * DNSMsg_printRRwithAnotherTTL(uint8_t *buf, uint16_t maxLen, uint8_t *p, const DNSRR * rr, uint32_t ttl){
  const char * s;
  uint8_t *pDATALEN;
  uint16_t len;
  if(p == NULL || rr == NULL)
    return NULL;
  s = rr->Name;
  p = _printDomainNameTypeClass(p, s, rr->Type, rr->Class);
  pDATALEN = NetworkStream_putUint32(p, ttl);
  p = pDATALEN + 2;
  switch (rr->Type){
  case DNSQT_A:
    len = 4;
    *(uint32_t *)p = *(uint32_t *)rr->RData.A.addr;
    p += 4;
    break;
  case DNSQT_AAAA:
    len = 16;
    (void)memcpy(p, rr->RData.AAAA.addr,16);
    p += 16;
    break;
  case DNSQT_CNAME: case DNSQT_PTR: case DNSQT_MB: case DNSQT_MD: 
  case DNSQT_MF: case DNSQT_MG: case DNSQT_MR: case DNSQT_NS:
    buf = p;
    p = _putDomainName(p, rr->RData.CNAME.dname);
    len = p - buf;
    break;
  case DNSQT_NULL:
    len = 0;
    // append nothing
    break;
  case DNSQT_SOA:
    buf = p;
    p = _putDomainName(p, rr->RData.SOA.MName);
    p = _putDomainName(p, rr->RData.SOA.RName);
    p = NetworkStream_putUint32(p, rr->RData.SOA.serial);
    p = NetworkStream_putUint32(p, rr->RData.SOA.refresh);
    p = NetworkStream_putUint32(p, rr->RData.SOA.retry);
    p = NetworkStream_putUint32(p, rr->RData.SOA.expire);
    p = NetworkStream_putUint32(p, rr->RData.SOA.minimum);
    len = p - buf;
    break;
  case DNSQT_NSEC:
    buf = p;
    p = _putDomainName(p, rr->RData.NSEC.NextDName);
    *p++ = 0x00;  // currently, only support first window blocks
    if(rr->RData.NSEC.TypeBitMapsLength > 32)
      return NULL;
    *p++ = (uint8_t)rr->RData.NSEC.TypeBitMapsLength;
    memcpy(p, rr->RData.NSEC.TypeBitMaps, rr->RData.NSEC.TypeBitMapsLength);
    p += rr->RData.NSEC.TypeBitMapsLength;
    len = p - buf;
    break;
  case DNSQT_SRV:
    buf = p;
    p = NetworkStream_putUint16(p, rr->RData.SRV.priority);
    p = NetworkStream_putUint16(p, rr->RData.SRV.weight);
    p = NetworkStream_putUint16(p, rr->RData.SRV.port);
    p = _putDomainName(p, rr->RData.SRV.target);
    len = p - buf;
    break;
  case DNSQT_TXT:
    len = rr->RData.TXT.len;
    if(len > 0 && rr->RData.TXT.data == NULL)
      return NULL;
    (void)memcpy(p, rr->RData.TXT.data, rr->RData.TXT.len);
    p += len;
    break;
  default:
    // return NULL if unknown Type
    return NULL;
  }
  (void)NetworkStream_putUint16(pDATALEN, len);
  return p;
}

uint8_t * DNSMsg_printRR(uint8_t *buf, uint16_t maxLen, uint8_t *p, const DNSRR * rr){
  if(rr == NULL)
    return NULL;
  return DNSMsg_printRRwithAnotherTTL(buf, maxLen, p, rr, rr->TTL);
}

const uint8_t * DNSMsg_scanHeader(const uint8_t *msg, uint16_t msgLen, DNSHdr * ret){
  uint8_t b;
  const uint8_t *p = msg;
  if(msg == NULL || msgLen < 12 || ret == NULL)
    return NULL;
  memset(ret, 0, sizeof(DNSHdr));
  p = NetworkStream_getUint16(p, &ret->TransID);
  b = *p++;
  ret->QR = !!(b & MASK_QR);
  ret->opcode = b >> OFST_opcode;
  ret->AA = !!(b & MASK_AA);
  ret->TC = !!(b & MASK_TC);
  ret->RD = !!(b & MASK_RD);
  b = *p++;
  ret->RA = !!(b & MASK_RA);
  ret->rcode = ((b & MASK_rcode) >> OFST_rcode);
  p = NetworkStream_getUint16(p, &ret->Questions);
  p = NetworkStream_getUint16(p, &ret->AnswerRRs);
  p = NetworkStream_getUint16(p, &ret->AuthorityRRs);
  p = NetworkStream_getUint16(p, &ret->AdditionalRRs);
  return p;
}

const uint8_t * DNSMsg_scanQuery(const uint8_t *msg, uint16_t msgLen, const uint8_t *p, DNSQue * ret){
  if(p == NULL || ret == NULL)
    return NULL;
  memset(ret, 0, sizeof(DNSQue));
  if((ret->Name = DNSMsg_getDomainName(msg,msgLen,p)) == NULL)
    return NULL;
  p = _skipDomainName(p);
  if(msg && (p - msg + 4 > msgLen)){
    free((void *)ret->Name);
    ret->Name = NULL;
    return NULL;
  }
  p = NetworkStream_getUint16(p, &ret->Type);
  p = NetworkStream_getUint16(p, &ret->Class);
  return p;
}

const uint8_t * DNSMsg_scanRR(const uint8_t *msg, uint16_t msgLen, const uint8_t *p, DNSRR * ret){
  uint16_t RDataLen, len;
  const uint8_t * p2;
  memset(ret, 0, sizeof(DNSRR));
  if((p = DNSMsg_scanQuery(msg,msgLen,p,(DNSQue *)ret)) == NULL)
    return NULL;
  p = NetworkStream_getUint32(p, &ret->TTL);
  p = NetworkStream_getUint16(p, &RDataLen);
  if(RDataLen == 0)
    return p;
  switch (ret->Type){
    case DNSQT_A:case DNSQT_AAAA:
      len = (ret->Type == DNSQT_A)? 4: 16;
      if((ret->RData.A.addr = (uint8_t *)malloc(len)) == NULL)
        break;
      (void)memcpy((uint8_t *)ret->RData.A.addr, p, len);
    break;
    case DNSQT_CNAME: case DNSQT_PTR: case DNSQT_MB: case DNSQT_MD: 
    case DNSQT_MF: case DNSQT_MG: case DNSQT_MR: case DNSQT_NS:
      ret->RData.CNAME.dname = DNSMsg_getDomainName(msg,msgLen,p);
      break;
    case DNSQT_SOA:
      p2 = p;
      if((ret->RData.SOA.MName = DNSMsg_getDomainName(msg,msgLen,p2)) == NULL)
        break;
      p2 = _skipDomainName(p2);
      if((ret->RData.SOA.RName = DNSMsg_getDomainName(msg,msgLen,p2)) == NULL)
        break;
      p2 = _skipDomainName(p2);
      p2 = NetworkStream_getUint32(p2, &ret->RData.SOA.serial);
      p2 = NetworkStream_getUint32(p2, &ret->RData.SOA.refresh);
      p2 = NetworkStream_getUint32(p2, &ret->RData.SOA.retry);
      p2 = NetworkStream_getUint32(p2, &ret->RData.SOA.expire);
      (void)NetworkStream_getUint32(p2, &ret->RData.SOA.minimum);
      break;
    case DNSQT_SRV:
      p2 = p;
      p2 = NetworkStream_getUint16(p2, &ret->RData.SRV.priority);
      p2 = NetworkStream_getUint16(p2, &ret->RData.SRV.weight);
      p2 = NetworkStream_getUint16(p2, &ret->RData.SRV.port);
      ret->RData.SRV.target = DNSMsg_getDomainName(msg,msgLen,p2);
      break;
    case DNSQT_NSEC:
      p2 = p;
      if((ret->RData.NSEC.NextDName = DNSMsg_getDomainName(msg,msgLen,p2)) == NULL)
        break;
      p2 = _skipDomainName(p2);
      if(*p2 != 0 || *(p2 + 1) > 32 ){  // check is window block 0
        ret->RData.NSEC.TypeBitMapsLength = 0;
        break;
      }
      ++p2;
      ret->RData.NSEC.TypeBitMapsLength = *p2++;
      if((ret->RData.NSEC.TypeBitMaps = (uint8_t *)malloc(ret->RData.NSEC.TypeBitMapsLength)) == NULL)
        break;
      (void)memcpy(ret->RData.NSEC.TypeBitMaps, p2, ret->RData.NSEC.TypeBitMapsLength);
      break;
    case DNSQT_TXT:
      ret->RData.TXT.len = RDataLen;
#ifndef _DNS_SCAN_TXT_MALLOC
      ret->RData.TXT.data = p;
#else
      if(RDataLen == 0)
        break;
      // one more slot for convenient of printf. 
      if((ret->RData.TXT.data = (uint8_t *)malloc(RDataLen)) == NULL)
        break;
      (void)memcpy((void *)ret->RData.TXT.data, p, RDataLen);
#endif
      break;
    default:
      break;
  }
  p += RDataLen;
  return p;
}

void DNSMsg_freeQueReturnByScan(DNSQue * que){
  if(que && que->Name){
    free((void *)que->Name);
    que->Name = NULL;
  }
}

void DNSMsg_freeRRReturnByScan(DNSRR * rr){
  if(rr == NULL)
    return;
  if(rr->Name){
    free((void *)rr->Name);
    rr->Name = NULL;
  }
  switch (rr->Type){
    case DNSQT_A: case DNSQT_AAAA:
      if(rr->RData.A.addr){
        free((void *)rr->RData.A.addr);
        rr->RData.A.addr = NULL;
      }
    break;
    case DNSQT_CNAME: case DNSQT_PTR: case DNSQT_MB: case DNSQT_MD: 
    case DNSQT_MF: case DNSQT_MG: case DNSQT_MR: case DNSQT_NS:
      if(rr->RData.CNAME.dname){
        free((void *)rr->RData.CNAME.dname);
        rr->RData.CNAME.dname = NULL;
      }
      break;
    case DNSQT_SOA:
      if(rr->RData.SOA.MName){
        free((void *)rr->RData.SOA.MName);
        rr->RData.SOA.MName = NULL;
      }
      if(rr->RData.SOA.RName){
        free((void *)rr->RData.SOA.RName);
        rr->RData.SOA.RName = NULL;
      }
      break;
    case DNSQT_SRV:
      if(rr->RData.SRV.target){
        free((void *)rr->RData.SRV.target);
        rr->RData.SRV.target = NULL;
      }
      break;
#ifdef _DNS_SCAN_TXT_MALLOC
    case DNSQT_TXT:
      if(rr->RData.TXT.data){
        free((void *)rr->RData.TXT.data);
        rr->RData.TXT.data = NULL;
        rr->RData.TXT.len = 0;
      }
      break;
#endif
    case DNSQT_NSEC:
      if(rr->RData.NSEC.NextDName){
        free((void *)rr->RData.NSEC.NextDName);
        rr->RData.NSEC.NextDName = NULL;
      }
      if(rr->RData.NSEC.TypeBitMaps){
        free((void *)rr->RData.NSEC.TypeBitMaps);
        rr->RData.NSEC.TypeBitMaps = NULL;
      }
      break;
    default:
    break;
  }
}

int16_t DNSMsg_getDomainNameLen(const uint8_t *msg, uint16_t msgLen, const uint8_t *p){
  int16_t len = 0;
  uint8_t labelLen;
  uint16_t offset;
  uint16_t ptrJumpCnt = 0;

  if(p == NULL)
    return -1;

  while(labelLen = *p){
    if(_isDomainNamePtr(labelLen)){
      if(++ptrJumpCnt >= 10){
        //_dbg_printf0("ptr infinite loop doubt\n");
        return -1;
      }
      if(msg == NULL){
        //_dbg_printf0("ptr when msg == NULL error\n");
        return -1;
      }
      (void)NetworkStream_getUint16(p, &offset);
      offset &= 0x3FFF;
      //_dbg_printf1("ptr to %u \n", offset);
      p = msg + offset;
      if(p - msg >= msgLen){
        //_dbg_printf0("ptr out of msg.\n");
        return -1;
      }
      continue;
    }
    if(labelLen > DNS_MAXSIZE_LABELS){
      //_dbg_printf0("label length octets error\n");
      return -1;
    }
    //_dbg_printf1("label len: %d ", labelLen);
    ++p;
    len += labelLen + 1;
    if((msg != NULL) && (p + labelLen - msg > msgLen)){
      //_dbg_printf0("domain name outof message error\n");
      return -1;
    }
    // check no 0 in the label
    while(labelLen > 0){
      if(*p == 0){
        //_dbg_printf0("find 0 in label, error\n");
        return -1; 
      }
      //_dbg_printf1("%c ", *p);
      --labelLen;
      ++p;
    }
    //_dbg_printf0("\n");
  }
  if(len > 0)
    --len;
  return len;
}

const char *DNSMsg_getDomainName(const uint8_t *msg, uint16_t msgLen, const uint8_t *p){
  int iMsg = 0, iName = 0;
  uint8_t labelLen;
  uint16_t offset;
  int16_t nameLen;
  char * name;
  // format check depends on getDomainNameLen
  nameLen = DNSMsg_getDomainNameLen(msg, msgLen, p);
  if(nameLen < 0)
    return NULL;
  name = (char *)malloc(nameLen + 1);  // 1 slot for '\0'
  if(!name)
    return NULL;

  while(p[iMsg] != 0){
    labelLen = p[iMsg];
    if(_isDomainNamePtr(labelLen)){
      (void)NetworkStream_getUint16(&p[iMsg], &offset);
      p = msg + (offset & 0x3FFF);
      iMsg = 0;
      continue;
    }
    if(iName != 0)
      name[iName++] = '.';
    ++iMsg;
    if(iName + labelLen > nameLen){
      free(name);
      return NULL;
    }

    memcpy(&name[iName], &p[iMsg], labelLen);
    iName += labelLen;
    iMsg += labelLen;
  }

  name[iName] = '\0';
  return name;
}

static const uint8_t * _resolveRRSection(const uint8_t * msg, uint16_t msgLen, const uint8_t * p, const char * secName, uint16_t rrCnt){
  DNSRR  rr;
  uint16_t i;
  for(i = 0; i < rrCnt; i++){
    printf("%s %u\r\n", secName, i + 1);
    p = DNSMsg_scanRR(msg, msgLen, p, &rr);
    if(p == NULL){
      printf("resolve fail.\r\n");
      return NULL;
    }
    _resolveRR(&rr);
    DNSMsg_freeRRReturnByScan(&rr);
  }
  return p;
}

void DnsMsg_resolveMessage(const uint8_t * msg, uint16_t msgLen){
  DNSHdr hdr;
  DNSQue que;
  const uint8_t * p;
  uint16_t i;
  p = DNSMsg_scanHeader(msg, msgLen, &hdr);
  if(p == NULL){
    printf("Resolve DNS message header fail.\r\n");
    return;
  }
  printf("Resolving DNS message.\r\n");
  _resolveHeader(&hdr);

  for(i = 0; i < hdr.Questions; i++){
    printf("Questions %2u\r\n", i + 1);
    p = DNSMsg_scanQuery(msg, msgLen, p, &que);
    if(p == NULL){
      printf("resolve fail.\r\n");
      return;
    }
    _resolveQuery(&que);
    DNSMsg_freeQueReturnByScan(&que);
  }

  if((p = _resolveRRSection(msg, msgLen, p, "AnswerRRs", hdr.AnswerRRs)) == NULL)
    return;
  if((p = _resolveRRSection(msg, msgLen, p, "AuthorityRRs", hdr.AuthorityRRs)) == NULL)
    return;

  _resolveRRSection(msg, msgLen, p, "AdditionalRRs", hdr.AdditionalRRs);
}

int DNSMsg_supportType(uint16_t Type){
  return _getNameOfQueryType(Type) != NULL;
}

/*
*******************************************************************************************
*                             LOCAL FUNCTIONS IMPLEMENTATIONS
*******************************************************************************************
*/

static const uint8_t * _skipDomainName(const uint8_t *p){
  uint8_t labelLen;
  for(;;){
    labelLen = *p++;
    if(labelLen == 0)
      break;
    if(_isDomainNamePtr(labelLen)){
      ++p;
      break;
    }
    p += labelLen;
  }
  return p;
}

static uint8_t * _putDomainName(uint8_t * buf, const char *s){
  uint8_t * p = buf;
  if(s != NULL)
    while(*s != '\0'){
      p = buf + 1;
      while(*s != '.' && *s != '\0')
        *p++ = *s++;
      *buf = p - buf - 1;
      buf = p;
      if(*s == '.')
        ++s;
    }
  *buf++ = '\0';
  return buf;
}

static uint8_t * _printDomainNameTypeClass(uint8_t *buf, const char * s, uint16_t t, uint16_t c){
  buf = _putDomainName(buf, s);
  buf = NetworkStream_putUint16(buf, t);
  buf = NetworkStream_putUint16(buf, c);
  return buf;
}

static const char * _getNameOfQueryType(uint16_t qt){
  int i;
  for(i = 0; i < (sizeof(NameList_QueryType)/ sizeof(NameList_QueryType[0])); i++){
    if(NameList_QueryType[i].num == qt)
      return NameList_QueryType[i].s;
  }
  return NULL;
}

static const char * _getNameOfQueryClass(uint16_t qc){
  int i;
  qc &= 0x00FF;
  for(i = 0; i < (sizeof(NameList_QueryClass)/ sizeof(NameList_QueryClass[0])); i++){
    if(NameList_QueryClass[i].num == qc)
      return NameList_QueryClass[i].s;
  }
  return NULL;
}

static void _resolveHeader(const DNSHdr *hdr){
  printf("ID: %u\r\n",hdr->TransID);
  if(hdr->QR)
    printf("Respond message.");
  else
    printf("Query message.");
  if(hdr->RD)
    printf("Recursion expected.");
  if(hdr->RA)
    printf("Recursion avail.");
  if(hdr->AA)
    printf("Authoritative.");
  if(hdr->TC)
    printf("truncated.");
  printf("\r\nOperation:");
  switch (hdr->opcode){
  case DNSOC_Query:
    printf("Standard Query");
    break;
  case DNSOC_IQuery:
    printf("Inverse IQuery");
    break;
  case DNSOC_Status:
    printf("Server Status Request");
    break;
  case DNSOC_Notify:
    printf("Request Zone Transfer");
    break;
  case DNSOC_Update:
    printf("Change Resource Records");
    break;
  default:
    printf("0x%2x", hdr->opcode);
    break;
  }
  printf("\r\n%u Questions\r\n%u AnswersRR\r\n%u AuthorityRR\r\n%u AdditionalRRs\r\n",
    hdr->Questions,hdr->AnswerRRs,hdr->AuthorityRRs,hdr->AdditionalRRs);
}

static void _resolveQuery(const DNSQue *que){
  const char *s;
  printf("Domain Name: %s\r\nQuery Type: ", que->Name);
  s = _getNameOfQueryType(que->Type);
  if(s != NULL)
    printf(s);
  else
    printf("0x%2x", que->Type);
  printf("\r\nQuery Class: ");
  s = _getNameOfQueryClass(que->Class);
  if(s != NULL)
    printf(s);
  else
    printf("0x%2x", que->Class);
  if(que->Class & 0x8000)
    printf(",unicast accept(mDNS)");
  printf("\r\n");
}

static void _resolveRR(const DNSRR *rr){
  const char *s;
  uint16_t i;
  printf("Domain Name: %s\r\nQuery Type: ", rr->Name);
  s = _getNameOfQueryType(rr->Type);
  if(s != NULL)
    printf(s);
  else
    printf("0x%2x", rr->Type);
  printf("\r\nQuery Class: ");
  s = _getNameOfQueryClass(rr->Class);
  if(s != NULL)
    printf(s);
  else
    printf("0x%2x", rr->Class);
  if(rr->Class & 0x8000)
    printf(",cache flush(mDNS)");
  printf("\r\n");

  printf("TTL: %lu\r\n", rr->TTL);
  switch (rr->Type){
  case DNSQT_A:
    printf("IPv4: ");
    if(rr->RData.A.addr)
      printf("%u.%u.%u.%u", rr->RData.A.addr[0], rr->RData.A.addr[1], 
           rr->RData.A.addr[2], rr->RData.A.addr[3]);
    break;
  case DNSQT_AAAA:
    printf("IPv6: ");
    if(rr->RData.AAAA.addr)
      for(i = 0; i < 15; i += 2){
        if(i > 0)
          printf(":");
        printf("%02X%02X", rr->RData.AAAA.addr[i], rr->RData.AAAA.addr[i + 1]);
      }
    break;
  case DNSQT_CNAME: case DNSQT_PTR: case DNSQT_MB: case DNSQT_MD: 
  case DNSQT_MF: case DNSQT_MG: case DNSQT_MR: case DNSQT_NS:
    printf("Name: ");
    if(rr->RData.CNAME.dname)
      printf("%s",rr->RData.CNAME.dname);
    break;
  case DNSQT_NULL:
    printf("NULL");
    break;
  case DNSQT_SOA:
    printf("Server: ");
    if(rr->RData.SOA.MName)
      printf("%s",rr->RData.SOA.MName);
    printf("\r\nContact: ");
    if(rr->RData.SOA.RName)
      printf("%s",rr->RData.SOA.RName);
    printf("\r\nSerial: %lx", (long) rr->RData.SOA.serial);
    printf("\r\nRefresh: %6lu", (long) rr->RData.SOA.refresh);
    printf("\r\nRetry: %6lu", (long) rr->RData.SOA.retry);
    printf("\r\nExpire: %6lu", (long) rr->RData.SOA.expire);
    printf("\r\nMinimum: %6lu", (long) rr->RData.SOA.minimum);
    break;
  case DNSQT_SRV:
    printf("Priority: %u", rr->RData.SRV.priority);
    printf("\r\nWeight: %u", rr->RData.SRV.weight);
    printf("\r\nPort: %u", rr->RData.SRV.port);
    printf("\r\nTarget: ");
    if(rr->RData.SRV.target)
      printf("%s", rr->RData.SRV.target);
    break;
  case DNSQT_NSEC:
    printf("Next Domain field: ");
    if(rr->RData.NSEC.NextDName)
      printf("%s",rr->RData.NSEC.NextDName);
    printf("\r\nBit Map(only for the first 256 bits): ");
    for(i = 0; i < rr->RData.NSEC.TypeBitMapsLength; i++)
      printf("%2x ", rr->RData.NSEC.TypeBitMaps[i]);
    break;
  case DNSQT_TXT:
    printf("len: %u\r\ndata:", rr->RData.TXT.len);
    for(i = 0; i < rr->RData.TXT.len; i++){
      if(isprint(rr->RData.TXT.data[i])){
        printf("%c", rr->RData.TXT.data[i]);
      }else{
        printf(" %#X ", rr->RData.TXT.data[i]);
      }
    }
    break;
  default:
    printf("Unrecognized type");
    break;
  }
  printf("\r\n");
}

BOOL DNSQue_isEqual(const DNSQue *que1, const DNSQue *que2){
  if(!DNSDName_isEqual(que1->Name,que2->Name))
    return FALSE;
  if(que1->Type != que2->Type)
    return FALSE;
  if(que1->Class != que2->Class)
    return FALSE;
  return TRUE;
}

int DNSRR_isEqual(const DNSRR *rr1, const DNSRR *rr2){
  if(rr1->TTL != rr2->TTL)
    return FALSE;
  return DNSRR_isEqualIgnoreTTL(rr1, rr2);
}

int DNSRR_isEqualIgnoreTTL(const DNSRR *rr1, const DNSRR *rr2){
  int i, len;
  if(!DNSQue_isEqual((const DNSQue *)rr1, (const DNSQue *)rr2))
    return FALSE;
  switch ((uint8_t)rr1->Type){
  case DNSQT_A:case DNSQT_AAAA:
    len = ((uint8_t)rr1->Type == DNSQT_A)? 4: 16;
    return memcmp(rr1->RData.A.addr,rr1->RData.A.addr, len) == 0;
  case DNSQT_CNAME: case DNSQT_PTR: case DNSQT_MB: case DNSQT_MD: 
  case DNSQT_MF: case DNSQT_MG: case DNSQT_MR: case DNSQT_NS:
    return DNSDName_isEqual(rr1->RData.CNAME.dname,rr2->RData.CNAME.dname);
  case DNSQT_SOA:
    if(!DNSDName_isEqual(rr1->RData.SOA.MName,rr2->RData.SOA.MName))
      return FALSE;
    if(!DNSDName_isEqual(rr1->RData.SOA.RName,rr2->RData.SOA.RName))
      return FALSE;
    if(rr1->RData.SOA.serial != rr2->RData.SOA.serial)
      return FALSE;
    if(rr1->RData.SOA.refresh != rr2->RData.SOA.refresh)
      return FALSE;
    if(rr1->RData.SOA.retry != rr2->RData.SOA.retry)
      return FALSE;
    if(rr1->RData.SOA.expire != rr2->RData.SOA.expire)
      return FALSE;
    return rr1->RData.SOA.minimum == rr2->RData.SOA.minimum;
  case DNSQT_SRV:
    if(rr1->RData.SRV.priority != rr2->RData.SRV.priority)
      return FALSE;
    if(rr1->RData.SRV.weight != rr2->RData.SRV.weight)
      return FALSE;
    if(rr1->RData.SRV.port != rr2->RData.SRV.port)
      return FALSE;
    return DNSDName_isEqual(rr1->RData.SRV.target,rr2->RData.SRV.target);
  case DNSQT_NSEC:
    if(!DNSDName_isEqual(rr1->RData.NSEC.NextDName,rr2->RData.NSEC.NextDName))
      return FALSE;
    len = (rr1->RData.NSEC.TypeBitMapsLength < rr2->RData.NSEC.TypeBitMapsLength)? 
      rr1->RData.NSEC.TypeBitMapsLength: rr2->RData.NSEC.TypeBitMapsLength;
    if(len > 0 && memcmp(rr1->RData.NSEC.TypeBitMaps, rr2->RData.NSEC.TypeBitMaps, len)!= 0)
      return FALSE;
    for(i = len; i < rr1->RData.NSEC.TypeBitMapsLength; i++)
      if(rr1->RData.NSEC.TypeBitMaps[i] != 0)
        return FALSE;
    for(i = len; i < rr2->RData.NSEC.TypeBitMapsLength; i++)
      if(rr2->RData.NSEC.TypeBitMaps[i] != 0)
        return FALSE;
    return TRUE;
  case DNSQT_TXT:
    if(rr1->RData.TXT.len != rr2->RData.TXT.len)
      return FALSE;
    return rr1->RData.TXT.len == 0 || 
      memcmp(rr1->RData.TXT.data, rr2->RData.TXT.data, rr1->RData.TXT.len) == 0;
  default:
    return -1;
  }
}

BOOL DNSDName_isEqual(const char *dn1, const char *dn2){
  dn1 = (dn1 == NULL)? _emptyStr: dn1;
  dn2 = (dn2 == NULL)? _emptyStr: dn2;
  return dn1 == dn2 || strcasecmp(dn1, dn2) == 0;
}

依赖

“NetworkLib” 在NTP模块里头已经发出来了,就不再发一遍了。直接点进去下。
https://blog.csdn.net/lin_strong/article/details/90678838#t5

“lib_lite” 主要是为了 strcasecmp 这个函数,如果你的平台上没有的话,我直接这里贴出实现。

#include <ctype.h>
int strcasecmp (const char *s1, const char *s2){
  char c1, c2;
  while (*s1 && *s2){
    c1 = *s1;
    c2 = *s2;
    c1 = isupper(c1)?(c1 - ('A' - 'a')): c1;
    c2 = isupper(c2)?(c2 - ('A' - 'a')): c2;
    if(c1 != c2)
      return c1 - c2;
    s1++;
    s2++;
  }
  return *s1 - *s2;
}

“common.h” 则只是宏定义了下BOOL、TRUE、FALSE而已。

模块简介

这个模块简单来说就是实现了对DNS报文的解析和构造功能。

RR记录的RDATA部分由于不同的记录类型不一样,需要分别实现。目前支持: A、AAAA、CNAME、NSEC、MB、MD、MF、MG、MR、NS、PTR、NULL、SOA、SRV、TXT类型。

支持域名指针。

不支持构造时的域名压缩。

解析时使用了malloc进行一些内部内存分配,务必记得调用对应接口进行解析,否则会有内存泄露。

结构体DNSHdr、DNSQue和DNSRR分别对应报文的报头、Queries区域以及RR区域。

DNSRR中RDATA根据不同的类型有不同的结构,选择对应的使用。

含构造的接口名字都为printXXXX、解析接口都为scanXXXX。

使用示例

构造报文

这里我们示例按之前说的请求过程构造一个对www.baidu.com的DNS请求报文:

uint8_t buf[300];
uint8_t *p = buf;
DNSQue que;
DNSHdr hdr;

// Header全部字段清零
memset(&hdr, 0, sizeof(DNSHdr));
// 请求类型为标准查询
hdr.opcode = DNSOC_Query;
// 是查询报文
hdr.QR = 0;
// ID随便设为0x5343
hdr.TransID = 0x5343;
// 递归查询
hdr.RD = 1;
// 问题部分有一个记录
hdr.Questions = 1;
// 构造报头部分,返回报头的下一个位置的指针
p = DNSMsg_printHeader(buf, sizeof(buf), &hdr);

// 询问的域名为www.baidu.com
que.Name = "www.baidu.com";
// 查询类型为A(IPv4)
que.Type = DNSQT_A;
// 查询类为IN因特网
que.Class = DNSQC_IN;
// 从报头的下一个位置起打印一个询问,返回下一个位置的指针
p = DNSMsg_printQuery(buf, sizeof(buf), p, &que);
// buf到p的之间的内容即为请求报文

当然,因为这个需求太常见了,我已经将其进行了封装,其实直接这样调用就行了,打开看内部源码几乎一样的:

msgLen = DnsMsg_printStandardQueryIPv4Message(buf,sizeof(buf), 0x5343,"www.baidu.com");

然后根据你实际的平台,将其发送给DNS服务器的53端口。正常就能得到答复报文了。

解析报文

收到了答复报文后我们就要进行解析了,基本就是这个样:

// 假设收到的报文存在recvBuf里,报文长度为recvSize
uint8_t recvBuf[500];
uint16_t recvSize;

DNSHdr hdr;
DNSQue que;
DNSRR  rr;
const uint8_t * p;
uint16_t i;
// 解析DNS报头,成功则返回的指针指向报头的下一个字节,否则返回NULL
p = DNSMsg_scanHeader(msg, msgLen, &hdr);
if(p == NULL){
  // 解析报头有问题,对应处理
}
// 现在DNS报头的各个信息就存在hdr里了,比如此时,hdr.TransID应为0x5343,hdr.QR应为1为答复报文

// 先解析Queries区域
for(i = 0; i < hdr.Questions; i++){
  // 解析Queries区域的一个记录,成功则返回的指针指向下一个字节,否则返回NULL
  p = DNSMsg_scanQuery(msg, msgLen, p, &que);
  if(p == NULL){
    // 解析有问题,对应处理
  }
  // 解析结果存在que里。
  // 解析成功务必记得调用以下接口释放内存
  DNSMsg_freeQueReturnByScan(&que);
}
// 然后解析Answers区域,里头应该有放着需要的那个地址记录
for(i = 0; i < hdr.AnswerRRs; i++){
  p = DNSMsg_scanRR(msg, msgLen, p, &rr);
  if(p == NULL){
    // 解析有问题,对应处理
  }
  // 解析结果存在rr里。
  if(rr.Type == DNSQT_A){
    printf("地址为%u.%u.%u.%u\r\n", rr.RData.A.addr[0], rr.RData.A.addr[1],
       rr.RData.A.addr[2], rr.RData.A.addr[3]);
  }
  // 解析成功务必记得调用以下接口释放内存
  DNSMsg_freeRRReturnByScan(&rr);
}
// 然后依次解析剩下两个sections,格式同上。

当然,因为这个需求也很常见,所以我把打印整个报文信息的接口也进行了封装:

DnsMsg_resolveMessage((const uint8_t *)recvBuf, recvSize);

综合示例

下面是使用我这个模块在window平台上进行DNS请求的程序示例:

#include <stdio.h>
#include <winsock2.h>
extern "C"{
#include "DNS.h"
};
#pragma comment(lib, "ws2_32.lib") 

static char _recvBuf[513];     
static uint8_t _sendBuf[300];
int main(int argc, char* argv[])
{
  WORD socketVersion = MAKEWORD(2,2);
  WSADATA wsaData; 
  if(WSAStartup(socketVersion, &wsaData) != 0)
  {
    return 0;
  }
  SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

  sockaddr_in sin;
  sin.sin_family = AF_INET;
  sin.sin_port = htons(53);
  // 一般直接发给网关就好了,现在的路由器上一般都提供DNS服务
  sin.sin_addr.S_un.S_addr = inet_addr("192.168.1.1");
  int len = sizeof(sin);
  int msgLen;
  // 构造请求报文
  msgLen = DnsMsg_printStandardQueryIPv4Message(_sendBuf,sizeof(_sendBuf), 0x5343,"www.baidu.com");
  printf("发送DNS报文,长度:%i\r\n", msgLen);
  // 打印请求报文
  DnsMsg_resolveMessage((const uint8_t *)_sendBuf,msgLen);
  // 发送请求
  sendto(sclient,(const char *) _sendBuf, msgLen, 0, (sockaddr *)&sin, len);
  // 接收答复
  int ret = recvfrom(sclient, _recvBuf, 513, 0, (sockaddr *)&sin, &len);
  if(ret > 0){
    printf("收到答复报文,长度:%i\r\n", ret);
    // 打印答复报文
    DnsMsg_resolveMessage((const uint8_t *)_recvBuf,ret);
  }

  closesocket(sclient);
  WSACleanup();
  return 0;
}

这是运行结果:

我们可以看到答复报文重复了Queries部分,然后在Answers部分表示:www.baidu.com的别名为www.a.shifen.com,而www.a.shifen.com又对应着182.61.200.6以及182.61.200.7这两个地址。然后在后面的授权部分和额外部分又带上了授权服务器的相关信息。

如果你想进一步深入研究下的话,这是请求报文:

这是答复报文:

一个个字节推一遍整个报文的话绝对可以加深理解,立刻尝试下吧,看好你哦!

后记

有什么建议和意见欢迎留言。

另,现在还没想清楚怎么实现构造报文时的域名压缩算法,哪个大神知道的话请赐教!

更新历史

2019/10/02 第一版
2020/06/04 更新到V1.1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章