djbdns:不仅仅是一堆辅音

作者:Cory Wright

让我们面对现实,DNS 并不是互联网基础设施中最吸引人的组件。它是一项古老的技术,不像更新、更炫酷的工具和软件那样引人注目。您的网站访问者可能会评论您的新 AJAX 小部件有多酷,但我保证他们永远不会告诉世界他们对您的 DNS 响应时间有多满意。

尽管如此,DNS 对于互联网至关重要。它属于那种应该“正常工作”的服务之一,只有当它不工作时,人们才会注意到(并大声抱怨)。读者可能还记得 2005 年 5 月发生的“谷歌消失事件”,当时这家搜索引擎巨头短暂地从互联网上消失了。许多人迅速认为该网站遭到了黑客攻击,但问题最终被证明是 DNS 配置问题。这个事故很快就被修复了,但也突显了即使是最强大的公司也可能因为一个简单的配置错误而轻易瘫痪。

我撰写本文的主要目的是为了证明,BIND 存在一个免费、安全且易于配置的替代方案:djbdns。本文旨在为那些可能有一些 DNS 经验,但又想考虑新方法的人而写。我假设读者仅需对 DNS 有基本的了解——特别是熟悉基本的记录类型,例如 A、CNAME、MX、NS 和 SOA,以及 TTL(生存时间)的概念。

BIND 和 djbdns 的简要历史

在我们所知的互联网最初 15 年里,在 DNS 服务器软件方面只有一个真正的选择:BIND。BIND 最初是加州大学伯克利分校(因此得名,伯克利互联网域名)几位研究生的一个项目。在 20 世纪 90 年代初期,互联网系统协会 (ISC) 成立,旨在正式维护、分发和支持这个关键软件。ISC 于 1997 年 5 月发布了 BIND 8,作为对老化的 BIND 4 的重大更新。尽管在配置上存在重大差异,但 BIND 4 和 8 都是基于 20 世纪 80 年代初期和中期的原始伯克利代码。在试图为重大重写筹集资金时,BIND 的一位作者将这段代码描述为“在醉酒的狂怒中产生的劣质软件”。一个新的团队为重写工作了几年,BIND 9 于 2000 年 9 月正式发布。

在多年处理 BIND 中的安全问题以及对其配置语法的挫败感之后,Dan J. Bernstein 于 1999 年开始着手开发 djbdns。Bernstein(或通常被称为 DJB)早已因 qmail 的作者而声名鹊起,qmail 是一款在系统管理员中迅速普及的邮件服务器软件。当时,Sendmail 是互联网上占主导地位的邮件服务器,并且与 BIND 一样,它以配置极其困难和存在安全问题而臭名昭著。Bernstein 关于安全性和配置简单性的“跳出固有思维模式”的设计决策不仅使 qmail 获得了成功,而且还影响了开发人员思考如何为日益动荡的互联网编写软件的方式(Postfix、Courier 和其他软件都受到了 qmail 安全分区设计的启发)。既然 Bernstein 已经确保了邮件的安全性和简化性,那么现在是时候对 DNS 做同样的事情了。djbdns 的第一个 alpha 版本于 1999 年 12 月发布,当前版本 djbdns 1.05 最终于 2001 年 2 月 11 日发布。没错,当前版本已经有七年多的历史了。请记住,DNS 是一个古老的协议,它不会经常改变。BIND 软件更新几乎总是为了修复错误或安全补丁。

过去,Bernstein 的软件备受争议,因为它缺乏明确的许可证。由于其许可的不确定性,操作系统供应商不愿分发他的软件包。然而,在 2007 年 12 月,Bernstein 将 djbdns(以及 daemontools 和 qmail)置于公共领域,允许人们随意使用或分发它。

为什么选择 djbdns?

BIND 自互联网早期就已存在。它仍然是最流行的 DNS 服务器,那么为什么要考虑切换到 djbdns 呢?首先,djbdns 没有 BIND 那样的历史问题。BIND 的安全记录与 Sendmail 的不相上下(这不是一件值得骄傲的事情),而且将其配置超出基本水平可能会非常痛苦。

更复杂的是,BIND 模糊了 DNS 不同功能之间的区别。DNS 服务主要有两种类型:DNS 缓存(也称为递归 DNS 服务器)和 DNS 服务器(也称为权威服务器或名称服务器)。

