来,建个DNS服务器吧(Part3)

来稍微说点高级的话题: 子域授权 转发服务器的建立 Bind的安全和权限管理 Bind的编译安装 和 压力测试

子域授权和转发相关话题

为啥我们要做子域授权 ? 这是我们实现分布式DNS数据库的一个前提. 由于全球DNS的查询量太大了, 所以我们才进行切割不同的部分, 授权不同的服务器, 自顶向下分成N层结构.

要想完成子域授权(正向解析, 反向解析略麻烦暂且不提), 首先我们需要在正向区域中设定一个子区域, 比如:

1
2
play.yaoxuannn.com.
music.yaoxuannn.com.

这里已经是三级域了, play,music已经不再是作为主机了, 而是域名. 因此这个时候他们的记录就已经不再是A了, 而是NS. 接着就在后面写上服务器地址就行了, 就好像是:

1
2
3
4
5
6
play.yaoxuannn.com.	IN	NS	ns1.play.yaoxuannn.com.
play.yaoxuannn.com. IN NS ns2.play.yaoxuannn.com. # 当然也是可以出现两个的啦
music.yaoxuannn.com. IN NS ns1.music.yaoxuannn.com.
ns1.play.yaoxuannn.com. IN A 192.168.199.33
ns2.play.yaoxuannn.com. IN A 192.168.199.44
ns1.music.yaoxuannn.com IN A 192.168.199.20

这样的记录其实就说明, 在yaoxuannn.com这个域下有两个子域play和music, 分别有2和1台服务器. 但是, 这里的大大大前提是:父域justin13wyx必须在com域下获得授权. 否则没法在互联网上获得授权.

现在我们来说一个稍微有一点混乱的事情: 假设现在有这样的结构(我就是不画图, 你来打我啊hhh): 根域下有一个叫me的子域, me下面又授权一个justin13wyx的子域, 而justin13wyx这个子域里面又有一个子域叫music, 一个DNS解析服务器ns1, 一台主机www, 而在子域music下有一台主机叫s1, 而这个子域music同时又是自己DNS解析服务器. OK , 环境介绍完了. 那么现在来讲这个事情, 如果music.yaoxuannn.com下的主机s1.music.jsutin13wyx.me想要知道www.yaoxuannn.com的地址, 那么查询的过程是什么样的呢?

首先, 要明确的是子域不知道自己父域在哪里, 只有父域知道自己的直接子域的位置. 这种感觉就好像和Java中继承正好相反. 在Java中我们从父类中派生出很多子类. 而父类并不知道自己有哪些子类, 而子类却唯一知道自己的父类是谁. 接着, 主机s1像他所在的music子域中的DNS服务器music发起请求, 那么这个时候music会怎么做呢? 我们说过子域是不知道自己父域的位置的, 那么此时music会直接去询问根域, 根域告诉他我授权给me这个家伙了, 你去找他. 于是接着me说你去找justin13wyx这个家伙, 我授权给他了. 晕+_+. 结果搞来搞去又回来了, 自己家的事, 结果绕了这么大的圈子.

那怎么办呀? 在这种情况下, 我们就可以定义转发区域了. 其实很简单, 就是当出现了我不负责的域的请求的时候或者我找不到的时候, 那么我就会去将这个请求转交给指定的服务器. 怎么样? 是不是有点像我们的默认路由呢 ? 定义转发 就是这个意思.

定义转发服务器也是有要求的: 被转发的服务器必须能够进行递归, 否则不予转发. 另外, 如果我们的请求被转发的服务器也不知道的话, 他还是要去根域去找. 那与其这样干嘛还要麻烦人家呀. 所以这个时候我们不如直接就去找根就好了嘛. 所以我们加上特殊的规则样的约束, 只有当请求这个域的位置的时候, 我们才把他转发到那里, 其他的情况的话我们就直接去找根就好了. (越来越像我们的路由表了..)

前者叫做全局转发(全部转发), 也就是凡是非本机所负责的解析的区域的请求, 统统转发到指定的服务器, 使用一下结构:

1
2
3
4
Options {
forward {first|only}
forwarders
}

而第二种叫做区域转发: 仅转发对特定区域的请求至某服务器:

1
2
3
4
5
zone "ZONE_NAME" {
type forward;
forward {first|only}
forwarders
}

