最近在优化一个监控模块里面的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