DNS 缓存是您的桌面计算机在需要查找您尝试访问的网站地址时与之通信的对象。当缓存收到您对 www.google.com 位置的请求时,它首先检查是否已经知道您问题的答案。如果知道,它会很快告诉您。如果它还不知道答案,它会首先向根服务器询问答案。根服务器会回复类似于“我不知道答案,但 .com 服务器可能知道;这是它们的地址,去问它们。”缓存服务器会继续这样做,直到获得 www.google.com 的 IP,然后将答案返回给您的计算机。您在 /etc/resolv.conf 中看到的 IP 地址是 DNS 缓存的地址。缓存与权威服务器通信以获取答案。

权威服务器的职责要简单得多。它的工作只是发布它“权威”管理的域的信息。权威服务器只会回答关于它已被明确配置的域的问题。例如,ns1.google.com(谷歌的权威 DNS 服务器之一)永远不会回答关于 www.microsoft.com 地址的请求(除非微软和谷歌有一天合并)。

尽管这些是完全不同的服务,但 BIND 对两者都使用相同的服务器。这似乎很方便,但它使配置复杂化,并且很快就会成为安全隐患。

另一方面,djbdns 坚持 UNIX 的“做好一件事”的哲学。djbdns 的服务器组件是分离的,其中 dnscache 作为缓存组件,tinydns 作为权威服务器(我将在稍后详细介绍每个组件的优点)。

这种分离允许每个程序以其自己的非特权用户身份独立地在 chroot 环境中运行。如果攻击者能够使您的 DNS 缓存崩溃,它也不会影响您的权威 DNS 服务。这样做的一个副作用是 dnscache 和 tinydns 需要单独的 IP 地址,以便每个都可以绑定到端口 53。您不能在同一个 IP 地址上同时运行两者。

安装 djbdns

最新版本的 djbdns 可以在所有主要的 Linux 发行版上编译。您还需要安装 daemontools(请参阅侧边栏),这是 Dan Bernstein 的另一个软件包。

daemontools

daemontools 是一组用于管理 UNIX 服务的工具集合。与 Dan Bernstein 的大多数软件一样,djbdns 依赖于 daemontools。

daemontools 服务在目录中创建,并且必须包含一个名为 run 的可执行脚本。要启动服务,您只需从该目录创建一个符号链接到 /service 中。在五秒钟内,svscan 进程将注意到新服务,启动它并开始监视它。

日志记录由 daemontools 软件包中的 multilog 程序处理。程序写入标准输出的任何内容都会记录在名为 current 的进程日志文件中。通常,日志存储在服务目录中。例如,dnscache 的日志将位于 /service/dnscache/log/main/current 中。multilog 会在 current 文件达到一定大小后自动轮换它。

现在,我更喜欢 Ubuntu 服务器发行版,它最近引入了 upstart 来替代 init。我已经为 daemontools 编写了一个补丁,使其与 upstart 兼容。请参阅 dnsfool.com/tips 获取补丁。daemontools 可从 cr.yp.to/daemontools.html 获取。

从 Bernstein 的网站下载 djbdns,并运行以下命令。第三行是针对 Linux 上 glibc 错误的解决方法

# tar xzf djbdns-1.05.tar.gz
# cd djbdns-1.05
# echo gcc -O2 -include /usr/include/errno.h > conf-cc
# make
# make setup check

如果您对安装 djbdns 有其他问题,请参阅 Bernstein 的官方文档。

使用 dnscache,DNS 缓存

开始使用 djbdns 的最简单方法之一是在本地网络上配置 DNS 缓存。您可能希望这样做有很多原因——从更快的 DNS 查找时间到避免那些烦人的拼写错误的域名搜索结果页面。在任何情况下,安装 dnscache 都可以有所帮助。

假设您有一个家庭网络,其中多台计算机位于 192.168.10.0/24 网段。此外,一台 Linux 机器(名为 linux1)正在 192.168.10.10 上运行。您希望在 linux1 上安装 dnscache,以便它可以为网络上的其他机器提供 DNS 解析服务。

幸运的是,由于 djbdns 提供的 dnscache-conf 实用程序,安装 dnscache 非常简单。在运行 dnscache-conf 之前,您需要在 linux1 上创建一个新组和两个帐户。这些帐户将专门供 djbdns 使用,并且不应可用于登录

# groupadd djbdns
# useradd -s /bin/false -d /etc/dnscache -g djbdns dnscache
# useradd -s /bin/false -d /dev/null -g djbdns dnslog

下一步是运行 dnscache-conf 并为其提供四个参数:dnscache 进程的帐户、日志记录进程的帐户、dnscache 服务目录以及 dnscache 应监听的 IP。

# dnscache-conf dnscache dnslog /etc/dnscache 192.168.10.10

