DNS解析

1. 背景

最近遇到DNS解析慢的问题,梳理了一下DNS消息结构,用C++写了一个DNS解析过程。

2. 相关数据结构

2.1. DNS消息基类

class dns {
public:
    int hostlen(const unsigned char *buffer)
    {
        int length = 0;

        for (; ; ) {
            if ('\0' == buffer[length]) {
                length++;
                break;
            } else if (0xc0 == (buffer[length] & 0xc0)) {
                length += 2;
                break;
            }

            length++;
        }

        return length;
    }

    const char *hostname(const unsigned char *raw, int len, char *buffer, size_t size)
    {
        for (int res2 = 1; res2 < len - 1; res2++) {
            if ((raw[res2] >= 'a' && raw[res2] <= 'z') || (raw[res2] >= 'A' && raw[res2] <= 'Z')
                || (raw[res2] >= '0' && raw[res2] <= '9') || ('-' == raw[res2])) {
                buffer[res2 - 1] = raw[res2];
            } else {
                buffer[res2 - 1] = '.';
            }
        }

        return buffer;
    }

    const char *ipv4host(const unsigned char *raw, int len, char *buffer, size_t size)
    {
        int length = 0;

        for (int res = 0; res < len; res++) {
            length += snprintf(buffer + length, size - length - 1, "%d.", raw[res]);
        }
        buffer[length - 1] = '\0';

        return buffer;
    }

    const char *ipv6host(const unsigned char *raw, int len, char *buffer, size_t size)
    {
        int length = 0;

        for (int res = 0; res < len; res++) {
            length += snprintf(buffer + length, size - length - 1, "%02x:", raw[res]);
        }
        buffer[length - 1] = '\0';

        return buffer;
    }
protected:
private:
};

2.2. DNS header处理数据结构

class header {
public:
    explicit header(unsigned char *buffer) : head(nullptr)
    {
        ::memcpy(data, buffer, sizeof(data));
        head = (header_msg_t *)data;
    }

    int display() const
    {
        fprintf(stdout, "DNS header information:\n");
        fprintf(stdout, "    binary(%d):\n        ", (int)sizeof(header_msg_t));
        for (int res = 0; res < sizeof(header_msg_t); res++) {
            printf("0x%02x ", data[res]);
        }
        printf("\n\n");
        fprintf(stdout, "    0x%x \t\t Transaction ID\n", head->id);
        fprintf(stdout, "    Flags:\n");
        fprintf(stdout, "        %d \t\t %s\n", head->qr, head->qr ? "Response: Message is a response" : "Request: Message is a query");
        fprintf(stdout, "        %d \t\t %s\n", head->opcode, opcode());
        fprintf(stdout, "        %d \t\t %s\n", head->aa, head->qr ? (head->aa ? "Authoritative server" : "Non authoritative server") : "meaningless for request");
        fprintf(stdout, "        %d \t\t %s\n", head->tc, head->tc ? "Message is not truncated" : "Message is truncated");
        fprintf(stdout, "        %d \t\t %s\n", head->rd, head->rd ? "Do query recursively" : "Not do query recursively");
        fprintf(stdout, "        %d \t\t %s\n", head->ra, head->ra ? "Server can recursive queries" : "Server can not recursive queries");
        fprintf(stdout, "        %d \t\t reserved(0)\n", head->z);
        fprintf(stdout, "        %d \t\t %s\n", head->rcode, rcode());
        fprintf(stdout, "    %3d \t\t Questions\n", ntohs(head->qdcount));
        fprintf(stdout, "    %3d \t\t Answer RRs\n", ntohs(head->ancount));
        fprintf(stdout, "    %3d \t\t Authority RRs\n", ntohs(head->nscount));
        fprintf(stdout, "    %3d \t\t Additional RRs\n\n", ntohs(head->arcount));

        return sizeof(header_msg_t);
    }

    int questions() const
    {
        return ntohs(head->qdcount);
    }

    int answers() const
    {
        return ntohs(head->ancount);
    }

    int authority() const
    {
        return ntohs(head->nscount);
    }

    int additional() const
    {
        return ntohs(head->arcount);
    }
protected:
    const char *opcode(void) const
    {
        switch (head->opcode) {
        case 0:
            return "query: standard query";
        case 1:
            return "iquery: inverse query";
        case 2:
            return "status: DNS request status";
        case 5:
            return "update: DNS request update";
        default:
            break;
        }

        return "reserved for future use";
    }

