简单高效dns ipv4解析

最近在优化一个监控模块里面的dns解析功能,原来的做法是用旁路线程调用getaddrinfo去同步解析,解析实时性非常差,所以决定将解析改成异步执行。网上搜索了一些dns异步解析的库/接口, 大致有:getaddrinfo_a 、adns、c-ares、udns等等。这些库虽然功能相对比较全,但是在我的场景各种别扭或者低效,不能很好地满足我的需求:其实很简单,我就需要解析ipv4的协议编解码接口就行了,然后结合epoll完成异步解析。在纠结了很久之后,决定按照dns协议自己实现解析ipv4的协议编解码接口,其实就实现了2个接口: IPv4ReqPack、IPv4RspUnpack。



【头文件】
/// dns协议打包解包(ipv4地址)
/// @file
/// @date 2014-08-29 22:13:30
/// @version 1.0.0
/// @author ling-zhou(周龄), [email protected]
/// @copyright Tencent
/// @namespace hydra

#ifndef __DNS_PACKER_H__
#define __DNS_PACKER_H__
    "$Id: dns_packer.h 2207 2014-09-02 04:32:47Z zhouling $"

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <endian.h>
#include <netinet/in.h>
#include <vector>

namespace hydra {

const short DNS_ERR_BAD_DOMAIN    = 32767;
const short DNS_ERR_BAD_BUFFER    = 32766;
const short DNS_ERR_NULL_CNT      = 32765;
const short DNS_ERR_BAD_ID        = 32764;
const short DNS_ERR_UNPACK        = 32763;
const short DNS_ERR_TIMEOUT       = 32762;

/// @attention 要特别注意字节序
struct DnsHeader
{
    uint16_t id; // bits: 0-15; Identification
    union        // bits: 16-31; Flag
    {
        struct // 这个结构体的定义是为了方便操作具体的bit fields
        {
#if __BYTE_ORDER == __LITTLE_ENDIAN
            uint8_t rd : 1;     // bit:  23; Recursion Desired
            uint8_t tc : 1;     // bit:  22; Truncated
            uint8_t aa : 1;     // bit:  21; Authoritative Answer
            uint8_t opcode : 4; // bits: 17,18,19,20; Operation Code
            uint8_t qr : 1;     // bit:  16; Query/Response

            uint8_t rcode : 4;  // bits: 28,29,30,31; Return Code
            uint8_t cd : 1;     // bit:  27; Checking Disabled
            uint8_t ad : 1;     // bit:  26; Authenticated Data
            uint8_t z : 1;      // bit:  25; Zero, Reserved
            uint8_t ra : 1;     // bit:  24; Recursion Available
#else
            uint8_t qr : 1;     // bit:  16; Query/Response
            uint8_t opcode : 4; // bits: 17,18,19,20; Operation Code
            uint8_t aa : 1;     // bit:  21; Authoritative Answer
            uint8_t tc : 1;     // bit:  22; Truncated
            uint8_t rd : 1;     // bit:  23; Recursion Desired

            uint8_t ra : 1;     // bit:  24; Recursion Available
            uint8_t z : 1;      // bit:  25; Zero, Reserved
            uint8_t ad : 1;     // bit:  26; Authenticated Data
            uint8_t cd : 1;     // bit:  27; Checking Disabled
            uint8_t rcode : 4;  // bits: 28,29,30,31; Return Code
#endif
        };
        uint16_t flag; // 如果要同时操作多个bit fields,可以把值打包好一次性赋值给flag
    };
    uint16_t qdcount; // bits: 32-47; Total Questions; qd means Question Domain?
    uint16_t ancount; // bits: 48-63; Total Answer RRs; an means ANswer
    uint16_t nscount; // bits: 64-79; Total Authority RRs; ns means Name Server
    uint16_t arcount; // bits: 80-95; Total Additional RRs; ar means Additional Record

    void Clear() { memset(this, 0, sizeof(*this)); };

