Linux下C/C++实现DNS查询(DNS QUERY)

DNS 的全称是 Domain Name System 或者 Domain Name Service,它主要的作用就是将人们所熟悉的网址 (域名) “翻译”成电脑可以理解的 IP 地址,这个过程叫做 DNS 域名解析。域名是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识。

DNS 查询到底是怎么完成的?

我们输入域名,浏览器就会在后台,自动向 DNS 服务器发出请求,获取对应的 IP 地址。这就是 DNS 查询。

Linux下C/C++实现DNS查询(DNS QUERY)_第1张图片

命令行工具 dig 可以跟 DNS 服务器互动,我们就用它演示 DNS 查询。简单介绍dig,dig(域信息搜索器)是一个用于询问 DNS 域名服务器的灵活的工具。它执行 DNS 搜索,显示从受请求的域名服务器返回的答复。

Linux下C/C++实现DNS查询(DNS QUERY)_第2张图片
然后说了查询的目标数例如上述命令只查询了一个网站,所以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协议。

  • 域名解析 - UDP

客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。虽然从理论上说,客户端也可以指定向DNS服务器查询的时候使用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。

  • 区域传输 -TCP

实现这种功能时则有时需要TCP协议,即进行与主域名服务器进行查询以确认数据是否有效,用TCP则是依赖了其可靠性

理论上来说,在客户端与DNS进行通信的过程中,使用两种传输协议在理论上都是可以实行的,但是事实上在目前的浏览器或者说在目前的清醒进行客户端与DNS的通信时一般默认使用UDP,而且某些客户端与DNS进行通信的时候还指定了使用UDP的通信方式,这就和当前HTTP与HTTPS对比下,在民用,安全问题不严峻的前提下,会偏向于使用速度更快的协议。

域名结构

Linux下C/C++实现DNS查询(DNS QUERY)_第3张图片
DNS 整个结构图是树状结构,最顶层称为 根域, 用 点 " . " 表示,相应服务器称为根服务器,整个域名空间的解析权都归根服务器所有。

因为负载庞大,所以采用 “ 委派” 机制,根域下设置一级域,将顶级域解析权委派给一级域服务器。同理,顶级域 下面设置 二级域, 二级域 下设 三级域。

根域:

位于域名空间最顶层, 一般用 一个 点 " . " 表示

一级域:

一般代表一种类型的组织机构或者国家地区。如 .net (网络供应商), .com (工商企业) , .org (团体组织), .edu (教育机构) , .gov (政府部门) , .cn (中国国家域名)

二级域:

用来标明顶级域内的一个特定组织,国家顶级域下面的二级域名有国家网络部门统一管理,如 .cn 顶级域名下面设置的二级域名 : .com.cn , .net.cn , .edu.cn

子域:

二级域下所创建的各级域统称为子域,各个组织或用户可以自由申请注册自己的域名

DNS Packet Structure

DNS 分为查询请求和查询响应,请求和响应的报文结构基本相同。DNS 报文格式如图所示。

Linux下C/C++实现DNS查询(DNS QUERY)_第4张图片
上图中显示了 DNS 的报文格式。其中,事务 ID、标志、问题计数、回答资源记录数、权威名称服务器计数、附加资源记录数这 6 个字段是DNS的报文首部,共 12 个字节。整个 DNS 格式主要分为 3 部分内容,即基础结构部分(报文首部)、问题部分、资源记录部分。
Linux下C/C++实现DNS查询(DNS QUERY)_第5张图片

DNS Headers
DNS数据包具有如下所示的标头。请注意,请求和回复遵循相同的内容标头格式。

Linux下C/C++实现DNS查询(DNS QUERY)_第6张图片

标志字段中每个字段的含义如下:

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),一般是服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答
Linux下C/C++实现DNS查询(DNS QUERY)_第7张图片图中的数据包为 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);
	}
...
}

运行结果:

Linux下C/C++实现DNS查询(DNS QUERY)_第8张图片
Linux下C/C++实现DNS查询(DNS QUERY)_第9张图片
tcpdump观察:

Linux下C/C++实现DNS查询(DNS QUERY)_第10张图片同时我们保存了dns_query报文

Linux下C/C++实现DNS查询(DNS QUERY)_第11张图片If you need the complete source code, please add the WeChat number (c17865354792)

总结

DNS是一个域名系统,是万维网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。

Welcome to follow WeChat official account【程序猿编码

参考:RFC 1035

你可能感兴趣的:(网络协议,linux,c语言,c++,dns,tcp/ip)