Wei / DNS 知识笔记

Created Sun, 22 Mar 2020 15:15:45 +0000 Modified Mon, 23 Sep 2024 07:21:40 +0000

这篇笔记用来记录 DNS 的相关知识。


                                       (@@) (  ) (@)  ( )  @@    ()    @     O     @     O      @
                                  (   )
                              (@@@@)
                           (    )

                         (@@@)
                       ====        ________                ___________
                   _D _|  |_______/        \__I_I_____===__|_________|
                    |(_)---  |   H\________/ |   |        =|___ ___|      _________________
                    /     |  |   H  |  |     |   |         ||_| |_||     _|                \_____A
                   |      |  |   H  |__--------------------| [___] |   =|                        |
                   | ________|___H__/__|_____/[][]~\_______|       |   -|                        |
                   |/ |   |-----------I_____I [][] []  D   |=======|____|________________________|_
                 __/ =| o |=-~O=====O=====O=====O\ ____Y___________|__|__________________________|_
                  |/-=|___|=    ||    ||    ||    |_____/~\___/          |_D__D__D_|  |_D__D__D_|
                   \_/      \__/  \__/  \__/  \__/      \_/               \_/   \_/    \_/   \_/

前言

网络服务是一般是基于 IP 地址提供的,但是 IP 地址比较难记,所以现实中使用了人类便于记忆的域名来访问网络服务。

而 DNS 服务器负责提供域名和 IP 地址映射关系查询的服务,一般叫做域名解析。

DNS 配置文件

在使用网络服务时,域名解析的工作由操作系统的全局配置的 DNS 服务器负责。

DNS 带有缓存机制,短时间的相同请求并不会多次请求 DNS 服务器,而会直接使用缓存,直到缓存过期后发起新的查询请求。

除了自身的 DNS 缓存和发起新的查询请求,本地 hosts 也可以提供类似于 DNS 的服务,它是可以自行定义的域名和 IP 地址的映射表,能够一定程度上优化 DNS 解析的进行,但现实中它多用于开发过程中的调试。

# 全局配置的 DNS 服务器
$ cat /etc/resolv.conf 
; generated by /sbin/dhclient-script
nameserver 192.168.0.1

# 本地 hosts 定义的映射关系
$ cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
# 在这个文件中写入 IP 地址和对应域名,在访问对应域名时会直接使用文件中给定的 IP 地址

DNS 查询规则

域名解析记录类型

发起域名查询的工具有 nslookupdig ,使用 dig 可以输出详细的域名解析记录。

dig 查询返回的域名解析记录的类型比较多,下面列出常见的域名解析记录类型:

  • A :解析对应域名的 IPv4 地址。
  • AAAA :解析对应域名的 IPv6 地址。
  • CNAME :全称为 Canonical Name ,用它来将为一个域名设置多个别名,使得不同域名最终解析到相同的 IP 地址。
  • SOA :用来表示此指明域名的主要 DNS 服务器,并设置记录的过期时间和版本信息。
  • NS :用来指明 DNS 服务器的 IP 地址, NS 记录可以有多个。
  • MX :全称为 Mail Exchange ,用来指定邮件服务器的 IP 地址。
  • PTR :反向解析记录,用来记录 IP 地址对应的域名信息。
  • SRV :用来记录提供特定服务的域名。
  • TXT :用来设置域名的说明信息。
# 使用 dig 对域名发起解析请求
$ dig github.com +noauthority +noadditional

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.4 <<>> github.com +noauthority +noadditional
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8528
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;github.com.			IN	A

;; ANSWER SECTION:
github.com.		572	IN	A	20.205.243.166

;; Query time: 41 msec
;; SERVER: 192.168.0.1#53(192.168.0.1)
;; WHEN: Sat Jan 08 16:24:44 CST 2022
;; MSG SIZE  rcvd: 44

访问站点需要的是 A 记录,这里省去了 dig 输出的 authority 和 additional 部分, authority 包含了所有的权威 DNS 服务器, additional 包含了 authority 中服务器的 A 记录。

