DNS 的全称是 Domain Name System 或者 Domain Name Service,它主要的作用就是将人们所熟悉的网址 (域名) “翻译”成电脑可以理解的 IP 地址,这个过程叫做 DNS 域名解析。域名是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识。
DNS 查询到底是怎么完成的?
我们输入域名,浏览器就会在后台,自动向 DNS 服务器发出请求,获取对应的 IP 地址。这就是 DNS 查询。
命令行工具 dig 可以跟 DNS 服务器互动,我们就用它演示 DNS 查询。简单介绍dig,dig(域信息搜索器)是一个用于询问 DNS 域名服务器的灵活的工具。它执行 DNS 搜索,显示从受请求的域名服务器返回的答复。
然后说了查询的目标数例如上述命令只查询了一个网站,所以query就为1,但是有三个answer,所以answer=3.
上面这几行字需要注意question section,这个显示的是你查询了什么,这边显示的是你在查询www.baidu.com的A记录。也是仅仅只有一条查询。
最后显示查询结果及查询时间与DNS服务器。
DNS用的是TCP协议还是UDP协议
DNS占用53号端口,同时使用TCP和UDP协议。那么DNS在什么情况下使用这两种协议?
首先了解一下TCP与UDP传送字节的长度限制:
UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。当DNS查询超过512字节时,协议的TC标志出现删除标志,这时则使用TCP发送。通常传统的UDP报文一般不会大于512字节。
DNS在区域传输的时候使用TCP协议,其他时候使用UDP协议。
客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。虽然从理论上说,客户端也可以指定向DNS服务器查询的时候使用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。
实现这种功能时则有时需要TCP协议,即进行与主域名服务器进行查询以确认数据是否有效,用TCP则是依赖了其可靠性
理论上来说,在客户端与DNS进行通信的过程中,使用两种传输协议在理论上都是可以实行的,但是事实上在目前的浏览器或者说在目前的清醒进行客户端与DNS的通信时一般默认使用UDP,而且某些客户端与DNS进行通信的时候还指定了使用UDP的通信方式,这就和当前HTTP与HTTPS对比下,在民用,安全问题不严峻的前提下,会偏向于使用速度更快的协议。
域名结构
DNS 整个结构图是树状结构,最顶层称为 根域, 用 点 " . " 表示,相应服务器称为根服务器,整个域名空间的解析权都归根服务器所有。
因为负载庞大,所以采用 “ 委派” 机制,根域下设置一级域,将顶级域解析权委派给一级域服务器。同理,顶级域 下面设置 二级域, 二级域 下设 三级域。
根域:
位于域名空间最顶层, 一般用 一个 点 " . " 表示
一级域:
一般代表一种类型的组织机构或者国家地区。如 .net (网络供应商), .com (工商企业) , .org (团体组织), .edu (教育机构) , .gov (政府部门) , .cn (中国国家域名)
二级域:
用来标明顶级域内的一个特定组织,国家顶级域下面的二级域名有国家网络部门统一管理,如 .cn 顶级域名下面设置的二级域名 : .com.cn , .net.cn , .edu.cn
子域:
二级域下所创建的各级域统称为子域,各个组织或用户可以自由申请注册自己的域名
DNS Packet Structure
DNS 分为查询请求和查询响应,请求和响应的报文结构基本相同。DNS 报文格式如图所示。
上图中显示了 DNS 的报文格式。其中,事务 ID、标志、问题计数、回答资源记录数、权威名称服务器计数、附加资源记录数这 6 个字段是DNS的报文首部,共 12 个字节。整个 DNS 格式主要分为 3 部分内容,即基础结构部分(报文首部)、问题部分、资源记录部分。
DNS Headers
DNS数据包具有如下所示的标头。请注意,请求和回复遵循相同的内容标头格式。
标志字段中每个字段的含义如下:
QR(Response):查询请求/响应的标志信息。查询请求时,值为 0;响应时,值为 1。
Opcode:操作码。其中,0 表示标准查询;1 表示反向查询;2 表示服务器状态请求。
AA(Authoritative):授权应答,该字段在响应报文中有效。值为 1 时,表示名称服务器是权威服务器;值为 0 时,表示不是权威服务器。
TC(Truncated):表示是否被截断。值为 1 时,表示响应已超过 512 字节并已被截断,只返回前 512 个字节。
RD(Recursion Desired):期望递归。该字段能在一个查询中设置,并在响应中返回。该标志告诉名称服务器必须处理这个查询,这种方式被称为一个递归查询。如果该位为 0,且被请求的名称服务器没有一个授权回答,它将返回一个能解答该查询的其他名称服务器列表。这种方式被称为迭代查询。
RA(Recursion Available):可用递归。该字段只出现在响应报文中。当值为 1 时,表示服务器支持递归查询。
Z:保留字段,在所有的请求和应答报文中,它的值必须为 0。
rcode(Reply code):返回码字段,表示响应的差错状态。当值为 0 时,表示没有错误;当值为 1 时,表示报文格式错误(Format error),服务器不能理解请求的报文;当值为 2 时,表示域名服务器失败(Server failure),因为服务器的原因导致没办法处理这个请求;当值为 3 时,表示名字错误(Name Error),只有对授权域名解析服务器有意义,指出解析的域名不存在;当值为 4 时,表示查询类型不支持(Not Implemented),即域名服务器不支持查询类型;当值为 5 时,表示拒绝(Refused),一般是服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答
图中的数据包为 DNS 请求包,Domain Name System(query) 部分方框标注中的信息为 DNS 报文中的基础结构部分。
Linux下C/C++实现DNS查询(DNS QUERY)
// query type
enum {
QTYPE_A = 1,
QTYPE_NS = 2,
QTYPE_MD = 3,
QTYPE_MF = 4,
QTYPE_CNAME = 5,
QTYPE_SOA = 6,
QTYPE_MB = 7,
QTYPE_MG = 8,
QTYPE_MR = 9,
QTYPE_NULL = 10,
QTYPE_WKS = 11,
QTYPE_PTR = 12,
QTYPE_HINFO = 13,
QTYPE_MINFO = 14,
QTYPE_MX = 15,
QTYPE_TXT = 16,
QTYPE_AAAA = 28,
QTYPE_ANY = 255
};
// options
enum {
DNS_OPT_TIMEOUT,
DNS_OPT_RETRY,
DNS_OPT_IPV4,
DNS_OPT_IPV6,
DNS_OPT_PORT,
DNS_OPT_BUF,
DNS_OPT_BUFSIZE
};
struct dns_query *dns_init(struct dns_query *dns, int query_type);
int dns_set_option(struct dns_query *dns, int opt, ...);
int dns_add_question(struct dns_query *dns, const char *domain, uint16_t qtype);
int dns_send_query(struct dns_query *dns);
struct dns_packet *dns_response_packet(struct dns_query *dns);
int dns_next_rr(struct dns_rr *rr, struct dns_query *dns);
char *tdns_extract_domain(struct dns_query *query, unsigned char *domain);
.....
int main(int argc, char **argv)
{
...
dns_init(&dns, QUERY_STANDARD);
// 超时(毫秒)
dns_set_option(&dns, DNS_OPT_TIMEOUT, 3000);
// 超时超过时重试的次数
dns_set_option(&dns, DNS_OPT_RETRY, 2);
// DNS服务器ipv4
dns_set_option(&dns, DNS_OPT_IPV4, "8.8.8.8");
// 或者可以使用ipv6
// tinydns_set_option(&dns, DNS_OPT_IPV6, "2001:4860:4860::8888");
// 响应缓冲区,它应该大于或等于512字节
dns_set_option(&dns, DNS_OPT_BUF, buf);
dns_set_option(&dns, DNS_OPT_BUFSIZE, sizeof(buf));
...
if (dns_add_question(&dns, argv[1], QTYPE_A))
{
printf("failed to add a question\n");
goto end;
}
if (dns_send_query(&dns))
{
printf("failed to send dns query...\n");
goto end;
}
response = dns_response_packet(&dns);
if (response == NULL)
{
printf("response is too short...\n");
goto end;
}
printf("-- header --\n");
printf("id: %u\n", response->id);
printf("query(0) or response(1): %u\n", response->qr);
printf("opcode: %u\n", response->opcode);
printf("authoritative answer: %u\n", response->aa);
printf("truncated packet: %u\n", response->tc);
printf("recursion desired: %u\n", response->rd);
printf("recursion available: %u\n", response->ra);
printf("zero: %u\n", response->z);
printf("response code (0 is okay): %u\n", response->rcode);
printf("questions: %u\n", response->qdcount);
printf("answers: %u\n", response->ancount);
printf("name servers: %u\n", response->nscount);
printf("additional records: %u\n", response->arcount);
printf("---\n\n");
printf("RRs (resource records):\n");
while (dns_next_rr(&rrbuf, &dns))
{
char *domain = dns_extract_domain(&dns, rrbuf.name);
printf("\n\n");
printf("domain: %s\n", domain);
printf("type: %u\n", rrbuf.type);
printf("class: %u\n", rrbuf.class);
printf("ttl: %u\n", rrbuf.ttl);
printf("rdlength: %u\n", rrbuf.rdlength);
...
free(domain);
}
...
}
运行结果:
同时我们保存了dns_query报文
If you need the complete source code, please add the WeChat number (c17865354792)
总结
DNS是一个域名系统,是万维网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。
Welcome to follow WeChat official account【程序猿编码】
参考:RFC 1035