天天看点

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

现代生活中,网络可谓是无处不在,购物需要网络,付款需要网络,各种生活缴费需要网络,在各行各业的工作中,更是离不开网络。说到底,网络的作用无非是支持计算机间进行数据交换。世界各地有着不计其数的网络设备,这些网络设备是如何有序正常的进行数据交流的呢?网络以及各种协议的工作原理又是怎样的呢?本系列博客,我们将尝试自上而下的对网路的工作原理进行介绍,从应用层开始,逐层向下,详细的帮助你理解网络的核心工作原理。当然,网络协议多如牛毛,在网络分层中每一层的知识也是非常浩渺,希望这些博客可以起到抛砖引玉的作用,能够使你对于天天使用的互联网网络在宏观上有认识,在微观上也有了解。

说到网络,对于普通用户来说,使用最多的可能就是浏览各种网站了,虽然现在移动设备上的app基本代替了传统的pc应用和网站,但是这些app里提供的数据本质上网站中提供的数据并无不同,使用的网络技术并无不同。

我们知道,不论是访问网站还是app内进行接口请求,这些数据都是存储在“服务器”这种特殊的远程设备上的,要向服务器获取数据,首先我们需要找到服务器的位置,这很好理解,只有找到它,我们才能和它产生数据交流。互联网无论多大,本质上依然是通过电缆、光纤或各种无线设备这类连接介质连接在一起的,如果一台设备没有硬件上连接入互联网,那么说破天我们也无法和它产生数据交互。要找到一台互联网设备,实际上是通过其物理mac地址来找到的,这就像现实中的门牌号一样,每家的门牌号都不同,说到这,我们要再老生常谈一下,抛出网络分层模型给你看:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

关于这个网络分层模型,它在我们后面博客中的出境还少不了,现在你可以先不用管它,你只需要先知道物理层是负责设备物理媒介相关的协议,数据链路层通过硬件的mac地址找到具体要网络设备,网络层通过ip协议来封装真实的mac地址,传输层是对网络层的一种封装,tcp,udp等传输协议在这一层工作,而最上层的应用层就是我们常说的网络应用协议,如dns,http,https和fpt协议工作在这一层。

关于网络分层模型,我们先把多说了,我们的宗旨是自上而下的理解网络,那么还是回到第一步来。我们在访问网站时,都会现在浏览器输入网站的地址,这通常是一个域名,例如我要访问自己的技术博客网站,我会在浏览器输入如下的地址:

https://huishao.cc/

huishao.cc就是一个域名,首先只通过域名我们是找不到要访问的对方服务器的,这就好像现实中我要去小王家,可以我只知道小王的名字“王某某”是无法找到他的家的,我需要有一个住址簿,告诉我小王究竟住在哪了,这样我才能找到他。当然,此住址可能也不是真正的物理位置,可能是一个社区,比如小王住在“光明社区”,具体光明社区在哪,我们可以再通过查看地图获取。对应到互联网中,域名就是一个名字,它方便我们对网站进行记忆,ip地址则是要访问的对方在逻辑上的地址,这方便互联网的网络管理,最终的硬件地址则是真正的对方位置。ip地址到硬件地址的映射,等我们讨论到了再细聊,本篇博客我们就说域名到ip地址映射这一过程。

现在你应该已经明确,要通过域名找到某个设备,第一步是先得到此域名对应的ip地址,那么此ip地址是怎么得到的呢?首先,一定有一个地方维护了域名与ip地址的映射关系,如果你有过建站的经历,那么你一定进行过域名绑定操作,一个网站建成后,理论上就已经可以使用ip的方式来进行访问,但是为了易记和动态变动ip,通常会对其进行域名绑定。由域名获取到ip的这一过程,我们称之为域名解析。

域名解析是一种服务,提供域名解析服务的服务器即是dns服务器,下图可以很形象的表示域名服务器的工作方式:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

可以发现,映射表中记录了域名与ip间的映射关系,在实际的应用中,上图中描述的场景看似可行,实际却并非如此,世界上的域名与ip总数是一个非常庞大的数字,由一台服务器来维护所有域名ip信息几乎不可能,而且对于域名解析服务,请求量是巨大的,会有大量的用户频繁的进行域名解析请求,单服务器明显是不能满足需求的。因此,实际生产环境中的dns解析是采用层层递进,多级缓存,递归查询的方式进行的。再看下图:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

上图看似复杂,实际上只是描述了三个关键词:层层递进,多级缓存,递归查询。