根服务器和权威服务器

实际中使用的域名通过 . 来划分不同的域,例如 github.com 这个域名就可以划分为二级域 github 和一级域 com

在前面使用 dig 进行域名查询时,可以看到在 A 记录中,它的域名显示为 github.com. ,这个 . 代表根域,它是所有域名的起源。

$ dig . -t soa +noadditional
;; Warning: Message parser reports malformed message packet.

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.4 <<>> . -t soa +noadditional
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32440
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 13, ADDITIONAL: 26
;; WARNING: Message has 7 extra bytes at end

;; QUESTION SECTION:
;.				IN	SOA

;; ANSWER SECTION:
.			3595	IN	SOA	a.root-servers.net. nstld.verisign-grs.com. 2022010700 1800 900 604800 86400

;; AUTHORITY SECTION:
.			260739	IN	NS	j.root-servers.net.
.			260739	IN	NS	f.root-servers.net.
.			260739	IN	NS	c.root-servers.net.
.			260739	IN	NS	i.root-servers.net.
.			260739	IN	NS	h.root-servers.net.
.			260739	IN	NS	d.root-servers.net.
.			260739	IN	NS	k.root-servers.net.
.			260739	IN	NS	l.root-servers.net.
.			260739	IN	NS	a.root-servers.net.
.			260739	IN	NS	b.root-servers.net.
.			260739	IN	NS	g.root-servers.net.
.			260739	IN	NS	e.root-servers.net.
.			260739	IN	NS	m.root-servers.net.

;; Query time: 11 msec
;; SERVER: 192.168.0.1#53(192.168.0.1)
;; WHEN: Sat Jan 08 16:50:46 CST 2022
;; MSG SIZE  rcvd: 512

根域一共有 13 台 DNS 服务器,由于根域的需要处理的请求数量比较大,所以根域 DNS 服务器中只保存解析一级域的相关记录。

# 使用 dig 获取域名的权威 DNS
 dig github.com +noadditional -t soa
;; Warning: Message parser reports malformed message packet.

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.4 <<>> github.com +noadditional -t soa
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61395
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 8, ADDITIONAL: 17

;; QUESTION SECTION:
;github.com.			IN	SOA

;; ANSWER SECTION:
github.com.		3478	IN	SOA	dns1.p08.nsone.net. hostmaster.nsone.net. 1641583307 43200 7200 1209600 3600

;; AUTHORITY SECTION:
github.com.		520	IN	NS	dns2.p08.nsone.net.
github.com.		520	IN	NS	ns-421.awsdns-52.com.
github.com.		520	IN	NS	dns4.p08.nsone.net.
github.com.		520	IN	NS	ns-1283.awsdns-32.org.
github.com.		520	IN	NS	ns-520.awsdns-01.net.
github.com.		520	IN	NS	dns1.p08.nsone.net.
github.com.		520	IN	NS	ns-1707.awsdns-21.co.uk.
github.com.		520	IN	NS	dns3.p08.nsone.net.

;; Query time: 16 msec
;; SERVER: 192.168.0.1#53(192.168.0.1)
;; WHEN: Sat Jan 08 22:08:46 CST 2022
;; MSG SIZE  rcvd: 512

权威 DNS 服务器是域名的可信解析来源,一般来说 SOA 记录指定的 DNS 服务器就是权威 DNS ,但是为了保证域名查询的可用性,一般会设置多个 NS 记录,它们会在 dig 输出的 authority 部分,它们也属于权威 DNS 服务器。

递归查询和迭代查询

一次 DNS 查询行为一般由递归查询和迭代查询协作完成。

我们在浏览器上访问一个新的网站,这个时候一般需要向全局设置的 DNS 发起一个新的 DNS 递归查询。

