来,建个DNS服务器吧(Part 1)

DNS与BIND.

DNS服务

大家都知道啦, 我们的DNS就是Domain Name Service, DNS作为一个(C/S架构的)协议, 也是一个规范. 对DNS协议的实现, 最标准的就是BIND了. 原先是Bekerley Internet Name Domain, 后来移交给了ISC来维护.

既然是C/S架构, 那么不可避免会使用到套接字, 那么也就需要端口和双方地址. 作为一个基础服务, 端口必须是众所周知的. DNS使用UDP/TCP的53端口, 在我们正常使用的一般是UDP协议的53端口, 原因也很简单了: 快啊~ 事实上, DNS服务不是在网络开始的时候出现的, 只是因为时间的推移, 主机越来越多, 人记忆IP地址实在是太难了所以才催生了DNS的诞生.

所以在互联网上使用名字也就不能随便想叫什么就叫啥了. 管理这些的组织是IANA, 一旦一个地址被分配出去了决不能在分配给另外一个主机了. 因此, 出现了一种应用机制: 比如/etc/hosts这个文件 , 而Windows是%WINDOWS%/system32/drivers/etc/hosts

他的格式就像是: X.X.X.X www.test.com 其实就类似的数据库的查找匹配

但是, 这种实现方式在后来接入网络主机越来越多, 越来越快, 本机维护hosts文件就越来越困难了. 于是IANA就维护一个单独的公共的服务器, 当有人申请的时候就会想办法把映射存进去. 后来条目又越来越多, 使得检索变得困难.所以这个服务器的数据进行了Hash化, 并且是Hash桶的形式, 存入内存 这样就会更快了.

但是, 随着主机记录更多了的时候, 这样一个服务器先不说需要多大的内存来存储, 还需要应付同时几十万的查询请求, 甚至还出现了Hash碰撞. 而每次加入数据库, 都需要重新更新, 载入内存. 这样对于一个服务器来说, 压力太多了!

所以, 我们就采取分布式的办法, 进行功能划分. 采用层级管理的方法, 每一级只需要知道自己一级的和自己下一级的地址就好.

这样最上级来管理的服务器, 我们就称为根名称服务器(root nameserver): 根服务器一共有13个IP, 但是服务器本身并不是就这么13个设备, 有很多镜像, 他们的安全等级非常高.

接下来说说解析的类型, 我们的名称解析是可以进行反向解析的, 在DNS诞生之前, 反向解析是十分简单的, 因为他就是两个键而已.但是DNS之后再想进行反向解析就不是这么简单了, 为什么? 请问: IP地址怎么做分布式? 很困难, 即使是现在(可以通过线索提示的方式进行检索), 反向解析也是一件尚未解决的问题. 而反向解析其实也是很重要的, 比如在我们的垃圾邮件判断上, 反向解析就是一种有效的手段, 通过判断名称和IP双向对应才会认为不是垃圾邮件. 需要注意的是: 正向解析和反向解析是两个不同的命名空间, 是两颗不同的树.

我们的DNS服务器的类型也是有很多的:

  • 主DNS服务器: 维护所负责的解析域内的解析库DNS服务器. 这个解析域是管理员维护的.
  • 从DNS服务器: 从主DNS服务器或者其他的DNS服务器那里”复制” ( 区域同步 )
    • 由于 ,这个复制是自动的, 没有人工. 所以要考虑到主服务器内记录发生变化,而从服务器却没有得到同步的情况. 这个就是通过解析记录来完成的, 首先, 从服务器定期进行检查. 早期是通过版本号(序列号)来实现的, 每次主服务器解析库发生变化, 他的序列号就会发生变化. 一旦发生出现变化, 从服务器就会请求同步. 同步的方式有两种, 一种叫做全量传送: 传递整个解析库, 一种叫做增量传送: 传递解析库变化的那部分内容. 那好, 定期检查, 这个定期到底改定成多少呢? 我们把这个时间间隔叫做刷新时间, 一旦刷新失败了, 进行重试. 这里就又会有一个重试时间, 也就是再次尝试的时间间隔. 很显然, 重试时间一定小于刷新时间. 另外, 还有一个时间叫做过期时长. 当主服务器始终不上线, 连不上的话, 从服务器就会!放弃自己的一切职务!停止提供服务! 所以如果出现故障的话就要立即修复. 为了避免从服务器落后于主服务器, 主服务区还可以有一种通知机制来告知从服务器内容有更新快来更新.
  • 缓存DNS服务器: 为了加速每一个用户的互联网访问, 才会需要进行缓存.所谓缓存就是一段内存空间, 向上面一样使用Hash存储, O(1)的速度. 在缓存失效之前, 所有的查询都会使用缓存. 缓存时间长, 可以减少耗时,性能优秀, 但是过时内容可能积攒太多. 缓存时间短, 内容可得到及时的更新, 但是带宽和性能消耗大. 所有这个时间的折中很重要. 其实每一个条目都有他自己的缓存.
  • 转发器