/etc/dnscache 目录现在应该存在。在您可以开始使用新缓存之前,您需要允许从本地网络访问它。dnscache 通过将传入请求地址的 IP 与 /etc/dnscache/root/ip/ 中的文件进行比较来检查是否允许机器访问它。您只需触摸一个文件即可授予对整个网络的访问权限

# touch /etc/dnscache/root/ip/192.168.10

此时,您已准备好启动缓存。如果您正在运行 BIND,则需要停止并禁用它,以便 dnscache 可以接管端口 53。假设 daemontools 已安装并正在运行,您现在可以启动 dnscache

# ln -s /etc/dnscache /service/

就是这样。您现在在本地网络上运行了一个 DNS 缓存。您的下一步是更新所有机器上的 /etc/resolv.conf 文件,使其指向 192.168.10.10

nameserver 192.168.10.10

如果您的网络非常繁忙,您可能会发现需要增加分配给缓存的内存量。Dan Bernstein 在他的网站上提供了有关调整缓存大小的说明,但您可能还需要查看 Paul Jarc 的 cache-effect.pl Perl 脚本或 Mike Babcock 的 dnscacheproc.py Python 脚本。

使用 tinydns,权威 DNS 服务器

如果您曾经以权威 DNS 服务器身份运行过 BIND,那么很可能在某个时候您会忘记增加 SOA 记录上的序列号,忽略了某个地方缺少的半角分号,或者只是忘记在记录末尾添加句点 (.)。这些只是人们在处理 BIND 的区域文件时常犯的一些错误。如果您曾因其中任何一个问题而困扰,您可能还记得它给您带来的麻烦。这些错误可能会导致很大的麻烦(可以问问谷歌)。

tinydns 是 djbdns 中的权威 DNS 服务器,它采用了一种完全不同的方法,使您更难以陷入困境。一个主要区别是,tinydns 没有为每个域使用单独的区域文件,而是使用一个名为 data 的文本文件来存储每个域的每个记录。然后,这个数据文件被编译成 cdb 格式的非常快速的数据库。当然,如果您喜欢在单独的文件中管理域,您仍然可以这样做,只需在编译数据库之前将它们连接在一起即可。

让我们开始配置我们的 tinydns 实例。您应该已经安装并运行了 daemontools。同样,假设我们正在运行一个 192.168.10.0/24 的家庭网络,并且我们现在希望使用 DNS 通过名称访问每个主机。我们有另一台 Linux 机器(名为 linux2)在 192.168.10.20 上运行,它将使用 tinydns 发布 DNS 信息。

首先,创建 tinydns 用户

# useradd -s /bin/false -d /etc/tinydns -g djbdns tinydns

与 dnscache 类似,有一个实用程序用于创建和配置 tinydns 实例。它也需要四个参数:tinydns 进程的帐户、日志记录进程的帐户、tinydns 服务目录以及 tinydns 应监听的 IP。

# tinydns-conf tinydns dnslog /etc/tinydns 192.168.10.20

这将创建 /etc/tinydns 目录,并使用开始发布 DNS 数据所需的一切内容填充它。最后一步是为 tinydns 服务创建到 /service 中的符号链接。同样,请务必先停止并禁用任何 BIND 实例

# ln -s /etc/tinydns/ /service/

现在您可以开始为网络上的每个主机添加记录了。

添加 DNS 记录

在开始之前,让我们看看我们的 DNS 数据在传统的 BIND 区域文件格式(8.2 及更高版本)中会是什么样子。列表 1 显示了配置 example.com 的正向记录和 192.168.10.0/24 的反向记录所需的一切内容。这包括 named.conf 的配置,以及 example.com 和 10.168.192.in-addr.arpa 的区域数据。我们这两个域的配置总共需要 38 行。

列表 1. example.com 的 BIND 配置

;-- BIND named.conf excerpt
zone "example.com" in {
    type master;
    file "db.example.com";
};

zone "10.168.192.in-addr.arpa" in {
    type master;
    file "db.10.168.192.in-addr.arpa";
};

;-- BIND zone file: db.example.com
$TTL 86400
example.com.  IN SOA linux2.example.com. hostmaster.example.com. (
                 2008090101 ; serial number
                 3h         ; refresh
                 15m        ; update retry
                 3w         ; expire
                 3h         ; negative cache ttl
              )
              IN  NS      linux2.example.com.
              IN  MX  0   mail.example.com.
mail          IN  A       192.168.10.10
linux1        IN  A       192.168.10.10
linux2        IN  A       192.168.10.20
linux3        IN  A       192.168.10.30
flying        IN  A       192.168.10.10
spaghetti     IN  A       192.168.10.20
monster       IN  A       192.168.10.30
noodly-appendage IN CNAME linux1.example.com.