假设全局设置的 DNS 服务器没有所请求域名的缓存,并且不是所请求域名的权威 DNS ,那么在它允许接受递归查询的前提下,会进行如下工作:

  • 本地全局 DNS 服务器向根服务器发起目标一级域的查询请求。
  • 本地全局 DNS 服务器接收到目标一级域权威 DNS 的记录信息应答,向这个权威 DNS 发起目标二级域的查询请求。
  • 本地全局 DNS 服务器接收到目标二级域权威 DNS 的记录信息应答,向这个权威 DNS 发起目标域名的查询请求。
  • 本地全局 DNS 服务器接收到目标域名的 A 记录信息应答,将结果返回给我们。

以上是一个理想化的工作链,实际中这个过程会因为缓存而加快。

在这个过程,用户向本地 DNS 发起的是递归请求,它的特点是发起一次请求,要求返回所请求域名的最终结果。本地 DNS 服务器接收了递归请求,会发起多个请求按层次解析域名,这些请求就是迭代请求。为了提高性能,一些 DNS 服务器不应该接受递归请求,比如根服务器和一级域权威 DNS 服务器。

允许接受递归请求的 DNS 服务器一般都开启了缓存功能,而且它们都保存了根服务器的地址,以保证前面所述的工作链能正常执行。

DNS 服务软件 BIND

BIND (Berkeley Internet Name Domain) 是最常用的 DNS 服务软件,我们通过它的服务配置可以更好理解 DNS 解析的运行。

BIND 基本配置

BIND 的配置文件 named.conf 语法和一些常规软件相似,在 /usr/share/doc/bind-<version>/sample/etc/ 中找到配置文件的参考模板。

BIND 还提供了管理工具 rndc ,这个管理工具的配置文件为 /etc/rndc.conf

named.conf 中,比较重要的配置项如下:

  • listen-on 和 listen-on-v6 定义了服务的 IP 地址和端口, DNS 服务的默认端口为 53 。
  • directory 定义了工作目录,默认为 /var/named
  • recursion 定义是否允许接受递归查询请求。
  • allow-query 定义了允许访问本机 DNS 服务的主机。
  • allow-query-cache 定义了规定允许访问本机 DNS 服务的缓存的主机。
# 查看 named.conf
$ cat /etc/named.conf 
options {
	listen-on port 53 { 127.0.0.1; };
	listen-on-v6 port 53 { ::1; };

	# 默认工作目录	
	directory 	"/var/named";
	
	# 运行 rndc dumpdb 后,named 服务缓存信息的保存路径
	dump-file 	"/var/named/data/cache_dump.db";
	
	# 运行 rndc stats 后,named 服务的统计信息的保存路径
	statistics-file "/var/named/data/named_stats.txt";

	memstatistics-file "/var/named/data/named_mem_stats.txt";
	
	# 运行接入的主机名单
	allow-query     { localhost; };
	allow-query-cache     { localhost; };
	
	# 递归查询
	recursion no;
	
	# dns 安全扩展
	dnssec-enable no;
	dnssec-validation no;

	bindkeys-file "/etc/named.root.key";
	managed-keys-directory "/var/named/dynamic";
};

logging {
	channel default_debug {
		file "data/named.run";
		severity dynamic;
	};
};

zone "." IN {
	type hint;
	file "named.ca";
};

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

配置 zone

除了基本的服务信息的配置,每个 zone 的配置是实际域名记录的来源。

在前面的 named.conf 已经有了关于根域的配置,可以看到域的配置比较简单。

域使用 file 来指向位于工作目录中,包含域解析信息的文件。

# named.conf 中包含的 named.rfc1912.zones 定义了标准的本地域名
$ cat /etc/named.rfc1912.zones
...
zone "." IN {
	type hint;
	file "named.ca";
};
zone "localhost" IN {
	type master;
	file "named.localhost";
	allow-update { none; };
};
zone "1.0.0.127.in-addr.arpa" IN {
	type master;
	file "named.loopback";
	allow-update { none; };
};
...
# 查看 zone file 的具体内容:
$ cat /var/named/named.loopback
$TTL 1D
@	IN SOA	@ rname.invalid. (
					0	; serial 
					1D	; refresh
					1H	; retry  
					1W	; expire 
					3H )	; minimum
	NS	@
	A	127.0.0.1
	AAAA	::1
	PTR	localhost.