其中, forward定义转发模式 , 转发模式有两种, 一种叫做first , 一种是only. 什么意思呢? first的意思就是说, 遇到困难我先都不想就把问题抛过去, 如果对方不搭理我, 那就只好乖乖的去找根了. 而only的意思就是说, 如果不搭理我那就放弃了..不玩了

那现在就来创建一下试试吧!

还是先来说一下现在的实验环境, 依然是那两台CentOS7主机, 但是我将之前所有的测试解析库文件都挪走了, 也就是说现在的状态是刚刚安装配置好的bind的样子.

先给我们的实验机一号配上yaoxuannn.com的zone:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$TTL 1D
$ORIGIN yaoxuannn.com.
@ IN SOA ns1.yaoxuannn.com. admin.yaoxuannn.com. (
2017091001
1H
5M
3D
1D )
IN NS ns1
IN NS ns2
ns1 IN A 192.168.56.101
ns2 IN A 192.168.56.102
www IN A 192.168.199.10
* IN CNAME www

接着又是熟悉的权限配置了.

1
2
3
[root@WWW ~]# cd /var/named/
[root@WWW named]# chown :named yaoxuannn.com.zone
[root@WWW named]# chmod 640 yaoxuannn.com.zone

接下来, 在yaoxuannn.com的zone中定义子域music, 这一步其实就是子域授权了:

1
2
music   IN      NS      ns1.music
ns1.music IN A 192.168.56.103

这个时候可先别做测试呀, 你肯定会失败的. 为什么?我们的子域没有配置啊 这样你怎么也不会查询成功的.

那现在就开始配置子域, 进入实验二号机:

现在rfc1912.zones里面加上zone记录:

1
2
3
4
zone "music.yaoxuannn.com" {
type master;
file "music.yaoxuannn.com.zone";
};

接着书写解析库文件:

1
2
3
4
5
6
7
8
9
10
11
12
$TTL 1D
$ORIGIN music.yaoxuannn.com.
@ IN SOA ns1.music.yaoxuannn.com. admin.music.yaoxuannn.com. (
2017091001
1H
5M
3D
1D )
IN NS ns1
ns1 IN A 192.168.56.103
www IN A 192.168.199.20
* IN CNAME www

那么现在, 在子域的DNS服务器上, 我们可以进行子域的解析但是不能进行父域的解析. 对于父域的DNS服务器来说, 除了可以进行自己域内的解析以外, 还可以进行子域的DNS服务器(也就是music.yaoxuannn.com)的解析. 你可能在测试的时候发现, 你父域的DNS解析子域的时候总是显示服务器无法到达. 一直报错.

是这样的, 因为默认我们的各种工具提供的都是递归查找, 这样造成的后果就是每一次都会去找根域. 所以你应该使用dig工具提供的+norecurse选项. 这样才可以得到结果, 但是依然你是得不到答复的 你只能得到子域的DNS服务器的地址而已. 那么怎么样才能得到准确的解析结果呢? 很简单, 配置转发!

首先我们先来试一试给子域加上转发, 使得他可以进行对父域的解析: (rfc1912.zones)

1
2
3
4
5
zone "yaoxuannn.com" IN {
type forward;
forward only;
forwarders { 192.168.56.101; };
};

先别急着测试, 接下来我们再把一号试验机加上转发, 使得它可以解析子域的位置:(named.conf)

1
2
3
4
5
6
7
8
9
10
options {
listen-on port 53 { 192.168.56.101; };
// 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 { any; };
forward first;
forwarders { 192.168.56.103; };

行了, 相信你开心的配置完毕了, 但是却发现似乎P用都没有.

那么, 接下来将介绍一个大大大大超级无敌大大大大的:

请务必要把:

1
2
dnssec-enable no;
dnssec-validation no;

关闭 而不是 注释 因为注释的默认值是yes. 所以一定要写清楚NO! 接下来你就可以的得到你想看到的结果了.

行了, 现在转发配置就这样了吧~ 下面说说简单的安全机制和权限管理

权限管理

这里我们说一说Bind的安全项目的配置. 首先是ACL 访问控制列表

也就是把一个或者多个主机归并成一个集合, 并通过一个统一的名称调用.这个配置起来就和他的功能一样好懂:

1
2
3
4
5
6
7
acl acl_name {
ip;
ip;
hostname;
net/prelen;
...
}

示个例:

1
2
3
acl mynet {
172.16.0.0/16;
}

ACL提供了几个内定义的: none(没有一个主机), any(任意主机), local(本机),localnet(本机IP同掩码进行计算的网络地址) 还记得我们在最一开始提到了allow-query这个属性不? 这个地方我们就写了any啊. 如果内置的不满意, 那么就手写一acl就可以了. 但是注意: 只能先定义后使用, 而且一般我们都把他定义在配置文件中options的前面.

我们可以来做个试验咯, 比如:

1
2
3
4
5
6
zone "yaoxuannn.com" IN {
type master;
file "yaoxuannn.com.zone";
allow-query { 127.0.0.1; };
};
# 加入了一个allow-query属性

接下来我们尝试用本机的外部IP地址来进行解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@WWW named]# dig yaoxuannn.com @192.168.56.101

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

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

