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值