在上述 zone file 中,你可以发现 PTR 记录为 localhost. ,它以 . 作为结尾,说明它是完全限定域名 FQDN 。如果在 zone file 中的域名记录不是 FQDN ,会被执行额外的替换工作,这将会由 $ORIGIN 决定。

替换的规则大概如下:

  • @ 用来代表 $ORIGIN
  • 如果 zone file 中没有定义过 $ORIGIN ,那么 $ORIGIN 默认是 zone name 加上 .
  • zone file 在加载时会检查非 FQDN ,并使用 $ORIGIN 修改它,即当 $ORIGIN localhost.hostname A 127.0.0.2 时,这条记录实际为 hostname.localhost. A 127.0.0.2
  • $ORIGIN 可以在 zone file 中任意位置重新定义,后续的记录会按照新定义的 $ORIGIN 进行对应的 FQDN 修改,定义 $ORIGIN 前的记录会使用默认的 $ORIGIN ,也就是 zone name 加上 .
  • 如果自定义 $ORIGIN 不以 . 结尾,那么这个 $ORIGIN 也会自动补上 zone name 作为新的 $ORIGIN ,然后再替换后续的非 FQDN 。
  • 自定义的 $ORIGIN 不应该脱离原有的 zone name ,否则记录无法找到对应的 zone ,将无法正确解析。

除了特殊变量,其他的域名记录一般以 hostname A 127.0.0.1 这样的格式定义,前面的域名解析记录类型都可以在 zone file 中定义。

写入了 zone file 后,还需要在 name.conf 中定义 zone 和它的对应名称,并且有相应的关键字去实现细节定义。

在 named.conf 中,使用 allow-update 关键字用来声明允许动态更新该域的主机,并且有四种 zone type :

  • hint 表示该域是根域, /etc/named.ca 中保存了根服务器的地址。
  • master 表示该域为权威域。
  • slave 表示该域为辅助域。
  • forward 表示该域为转发域。

使用不同的 zone type 共同协作才能支持 DNS 服务。

使用 master 和 slave 设置主从域

一般来说,合理的 DNS 服务需要两台 DNS 服务器,分别用来设置主 DNS 服务器和从 DNS 服务器,提高整体服务的可靠性。

我们将 type 为 master 的 zone 放在主 DNS 服务器中,并将相同 zone name 的 salve 域放在从 DNS 服务器,做好同步设置,它们之间就可以同步域名记录。

# 假设主 DNS 的 IP 地址为 192.168.0.1 ,从 DNS 的 IP 地址为 192.168.0.2

# 写入到主 DNS 的 named.conf 中
zone "example.com" IN {
	type master;
	file "zone.file";
	allow-transfer{
		192.168.0.2;
	};
	notify yes;
	also-notify{
		192.168.0.2;
	};
};

# 写入到从 DNS 的 named.conf 中
zone "example.com" IN {
	type slave;
	file "salve/zone.file";
	masters{
		192.168.0.1;
	};
};

配置的主要关键字有以下几个:

  • allow-transfer 用于声明允许同步的从 DNS 服务器。
  • notify 用于开启域名记录更新通知。
  • also-notify 用于声明在域名记录更新后,需要主动通知的从 DNS 服务器。
  • masters 用于声明该域的主 DNS 服务器,会使用在从 DNS 服务器中。

这几个关键字可以在 named.conf 的 options 中使用,这样全局的域都会遵从关键字定义的主从配置。

主从同步的步骤大概如下:

  1. 当主 DNS 服务器的域名记录更新后,启用了 notify 的域会向 also-notify 的从 DNS 服务器列表发送 notify 消息。
  2. 接收 notify 后,从 DNS 服务器发起 SOA 请求,将结果和该域在本机中 SOA 记录中的 serial 进行对比,如果小于本机的 serial 就结束同步过程。
  3. 如果大于本机的 serial ,则从 DNS 服务器会向主 DNS 服务器发起 transfer 请求,一般是 IXFR 增量同步请求。
  4. 主 DNS 服务器检查 transfer 请求是否来自 allow-transfer 的从 DNS 服务器。
  5. 检查通过则传输数据,完成同步。