;-- BIND zone file: db.10.168.192.in-addr.arpa
$TTL 86400
10.168.192.in-addr.arpa. IN SOA linux2.example.com. hostmaster.example.com. (
                 2008090101 ; serial number
                 3h         ; refresh
                 15m        ; update retry
                 3w         ; expire
                 3h         ; negative cache ttl
              )
              IN  NS   linux2.example.com.
10            IN  PTR  linux1.example.com.
20            IN  PTR  linux2.example.com.
30            IN  PTR  linux3.example.com.

正如我所提到的,tinydns 采用了不同的方法。tinydns 没有为正向和反向区域分别定义记录,而是允许您将它们组合到单个记录中。列表 2 包含与列表 1 完全相同的配置,只是采用 tinydns 格式。现在我们只有十行配置,而不是 38 行。让我们来看看这些行的作用。

列表 2. example.com 的 tinydns 配置

# /service/tinydns/root/data
.example.com::linux2.example.com
.10.168.192.in-addr.arpa::linux2.example.com
@example.com:192.168.10.10:mail.example.com:0
=linux1.example.com:192.168.10.10
=linux2.example.com:192.168.10.20
=linux3.example.com:192.168.10.30
+flying.example.com:192.168.10.10
+spaghetti.example.com:192.168.10.20
+monster.example.com:192.168.10.30
Cnoodly-appendage.example.com:linux1.example.com

每行的第一个字符用于指定应创建的记录类型或记录。句点 (.) 行告诉 tinydns 它对 example.com 具有权威性

.example.com::linux2.example.com

这将创建一个 SOA(起始授权)记录,并将 linux2.example.com 设置为 NS 记录。如果在两个冒号之间提供了 IP 地址,则还会为 linux2.example.com 创建一个具有该 IP 地址的 A 记录。这一行 @ 行替换了 BIND 区域文件中的八行

@example.com:192.168.10.15:mail.example.com:0

此行创建两个记录。为 mail.example.com 创建一个地址为 192.168.10.15 的 A 记录,并为 example.com 创建一个指向 mail.example.com 且距离为 0 的 MX 记录。现在,让我们开始定义我们的主机

=linux1.example.com:192.168.10.10
=linux2.example.com:192.168.10.20
=linux3.example.com:192.168.10.30

这些行每行创建两个记录。例如,第一行为 linux1.example.com 创建一个地址为 192.168.10.10 的 A 记录,并为 10.10.168.192.in-addr.arpa 创建一个指向 linux1.example.com 的 PTR 记录(反向记录)。如果您管理网络的正向和反向区域,您可能已经可以看出这可以节省多少时间。

最后,我们为我们的主机定义简单的别名。每个主机都有一个我们更喜欢使用的别名,而不是通用的 linux{1,2,3} 名称。要创建别名 A 记录,我们使用 + 行,它们与 = 行完全相同,只是不创建 PTR 记录

+flying.example.com:192.168.10.10         # alias for linux1
+spaghetti.example.com:192.168.10.30      # alias for linux2
+monster.example.com:192.168.10.30        # alias for linux3

虽然不鼓励这样做,但您也可以使用 C 行使用 CNAME 定义别名

Cnoodly-appendage.example.com:linux1.example.com

所有这些记录都放在一个文件中,在我们的例子中是 /service/tinydns/root/data。保存该文件,并从该目录运行make。这会将文本文件编译成 data.cdb,一个常量数据库。如果 data.cdb 已经存在,tinydns 将继续从中提供服务,直到新的 data.cdb 准备就绪,届时它将被移动到位,并且 tinydns 立即开始使用它。Makefile 只是调用 tinydns-data 命令

data.cdb: data
    /usr/local/bin/tinydns-data

您可以使用 tinydns-get 实用程序测试您的新记录是否在数据库中。tinydns-get 直接访问 data.cdb 文件,因此您无需担心您的测试查询被缓存在任何地方。例如,您可以使用 tinydns-get 来查看您的 MX 记录是否配置正确。首先,确保您位于 /service/tinydns/root 目录中,并且您已运行 make 以使数据库是最新的

# tinydns-get mx example.com
15 example.com:
103 bytes, 1+1+1+2 records, response, authoritative, noerror
query: 15 example.com
answer: example.com 86400 MX 0 mail.example.com
authority: example.com 259200 NS linux2.example.com
additional: mail.example.com 86400 A 192.168.10.15
additional: linux2.example.com 86400 A 192.168.10.20