下面我们来解释域名要解析成正确的ip地址,要经过的几个重要过程。

1. 本机hosts文件

本机hosts文件是优先级最高的域名ip映射表,对于mac操作系统,这个文件在根目录的etc文件夹下,我们可以直接将域名与对应的ip写在这个文件中,在进行域名解析时,首先会从这个文件中找。广播ip和本机ip对应的域名实际上就定义在这里,如下:

你也可以在其中新增任意映射,例如将huishao.cc的域名映射到127.0.0.1的本机ip,保存后,在浏览器再输入huishao.cc,你将无法再访问到珲少的博客网站,如下图所示:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

更多时候,hosts的正确用法是开发应用程序时,测试环境和正式环境可以将域名配置到不同的ip,这样无需应用程序代码中做逻辑,只需要切换hosts文件即可实现环境的切换。

2. 本机应用缓存

本机应用缓存是多级缓存中的第一级,例如当我们在浏览器中访问过某个域名后,其解析的结果会被浏览器缓存下来,当我们再次访问这个域名时,其首先会检查浏览器缓存,如果缓存能够命中此域名,则直接使用,缓存的有效时间会受ttl配置影响(我们后面会介绍)。

3. 本机系统缓存

与本机应用缓存类似,操作系统中也会有一份域名解析的缓存,如果本机应用缓存中没有命中,会从操作系统缓存中检查是否之前有过此域名的解析记录。如果能够命中则会直接使用。

4. 路由器域名解析缓存

如果本机系统缓存依然没有命中,而你的设备又是通过路由器接入的公网,此时你的域名解析服务很大可能是路由器提供的,可以打开网络设置的dns一栏,观察dns服务器的地址,如果是192.168.x.x类型内网地址,则说明是由路由器来完成dns解析了。如下图所示:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

路由器内,实际上也会缓存一张dns解析表,会从其中寻找是否有可以命中的缓存,如果存在并且未过期,则直接使用。有时候,你会发现电脑可以直接使用ip访问网站但是无法使用域名进行访问,很大可能是路由器的dns服务出问题了,最简单的解决方式就是将配置的dns服务器ip地址改成公共的。

5. 访问本地域名服务器

如果以上的缓存都没有命中,那么逻辑上我们就需要通过外网的dns服务来进行解析了,首先本地服务器(ldns)来解析域名,这里的本地服务器是指城市或区域的dns服务器,一般就有运营商部署在当地,距离近,性能好,并且也有缓存机制,几乎可以覆盖大多数的域名解析请求。

6. 转发与递归

如果你访问的域名比较冷门,本地服务器依然无法解析,则会进行转发,将此请求转发到更高级的运营商dns服务器或者根dns服务器,根dns服务器会根据域名来返回顶级的域名服务器地址,本地服务器可以继续向顶级域名服务器请求解析。如此递归进行,直到解析成功,再将ip地址依次返回到我们的设备,并逐层做缓存,以便我们下次访问时可以快速得到响应。

上面过程中,我们有提到根域名服务器,其是最高级别的域名服务器,它负责返回顶级域名服务器,目前全球有13个根域名服务器站。顶级域名服务器用来针对某个顶级域名进行解析,例如.com顶级域名,.edu顶级域名,.cc顶级域名和.cn顶级域名等。顶级域名服务器在解析时会将查询到的主域名服务器返回。主域名服务器负责某个区域的域名解析,同样,主域名服务器会配套辅助域名服务器进行备份与分担负载。

前面说了这么多,都是宏观上的认识。现在,我们要讨论一些更深入的东西了。虽然对于dns是干什么的,解析的过程是怎样的我们有了一些了解。但是dns协议究竟是怎么操作的呢?ip数据是怎么得到的?我们可以手动来进行dns解析么?要了解这些问题,首先需要对dns协议本身做个了解。

dns协议是工作在应用层的一种协议,全称domain name system。dns协议是基于udp之上实现的,前面说过udp是工作在传输层的一种网络协议,等我们说到它的时候再深入探讨。现在你只需要知道,基于udp任何人都可以实现一个dns解析服务。dns解析分为两步,首先需要客户端向服务器发送一个dns请求报文,服务器收到报文,解析完成后再返回一个dns报文给客户端,此报文中就包含解析的数据。

dns协议规定其请求报文与响应报文的结构是一致的,都包含header,question,answer,authority,additional这5个部分。

header部分的长度是一定的,固定为12个字节。dns协议文档中有一张图,很好的描述了header的数据结构:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