    const char *rcode(void) const
    {
        switch (head->rcode) {
        case 0:
            return "success";
        case 1:
            return "Format error";
        case 2:
            return "Server failure";
        case 3:
            return "Name Error";
        case 4:
            return "Not Implemented";
        case 5:
            return "Refused";
        default:
            break;
        }

        return "Reserved for future use";
    }
private:
    /*! RFC 1035. */
    typedef struct header_msg {
        short   id;
#if __BYTE_ORDER == __BIG_ENDIAN
        unsigned char    qr:1;
        unsigned char    opcode:4;
        unsigned char    aa:1;
        unsigned char    tc:1;
        unsigned char    rd:1;

        unsigned char    ra:1;
        unsigned char    z:3;
        unsigned char    rcode:4;
#else
        unsigned char    rd:1;
        unsigned char    tc:1;
        unsigned char    aa:1;
        unsigned char    opcode:4;
        unsigned char    qr:1;

        unsigned char    rcode:4;
        unsigned char    z:3;
        unsigned char    ra:1;
#endif
        unsigned short   qdcount;
        unsigned short   ancount;
        unsigned short   nscount;
        unsigned short   arcount;
    } __attribute__((__packed__)) header_msg_t;

    unsigned char data[sizeof(header_msg_t)];
    header_msg_t  *head;
};

2.3. 请求消息处理数据结构

class request : private dns {
public:
    explicit request(unsigned char *buffer, int offset, int num = 1)
        : buffer(buffer), rnum(num), offset(offset)
    {}

    int display()
    {
        unsigned char *raw = buffer + offset;
        int length = 0;
        char binary[1024] = {0};
        int binlen = 0;

        printf("Queries information:\n");
        for (int res = 0; res < rnum; res++) {
            int len = hostlen(raw);
            char name[128] = {0};

            length += len;
            query_t query = {0};
            memcpy(&query, raw + length, sizeof(query));
            binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    Name: %s\n", hostname(raw, len, name, sizeof(name)));
            binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    Type: %s (%d)\n", qtype(ntohs(query.qtype)), ntohs(query.qtype));
            binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    Class: %s (%d)\n", qclass(ntohs(query.qclass)), ntohs(query.qclass));
        }

        printf("    binary(%d):\n        ", (int)sizeof(query_t) + length);
        for (int res = 0, res2 = 1; res < sizeof(query_t) + length; res++, res2++) {
            printf("0x%02x ", raw[res]);
            if (0 == (res2 % 16)) {
                printf("\n        ");
            }
        }
        printf("\n\n%s\n", binary);

        return sizeof(query_t) + length;
    }
protected:
    const char *qtype(unsigned short type) const
    {
        static const char *reqtype[] = {
            "unknow type",
            "a host address",
            "an authoritative name server",
            "a mail destination (Obsolete - use MX)",
            "a mail forwarder (Obsolete - use MX)",
            "the canonical name for an alias",
            "marks the start of a zone of authority",
            "a mailbox domain name (EXPERIMENTAL)",
            "a mail group member (EXPERIMENTAL)",
            "a mail rename domain name (EXPERIMENTAL)",
            "a null RR (EXPERIMENTAL)",
            "a well known service description",
            "a domain name pointer",
            "host information",
            "mailbox or mail list information",
            "mail exchange",
            "text strings"
        };

        return reqtype[type];
    }

    const char *qclass(unsigned short cls) const
    {
        static const char *reqclass[] = {
            "unknow class",
            "the Internet",
            "the CSNET class",
            "the CHAOS class",
            "Hesiod"
        };

        return reqclass[cls];
    }
private:
    typedef struct query {
        unsigned short   qtype;
        unsigned short   qclass;
    } __attribute__((__packed__)) query_t;

    unsigned char   *buffer;
    int             rnum;
    int             offset;
};

2.4. DNS响应处理数据结构

class response  : private dns {
public:
    explicit response(unsigned char *buffer, int offset, int num = 1)
        : buffer(buffer), anum(num), offset(offset)
    {}

