必要性

看到这个标题,各位巨巨首先的疑问是是否真的有必要自己来 host 域名。毕竟有那么多良好的服务商,为什么要自己来填这个坑呢。

这里有个讨论,大意是不会自己来运营 DNS 服务器,最主要因素就是没有全球范围的网络支持。

这篇文章也不是教大家如何自己运营一台全球范围的 DNS 服务器,最终它还是需要有其他域名服务器来支持的——比如本身就是演示如何将 Linode 的 DNS 作为 Slave 来使用。

不过某的最主要需求还是来自

  1. 动态域名
  2. 折腾…
  3. 这看上去很 Cool

关于第一条,某一直使用 dyndn.org 的免费服务,不过近一年来, dyndns.org 把免费服务改成了需要每 30 天去网站上激活一下域名,否则将自动过期。这着实很令人烦恼,比如8月底的时候某就把这事给忘了(周末某有避开一切社交网络的习惯,连邮件都不看,只是习惯地上下Twittter)。

来源参考

基础,搭建一台 DNS 服务器

DNS 服务器的软件有很多,比如 MaraDNS , PowerDNS 。网上搜到的资源里配置都相对简单。不过某介绍的是依旧老牌的 BIND 。不过需要注意使用的版本为 9 ,不是 10 哦,后者的变化非常大,可以说是另外一个软件了。

对应的操作系统为 Gentoo Linux (这个应该不用再多介绍了)。 所有配置基本没有特殊的地方,所以其他发行版也可以参考。

安装 BIND

Gentoo 下安装 BIND 依旧简单。

$ emerge -av net-dns/bind

# 注意这个包会自动依赖,不过对于客户端来说只需要 bind-tools 就行了
$ emerge -av net-dns/bind-tools

配置 BIND

这一步算最复杂的一步,某不做过多的说明,因为牵涉的 BIND DNS 的背景比较多,可能需要参考 BIND 的书,有个在线网站,可以当作文档来浏览。

BIND 在 UNIX 下还叫做 named ,配置文件一般在 /etc/bind 下。我们需要建立一个域名的 zone 来负责管理这个域名。虽然空格和 tab 等空白字符没有严格限制,但由于配置文件的其他格式要求比较高,比如主配置里的分号,所以请确保格式一致。

主配置文件里会定义一个 view 来匹配不同的访问,作出不同的逻辑,比如我们可以根据请求的来源地址,来分配不同的 DNS 解析来达到均衡的目的等等。 view 是可选的,但需要注意一旦使用 view 后,所有的 zone 就必须被包裹在 view 里。

假设我们配置的主域名是 example.com ,而需要动态更新的域名为 dyn.example.com

主配置文件 /etc/bind/named.conf

options {
    directory "/var/bind";

    listen-on-v6 { none; }; // 表示 IPv6 上监听服务,不需要的可以关闭
    listen-on port 53 { 127.0.0.1; $your_server_ip; }; // 在 loopback 和 对外网卡上监听

    // 定义全局的变更通知对象 本 DNS 出现更新的时候自动通知这些服务器
    also-notify {
        69.93.127.10;  # ns1.linode.com
        65.19.178.10;  # ns2.linode.com
    };

    pid-file "/var/run/named/named.pid";
};

// 定义两台 Linode 的 name server ,在后面用作 slave
acl slaves {
    69.93.127.10;  # ns1.linode.com
    65.19.178.10;  # ns2.linode.com
};

view "external" {
    match-clients { any; }; // 所有查询都走进这个 view
    recursion on;

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

    zone "127.in-addr.arpa" in {
        type master;
        file "pri/127.zone";
        allow-update { none; };
        notify no;
    };

    zone "example.com" in {
        type master;
        file "dyn/example.com.external"; // 注意 Gentoo 下面动态域名配置放在 /etc/bind/dyn 下面
        notify yes;
        allow-query { any; };
        allow-update { key your_key_name; }; // 设置允许更新 DNS 记录的 key ,后面详细介绍
        allow-transfer { slaves; }; // 定义允许这些服务器从本 DNS 上 AXFR 完整转移 DNS 配置记录
    };

};