id:id占了两个字节,它是一个标识符,由客户端请求的时候填充,dns服务器解析后,会将此id返回,用来让客户端将响应与请求对应起来。

配置字段:上图中第2行的都是配置字段,其占了两个字节。

qr占1为,设置为0表示当前是dns请求报文,设置为1表示当前为dns响应报文。

opcode占4位,此值由请求报文设置,并且被复制到响应报文返回。其用来设置查询的类型,设置为0表示标准查询,即由域名解析出ip,设置为1表示反向查询,即由ip反查出域名,设置为2用来查询服务器的状态,3-15为保留字段,以待后续使用。

aa字段占1位,只在返回的响应报文中有,0表示返回数据的服务器不是权威服务器,1表示返回数据的服务器是权威服务器。需要注意,返回的响应报文中可能有多个应答,此字段表明的是第一个应答的服务器类型。

tc字段占1位,表示此报文是否由于数据的传输大小而被截断,当此字段的为1时,数据不可信。

rd字段占1位,该值需要在请求报文中设置,响应报文会直接复制该值。此值表示是否希望服务器进行递归查询。

ra字段占1位,其在响应报文中设置,表示服务端是否支持递归查询。

z字段占3位,是保留字段。

rcode字段占4位,是响应报文的响应码,0表示没有错误;1表示请求格式有误,服务端无法解析;2表示服务器出错;3表示请求的域名不存在;4表示服务器不支持这类请求;5表示服务器拒绝此次请求;6-15是保留参数。

qdcount:占16位,表明question部分包含的实例个数,是无符号数。

ancount:占16位,表明answer部分包含的回答个数,是无符号数。

nscount:占16位,表明authority部分包含的授权服务器数量,是无符号整数。

arcount:占16位,表明additional部分中包含的资源记录数量,是无符号整数。

这个部分用来定义查询的问题,问题的个数在qdcount指明,通常只会携带一个问题。每个问题的格式定义如下:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

qname:此部分字节数不定,描述要查询的域名。在解析的时候,这部分以0x00结尾。需要注意,域名通常由符号“.”进行分割,每段的长度不定,qname每段的开头会先指明此段的长度,以huishao.cc域名为例,其构造出的qname部分如下:

0x07 0x68 0x75 0x69 0x73 0x68 0x61 0x6f 0x02 0x63 0x63 0x00

其中最后一个字节0x00标记了qname部分的结束,0x07表示第一段的长度为7个字节,即0x68 0x75 0x69 0x73 0x68 0x61 0x6f是第一段,通过查询ascii码对照表可知,这段数据就是huishao,同理,之后的一个字节为0x02,表示第二段的长度为2个字节,0x63对应ascii表中的字母c,最终可以解析为huishao.cc。

qtype:占两个字节,对应查询的类型,定义如下:

type:意义

对应的值

a:ipv4主机地址

1

ns:权威域名服务器

2

md:邮箱地址(弃用,使用mx)

3

mf:转发邮箱(弃用,使用mx)

4

cname:规范的别名

5

soa:标记权威区域开始

6

mb:邮箱域名

7

mg:邮箱成员

8

mr:邮箱重命名域名

9

null:空的类型

10

wks:服务描述

11

ptr:域名指针

12

hinfo:主机信息

13

minfo:邮箱或者邮件列表信息

14

mx:邮件交换

15

txt:字符串

16

aaaa: ipv6域名

28

上面列举的查询类型中,有两个我们需要额外关注,a和cname,a类型即是我们查询域名ip所要使用的,cname别名技术也很常用,后面会介绍。

qclass:占两个字节,表明查询的类别,定义如下:

class:意义

in:internet查询

cs:弃用,rfc查询

ch:the choas class

hs:hesiod

进行dns解析时,只需要设置成in类即可。

这部分是响应的返回数据,可能包含多条资源记录,其格式如下:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

name:此记录所属的域名,长度不定,需要注意,这一部分存放的可能是真正的域名(格式和qname一致),也可能是指针,指向真正存放域名的字节位置,甚至可以是一部分是域名,一部分是指针。这样做的好处是可以节省响应报文的数据空间,当检查到某个字节的高两位为11时,则此字节及之后一个字节就是一个指针。例如对于huishao.cc域名的解析,其响应的完整的dns报文如下(16进制):

b3 a4 81 80 00 01 00 01 00 00 00 00 07 68 75 69

73 68 61 6f 02 63 63 00 00 01 00 01 c0 0c 00 01

00 01 00 00 02 58 00 04 b9 c7 6d 99