我们刚才一直在说区域 (区域传送) 而没有说 这两者是有区别的. 对于DNS服务器来说, 他有正向的和反向的两棵树, 对于任意一个域(Domain)来说:

正向: FQDN –> IP, 反向: IP –> FQDN. 他们的解析库也不是一个解析库, 来分别负责本地域名的正反向解析. 对于正向解析库来讲, 它属于正向解析区域. 所有我们说的时候, 可能是说正向和反向 而说区域的时候, 只能是单向的, 有可能是正向有可能是反向的.

FQDN就是指 完全合格域名 比如: www.baidu.com. 而 www 就不是.

DNS查询

Client –> hosts (如果没有结果) –> DNS Service

而在查询DNS Server的时候, 先检查Local DNS Cache –> 如果没有结果 –> DNS server(recursion) –> Server DNS Cache –> iteration(迭代)

解析答案有两种: 一种是肯定答案, 一种是否定答案(请求的条目不存在). 除此之外, 我们还有权威答案和非权威答案(缓存)

而我们每一个解析条目在解析库中, 叫做一个资源记录, 简称为RR. 记录有类型:

A记录, Address之意, 标记FQDN–>ipv4. 反过来的是PTR记录, CNAME(Canonical Name), 别名记录. SOA(Start Of Authority起始授权记录)定义域的负责, 一个区域解析库只能有一个SOA记录而且必须出现在第一条, NS就是Name Server的记录, 专门用于表明当前区域的DNS服务器, MX(mail exchange), 邮件交换器,AAAA定义ipv6的地址, 也就是FQDN–>ipv6 这些就是最常用的几个.

一条资源记录是有着格式的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
语法: name [TTL] IN rr_type value
1) TTL 可以从全局继承
2) 可以直接使用@来引用当前区域的名字
3) 相邻的两个记录name相同的时候, 可以省略
接下来就一个一个来看上面各个记录的格式:
SOA:
name: 当前区域的名字, 例如'yaoxuannn.com.'
value: 有多部分组成
1) 当前区域的主DNS服务器的FQDN, 也可以使用当前区域的名字
2) 当前区域管理员的邮箱地址, 但是地址中不能使用@符号, 一般使用.来替换. e.g:justin.126.com
3) (主从服务协调属性(各种时间间隔)的定义以及否定答案的统一TTL值)

比如请求stackoverflow.com的记录:
;; AUTHORITY SECTION:
com. 815 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1504938845 1800 900 604800 86400
后面的数字分别代表: 序列号, 刷新时间, 重试时间, 过期时间, 否定答案的TTL值.

NS:
name: 当前区域的名字, 和SOA的name一样
value: 当前区域的DNS服务器名字, 一个区域可以有多个NS记录.
后续都应该有一个A记录

MX:
name: 当前区域的名字
value: 当前区域的某邮件服务器的主机名
MX记录可以有多个, 但是每个记录的value之前都应该有一个数字(0-99), 表示此服务器的优先级, 数字越小,优先级越高
后续都应该有一个A记录