key your_key_name {
    algorithm HMAC-MD5;
    secret "secret_fill_in_here";
};

// 配置日志
logging {
    channel default_syslog {
        file "/var/log/named/named.log" versions 3 size 5m;
        severity debug;
        print-time yes;
        print-severity yes;
        print-category yes;
    };
    category default { default_syslog; };
};

接下来配置 zone 文件。 /etc/bind/dyn/exmaple.com.external

$ORIGIN .
$TTL 60 ; 1 分钟
example.com    IN SOA  ns.example.com. email.exmaple.com. (
    2013083123 ; serial
    10800      ; refresh (3 hours)
    3600       ; retry (1 hour)
    604800     ; expire (1 week)
    86400      ; minimum (1 day)
)

;; 下面开始是域名记录的配置,第一列空白表示不带前缀,也可以用 @ 符号代替
        NS ns.exmaple.com.
        A your_server_ip
        MX 0 mail.example.com.
        AAAA your_server_ipv6


$ORIGIN example.com.
www     A your_server_ip
@       AAAA your_server_ipv6
dyn     A your_dynamic_ip

除了上面提到的 key 没生成外,至此我们已经完成了配置,可以先把和 key 有关的几行注释掉(named 主配置里是 // )。

然后启动服务

$ /etc/init.d/named start

测试验证

我们会用到 bind-tools 里提供的命令 dig 来验证效果,用法大致是这样的。

$ dig -t A example.com @$dns_server

-t 指定查询的记录类型,@$dns_server 表示从哪台服务器上查询。默认是 53 端口,如果需要指定其他端口,还可以使用 -p 参数。

如果查询结果类似如下

;; ANSWER SECTION:
areverie.org.		60	IN	A	106.187.55.63

;; AUTHORITY SECTION:
areverie.org.		60	IN	NS	ns.areverie.org.

;; ADDITIONAL SECTION:
ns.areverie.org.	60	IN	A	106.187.55.63

说明设置OK了。

然后是验证 transfer 的可用性。可以在 allow-transfer 里加上自己机器的 IP ,然后重启。使用以下命令来做 transfer

dig -t AFXR example.com @$dns_server_ip

如果命令返回的是 transfer failed ,则表明权限设置有问题,确保来自 ns1.linode.comns2.linode.com 或者其他正在使用的 name server 能使用该权限。

设置 slave DNS

某使用的是 Linode ,所以在后台的 DNS Manager 里把原来的 domain 配置 disable 掉,重新添加 domain ,选择 slave 后,在 master 里把自己服务器的 IP 填上即可。 回头看 DNS 服务器的查询记录,会看到来自 ns1.linode.comns2.linode.com 的 transfer 请求。再使用其他 DNS 查询 生效就说明配置一切正常了。

如果各位巨巨是使用其他 DNS 服务商,则需要在后台将 name server 指定记录到前面配置里的 NS 记录上,并允许相应服务商的服务器 IP 允许 transfer。

事实上,某在 name.com 上买的域名把 name server 设置到 linode 上,然后把 linode 当作 slave 使用是也是完全可以的。

设置动态域名更新

还记得前面 named.conf 里 那段 allow-update 和定义 key 的地方吗。更新 DNS 记录将采用 key 授权的方式。原理上和 ssh 的 key 认证类似。所以首先需要生成 key 。会用到 bind-tools 里提供的 dnssec-keygen 命令。

$ dnssec-keygen -a HMAC-MD5 -b 512 -n USER -r /dev/urandom dyn.example.com

-a 指定算法, -b 指定长度, -n 指定类型, -r 指定随机数据源的设备,最后是名字。该命令会生成出两个2文件,分别是公钥与私钥。

Kdyn.example.com.+157+27661.key
Kdyn.example.com.+157+27661.private

打开 private 文件,把里面 Key: 后面的值复制出来,填到服务器上 key 定义下,secrets 里。过程请确保安全,比如用 ssh 方式登录服务器等。改完配置记得重启 BIND 。

然后我们会用到 nsupdate 这个命令,同样还是 bind-tools 里提供。

可以用交互的方法打开,用 -k 指定 key 文件(公钥哦,*.key那个)。命令的详细用法可以参考维基百科上的示例

这个过程即向服务器发送指令,以此为删除 dyn.example.com 这条 A 记录,然后新添加一条同样名字的 A 记录,带上新的IP。

$ nsupdate -k /path/to/Kdyn.example.com.+157+27661.key
> server $your_server_ip
> zone example.com
> update del dyn.example.com A
> update add dyn.example.com 60 A $NEW_IP
> send

IP 变化时自动更新

像前面提到的那样手工更新未免太累,所以我们可以在 IP 变化时,利用脚本来自动更新。主要涉及到的变化和场景有关系。这个动态域名主要是用作家里的对外 IP ,会产生变化的无非两种。

原本使用 dyndns 的服务,我们会使用 ddclient 来更新。由于现在协议不兼容,所以要自己动手啦。

  1. PPPoE 重新拨号
  2. 网卡 IP 变了 (本质上其实还是第1条,因为不太会有其他情况变化)

所以这个事情可以分解为以下几步

  1. 获取新的 IP
  2. 检测 IP 是否变动
  3. 自动请求服务器更新 IP

第 1 、 2 步很简单,使用 shell 下的命令组合即可。例如 ipaddr=$(ip -f inet addr show dev ppp0 | grep inet | awk '{print $2}') 。把结果存在一个文件里,下次直接比较就可以达到检测是否变动的目的,最终也是为了减少向服务器无用的请求。

第 1 步中 PPP 拨号成功后有一个类似 callback 脚本,在 /etc/ppp/if-up.d 中的脚本会运行,第 4 个参数为新 IP,这个在以前写的使用 RPi 作为家用路由器里更新 IPv6 地址是一个用法。

而启动请求服务器更新,同样还是用到 nsupdate ,它还有一种用法是

$ nsupdate -k keyfile instruction_file

指令文件里可以带上前面那些交互的命令。所以这个脚本示例如下

#!/bin/bash

PID=$$
NEW_IP=$1
WROOT=$(cd $(dirname $0); pwd)
INST_FILE=$WROOT/ddns_inst.txt
OLD_IP_FILE=$WROOT/ip_cur.txt
KEY_FILE=/path/to/key/file

function update_dns {
    echo "server $your_server_ip" > $INST_FILE
    echo "  zone example.com" >> $INST_FILE
    echo "  update del dyn.example.com. A" >> $INST_FILE
    echo "  update add dyn.example.com. 60 A $NEW_IP" >> $INST_FILE
    echo "  send" >> $INST_FILE
    /usr/bin/nsupdate -k $KEY_FILE $INST_FILE
    if [[ $? == 0 ]]; then
        logger -t "ddns_update[$PID]" "updated dyn.example.com with ip: $NEW_IP"
        echo -n $NEW_IP > $OLD_IP_FILE
    else
        logger -t "ddns_update[$PID]" "update dyn.example.com with ip: $NEW_IP failed"
    fi
    rm $INST_FILE
}

if [[ -s $OLD_IP_FILE ]]; then
    OLD_IP=$(cat $OLD_IP_FILE)
fi

if [[ -z $OLD_IP ]]; then
    $(update_dns)
    exit 0
fi

if [[ -n "$NEW_IP" ]] && [[ n"$OLD_IP" != n"$NEW_IP" ]]; then
    $(update_dns)
    exit 0
fi

在检测到 IP 变化,或者 PPP 拨号完成后 带上新 IP 作为参数调用该脚本即可。

结语

至此,我们完成了一台在公网上可用的 DNS 服务器,并把自己的域名 hosting 在上面,同时设置其他 DNS 服务器把它作为 Master 使用。

嗯,也许配合一些图形界面的设置软件或者 web 项目,管理起来会更便捷吧。 XDDDD

__END__