使用 forward 设置转发域

如果 DNS 服务器不是某个 zone 的权威 DNS ,那么可以将这个域设置为转发域,将对该域的请求转发到指定的 DNS 服务器中。

# 转发域的设置用法
$ cat /etc/named.conf
...
options {
	forward [ first | only ];
	forwarders { ... };
};
...
zone "example.com" {
	type forward;
	forwarders { ... };
};
...

forward 提供 first 和 only 两个定义项:

  • first 会优先将请求转发到 forwarders 中,如果 forwarders 查询不到记录,则使用本机来进行查询。
  • only 会将所有请求转发到 forwarders 中,如果 forwarders 查询不到记录,则直接返回查询失败。

也可以在单一域中设置转发,并且在单一域中设置的 forwarders 可以覆盖掉全局的 forwarders 。

在上述的前提下,我们可以特殊置空某些域的 forwarders 。

# 置空某些域的转发,使其作为权威域
$ cat /etc/named.conf
...
options {
	forward [ first | only ];
	forwarders { ... };
};
...
zone "example.com" {
	type master;
	file "zone.file";
	forwarders {};  # 置空 forwarders
};
...

这样设置可以起到在全局转发的情况下,某些域可以直接在本地进行权威查询的效果。

使用 view 配置智能 DNS

在 named.conf 中使用 view 关键字可以实现简单的智能 DNS 。

$ cat /etc/named.conf
options {
...
	view 'viewA' {
		match-client {...};
		zone "A.com" IN {...};
		zone "B.com" IN {...};
	};
	view 'viewB' {
		match-client {...};
		zone "A.com" IN {...};
		zone "B.com" IN {...};
	};
...
};

这样设置可以实现用户分离,只要查询请求的来源符合对应的 match-client ,就使用对应的 view 去响应请求,达到智能化响应请求的效果。

使用 dnssec 应对域名欺骗

dnssec 是一种应对域名欺骗的 DNS 安全技术,在递归服务器向根服务器发起查询的过程中,迭代查询时返回的记录可能是一个伪造的响应,导致最终域名解析到了错误的地址,如果使用 dnssec ,就可以验证返回记录的完整性和真实性。

dnssec 引入了一些新的记录类型,比较重要的有以下几种:

  • RRSIG :域记录的数字签名,由服务端使用私钥生成。
  • DNSKEY :该域进行数字签名的公钥。
  • DS :用于记录下级域的公钥验证信息,是形成整条信任链的关键记录。

开启 dnssec 支持的查询过程大概如下:

  1. 在向权威 DNS 服务器发起正常 A 记录请求的同时,发起本次请求同时启用 dnssec 的信息。
  2. 如果目标 DNS 服务器不支持 dnssec ,则它只处理 A 记录请求。
  3. 如果目标 DNS 服务器支持 dnssec ,那么目标 DNS 服务器还会一并返回该 A 记录的 RRSIG 。
  4. 向目标 DNS 服务器请求 DNSKEY 和 DNSKEY 记录的 RRSIG ,使用 DNSKEY 验证上一步 A 记录的 RRSIG 。
  5. 向上级域请求 DS 记录和 DS 记录的 RRSIG ,然后使用 DS 记录验证上一步 DNSKEY 记录的 RRSIG 。
  6. 向更上一级域请求 DNSKEY 和对应的 RRSIG ,然后使用 DNSKEY 验证上一步 DS 记录的 RRSIG 。
  7. 重复步骤 5 和 步骤 6 ,验证一系列域的 DNSKEY 和 DS 记录的 RRSIG 。
  8. 到达根域后,由于根域的 DNSKEY 已经配置在服务中,所以整个查询链都是生效的。

可以看到,在这个过程中, DS 用于对 DNSKEY 的认证, RRSIG 用于对查询记录的认证, DNSKEY 是用于解密 RRSIG 的公钥, dnssec 的信任链机制类似于 SSL 的 CA ,因为根域的密钥已经默认配置,所以需要通过逐级信任 DS 记录来构建整条信任链,三种记录协作完成 dnssec 的验证。