这表明 linux2.example.com 被定义为 example.com 的权威名称服务器,mail.example.com 是该域的 MX 记录,并且其 IP 地址为 192.168.10.15。

便利功能

tinydns 提供了许多其他便利功能。例如,使用 tinydns,您无需记住每次更改区域文件中的内容时都增加 SOA 记录上的序列号。tinydns 会从数据文件的上次修改时间戳自动生成序列号,这确保了每次文件更改时它们都会递增。

如果您曾经必须迁移活动域的 DNS,您会欣赏每个记录的时间戳。您可以指定记录在未来更改的确切时间,而无需担心它在互联网上的缓存方式。tinydns 在响应查询时动态计算 TTL。例如,如果您想在 2008 年 10 月 15 日凌晨 2 点将 samba.example.com 从 192.168.10.25 迁移到 192.168.10.35,您可以添加以下两个记录

=samba.example.com:192.168.10.25:0:4000000048f594fa
=samba.example.com:192.168.10.35::4000000048f594fa

这些记录上的最后一个字段是 TAI64 时间戳,表示 2008-10-15 02:00:00。(有关生成 TAI64 时间戳的提示,请参阅资源。)

在 2008 年 10 月 15 日凌晨 1:50:00 请求 samba.example.com 的 A 记录的缓存将收到 192.168.10.25 的响应,TTL 为 600 秒(十分钟)。在凌晨 1:59:45 请求相同记录的缓存将收到相同的响应,但 TTL 为 15 秒。凌晨 2:00 之后,tinydns 将开始自动响应新的 IP,192.168.10.35。由于之前的所有响应都设置为在凌晨 2:00 准确过期,因此所有缓存都会立即重新检查新地址。

正是这些小细节使 djbdns 成为如此出色的软件。

DNS 复制

BIND 服务器使用区域传输在服务器之间复制 DNS 数据。此过程相当复杂,存在历史问题,并且配置起来也不是很容易。相反,Bernstein 建议使用现有的数据传输工具,例如 rsync 或 scp,这些工具以快速、高效和安全而著称。

让我们将 linux3.example.com 添加为 example.com 域的第二个 DNS 服务器。在 linux3 上安装 djbdns 并如上配置 tinydns(使用适当的 IP 地址)。使用新记录更新 linux2 上的数据文件(在文件中的任何位置都可以)

.example.com::linux3.example.com

接下来,使用新的 make 目标更新 linux2 上的 /service/tinydns/root/Makefile。将 Makefile 中的所有内容替换为以下内容

remote: data.cdb
    rsync -az -e ssh data.cdb \
        192.168.10.30:/service/tinydns/root/data.cdb
data.cdb: data
    /usr/local/bin/tinydns-data

请务必在 Makefile 中命令行的开头使用制表符而不是空格。现在,当您运行 make 时,它将编译 data.cdb 并立即将其 rsync 到 linux3。我们在 rsync 命令中使用了 linux3 的 IP,因为 DNS 不应依赖自身(如果您的 DNS 出现故障,它将失败)。此外,您可能需要为此目的创建一个特殊帐户,并使用密钥配置无密码 ssh 访问。Dan Bernstein 在他的网站上提供了更详尽的关于配置 DNS 复制的说明。

无痛 DNS

正如我希望您所看到的,DNS 不必令人头疼。虽然 BIND 在 Linux 上无处不在,但 djbdns 更安全、更高效,而且更易于使用。而且,既然它已经发布到公共领域,那么就没有理由在理念上拒绝它了。我们只是简要介绍了 djbdns 可以提供的功能,所以我希望您阅读在线文档,下载它并亲自试用。如果您曾经发现自己像照顾孩子一样照看着 BIND 实例,您可能需要考虑给 djbdns 一个机会。

资源

谷歌消失事件:tinyurl.com/ckx6x

daemontools: cr.yp.to/daemontools.html

DNS Fool 技巧:www.dnsfool.com/tips

如何安装 djbdns,作者:D. J. Bernstein:cr.yp.to/djbdns/install.html

Paul Jarc 的 cache-effect.pl:code.dogmap.org/djbdns

Mike Babcock 的 dnscacheproc.py:mikebabcock.ca/code/dnscacheproc

复制您的 DNS 服务:cr.yp.to/djbdns/run-server.html#replicate

Cory Wright 对 DNS 有着不健康的痴迷。他曾是 Rackspace 的首席 DNS 系统工程师,现在是 www.natuba.com 的开发人员和系统管理员。他喜欢在桌上足球和 Wii 网球中击败 Will Reese。他的网站是 dnsfool.com

加载 Disqus 评论