域名系统(DNS)
引言
什么是域名?域名系统又是什么?
让我们先来看看百度百科对域名和域名系统给出的解释的一部分内容:
网域名称(英语:Domain Name,简称:Domain),简称域名、网域,是由一串用点分隔的字符组成的互联网上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位。 由于IP地址具有不方便记忆并且不能显示地址组织的名称和性质等缺点,人们设计出了域名,并通过网域名称系统(DNS,Domain Name System)来将域名和IP地址相互映射,使人更方便地访问互联网,而不用去记住能够被机器直接读取的IP地址数串。域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用UDP端口53。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。
可以这么理解:域名可以方便大家记忆,DNS 目的是为了实现域名和主机地址之间的转换而存在的系统。
在部分情况下,域名不仅仅起到便于记忆的作用,服务端还会对其进行区分标记,如 a.example.com 和 b.example.com 可能是同一个 IP 的不同页面,由 Web 服务器进行展示,这时候直接访问 IP 地址就可能出现与期望不符的现象,所以我认为不能简单地说域名是代替 IP 地址进行访问。
DNS 解析的过程
在考虑如何完成这部分内容之前我参考了腾讯云社区中其他一些文章,也发现了一些问题(也可能是我的理解有问题),其中包括了腾讯认证的 IMWeb 前端团队这方面的文章,该团队的文章《DNS 解析》对 DNS 解析的整个过程进行了阐述,在这里我就简单通过对这篇文章的分析来讲述解析这一过程吧。
简单概括一下改文章中域名解析过程:
- 查找浏览器缓存
- 查找系统缓存
- 查找路由器缓存
- 查找ISP DNS 缓存
- 递归搜索
首先浏览器缓存是没有太大问题的,各家浏览器都有自己的缓存机制。
第二是系统缓存,确实系统会根据 TTL 缓存域名解析的结果,但文中所表述的 hosts 文件作为缓存是不正确的,在 Windows 操作系统下是由 DnsClient 服务负责实现的,在 Linux 系统下由 NSCD 类服务实现的。
第三路由器缓存,我本人对此表示疑惑(对不起,也许是我的路由器太弱了),虽然从道理上来说路由器确实可以劫持 DNS 请求进行解析缓存和返回,但是否真如此还得进一步讨论。
最后的查找 ISP DNS 缓存和递归搜索那就更值得怀疑了,首先我设置的 DNS 解析地址不一定是 ISP 所提供的地址,我大可选择一些公共 DNS ,谷歌、微软、腾讯等公司都对外提供公共 DNS 解析,并且并不是所有的 DNS 服务器都采用递归方式进行请求,迭代请求方式也是常见的。
这篇文章暂且分析到这里,在此对该团队该文章的严谨性提出质疑。
这里不得不再提的是 DNS 的迭代查询方式和递归查询方式,实际情景可能会较为复杂,甚至出现不同方式混合的情况,这里简单的进行如下不严谨的表述:
迭代解析方式中客户端依次访问不同级别的域名解析服务器进行查询
递归请求中由服务器完成解析直接返回给客户端(客户端仅发送一起解析请求即可完成解析)
主机记录和 TTL
在腾讯云 DNS 解析的常见问题文档中有了详细的陈述,这里直接引用:
要指向主机服务商提供的 IP 地址,选择类型 A;要指向一个域名,选择类型 CNAME。 A 记录:地址记录,用来指定域名的 IPv4 地址(例如 8.8.8.8),如果需要将域名指向一个 IP 地址(外网地址),就需要添加 A 记录。 CNAME 记录: 如果需要将域名指向另一个域名,再由另一个域名提供 IP 地址,就需要添加 CNAME 记录。 NS 记录:域名服务器记录,如果需要把子域名交给其他 DNS 服务商解析,就需要添加 NS 记录。 AAAA 记录:用来指定主机名(或域名)对应的 IPv6 地址(例如 ff06:0:0:0:0:0:0:c3)记录。 MX 记录:如果需要设置邮箱,让邮箱能收到邮件,就需要添加 MX 记录。 TXT 记录:如果希望对域名进行标识和说明,可以使用 TXT 记录,绝大多数的 TXT 记录是用来做 SPF 记录(反垃圾邮件)。 SRV 记录:SRV 记录用来标识某台服务器使用了某个服务,常见于微软系统的目录管理。主机记录处格式为:服务的名字.协议的类型。例如 _sip._tcp 。 隐、显性 URL 记录:将一个域名指向另外一个已经存在的站点,就需要添加 URL 记录。
其他和具体的解释参见下一部分内容
关于 TTL 的部分:
TTL 即 Time To Live,缓存的生存时间。指地方 DNS 缓存您域名记录信息的时间,缓存失效后会再次到 DNSPod 获取记录值。我们默认的600秒是最常用的。
DNS 请求
那么 DNS 请求是怎样的?
主要参考是 RFC 1034 Domain names - concepts and facilities 和 RFC 1035 Domain names - implementation and specification, RFC 1035 有更多的细节,1034 更为简洁。
该部分仅针对上述两个规范进行讨论,其他的 DNS 拓展(如 RFC 6891、1183、1706、2536、2539 等)规范不在该部分的分析范围内,后续可以进一步了解,对于在实例中出现的内容简单带过不作深入探讨。
约定
- 数据传输以 8 位构成的字节进行分割,每个单元内左侧为高位,如 1 0 1 0 1 0 1 0 表示十进制数 170
- 不区分大小写,但奇偶校验必须完全匹配
TPYE(类型)
类型 | 值 | 含义 |
---|---|---|
A | 1 | 主机地址 |
NS | 2 | NS 服务器 |
MD | 3 | 邮件目的地(已废弃 - 使用 MX) |
MF | 4 | 邮件转发器(已废弃 - 使用 MX) |
CNAME | 5 | 别名 |
SOA | 6 | 鉴权区域开始标志 |
MB | 7 | 邮箱域名(实验) |
MG | 8 | 邮件组成员(实验) |
MR | 9 | 邮件重命名域名(实验) |
NULL | 10 | 空 RR(实验) |
WKS | 11 | 服务描述 |
PTR | 12 | 域名指针 |
HINFO | 13 | 主机信息 |
MINFO | 14 | 邮箱或邮件列表信息 |
MX | 15 | 邮件交换 |
基本格式
包含:请求头(Header),请求(Question),回复(Answer),认证(Authority),附加(Additional)几个部分,在传输过程中,请求头必须存在,请求头内部表明了是否包括其他几个部分内容。
请求头格式
- ID 标识
- QR 0:查询 1:响应
- Opcode 0:标准查询 1:反向查询 2:状态请求 3-15:保留
- AA: 授权回答
- TC 报文截断
- RD 是否进行递归请求
- RA 是否支持递归
- Z 保留
- RCODE 应答码
- QDCOUNT 请求部分中的条目数。
- ANCOUNT 响应部分中的资源记录数。
- NSCOUNT 认证部分中名称服务器资源记录的数量。
- ARCOUNT 附加记录部分中的资源记录数量。
计数均为16位无符号整数
应答码:
值 | 应答 |
---|---|
0 | 无错误 |
1 | 格式错误,服务器无法解释查询 |
2 | 服务器故障 |
3 | 名称错误,针对权威 NS 查询中引用的域名不存在 |
4 | 未实现,不支持的查询类型 |
5 | 拒绝查询 |
6-15 | 保留 |
请求查询数据格式
- QNAME 请求名
- QTYPE 查询类型
- QCKASS 查询类,如IN
请求名:按 .
将域名进行分隔,将每一片的长度作为分隔符记录在该片之前,以 0 结尾,无需填充
如: cloud.tencent.com 分为 cloud tencent com 三部分,长度分别为 5,7,3,最终请求名为: x05cloudx07tencentx03com
类:
类型 | 值 | 说明 |
---|---|---|
IN | 1 | the Internet |
CS | 2 | the CSNET class (已过时) |
CH | 3 | the CHAOS class |
HS | 4 | Hesiod |
响应、认证、附加数据格式
- NAME 名称
- TYPE 类型
- CLASS 类
- TTL 生存时间
- RDLENGTH 附加数据长度(字节)
- RDDATA 附加数据
数据压缩
附加字段(RDDATA)中对于重复的域名可以进行压缩,其格式为:
由于约定但记录不超过 63 字符,所以其可作为指针,纸箱请求开始后的第 x 个字节开始以 0 结尾的内容。
DNS 请求实例
脱离了实例只看结构很难了解,想要了解 DNS 请求的内容和过程还是需要一步一步进行尝试。
此处以 腾讯公共 DNS 119.29.29.29
作为 DNS 服务器,请求解析 im.qq.com
、web.tdh6.top
两个域名为例,使用 Python 3.10
编程支持,利用 socket 库进行请求。
构造请求
首先明确所需要进行请求的基本格式,Header 和 Question 是必须的,Answer 无法预知也无需在查询请求中包含,Authority 和 Additional 也无需指定内容。
查询请求头
根据请求头包含的内容进行构建:
代码语言:python代码运行次数:0复制request_data = b"" # 初始化
request_data = b"