    int display()
    {
        unsigned char *raw = buffer + offset;
        char binary[1024] = {0};
        int binlen = 0;
        int length = 0;

        printf("Answers information:\n");
        for (int res = 0; res < anum; res++) {
            if (0xc0 == (raw[0] & 0xc0)) {
                int len = hostlen(buffer + raw[1]);
                answer_t answer;
                char name[128] = {0};

                memset(&answer, 0, sizeof(answer));
                memcpy(&answer, raw + 2, sizeof(answer));
                binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    Name: %s\n", hostname(buffer + raw[1], len, name, sizeof(name)));
                binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    Type: %s (%d)\n", rtype(ntohs(answer.rtype)), ntohs(answer.rtype));
                binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    Class: %s (%d)\n", rclass(ntohs(answer.rclass)), ntohs(answer.rclass));
                binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    Time to live: %d (%d minutes)\n", ntohl(answer.rttl), ntohl(answer.rttl) / 60);
                binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    Data length: %d\n", ntohs(answer.rdlen));

                char address[128] = {0};
                switch (ntohs(answer.rtype)) {
                case 1:
                    binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    %s: %s\n", rtype(ntohs(answer.rtype)), ipv4host(raw + 2 + sizeof(answer), ntohs(answer.rdlen), address, sizeof(address)));
                    break;
                case 28:
                    binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    %s: %s\n", rtype(ntohs(answer.rtype)), ipv6host(raw + 2 + sizeof(answer), ntohs(answer.rdlen), address, sizeof(address)));
                    break;
                default:
                    binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "    %s: %s\n", rtype(ntohs(answer.rtype)), hostname(raw + 2 + sizeof(answer), ntohs(answer.rdlen) - 1, address, sizeof(address)));
                    break;
                }
                raw += 2 + sizeof(answer) + ntohs(answer.rdlen);
                length += 2 + sizeof(answer) + ntohs(answer.rdlen);
            }
            binlen += snprintf(binary + binlen, sizeof(binary) - binlen, "\n");
        }

        printf("    binary(%d):\n        ", length);
        for (int res = 0, res2 = 1; res < length; res++, res2++) {
            printf("0x%02x ", (buffer + offset)[res]);
            if (0 == (res2 % 16)) {
                printf("\n        ");
            }
        }
        printf("\n\n%s", binary);

        return length;
    }

    virtual ~response()
    {
    }
protected:
    const char *rtype(unsigned short type) const
    {
        switch (type) {
        case 1:
            return "A"; /*! 主机地址记录。在 DNS 域名与 IP 地址之间建立映射关系 */
        case 2:
            return "NS"; /*! Name Server, 域名服务器记录, 用来指定该域名由哪个DNS服务器来进行解析. */
        case 5:
            return "CNAME"; /*! Canonical Name, 别名记录, 允许您将多个名字映射到同一台计算机 */
        case 6:
            return "SOA"; /*! SOA记录表明了DNS服务器之间的关系, SOA记录表明了谁是这个区域的所有者 */
        case 11:
            return "WKS";
        case 12:
            return "PTR"; /*! 指针记录, 用来指向域名空间中的某个位置 */
        case 13:
            return "HINFO"; /*! 主机信息 */
        case 15:
            return "MX"; /*! Mail Exchanger, 记录是邮件交换记录,它指向一个邮件服务器 */
        case 28:
            return "AAAA";
        case 33:
            return "SRV"; /*! 说明一个服务器能够提供什么样的服务 */
        case 252:
            return "AXFR";
        case 255:
            return "ANY";
        default:
            break;
        }

        return "unknow";
    }

    const char *rclass(unsigned short rclass) const
    {
        static const char *reqclass[] = {
            "unknow class",
            "the Internet",
            "the CSNET class",
            "the CHAOS class",
            "Hesiod"
        };

        return reqclass[rclass];
    }
private:
    typedef struct answer {
        unsigned short   rtype;
        unsigned short   rclass;
        unsigned int     rttl;
        unsigned short   rdlen;
    } __attribute__((__packed__)) answer_t;

    unsigned char   *buffer;
    int             anum;
    int             offset;
};

2.5. 主程序

int main(int argc, const char *argv[])
{
    struct          __res_state dns_state = {0};
    unsigned char   buffer[4096] = {0};
    int             offset = 0;

    if (argc < 2) {
        printf("Please input a address:\n");
        return -1;
    }

    res_ninit(&dns_state);
    int res = res_nsearch(&dns_state, argv[1], ns_t_a, ns_c_in, buffer, sizeof(buffer));
    if (res > 0) {
        printf("receive '%d' bytes data\n", res);

        header head(buffer);
        offset += head.display();

        request request(buffer, offset, head.questions());
        offset += request.display();

        response response(buffer, offset, head.answers());
        offset += response.display();

        res_nclose(&dns_state);
    } else {
        printf("Request failure!\n");
    }

    return 0;
}

