域名系统( 续)
关于 DNS 的更多信息
典型的 DNS 查询 和 DNS 回应通过 UDP 传输, 端口号 53.
如果查询( 更有可能是回应) 太大, 以至于一个 UDP 包装不下, 也会使用 TCP.
这种被称为 TCP fallback 或 DNS transport over TCP, 但大多数情况下使用的是 UDP.
关于 DNS 的更多信息, 查看:
DNS 消息格式
Header | 头部, 关于消息的各种信息 |
Question | 向域名服务器查询的问题 |
Answer | 对问题的回答, 答案可能不止一个 |
Authority | 指针, 指向权威服务器 |
Additional | 更多信息 |
DNS 消息头部格式
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ID | |||||||||||||||
QR | Opcode | AA | TC | RD | RA | Z | RCODE | ||||||||
QDCOUNT | |||||||||||||||
ANCOUNT | |||||||||||||||
NSCOUNT | |||||||||||||||
ARCOUNT |
DNS 查询 和 DNS 回应的消息头部的大小都是 12 字节, 包含 13 个字段.
ID | 16 位 | 标识 DNS 消息. 客户将任意值填充进 DNS 查询, DNS 服务器复制相同的值到 DNS 回应的 ID. |
QR | 1 位 | 0 代表 DNS 查询, 1 代表 DNS 回应. |
Opcode | 4 位 | 0 代表标准查询, 1 代表反查询( 解析 IP 地址到域名), 2 代表服务器状态请求, 其他值保留. |
AA | 1 位 | 表明一个权威答案(authoritative answer). |
TC | 1 位 | 表明消息是否被截断. 如果被截断, 就应该使用 TCP 重发. |
RD | 1 位 | 需要递归, 即需要联系其他服务器, 直至完成请求. |
RA | 1 位 | 在回应消息中表明服务器是否支持递归. |
Z | 3 位 | 未使用, 应置为 0. |
RCODE | 4 位 | 在 DNS 回应中表明错误情况. |
0 | 无错误, no error | |
1 | 格式错误, format error | |
2 | 服务器错误, server failure | |
3 | 名称错误, name failure | |
4 | 未实现, not implemented | |
5 | 被拒绝, refused | |
** | 更多出错信息, 查看 RFC 1035 |
QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT, 消息对应部分的数量.
QDCOUNT, 16 位, Qustion 数量, 只能为 1, 一条消息只能有一个问题.
ANCOUNT, 16 位, Answer 数量, DNS 服务器做回应时, 在一条消息中包含多个答案是很常见的.
Question 格式
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
NAME | |||||||||||||||
... | |||||||||||||||
QTYPE | |||||||||||||||
QCLASS |
QTYPE, 16 位, 表明要请求的记录的类型
QCLASS, 16 位, 置为 1, 表明 class 为 互联网(internet)
NAME 字段的编码
域名先被分成几个标签, 如 www.xyz123.com 被分为 3 个标签, www, xyz123 和 com.
每个标签前面添加 1 个字节, 用于表明标签的长度.
在末尾添加 0, 作为域名结束标识.
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3 | 'w' | ||||||||||||||
'w' | 'w' | ||||||||||||||
6 | 'x' | ||||||||||||||
'y' | 'z' | ||||||||||||||
'1' | '2' | ||||||||||||||
'3' | 3 | ||||||||||||||
'c' | 'o' | ||||||||||||||
'm' | 0 |
Answer 格式
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
NAME | |||||||||||||||
TYPE | |||||||||||||||
CLASS | |||||||||||||||
TTL | |||||||||||||||
TTL (续) | |||||||||||||||
RDLENGTH | |||||||||||||||
RDATA | |||||||||||||||
... |
TTL, 32 位, 缓存时长, 秒数
RDLENGTH, 16位, RDATA 字段的长度
DNS 消息的 Question 字段中包含了 NAME 信息.
所以, 大多数服务器在 Answer 字段的 NAME 中保存的是一个指针.
16 位值 11xx xxxx xxxx xxxx(x 为 0 或 1, 即最高 2 位置 1), 表明存储着一个指针.
指针的后 14 位表明指向的位置, 相对于消息开始处的偏移量.
DNS 消息在存储和传输时, 使用 大端序.
简单的 DNS 查询消息
查询 xyz123.com 的 IP.
- char dns_query[] = {
- 0xAA, 0xBB, /* ID */
- 0x01, 0x00, /* 递归查询 */
- 0x00, 0x01, /* QDCOUNT */
- 0x00, 0x00, /* ANCOUNT */
- 0x00, 0x00, /* NSCOUNT */
- 0x00, 0x00, /* ARCOUNT */
- 6, 'x', 'y', 'z', '1', '2', '3', /* 标签 */
- 3, 'c', 'o', 'm', /* 标签 */
- 0, /* NAME 结束 */
- 0x00, 0x01, /* QTYPE = 1 */
- 0x00, 0x01 /* QCLASS = 1 */
- };
DNS 查询程序示例
- /*
- query.dns.c
- 编译, 运行
- **$ gcc query.dns.c -o query
- **$ ./query aliyun.com a
- **$ ./query aliyun.com aaaa
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include "network.h"
- #define HEADER_LEN 12
- const char * print_name(
- const unsigned char * msg,
- const unsigned char * p,
- const unsigned char * end
- );
- void print_dns_message(const char * msg, int msg_length);
- int main(int argc, char ** argv) {
- if (argc != 3) {
- printf("Usage : %s hostname type\n", argv[0]);
- printf("Example: %s aliyun.com aaaa\n", argv[0]);
- return EXIT_SUCCESS;
- }
- if (strlen(argv[1]) > 255) {
- fprintf(stderr, "Hostname is too long ...\n");
- return EXIT_FAILURE;
- }
- int type;
- if (strcmp(argv[2], "a") == 0) {
- type = 1;
- }
- else if (strcmp(argv[2], "mx") == 0) {
- type = 15;
- }
- else if (strcmp(argv[2], "txt") == 0) {
- type = 16;
- }
- else if (strcmp(argv[2], "aaaa") == 0) {
- type = 28;
- }
- else if (strcmp(argv[2], "any") == 0) {
- type = 255;
- }
- else {
- fprintf(
- stderr,
- "Unkown type '%s'. Use 'a', 'aaaa', 'mx', 'txt', or 'any'.\n",
- argv[2]
- );
- return EXIT_FAILURE;
- }
- printf("Configuring remote address ...\n");
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_socktype = SOCK_DGRAM;
- struct addrinfo * peer_addr;
- /* 223.5.5.5, 阿里公共 DNS 服务器 */
- if (getaddrinfo("223.5.5.5", "53", &hints, &peer_addr)) {
- fprintf(stderr, "getaddrinfo() failed, errno: %d, %s\n", errno, strerror(errno));
- return EXIT_FAILURE;
- }
- printf("Creating socket ...\n");
- int peer_sock = socket(
- peer_addr->ai_family, peer_addr->ai_socktype, peer_addr->ai_protocol
- );
- if (peer_sock < 0) {
- fprintf(stderr, "socket() failed, errno: %d, %s\n", errno, strerror(errno));
- return EXIT_FAILURE;
- }
- printf("Constructing message ...\n");
- char query[1024] = {
- 0xAA, 0xBB, /* ID */
- 0x01, 0x00, /* Set recursion */
- 0x00, 0x01, /* QDCOUNT */
- 0x00, 0x00, /* ANCOUNT */
- 0x00, 0x00, /* NSCOUNT */
- 0x00, 0x00, /* ARCOUNT */
- };
- char * p = query + 12;
- char * q = argv[1];
- while (*q) {
- char * len = p;
- ++p;
- if (q != argv[1]) ++q;
- while (*q && *q != '.') *p++ = *q++;
- *len = p - len - 1;
- }
- *p++ = 0;
- *p++ = 0x00; *p++ = type; /* QTYPE */
- *p++ = 0x00; *p++ = 0x01; /* QCLASS */
- const int query_size = p - query;
- int bytes_sent = sendto(
- peer_sock,
- query, query_size,
- 0,
- peer_addr->ai_addr, peer_addr->ai_addrlen
- );
- printf("Sent %d bytes ...\n", bytes_sent);
- print_dns_message(query, query_size);
- char read_buff[1024];
- int bytes_recv = recvfrom(
- peer_sock,
- read_buff, sizeof(read_buff),
- 0,
- NULL, NULL
- );
- printf("Received %d bytes ...\n", bytes_recv);
- print_dns_message(read_buff, sizeof(read_buff));
- printf("\n");
- freeaddrinfo(peer_addr);
- close(peer_sock);
- return EXIT_SUCCESS;
- }
- const char * print_name(
- const unsigned char * msg,
- const unsigned char * p,
- const unsigned char * end
- ) {
- if (p + 2 > end) {
- fprintf(stderr, "End of message ...\n");
- exit(EXIT_FAILURE);
- }
- if ((*p & 0xC0) == 0xC0) {
- const int k = ((*p & 0x3F) << 8) + p[1];
- p += 2;
- printf(" (pointer %d) ", k);
- print_name(msg, msg+k, end);
- return p;
- }
- else {
- const int len = *p++;
- if (p + len + 1 > end) {
- fprintf(stderr, "End of message ...\n");
- exit(EXIT_FAILURE);
- }
- printf("%.*s", len, p);
- p += len;
- if (*p) {
- printf(".");
- return print_name(msg, p, end);
- }
- else
- return p + 1;
- }
- }
- void print_dns_message(const char * message, int msg_length) {
- if (msg_length < HEADER_LEN) {
- fprintf(stderr, "Message is too short ...\n");
- exit(EXIT_FAILURE);
- }
- const unsigned char * msg = (const unsigned char *) message;
- int i, j;
- for (i = 0; i < msg_length; ++i) {
- unsigned char r = msg[i];
- printf("%02d: %02X %03d '%c'\n", i, r, r, r);
- }
- printf("\n");
- printf("ID = %0X %0X\n", msg[0], msg[1]);
- const int qr = (msg[2] & 0x80) >> 7;
- printf("QR = %d %s\n", qr, qr == 0 ? "query" : "response");
- const int opcode = (msg[2] & 0x78) >> 3;
- printf("OPCODE = %d ", opcode);
- switch(opcode) {
- case 0: printf("standard\n"); break;
- case 1: printf("reverse\n"); break;
- case 2: printf("status\n"); break;
- default: printf("?\n"); break;
- }
- const int aa = (msg[2] & 0x04) >> 2;
- printf("AA = %d %s\n", aa, aa == 0 ? "" : "authoritative");
- const int tc = (msg[2] & 0x02) >> 1;
- printf("TC = %d %s\n", tc, tc == 0 ? "" : "message truncated");
- const int rd = msg[2] & 0x01;
- printf("RD = %d %s\n", rd, rd == 0 ? "" : "recursion desired");
- if (qr) {
- const int rcode = msg[3] & 0x07;
- printf("RCODE = %d ", rcode);
- switch(rcode) {
- case 0: printf("success\n"); break;
- case 1: printf("format error\n"); break;
- case 2: printf("server failure\n"); break;
- case 3: printf("name error\n"); break;
- case 4: printf("not implemented\n"); break;
- case 5: printf("refused\n"); break;
- default: printf("?\n"); break;
- }
- if (rcode != 0) return;
- }
- const int qdcount = (msg[4] << 8) + msg[5];
- const int ancount = (msg[6] << 8) + msg[7];
- const int nscount = (msg[8] << 8) + msg[9];
- const int arcount = (msg[10] << 8) + msg[11];
- printf("QDCOUNT = %d\n", qdcount);
- printf("ANCOUNT = %d\n", ancount);
- printf("NSCOUNT = %d\n", nscount);
- printf("ARCOUNT = %d\n", arcount);
- const unsigned char * p = msg + 12;
- const unsigned char * end = msg + msg_length;
- if (qdcount) {
- for (i = 0; i < qdcount; ++i) {
- if (p >= end) {
- fprintf(stderr, "End of message ...\n");
- exit(EXIT_FAILURE);
- }
- printf("Query %2d\n", i + 1);
- printf(" name: ");
- p = print_name(msg, p, end);
- printf("\n");
- if (p + 4 > end) {
- fprintf(stderr, "End of message ...\n");
- exit(EXIT_FAILURE);
- }
- const int type = (p[0] << 8) + p[1];
- printf(" type: %d\n", type);
- p += 2;
- const int qclass = (p[0] << 8) + p[1];
- printf(" class: %d\n", qclass);
- p += 2;
- }
- }
- if (ancount || nscount || arcount) {
- for (i = 0; i < ancount + nscount + arcount; ++i) {
- if (p >= end) {
- fprintf(stderr, "End of message ...\n");
- exit(EXIT_FAILURE);
- }
- printf("Answer: %2d\n", i + 1);
- printf(" name: ");
- p = print_name(msg, p, end);
- printf("\n");
- if (p + 10 > end) {
- fprintf(stderr, "End of message ...\n");
- exit(EXIT_FAILURE);
- }
- const int type = (p[0] << 8) + p[1];
- printf(" type: %d\n", type);
- p += 2;
- const int qclass = (p[0] << 8) + p[1];
- printf(" class: %d\n", qclass);
- p += 2;
- const unsigned int ttl = (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3];
- printf(" ttl: %u\n", ttl);
- p += 4;
- const int rdlen = (p[0] << 8) + p[1];
- printf(" rdlen: %d\n", rdlen);
- p += 2;
- if (p + rdlen > end) {
- fprintf(stderr, "End of message ...\n");
- exit(EXIT_FAILURE);
- }
- if (rdlen == 4 && type == 1) {
- printf("Address ");
- printf("%d.%d.%d.%d\n", p[0], p[1], p[2], p[3]);
- }
- else if (type == 15 && rdlen > 3) {
- const int preference = (p[0] << 8) + p[1];
- printf(" pref: %d\n", preference);
- printf("Mx: ");
- print_name(msg, p+2, end);
- printf("\n");
- }
- else if (rdlen == 16 && type == 28) {
- printf("Address ");
- for (j = 0; j < rdlen; j += 2) {
- printf("%02x%02x", p[j], p[j+1]);
- if (j + 2 < rdlen) printf(":");
- }
- printf("\n");
- }
- else if (type == 16) {
- printf("TXT: '%.*s'\n", rdlen-1, p+1);
- }
- else if (type == 5) {
- printf("CNAME: ");
- print_name(msg, p, end);
- printf("\n");
- }
- p += rdlen;
- }
- }
- if (p != end) {
- printf("There is some unread data left over ...\n");
- }
- printf("\n");
- }