    void Show() // for debugging
    {
        printf("%7s 0x%04x(%u)\n", "id", id, id);
        printf("%7s 0x%04x(%u)\n", "flag", flag, flag);
        printf("%7s 0x%04x(%u)\n", "qr", qr, qr);
        printf("%7s 0x%04x(%u)\n", "opcode", opcode, opcode);
        printf("%7s 0x%04x(%u)\n", "aa", aa, aa);
        printf("%7s 0x%04x(%u)\n", "tc", tc, tc);
        printf("%7s 0x%04x(%u)\n", "rd", rd, rd);
        printf("%7s 0x%04x(%u)\n", "ra", ra, ra);
        printf("%7s 0x%04x(%u)\n", "z", z, z);
        printf("%7s 0x%04x(%u)\n", "ad", ad, ad);
        printf("%7s 0x%04x(%u)\n", "cd", cd, cd);
        printf("%7s 0x%04x(%u)\n", "rcode", rcode, rcode);
        printf("%7s 0x%04x(%u)\n", "qdcount", qdcount, qdcount);
        printf("%7s 0x%04x(%u)\n", "ancount", ancount, ancount);
        printf("%7s 0x%04x(%u)\n", "nscount", nscount, nscount);
        printf("%7s 0x%04x(%u)\n", "arcount", arcount, arcount);
    }
} __attribute__((packed));

/// @brief ipv4请求包编码
/// @param[out] buf 编码后的数据
/// @param[in,out] buflen 输入:buf长度,输出:编码后的数据长度
/// @param[in] domain 要解析的域名
/// @param[in] domainlen 要解析的域名长度
/// @param[in] id dns header里面的Identification字段
/// @return 0: 成功,非0:失败
int IPv4ReqPack(
        char* buf, uint32_t& buflen, const char* domain, uint32_t domainlen, uint16_t id = 0);

/// @brief ipv4响应包解码 
/// @param[out] ips 获取到的ipv4地址列表(big endian)
/// @param[in] buf 待解码的数据
/// @param[in] buflen 待解码的数据长度
/// @param[in] id dns header里面的Identification字段, 如果id不为0的话跟请求包里面的id必须匹配
/// @return 0: 成功,非0:失败
int IPv4RspUnpack(std::vector<uint32_t>& ips, const char* buf, uint32_t buflen, uint16_t id = 0);

}

#endif

【cpp文件】
#include "dns_packer.h"

namespace hydra {

int IPv4ReqPack(char* buf, uint32_t& buflen, const char* domain, uint32_t domainlen, uint16_t id)
{
    // check domain
    if (NULL == domain or domainlen < 3U) // at least 3 bytes, such as "a.b" 
        return DNS_ERR_BAD_DOMAIN;
    if ('.' == domain[domainlen - 1]) // remove the last '.', otherwise dns server do not response
        if (--domainlen < 3U)
            return DNS_ERR_BAD_DOMAIN;

    // check buf
    uint32_t datalen = sizeof(DnsHeader) + 1 + domainlen + 1 + 2 + 2;
    if (NULL == buf or buflen < datalen)
        return DNS_ERR_BAD_BUFFER;
    buflen = datalen;

    // fill domain of question
    unsigned char ch = 0, cnt = 0;
    char* ptr = buf + sizeof(DnsHeader) + domainlen;
    for (int i = domainlen - 1; i >= 0; --i)
    {
        if ('.' == (ch = domain[i]))
        {
            *(ptr--) = cnt;
            cnt = 0;
        }
        else
        {
            *(ptr--) = ch;
            ++cnt;
        }
    }
    *ptr = cnt; // the first count
    ptr = buf + sizeof(DnsHeader) + domainlen + 1;
    *(ptr++) = 0; // the last count

    // fill type of question(big endian)
    *(ptr++) = 0;
    *(ptr++) = 1; // type 1 means ipv4

    // fill class of question(big endian)
    *(ptr++) = 0;
    *(ptr++) = 1; // class 1 means internet

    // fill dns header
    DnsHeader* header = (DnsHeader*)buf;
    header->Clear();
    if (id != 0) header->id = htons(id);
    header->rd = 1; // Recursion Desired
    header->qdcount = htons(1);
    return 0;
}

int IPv4RspUnpack(std::vector<uint32_t>& ips, const char* buf, uint32_t buflen, uint16_t id)
{
    ips.clear();

    // check buf
    if (NULL == buf or buflen < sizeof(DnsHeader))
        return DNS_ERR_BAD_BUFFER;

    const DnsHeader* header = (DnsHeader*)buf;
    uint32_t pos = sizeof(DnsHeader);
    uint16_t qdcount = ntohs(header->qdcount);
    uint16_t ancount = ntohs(header->ancount);
    unsigned char ch = 0;

    // check return code
    if (header->rcode != 0)
        return header->rcode;
    // check answer count
    if (0 == qdcount or 0 == ancount)
        return DNS_ERR_NULL_CNT;
    // check Identification
    if (id != 0 and ntohs(header->id) != id)
        return DNS_ERR_BAD_ID;

    ips.reserve(ancount);

#define DNS_SKIP_DOMAIN()  \
    while (pos < buflen) \
    { \
        if (0 == (ch = buf[pos])) /* the last count found */ \
        { \
            pos += 1; \
            break; \
        } \
        else if ((ch & 0xc0) != 0) /* pointer found */ \
        { \
            pos += 2; \
            break; \
        } \
        else \
            pos += ch + 1; /* skip count + 1 chars */ \
    }

    // skip Questions
    for (uint16_t i = 0; i < qdcount; ++i, pos += 4) // 4 == len of{type(2) + class(2)}
        DNS_SKIP_DOMAIN()

    // parse Answer RRs
    for (uint16_t i = 0; i < ancount; ++i)
    {
        DNS_SKIP_DOMAIN();

        if (pos + 10 > buflen) // 10 == len of{type(2) + class(2) + ttl(4) + rdlength(2)}
            return DNS_ERR_UNPACK;

        uint16_t rdlength = ((uint16_t)buf[pos + 8] << 8) | buf[pos + 9]; // big endian
        if (pos + 10 + rdlength > buflen)
            return DNS_ERR_UNPACK;

        if ((0 == buf[pos]) and (1 == buf[pos + 1])) // type(big endian) 1 means ipv4
        {
            if (rdlength != 4) // ipv4 should be 4 bytes
                return DNS_ERR_UNPACK;
            ips.push_back(*(uint32_t*)(buf + pos + 10));
        }

        pos += 10 + rdlength;
    }

    return 0;
}

}