A:
name: 某主机的完整名字, FQDN
value: 对应主机的IP地址
还有泛域名解析的方式, 避免用户写错名称时给出错误答案

AAAA:
name: FQDN
value: IPv6

PTR:
name: IP, 有特定格式,需要反过来写, 而且有特定后缀: in-addr.arpa. 但网络地址可省
value: FQDN

CNAME:
name: 别名的FQDN
value: 正式名称的FQDN

BIND

我们搭建DNS服务器的程序包叫做BIND. BIND这个程序包包含多个程序子包, 有趣的是, 虽然程序包叫做BIND, 但提供的工具却叫做named.

1
2
3
4
5
6
7
[root@WWW ~]# yum list bind*
Installed Packages
bind.x86_64 32:9.9.4-50.el7_3.1 @updates
bind-libs.x86_64 32:9.9.4-50.el7_3.1 @updates
bind-libs-lite.x86_64 32:9.9.4-50.el7_3.1 @updates
bind-license.noarch 32:9.9.4-50.el7_3.1 @updates
...(omitted)

其中有一个util包很重要, 提供基础DNS查询还有测试工具, 我们的dig工具就是这个包提供的. 在bind开头的程序包中有一个叫做chroot的包, 他可以使得我们的DNS圈进在/var/named/chroot/, 把这个当做根来使用.

bind的主配置文件在named.conf, 但是惯例了, 为了不使配置文件过于庞大, 所以进行切片到一个叫做named.rfc1912.zones的文件中. 另外还有一个文件叫做: /etc/rndc.key 这个rndc是什么玩意?

rndc == remote name domain controller, 远程名称域控制器. 能够实现清理缓存, 重新装载区域配置文件, 查看状态. 但是默认只适用于本地主机, 通过127.0.0.1本地回环地址来进行管理, 它提供了很多辅助性的管理功能.

rndc也是一个服务, 监听在953/tcp端口. 所有的认证就依靠那个key文件.

OK, 现在回到BIND上, 最重要的部分就是我们的解析库文件了对不. 但是现在很肯定是空的嘛. 默认我们把解析解析库放在/var/named这个目录下. 每一个区域由一个解析库文件, 一个DNS服务区可以同时解析多个区域, 而且都可以进行正向和反向的解析. 一般约定俗成的都叫做ZONE_NAME.ZONE文件, 负责保存本地的区域数据. 这里面应该还有一个最重要的区域文件, 叫做根区域文件 这个文件叫做named.ca 另外还会解析localhost, 对于这两个特殊的区域(甚至更多, 如果包括ipv6的话)来实现本地回环地址和localhost的解析库.

我们来到/var/named, 来看看这个目录下的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@WWW named]# pwd
/var/named
[root@WWW named]# ls
data dynamic named.ca named.empty named.localhost named.loopback slaves
[root@WWW named]# cat named.ca
...(omitted)

;; ANSWER SECTION:
. 518400 IN NS a.root-servers.net.
. 518400 IN NS b.root-servers.net.
. 518400 IN NS c.root-servers.net.
. 518400 IN NS d.root-servers.net.
. 518400 IN NS e.root-servers.net.
...(omitted)

;; ADDITIONAL SECTION:
a.root-servers.net. 3600000 IN A 198.41.0.4
a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30
b.root-servers.net. 3600000 IN A 192.228.79.201
b.root-servers.net. 3600000 IN AAAA 2001:500:84::b
c.root-servers.net. 3600000 IN A 192.33.4.12
c.root-servers.net. 3600000 IN AAAA 2001:500:2::c
...(omitted)

看, 这个就是我们的根区域的记录. 其实这个文件是使用dig工具生成的. 剩下两个localhost和loopback就是我们的本机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@WWW named]# cat named.localhost 
$TTL 1D
@ IN SOA @ rname.invalid. (
0 ; serial
1D ; refresh
1H ; retry
1W ; expire
3H ) ; minimum
NS @
A 127.0.0.1
AAAA ::1
[root@WWW named]# cat 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.

