[嵌入式開發模塊]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

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