;; AUTHORITY SECTION:
yaoxuannn.com. 86400 IN SOA ns1.yaoxuannn.com. admin.yaoxuannn.com. 2017091002 3600 300 259200 86400

;; Query time: 0 msec
;; SERVER: 192.168.56.101#53(192.168.56.101)
;; WHEN: Mon Sep 11 11:42:53 EDT 2017
;; MSG SIZE rcvd: 89

没有结果了. 不过说实话,作为一台DNS服务器, 我们一般还是应该允许查询的属性设置成any吧.

除了这个allow-query(允许查询的主机, 白名单), 我们就把他叫做访问控制的指令, 还有allow-transfer(允许进行区域传送的主机, 白名单)

之前就说过了, 区域传送是十分危险的, 但当时没有提到怎么进行限制, 现在就可已进行尝试了:

1
2
3
4
5
zone "yaoxuannn.com" IN {
type master;
file "yaoxuannn.com.zone";
allow-transfer { 127.0.0.1; };
};

用我们今天刚刚做好的传送服务器来试试:

1
2
3
4
5
[root@WWW named]# dig axfr yaoxuannn.com @192.168.56.101

; <<>> DiG 9.9.4-RedHat-9.9.4-50.el7_3.1 <<>> axfr yaoxuannn.com @192.168.56.101
;; global options: +cmd
; Transfer failed.

这样就达到我们的目的了. 当然ACL是贯穿所有的, 你可以直接把ACL写到这里面.

接着还有allow-recursion{} 和allow-update {} 这个update在默认的配置文件里也有, 什么意思? 其实就是字面意思了. 允许更新数据库中的内容, 因为有的时候我们的主机IP可能是通过DHCP协议获取的, 那么DNS服务器会得到通知, 于是自动进行改变.

bind view 视图

先来假设这样的情况, 假设中国移动, 中国电信之间的总入口带宽很小, 只有100G. 那么这样就会带来一个问题. 这样移动的用户访问位于电信机房内的服务器的时候, 就会变得异常的慢. 而访问移动的就不会有什么问题. 这样的影响是很大的. 为了留住用户, 那么站点的组织者就会在各大运营商机房内的都放置了一台服务器. 接着只要使得移动的用户访问移动机房内的机器, 电信的用户访问电信的机器就行了. 但是问题在于 这些机器的域名可都是一个. 也就是说来自不同运营商的用户得到同一个域名的解析是不一样的.

类似的, 如果小米公司的内部员工在公司访问自己家的服务器(私有地址), DNS解析服务器应该直接返回给他们私有地址, 但是外网用户访问的时候就应该得到小米的防火墙或其他设备的地址, 接着在经过NAT转换连接上. 类似这样的需求. 当然常见的还是第一个例子.

对于Bind而言, 这个东西就是Bind的View(视图). 简单的说, 你可以把View当做是容器, 就像Docker一样的感觉(不熟悉Docker, 日后再说) 我们可以定义多个view, 每一个view都有同一条域名的记录, 但是他们指向的文件却不同. 每个view指向一组特定的客户端. 原理其实也很简单: Bind提供多个View, 当客户端发起请求的时候, bind自上而下进行匹配, 一旦匹配到View和客户端, 即停止匹配将在得到的view中进行查询. 如果匹配到最后都没有检查到. 那么就直接拒绝请求. 为了防止这样的情况发生. 我们一般都会设置一个匹配特定网络以外的所有网络这么一条view.

定义一个view是很简单的了, 基本上在options中的属性都可以用在view中:

1
2
3
4
# 一个view的定义:
view VIEW_NAME {
match-clients { };
}

其中最重要的属性就是上面的那个match-clients了, 另外还有一些注意项: 一旦我们启用了view, 那么所有的zone必须全部放在view中. 由于我们是自上而下匹配的, 所有优先级问题和顺序问题需要规划好. 还有一个问题, 根区域记录放在哪里呢?

首先, 根区域的存在意义是什么呢? 我们知道当来自互联网的机器询问到我们这边的时候并且请去做递归查询, 遇到我们不知道或者不负责的查询的时候我们就会去请求根去了. 但是, 如果我们压根都不允许递归查询的话, 不写根都是可以的. 一般情况下, 只给本地的机器做递归, 所以 根区域的记录只要放在match本地的view中就可以了.

我也是拼了老命在做实验啊….我笔记本的风扇在疯狂的旋转.. 不说了, 一共开了三台虚拟机, 两台Cent OS, 一台Ubuntu. 其中两台CentOS的IP分别是192.168.56.101和192.168.56.103, 而Ubuntu是192.168.56.102. 101作为DNS服务器, 而其他的两台CentOS作为内部主机, Ubuntu作为一台外部主机, 其实就是在说他们的view不同罢了. 其中CentOS们是一个acl组的, 叫做test. 而Ubuntu没有分组, 默认直接在view中使用了IP来表示. 那么现在就直接看一下配置和最终的效果吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
acl test {
192.168.56.101;
192.168.56.103;
};
# 这是101DNS服务器中的acl配置.
view external {
match-clients { 192.168.56.102; };
zone "yaoxuannn.com" IN {
type master;
file "102yaoxuannn.com.zone";
};
};
view internal {
match-clients { test; };
allow-recursion { test; };
zone "yaoxuannn.com" IN {
type master;
file "yaoxuannn.com.zone";
allow-transfer { 127.0.0.1; };
};
};
#上面是两个view的设置

我直接将原来的yaoxuannn.com.zone文件进行cp -a 得到102.yaoxuannn.com, 接着把其中的www对应的记录值改成了192.168.56.20 ( 原来是192.168.56.10 )

最后测试的结果是什么样子呢?

Ubuntu能够进行解析, 并且得到192.168.56.20的结果:

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@ubuntu:~# dig www.yaoxuannn.com @192.168.56.101

; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> www.yaoxuannn.com @192.168.56.101
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56124
;; 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.20 # 得到20的结果

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

;; ADDITIONAL SECTION:
ns1.yaoxuannn.com. 86400 IN A 192.168.56.101
ns2.yaoxuannn.com. 86400 IN A 192.168.56.102

;; Query time: 7 msec
;; SERVER: 192.168.56.101#53(192.168.56.101)
;; WHEN: Tue Sep 12 03:54:49 PDT 2017
;; MSG SIZE rcvd: 131

而 ,另外一边103解析得到的结果就是:

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 ~]# dig www.yaoxuannn.com @192.168.56.101

; <<>> DiG 9.9.4-RedHat-9.9.4-50.el7_3.1 <<>> www.yaoxuannn.com @192.168.56.101
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44198
;; 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.56.101
ns2.yaoxuannn.com. 86400 IN A 192.168.56.102

;; Query time: 2 msec
;; SERVER: 192.168.56.101#53(192.168.56.101)
;; WHEN: Tue Sep 12 07:02:10 EDT 2017
;; MSG SIZE rcvd: 131

接下来我们说说CDN吧, 这个写前端的小伙伴肯定都很熟悉, 因为通常都会选择一些合适的CDN来进行JS库的快速加载. 那么什么到底啥是CDN嘞? 其实就是内容分发网络的意思, 能够把同一个服务的内容分发到多处. 说白了其实就是大量的缓存服务器, 假设我们很土豪, 在全世界各地的机房内都放上我们的服务器, 他们都保存(缓存)我们原始服务器的副本. 这样来自某个网络的用户进行查看的时候, 得到不同的DNS解析结果, 从而访问属于他那个位置的缓存服务器. 这样分布式的内容分发服务, 就叫做CDN. 而构建这样的CDN网络, 一种实现方式就是智能DNS. 除了这种方法, 还有负载均衡调度用户到不同的服务器上. 这种又叫做全局负载均衡网络.