其中开头的12个字节为header部,随后的16个字节为question部,后面的即为answer部,answer部分开头的c0字节高两位为11,表明其是一个指针,占两个字节,c0,0c两个字节将前两位的1去掉后为十进制数12,表明name的真实值在第12个字节处开始,即复用了qname的数据。

type:占两个字节,与qtype定义一致。

class:占两个字节,与qclass定义一致。

ttl:占4个字节,此字段非常重要,标记了缓存的有效时长,单位是秒。顺便分析一下上面的数据,此dns解析数据的缓存有效期为0x0258,即600秒,10分钟。

rdlength:占两个字节,表明rdata字段的字节数。

rdata:真正的解析数据,与type有关,如果是ipv4域名解析,此处为解析的结果。

这两部分的数据结构与answer部分完全一致,解析方式也完全一致。

通过前面的介绍,dns协议的工作原理应该是明了了,如果需要更深入的了解细节,可以阅读其官方的文档:

https://datatracker.ietf.org/doc/html/rfc1035

当然,如果你还是感觉云里雾里也没有关系,我们通过实践来验证理论。

wireshark是一个网络封包分析软件,能够截取网络封包,对于网络传输的数据包进行分析十分方便。我们打开此软件后,找一个域名进行访问,即可抓取到对应的dns数据包,以huishao.cc为例,如下图所示:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

可以看到,wireshark可以分析出此次网络交互的时间,发起方ip,目标方ip,协议类型,数据长度和相信信息。在上面的示例中,第一条记录是dns请求报文,第二条记录是dns响应报文。我们先看看dns请求报文的数据:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

可以看到,wireshark将每一层网络协议都分析了出来,我们先只关注最上层的domian name system部分,这部分的十六进制数据是上图中选中的部分。可以发现其和我们上面介绍的协议格式是一一对应的。在看响应报文:

自上而下的理解网络(1)——DNS篇自上而下的理解网络(1)——DNS篇

数据的格式也是完全对应的,理论诚不欺我啊。

下面,我们可以以huishao.cc域名为例,手动使用udp协议来试一试发送dns请求以及对请求到的数据进行解析。首先先看完整的测试代码:

上面的代码有详细的注释,你可以尝试运行下进行域名解析,需要注意,上面填写的192.168.1.1是本地路由器的域名服务器地址,你需要将其替换成自己的,当然你也可以使用通用的域名解析服务器,如114.114.114.144。上面的代码采用c语言编写,因此在处理数据的时候会有一些复杂,有些点需要注意。

简单理解,位域可以让结构体中的数据以byte为单位已经存储,例如上面定义的dns_header结构体,我们按照dns协议的结构对其内数据所占的位进行了定义,有一点需要额外注意,在定义结构体时,位域字段的顺序与实际填充的顺序是相反的,位域的填充是从低字节开始的,如上代码所示,对于1个字节的位域来说,我们定义的时候,先定义的rd字段,最后定义的qr字段,实际在存储数据时,这一个字节的最高位会存储qr,最低位会存储rd。

在定义结构体时,还有一个细节需要注意,如果结构体中的数据字节数不是一致的,则其创建的内存大小可能和实际所需要的并不一致,例如r_data结构体,其中有int和short类型的数据,则其会以4字节为标准进行对齐,我们需要手动设置其对齐位数,不然后续数据填充时会出现偏差。

网络字节序是tcp/ip协议中定义的一种数据格式,其采用的是大端(big-endian)的排序方式,即对于一个字(两个字节)的数据,低字节在前,高字节在后。这与我们可读的主机字节序刚好是相反的,在c语言中,使用htons可以把short类型的数据进行网络和主机字节序的转换,htonl把long类型的数据进行网络和主机字节序的转换。

可以在如下地址下载到完整的上述代码:

https://gitee.com/jaki/dns_c

温馨提示,上面代码中解析的域名只返回了一个a类型的解析应答,如果你解析其他域名,可能会有很多cname类型的应答,应答个数也可能不止一个,你可以尝试下优化下代码,完整的实现dns的解析逻辑。

本篇博客到此就结束了,我相信你对从域名获取到ip的过程有了更多的认识,如果遇到了域名解析的问题,你应该明白如何查看响应结果来定位问题了,但是,这只是我们日常使用的网络中的第一步,目前我们连应用层的核心都还没有接触到,不积跬步,无以至千里,与君共勉。

专注技术,热爱生活,交流技术,也做朋友。 ——珲少 qq:316045346

继续阅读