那么这个区域名称@是什么呢, 在哪里定义的? 我们转向主配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@WWW named]# vim /etc/named.conf 
# 这个配置文件有四个部分构成
# options 全局配置
# logging 日志子系统配置
# zone 对于单独的一个区域的定义, 像解析哪些就要定义哪些: zone "ZONE_NAME" IN {}
# include 引入其他的文件
...
include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";
# 引入了两个文件, 我们主要来看下rfc1912.zones
[root@WWW named]# cat /etc/named.rfc1912.zones
# 这两个就是刚刚localhost的配置项目
zone "localhost.localdomain" IN {
type master;
file "named.localhost";
allow-update { none; };
};

zone "localhost" IN {
type master;
file "named.localhost";
allow-update { none; };
};
...(omitted)

由于这个rpm包已经是来源做过的了的, 所以其实直接启动的话他已经可以作为一个缓存DNS服务器了, 虽然只能解析自己和根, 而且只能给localhost用. 所有我们需要让它监听在一个可以和外部连接的IP上. 那么我们现在来修改一下他的配置文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@WWW named]# cp -av /etc/named.conf{,.bak}
‘/etc/named.conf’ -> ‘/etc/named.conf.bak’
# 别忘了先做个备份
------
options {
listen-on port 53 { 192.168.56.101; 127.0.0.1; }; # 注意空格和分号, 否则语法错误
// listen-on-v6 port 53 { ::1; }; # 如果需要进行注释,使用//, 多行注释/**/也支持
directory "/var/named";
dump-file "/var/named/data/cache_dump.db";
statistics-file "/var/named/data/named_stats.txt";
memstatistics-file "/var/named/data/named_mem_stats.txt";
allow-query { localhost; };
...

接着重启服务就可以了.

考虑到现在DNS服务的安全性, 这些年流行了一种新的机制, 叫做dnssec, 也就是每一个DNS都加上数字签名来进行校验. 但是由于其配置很复杂, 而且如果大家都没开就你一个人开了, 意义不大. 所以建议测试的时候把他关掉:

1
2
3
4
5
6
7
dnssec-enable yes;
dnssec-validation yes;

/* Path to ISC DLV key */
bindkeys-file "/etc/named.iscdlv.key";

managed-keys-directory "/var/named/dynamic";

把上面这一段都注释掉吧, 注意注释的嵌套.

还有一个关键的一项, 就是allow-query这个选项 可以选择把他注释掉, 或者把里面的localhost改成any, 这个是named内置的访问控制列表, 允许任何主机进行查询.

好, 接下来我们来看看怎么配一个主DNS服务器.

首先第一步, 我们在rfc1912.zones文件中加上

1
2
3
4
zone "yaoxuannn.com" IN {
type master; # 主服务器类型, 这个地方只能写{master|salve|hint(根)|forward}
file "yaoxuannn.com.zone"; # 对应的文件, 当然现在是没有这个文件的
}

接下来我们进行文件的创建 (定义区域解析库文件), 这个时候就要好好看看上面的记录格式了:

1
2
3
4
5
6
7
8
9
10
11
12
13
$TTL 86400 # 定义宏
@ IN SOA ns1.justn13wyx.me. admin.yaoxuannn.com (
2017090101
1H
5M
7D
1D ) # 首先定义SOA记录, 上面说过的对不
IN NS ns1.yaoxuannn.com. # 接着是两个NS记录, 别忘记了加上根域的.
IN NS ns2.yaoxuannn.com.
ns1.yaoxuannn.com. IN A 192.168.199.1 # 必然会跟着NS的A记录
ns2.yaoxuannn.com. IN A 192.168.199.2
www IN A 192.168.199.10
wwww IN CNAME www # 别名指向www

这样写有没有觉得的很麻烦啊, 其实我们可以把区域名省略, 这样文件就变成了这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
$TTL 86400
@ IN SOA ns1.justn13wyx.me. admin.yaoxuannn.com (
2017090101
1H
5M
7D
1D )
IN NS ns1
IN NS ns2
ns1 IN A 192.168.199.1
ns2 IN A 192.168.199.2
www IN A 192.168.199.10
wwww IN CNAME www

注意啦, 如果采取省略写法,那么一定不要在后面加上.了哦.

这么麻烦, 那人工检查是不是太麻烦了啊, 所以named提供了两条命令帮助你进行检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@WWW named]# named-checkconf 
# 没有任何输出, 没有消息就是最好的消息
[root@WWW named]# named-checkzone "yaoxuannn.com" yaoxuannn.com.zone
zone yaoxuannn.com/IN: loaded serial 2017090101
OK
------------------------------
[root@WWW named]# rndc status # 通过rndc提供的小工具查看当前服务器状态
version: 9.9.4-RedHat-9.9.4-50.el7_3.1 <id:8f9657aa>
CPUs found: 1
worker threads: 1
UDP listeners per interface: 1
number of zones: 101
debug level: 0
xfers running: 0
xfers deferred: 0
soa queries in progress: 0
query logging is OFF
recursive clients: 0/0/1000
tcp clients: 0/100
server is up and running
[root@WWW named]# rndc reload # 使用rndc的reload子命令来进行解析库reload
server reload successful