Bind的编译安装

首先当然是获取源码咯. 接着解包之后我们进入目录看到的应该是下面这样的:

1
2
3
4
5
[root@WWW bind-9.10.6]# ls
acconfig.h bind.keys config.h.in configure doc HISTORY.md isc-config.sh.html ltmain.sh OPTIONS srcid win32utils
aclocal.m4 bind.keys.h config.h.win32 configure.in docutil install-sh isc-config.sh.in make OPTIONS.md unit
Atffile CHANGES config.sub contrib FAQ.xml isc-config.sh.1 lib Makefile.in README util
bin config.guess config.threads.in COPYRIGHT HISTORY isc-config.sh.docbook libtool.m4 mkinstalldirs README.md version

我们说过编译一个C语言程序无非就是configure, make, make install. 当然这个也不例外.

在这里说一下常用的configure选项, 首先是 –prefix和 –sysconfdir这两个超常用通用选项. 以后要是想删除这个就直接把这个而目录删除就好了. 接着是关闭ipv6和关闭chroot功能, 以及启用多线程功能. 这样Bind可以在多核机器上表现更优异.

最后的configure命令就是:

1
[root@WWW bind-9.10.6]# ./configure --prefix=/usr/local/bind9 --sysconfdir=/etc/bind9 --disable-ipv6 --disable-chroot --enable-threads

:arrow_right_hook:直接回车吧!

接着我们想到了一个问题, 我们使用rpm包进行安装完的之后, named会用普通用户named来运行named来着. 所以我们要先创建用户:

1
2
3
4
[root@WWW bind-9.10.6]# groupadd -g 53 named -r
[root@WWW bind-9.10.6]# useradd -u 53 -g named named -r
[root@WWW bind-9.10.6]# id named
uid=53(named) gid=53(named) groups=53(named)

好了, 接下来就是常规的几步了.

加入/etc/profile.d/内的PATH环境变量

导出链接库编辑/etc/ld.so.conf.d/*.conf加入自己的lib目录, 使用ldconfig重建

链接头文件到/usr/include下

编辑/etc/man.config, 加入man的路径

总之就先这样吧, 回去看一下我们的Bind源码目录, 里面有一个叫contrib的目录, 里面是一些第三方的辅助Bind的程序, 其中有个叫queryperf的, 这个是一个做压力测试的工具, 很小, 直接configure, make就可以使用了.

1
2
3
4
5
6
7
8
9
[root@WWW queryperf]# queryperf -h

DNS Query Performance Testing Tool
Version: $Id: queryperf.c,v 1.12 2007/09/05 07:36:04 marka Exp $

Usage: queryperf [-d datafile] [-s server_addr] [-p port] [-q num_queries]
[-b bufsize] [-t timeout] [-n] [-l limit] [-f family] [-1]
[-i interval] [-r arraysize] [-u unit] [-H histfile]
[-T qps] [-e] [-D] [-R] [-c] [-v] [-h]

首先我们需要一个测试文件很简单, 域名 类型就行了 .

比如:

1
www.yaoxuannn.com A

接下来使用queryperf -d CONFIGFILE -s SERVER 就行了. 完成后就会受到一份报告:

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
[root@WWW ~]# queryperf -d test -s 192.168.56.101

DNS Query Performance Testing Tool
Version: $Id: queryperf.c,v 1.12 2007/09/05 07:36:04 marka Exp $

[Status] Processing input data
[Status] Sending queries (beginning with 192.168.56.101)
[Status] Testing complete

Statistics:

Parse input file: once
Ended due to: reaching end of file

Queries sent: 1451520 queries
Queries completed: 1451520 queries
Queries lost: 0 queries
Queries delayed(?): 0 queries

RTT max: 0.035401 sec
RTT min: 0.000362 sec
RTT average: 0.001011 sec
RTT std deviation: 0.000838 sec
RTT out of range: 0 queries

Percentage completed: 100.00%
Percentage lost: 0.00%

Started at: Tue Sep 12 08:30:05 2017
Finished at: Tue Sep 12 08:31:27 2017
Ran for: 81.967886 seconds

Queries per second: 17708.398628 qps

不过这样的压力测试由于我是本机测试所以无法得到带宽带来的性能损耗. 接下来我们玩一个好玩的, 可以试试把rndc的日志记录功能打开, 再进行测试.