在 named.conf 中,使用以下关键字配置 dnssec :

  • dnssec-enable 定义了是否对本地权威域启用 dnssec 。
  • dnssec-validation 定义了是否对返回的 RRSIG 进行验证,如果开启这个选项,使用 dig 工具测试签名验证成功会带有 ad 标志。
  • bindkeys-file 用于内置信任的密钥文件,一般是根域的密钥信息。
  • managed-keys-directory 用于指定迭代过程中,可信任 dnssec 密钥存储目录。

如果要对本地的权威域需要开启 dnssec ,则需要使用配套工具生成密钥对,然后对 zone file 进行数字签名,并修改对应的配置文件。

在面向公众的 DNS 服务,可以选择开启 dnssec ,而作为内网 DNS 服务时则没有必要开启 dnssec 。

使用 rndc 管理 BIND

rndc 是 BIND 内置的管理工具,可以在本地和远端对运行中的 BIND 进行操作和管理。

bind 还提供了一个管理工具 rndc ,来对 named 进行操作和管理,这个工具的特点是可以在 named 运行过程中动态修改部分配置。它通过 TCP 连接和 named 进行交互,这个工具可以在本地和远端使用,通过 TCP 连接到 953 端口和 BIND 服务进行通信。

BIND 提供了 rndc-confgen 来帮助配置 rndc 。

# 生成 rndc.conf 文件:
$ rndc-confgen 
# Start of rndc.conf
key "rndc-key" {
	algorithm hmac-md5;
	secret "iY/Zua49dUCZroBHAysTqA==";
};

options {
	default-key "rndc-key";
	default-server 127.0.0.1;
	default-port 953;
};
# End of rndc.conf

# 以下需要去掉注释后写入到 /etc/named.conf
# Use with the following in named.conf, adjusting the allow list as needed:
# key "rndc-key" {
# 	algorithm hmac-md5;
# 	secret "iY/Zua49dUCZroBHAysTqA==";
# };
# 
# controls {
# 	inet 127.0.0.1 port 953
# 		allow { 127.0.0.1; } keys { "rndc-key"; };
# };
# End of named.conf

它也提供了使用说明,只需要将 rndc-confgen 的输出写入到 /etc/rndc.conf 中,并将输出中注释的 keycontrols 写入到 /etc/named.conf 中,就可以正常使用 rndc 了。

如果把 controls 部分的本地回环 IP 地址修改为对外的 IP 地址,则 rndc 可以从远端通过网络进行控制。

rndc 提供了很多功能,以下列出几个常用的命令:

  • rndc status :用于查看服务端的运行信息。
  • rndc stats :用于保存 named 服务的运行的统计信息,named.conf 中的 statistics-file 将会是统计的保存路径,这些数据可以用作监控。
  • rndc dumpdb :用于保存 named 服务所缓存的记录,named.conf 中的 dump-file 将会是缓存的保存路径。
  • rndc flush :用于清除已缓存的查询记录。
  • rndc reload :用来重新载入 named 服务的配置文件,也可以用于刷新某个域信息。
  • rndc addzone :用来动态添加域,使用的方法通常会是 rndc addzone example.com '{type master; file "example.com.zone";};'
  • rndc delzone :用来动态删除域,使用的方法通常会是 rndc delzone example.com

如果使用 addzone 和 delzone 来动态更新域,需要注意以下几个点:

  • 使用动态域更新需要在 /etc/named.confoptions 下添加 allow-new-zones yes
  • 新增域的文件需要放在 named 服务定义的工作目录中,并符合 named 服务应有的读写权限。
  • 动态域更新会在工作目录下使用 nzf 为结尾的文件来保存动态域的信息,所以这个文件不应该被手动修改。
  • 如果动态域更新失败,有可能是 selinux 安全策略导致的,请检查整体配置或者 named_write_master_zones 并使用 setsebool 修改后再尝试。