现在就可以进行尝试解析了.我们来看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@WWW named]# dig www.yaoxuannn.com

; <<>> DiG 9.9.4-RedHat-9.9.4-50.el7_3.1 <<>> www.yaoxuannn.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4592
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 3

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.yaoxuannn.com. IN A

;; ANSWER SECTION:
www.yaoxuannn.com. 86400 IN A 192.168.199.10

;; AUTHORITY SECTION:
yaoxuannn.com. 86400 IN NS ns2.yaoxuannn.com.
yaoxuannn.com. 86400 IN NS ns1.yaoxuannn.com.

;; ADDITIONAL SECTION:
ns1.yaoxuannn.com. 86400 IN A 192.168.199.1
ns2.yaoxuannn.com. 86400 IN A 192.168.199.2

;; Query time: 1 msec
;; SERVER: 192.168.56.101#53(192.168.56.101)
;; WHEN: Sat Sep 09 07:40:20 EDT 2017
;; MSG SIZE rcvd: 131

哇!出现了! 解析成功了! 经测试同一个子网的另外一台机器也能够成功解析!

OK, 那么现在回过头来看看/var/named这个目录:

1
2
3
4
5
6
7
8
9
10
11
[root@WWW named]# cd /var/named/
[root@WWW named]# ls -l
total 20
drwxrwx--- 2 named named 23 Sep 9 03:39 data
drwxrwx--- 2 named named 60 Sep 9 06:40 dynamic
-rw-r--r-- 1 root root 214 Sep 9 07:37 yaoxuannn.com.zone
-rw-r----- 1 root named 2281 May 22 05:51 named.ca
-rw-r----- 1 root named 152 Dec 15 2009 named.empty
-rw-r----- 1 root named 152 Jun 21 2007 named.localhost
-rw-r----- 1 root named 168 Dec 15 2009 named.loopback
drwxrwx--- 2 named named 6 Jul 5 06:15 slaves

发现我们创建的文件是属于root组的, 而且权限是644, 这样并不好, 所以我们要把它改成和其他一样的:

1
2
[root@WWW named]# chmod 640 yaoxuannn.com.zone 
[root@WWW named]# chown :named yaoxuannn.com.zone

好了. 至此我们的DNS服务器就初步搭建完成了.

DNS服务器的测试

dig

测试命令dig的使用:

dig [-t type] name [@SERVER] [query-options]

这个命令是不会进行dns系统hosts文件查询的

查询选项:

​ +[no]trace: 跟踪解析过程

​ +[no]recurse: 使用递归解析

host

测试命令host的使用:

host [-t type] name [SERVER]

十分简单, 就不说了

nslookup

更多的使用在交互式模式下,

nslookup>

​ server IP: 指明DNS服务器

​ set q=RR_TYPE: 指明查询的资源类型.

​ NAME: 接着输入需要查询的内容就可以了.