【测试工具】
/// dnsapi测试
/// @file
/// @date 2014-08-30 18:12:17
/// @version 1.0.0
/// @author ling-zhou(周龄), [email protected]
/// @copyright Tencent

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include "../src/dns_packer.cpp"

int main(int argc, char* argv[])
{
    if (argc < 3)
        return printf("usage: %s dnssvr domain\n", argv[0]);

    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0)
        return printf("socket err: %m\n");

    const char* dnssvr = argv[1];
    const char* domain = argv[2];
    char buf[1024];
    uint32_t buflen = sizeof(buf);
    uint32_t domainlen = strlen(domain);
    uint16_t id = random();

    int ret = hydra::IPv4ReqPack(buf, buflen, domain, domainlen, id);
    if (ret != 0)
        return printf("IPv4ReqPack ret: %d\n", ret);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof (addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(dnssvr);
    addr.sin_port = htons(53);

    ret = sendto(fd, buf, buflen, 0, (struct sockaddr*)&addr, sizeof(addr));
    if (ret != (int)buflen)
        return printf("sendto ret: %d, buflen: %u, err: %m\n", ret, buflen);

    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &(const timeval){5, 0}, sizeof(timeval));
    ret = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
    if (ret < (int)sizeof(hydra::DnsHeader))
        return printf("recvfrom ret: %d, err: %m\n", ret);

    hydra::DnsHeader* header = (hydra::DnsHeader*)buf;
    header->Show();

    std::vector<uint> ips;
    ret = hydra::IPv4RspUnpack(ips, buf, ret, id);
    if (ret != 0)
        return printf("IPv4RspUnpack ret: %d\n", ret);

    printf("ips:\n");
    for (size_t i = 0; i < ips.size(); ++i)
        printf("\t%s\n", inet_ntoa(*(in_addr*)&ips[i]));
    return 0;
}

测试正常请求:
$ ./a.out  114.114.114.114 www.baidu.com
     id 0x6745(26437)
   flag 0x8081(32897)
     qr 0x0001(1)
 opcode 0x0000(0)
     aa 0x0000(0)
     tc 0x0000(0)
     rd 0x0001(1)
     ra 0x0001(1)
      z 0x0000(0)
     ad 0x0000(0)
     cd 0x0000(0)
  rcode 0x0000(0)
qdcount 0x0100(256)
ancount 0x0300(768)
nscount 0x0000(0)
arcount 0x0000(0)
ips:
        61.135.169.125
        61.135.169.105

$ host www.baidu.com
www.baidu.com is an alias for www.a.shifen.com.
www.a.shifen.com has address 61.135.169.125
www.a.shifen.com has address 61.135.169.105

测试错误请求:
$ ./a.out  202.106.0.20 www.baidu. 
     id 0x6745(26437)
   flag 0x0581(1409)
     qr 0x0001(1)
 opcode 0x0000(0)
     aa 0x0000(0)
     tc 0x0000(0)
     rd 0x0001(1)
     ra 0x0000(0)
      z 0x0000(0)
     ad 0x0000(0)
     cd 0x0000(0)
  rcode 0x0005(5)
qdcount 0x0100(256)
ancount 0x0000(0)
nscount 0x0000(0)
arcount 0x0000(0)
IPv4RspUnpack ret: 5

$ ./a.out  114.114.114.114 www.baidu 
     id 0x6745(26437)
   flag 0x8381(33665)
     qr 0x0001(1)
 opcode 0x0000(0)
     aa 0x0000(0)
     tc 0x0000(0)
     rd 0x0001(1)
     ra 0x0001(1)
      z 0x0000(0)
     ad 0x0000(0)
     cd 0x0000(0)
  rcode 0x0003(3)
qdcount 0x0100(256)
ancount 0x0000(0)
nscount 0x0100(256)
arcount 0x0000(0)
IPv4RspUnpack ret: 3

错误码描述:
Rcode   Description References
0   No error. The request completed successfully.   RFC 1035
1   Format error. The name server was unable to interpret the query.    RFC 1035
2   Server failure. The name server was unable to process this query due to a problem with the name server. RFC 1035
3   Name Error. Meaningful only for responses from an authoritative name server, this code signifies that the domain name referenced in the query does not exist.   RFC 1035
4   Not Implemented. The name server does not support the requested kind of query.  RFC 1035
5   Refused. The name server refuses to perform the specified operation for policy reasons. For example, a name server may not wish to provide the information to the particular requester, or a name server may not wish to perform a particular operation (e.g., zone transfer) for particular data.  RFC 1035


你可能感兴趣的:(简单高效dns ipv4解析)