电子说
网上很多人都说 DNS 根服务器只有 13 台,中国一台也没有。在网络世界,中国被美国卡住了脖子。那 DNS 根服务器真的只有 13 台吗?如果是,那原因又是什么?今天就给大家说道说道。
DNS 基本概念
在回答这个问题之前,我们需要先回顾一些基本概念。DNS 是一种分层结构,这种层级就体现在域名的『点』里。以我的域名为例,TAOSHU.IN 它的完整域名其实是 TAOSHU.IN.。注意最后有一个点。它分三个层级,结结构为. ➜ IN ➜ TAOSHU。
DNS 又是分布式系统,每一层级都有自己的解析服务器。.是第一层级,它的解析服务器就是根服务器。第二层级是对应我们常说的COM/NET等顶级域名 TLD,而我用的IN是印度的国家域名,跟中国的CN一样,它们都是 CCTLD,也就是所谓的国家顶级域名。TAOSHU 就是普通的一级域名。每个域名都可以自行设置子域名,不受上级域名限制。
域名解析过程也是分布式的。还是以TAOSHU.IN为例。客户端先找到根服务器的地址,并其查询IN的解析服务器。再向IN的服务器查询TAOSHU.IN的服务器。最后向TAOSHU.IN 的服务器查询具体的解析记录,比如 A 记录等。
当前根服务器
从上面的过程可知,所有的 DNS 查询都从根服务器开始。所以根服务器是整个 DNS 系统的核心。如果根服务器出现故障,那所有 DNS 查询都会失败!为了避免出现这种问题,人们设置了多个 DNS 根服务器。发展到现在,互联网社区累计设置了 13 台,它们分别是:
主机名 | IP 地址 | 运营机构 | 国家 |
---|---|---|---|
a.root-servers.net | 198.41.0.4, 2001ba3e:30 | Verisign, Inc. | 美国 |
b.root-servers.net | 199.9.14.201, 2001200::b | University of Southern California, Information Sciences Institute | 美国 |
c.root-servers.net | 192.33.4.12, 20012::c | Cogent Communications | 美国 |
d.root-servers.net | 199.7.91.13, 20012d::d | University of Maryland | 美国 |
e.root-servers.net | 192.203.230.10, 2001a8::e | NASA (Ames Research Center) | 美国 |
f.root-servers.net | 192.5.5.241, 20012f::f | Internet Systems Consortium, Inc. | 美国 |
g.root-servers.net | 192.112.36.4, 200112::d0d | US Department of Defense (NIC) | 美国 |
h.root-servers.net | 198.97.190.53, 20011::53 | US Army (Research Lab) | 美国 |
i.root-servers.net | 192.36.148.17, 2001:53 | Netnod | 瑞典 |
j.root-servers.net | 192.58.128.30, 2001c27:30 | Verisign, Inc. | 美国 |
k.root-servers.net | 193.0.14.129, 2001:1 | RIPE NCC | 荷兰 |
l.root-servers.net | 199.7.83.42, 20019f::42 | ICANN | 国际 |
m.root-servers.net | 202.12.27.33, 2001:35 | WIDE Project | 日本 |
资料来源:IANA1 资料截止时间:2023年06月06日
在 1984 年,Jon Postel 和 Paul Mockapetris 在南加州大学设立了世界上第一台根服务器2。到了 1990 年,根服务器的数量扩展到了 7 台,分属不同的组织给护,但全都在美国。到了 1991 年,KTH 在瑞典设立一台根服务器。这是首次在美国之外部署根服务器。此后一直有旧的根服务器退役,新的服务器入役。到了 1995 年,根服务器已经扩展到 9 台。这个时候就遇到了技术瓶颈,无法添加新的根服务器了。
到底是什么技术瓶颈呢?这就得说说 DNS 的底层实现细节了。
Priming 查询
前面说所有的 DNS 查询都从根服务器开始。那客户端怎么知道当前有哪些根服务器呢?没什么好办法,就是在各自的代码中写死!对,是硬编码。但我们前面也说了,在役的根服务器并非一成不变,写死的话新添加的服务怎么生效呢?
这就用到了所谓的 Priming Queries3。简单来说,所有 DNS 解析客户端都随软件附带一个列表文件,里面有当前所有根服务器的信息,包括域名、IP地址等信息。这个文件叫 Root Hints,可以从 IANA 官网4下载。但考虑到根服务器的列表可能会变,所以客户端需要定期从已知的根服务器查询当前最新的服务器列表,用的也是 DNS 协议,这类请求叫作 Priming 查询。
对于客户端来说,它先从 Root Hints 中根据某种规则5选出一台根服务器,然后向它查询最新的根服务器列表,并本机缓存一段时间,过期之前都以该列表为准。
因为 Priming 查询也是用 DNS 协议,自然也走 UDP 传输。互联网早期 IP 网络的最大传输单元长度(MTU)也就五百多字节,所以 DNS 回复信息的最大长度同样不能太长。所以 DNS 协议规定回复信息不能超过 512 字节。这就是添加根服务器遇到的技术瓶颈。
其实解决这个问题很容易,完全可以要求客户端使用 TCP 连接传输 Priming 查询结果嘛。可惜当时没有采用这种方案。不过,如果是我,也不会选 TCP 方案。因为所有 DNS 查询都走 UDP 协议,简单而统一。虽然 DNS 也支持使用 TCP,但让 Priming 查询单独走 TCP 明显会让系统变得很复杂。
社区最终决定想办法压缩查询结果长度。
报文结构与编码
DNS 报文结构如下,分为五个部分6。
+---------------------+ | Header | +---------------------+ | Question | the question for the name server +---------------------+ | Answer | RRs answering the question +---------------------+ | Authority | RRs pointing toward an authority +---------------------+ | Additional | RRs holding additional information +---------------------+
Header
Header 为报文头信息,长度固定为 12 字节,结构如下:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Header 中跟本文内内直接相关的就是 QDCOUNT/ANCOUNT/NSCOUNT/ARCOUNT 这四个字段,分别表示后续 Question/Answer/Authority/Additional 段的数量。
Question
Question 段保存查询请求信息,通长只有一个。它分成三个部分。后两个部分表示查询类型和网络类型。含义不重要,重要的是长度固定为 4 字节。
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / QNAME / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QTYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
第一部分 QNAME 长度可变,保存查询的域名。但存储的方式有点特别。以域名A.ROOT-SERVERS.NET. 为例,它会分成三部分A、ROOT-SERVERS和NET。每一部分称作一个标签(Label), QNAME 字段只保存标签,不保存.。每个标签用第一个字节记录当前标签长度,后面跟着标签内容。最后用一个长度为零的标签表示结尾。所以完整的 QNAME 字段编码为:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 20 | 1 | A | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 22 | 12 | R | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 24 | O | O | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 26 | T | - | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 28 | S | E | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 30 | R | V | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 32 | E | R | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 34 | S | 3 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 36 | N | E | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 38 | T | 0 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
长度为 20 字节。特别的,对于根域名.,它的 QNAME 编码是:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 20 | 0 | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
长度为 1 字节。
Answer
Answer 段是服务器返回的响应结果。数量为一条到多条不等。每一条称为一个 RR,全称是 Resource Record。其结构如下:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TTL | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDLENGTH | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| / RDATA / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
RR 跟前面的 Question 相比多了 TTL/RDLENGTH/RDATA 三个字段。TTL 表示有效时长, RDLENGTH 表示后续 RDATA 的长度,RDATA 保存实际的响应数据。根据 TYPE 和 CLASS 的不同,RDATA 内容也各不相同。在 Priming 查询中,RDATA 保存各根服务器的域名,编码跟前 Question 中的 QNAME 一样。
如果服务器返回如下一条 RR 数据:
. 518400 IN NS a.root-servers.net.
那么 RR 的总长度是 1+2+2+4+2+20=31 字节。
Authority
Authority 段用来返回待查询域名的权威服务器信息。比如我们尝试向根服务器直接查询 TAOSHU.IN 的 A 记录,根服务器就会在 Authority 段返回 IN 域名的解析服器。因为根服务器并不保存TAOSHU.IN的域名信息。不过在本文中,Priming 查询的权威服务器就是根服务器,所以此段长度为零。
最后的 Additional 段用来返回一些附加信息。Answer 中只有域名信息。我们希望直接返回 IP 地址,所以需要用到 Additional 段。Additional 中也是一条一条的 PR,计算方式跟 Answer 的一模一样。
因为 Priming 查询会返回所有的根服务器域名及其对应的 IP 地址7,所以根服务器数量越多,返回的数据就越长。但 DNS 协议规定最长只能是 512 字节,这就产生了瓶颈。
到了 1995 年,已经开通了 9 台根服务器,Priming 查询结果快要超过 512 字节了。社区开始着手解决这个问题。方案是标签压缩。
标签压缩
压缩办法也很简单,就是在 NAME 中引入指针结构:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 1 1| OFFSET | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
DNS 还有一个规定,域名的长度不能超过 63 字节。NAME 中第一个字节表示长度,最大值就是 63,二进制表示为00111111,可见高两位是零。于是大家约定高两位设为11的时候,后面的 14 位就表示从报文 Header 开始的偏移量。这样一来,如果多个 RR 的域名中有相同的部分,就不需要重复传输,减少响应长度。
举个例子,比如要同时返回A.ROOT-SERVERS.NET和B.ROOT-SERVERS.NET两个域名,显然它们有共同的后缀ROOT-SERVERS.NET。假设A.ROOT-SERVERS.NET的偏移量为 20,那么可以表示为:
A.ROOT-SERVERS.NET +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 20 | 1 | A | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 22 | 12 | R | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 24 | O | O | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 26 | T | - | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 28 | S | E | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 30 | R | V | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 32 | E | R | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 34 | S | 3 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 36 | N | E | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 38 | T | 0 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ B.ROOT-SERVERS.NET +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 40 | 1 | B | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 44 | 1 1| 20 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 46 | 0 | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
利用标签压缩技术,域名B.ROOT-SERVERS.NET只需要占用 5 个字节,比 A.ROOT-SERVERS.NET 节省了 15 个字节。
根服务器更名
要想使用压缩,前提是所有域名有重复的部分。但之前的根服务器域名不相同,这就得改名。到了 1995 年,社区统一的域名为根服务器重新编组并部署标签压缩功能。
旧域名 | 新域名 | 运营机构 |
---|---|---|
NS.INTERNIC.NET | A.ROOT-SERVERS.NET | InterNIC (operated by NSI) |
NS1.ISI.EDU | B.ROOT-SERVERS.NET | Information Sciences Institute, USC |
C.PSI.NET | C.ROOT-SERVERS.NET | PSINet |
TERP.UMD.EDU | D.ROOT-SERVERS.NET | University of Maryland |
NS.NASA.GOV | E.ROOT-SERVERS.NET | NASA Ames Research Center |
NS.ISC.ORG | F.ROOT-SERVERS.NET | Internet Software Consortium |
NS.NIC.DDN.MIL | G.ROOT-SERVERS.NET | GSI (operated by NSI) |
AOS.ARL.ARMY .MIL | H.ROOT-SERVERS.NET | U.S. Army Research Lab |
NIC.NORDU.NET | I.ROOT-SERVERS.NET | NORDUnet |
上线之后,为新的根服务器留出了空间。于是在 1997 年,又上线了 J/K/L/M 四台根服务器。
这时候 Priming 查询响应的返回值有多大呢?我们可以算一下:
Header 固定 12 字节
第一个 PR 保存完整域名,31 字节
另外 12 PR 保存压缩后的域名,12*15 = 180 字节
13 个 PR 保存 A 记录,13 * 16 = 208 字节8
Question 段中 QTYPE 和 QCLASS 字段, 4 字节
Question 段中 QNAME 字段,1 字节
总共为 12+31+180+208+4+1=436 字节。剩余可用 512−436=76 字节。一组台服务器需要额外占用 15+16=31 字节。理论上还可以再添加两台根服务器,也就是最多15台。
如果只管根服务器功能,确实还可以添加。但是早期的根服务器同时也是COM/NET/ORG的解析服务器。客户端可以向根服器发起针对特定COM域名的 Priming 查询。因为响应结果需要包含查询域名 QNAME,所以上面说的 76 字节中至少要保留 64 字节给 QNAME。这样就只剩下 12 字节。所以就不能再添加新的根服务器了。
IPv6 与 Anycast
虽然理论上是不能再加新的根服务器了,但后来网络不断发展,UDP 报文早已不需要把长度限制到 512 字节。而且引入 IPv6 网络后,Priming 查询结果中还需要返回 AAAA 记录, 512 个字节肯定不够用。所以社区又设计了 EDNS09 来支持返回超过 512 字节的 DNS 响应。
理论上还是可以继续添加新的根服务器。但为什么不加了呢?那是因为有了更先进的技术 Anycast,中文译作任播。任播,可以简单理解为允许不同网络中的计算机共用一个 IP 地址,同时对外提供 DNS 查询服务。互联网会根据客户端的位置将请求路由到就近的计算机。
Anycast 技术将原来的单台服务器变成了一组多台服务器。到了2002年,J根服务器首次部署 Anycast 功能。到现在为止,前面说的13台根服务器严格来说是 13 个域名并且对应 13 对 IPv4 和 IPv6 地址。每对地址之后都通过 Anycast 部署了很多台实例,总计有超过 1500 台根服务器实例。这些实例又称为根镜像服务器。
中国根服务器镜像
虽说中国没有自己的根服务器,但境内还是有不少根镜像服务器:
编号 | 城市 |
---|---|
A | 广州 |
D | 香港 台北 |
E | 台北 |
F | 北京 重庆 杭州 高雄 南宁 台北 |
I | 北京 香港 沈阳 台北 |
J | 北京 香港 湖州 上海 |
K | 北京 广州 贵阳 台北 |
L | 北京 长沙 海口 上海 武汉 西宁 新北 郑州 |
M | 高雄 |
大家不妨通过 Ping 命令测一下,上面的几个根服务器的延迟都在 50ms 左右,一看就是在国内,不然不会这么快。
全部0条评论
快来发表一下你的评论吧 !