3. 运行结果(以www.baidu.com为例)

receive '90' bytes data
DNS header information:
    binary(12):
        0xee 0xaf 0x81 0x80 0x00 0x01 0x00 0x03 0x00 0x00 0x00 0x00 

    0xffffafee 		 Transaction ID
    Flags:
        1 		 Response: Message is a response
        0 		 query: standard query
        0 		 Non authoritative server
        0 		 Message is truncated
        1 		 Do query recursively
        1 		 Server can recursive queries
        0 		 reserved(0)
        0 		 success
      1 		 Questions          // 表明发送了一个请求消息
      3 		 Answer RRs         // 表明响应消息有三个
      0 		 Authority RRs
      0 		 Additional RRs

Queries information:
    binary(19):
        0x03 0x77 0x77 0x77 0x05 0x62 0x61 0x69 0x64 0x75 0x03 0x63 0x6f 0x6d 0x00 0x00 
        0x01 0x00 0x01 

    Name: www.baidu.com
    Type: a host address (1)
    Class: the Internet (1)

Answers information:
    binary(59):
        0xc0 0x0c 0x00 0x05 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x0f 0x03 0x77 0x77 0x77 
        0x01 0x61 0x06 0x73 0x68 0x69 0x66 0x65 0x6e 0xc0 0x16 0xc0 0x2b 0x00 0x01 0x00 
        0x01 0x00 0x00 0x00 0x40 0x00 0x04 0x24 0x98 0x2c 0x5f 0xc0 0x2b 0x00 0x01 0x00 
        0x01 0x00 0x00 0x00 0x40 0x00 0x04 0x24 0x98 0x2c 0x60 

    Name: www.baidu.com
    Type: CNAME (5)
    Class: the Internet (1)
    Time to live: 0 (0 minutes)
    Data length: 15
    CNAME: www.a.shifen

    Name: www.a.shifen.
    Type: A (1)
    Class: the Internet (1)
    Time to live: 64 (1 minutes)
    Data length: 4
    A: 36.152.44.95

    Name: www.a.shifen.
    Type: A (1)
    Class: the Internet (1)
    Time to live: 64 (1 minutes)
    Data length: 4
    A: 36.152.44.96

3.1. Queries information分析

Queries information:
    binary(19):
        0x03 0x77 0x77 0x77 0x05 0x62 0x61 0x69 0x64 0x75 0x03 0x63 0x6f 0x6d 0x00 0x00 
        0x01 0x00 0x01 

    Name: www.baidu.com
    Type: a host address (1)
    Class: the Internet (1)
  • 请求消息的QNAME是变长的,且以标签进行划分的,如上二进制中的:0x03,0x05,0x03表示name的长度
  • QNAME以‘\0’结尾,其后的二进制0x00表示结尾符
  • 0x00 0x01:表示QTYPE类型
  • 0x00 0x01:表示QCLASS类型

3.2. Answers information分析

Answers information:
    binary(59):
        0xc0 0x0c 0x00 0x05 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x0f 0x03 0x77 0x77 0x77 
        0x01 0x61 0x06 0x73 0x68 0x69 0x66 0x65 0x6e 0xc0 0x16 0xc0 0x2b 0x00 0x01 0x00 
        0x01 0x00 0x00 0x00 0x40 0x00 0x04 0x24 0x98 0x2c 0x5f 0xc0 0x2b 0x00 0x01 0x00 
        0x01 0x00 0x00 0x00 0x40 0x00 0x04 0x24 0x98 0x2c 0x60 

    Name: www.baidu.com
    Type: CNAME (5)
    Class: the Internet (1)
    Time to live: 0 (0 minutes)
    Data length: 15
    CNAME: www.a.shifen

    Name: www.a.shifen.
    Type: A (1)
    Class: the Internet (1)
    Time to live: 64 (1 minutes)
    Data length: 4
    A: 36.152.44.95

    Name: www.a.shifen.
    Type: A (1)
    Class: the Internet (1)
    Time to live: 64 (1 minutes)
    Data length: 4
    A: 36.152.44.96
  • Name:变长,若以0xc0开头,则表示name使用相对位置存放。如:0xc0 0x0c:表示name值相对于头开始偏移0x0c的位置即为name值;否则就是Name值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值