Ubuntu Weekly Recipe

第386回Unboundでお手軽に家庭内DNSサーバーを作ろう

社内や家庭内でローカルな名前解決をする際、⁠local.などのサブドメインをつけず)インターネット上に公開しているドメインと同じドメインを使いたいと思うことはないでしょうか。筆者はあります。とはいえ、インターネット上にある権威サーバーに、ローカルIPアドレスを返すレコードを登録するわけにもいきませんよね。そんな時はどうしたらよいでしょう?

ある一つのドメインの中で、ローカルな名前はローカル内だけで解決し、かつインターネット上に公開している名前は本来の権威サーバーにクエリーを投げて解決する、ということができればよさそうです。これが実現できると、グローバルIPとローカルIPを両方持つようなサーバー[1]は、家庭内で名前解決をした時のみはローカルIPを返す、というようなこともできそうです。

キャッシュリゾルバであるUnboundにはlocal-data(およびlocal-zoneのtransparent)という機能があり、この要求を実現することができます[2]⁠。今回はUbuntu 14.04 LTSとUnboundを用いて、ご家庭内キャッシュサーバーを構築してみましょう。

Unboundのインストール

UnboundはUbuntu[3]のリポジトリにパッケージがありますので、apt-getでインストール簡単にインストールできます。パッケージ名はunboundです。

Unboundのインストール
$ sudo apt-get install unbound

これだけで自動的にUnboundが動作します。ローカルホストの53番ポートをunboundがLISTENしているのを確認してみましょう[4]⁠。

ポートの状態
$ sudo ss -lntp | grep unbound
LISTEN     0      5                 127.0.0.1:8953                     *:*      users:(("unbound",17014,8))
LISTEN     0      5                 127.0.0.1:53                       *:*      users:(("unbound",17014,6))
LISTEN     0      5                       ::1:8953                    :::*      users:(("unbound",17014,7))
LISTEN     0      5                       ::1:53                      :::*      users:(("unbound",17014,4))

resolvconfとの連携

Ubuntuではresolvconfによって/etc/resolv.confが自動生成されます。そのためresolv.confを直接書き換えることはせず、使用するネームサーバーは/etc/network/interfaces内のdns-nameserversで指定する必要があるのは皆さんご存知の通りです。UbuntuのUnboundはresolvconfと連携し、Unbound(自分自身)で名前解決をするよう、サービスの起動時に/etc/resolv.confを書き換えます。また元々resolv.conf内で指定されていたnameserverを、Unboundのforwardに追加します。その仕組みを少し詳しく見てみましょう。

Unbound起動前後でのresolv.confの変化
$ sudo service unbound stop

$ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 192.168.1.3

$ sudo service unbound start

$ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.0.1

/etc/default/unboundを見てみてください。その中に以下の記述があります。

/etc/default/unbound
# If set, the unbound init script will provide unbound's listening
# IP addresses as nameservers to resolvconf.
RESOLVCONF=true

# If set, resolvconf nameservers will be configured as forwarders
# to be used by unbound.
RESOLVCONF_FORWARDERS=true

ここでRESOLVCONFとRESOLVCONF_FORWARDERSという2つの変数を設定しています。用途はコメントにある通りで、RESOLVCONFがtrueに設定されていると、/etc/resolv.confにUnboundが待ち受けているIPアドレスが設定されるようになります。またRESOLVCONF_FORWARDERSがtrueに設定されていると、元々dns-nameserversに指定されていたネームサーバーが、Unboundのforwardに追加されるようになります。これらはどちらもデフォルトでtrueに設定されているため、Unboundをインストールしただけで、サーバーは名前解決の際に自分自身のUnboundにクエリーを投げ、Unboundは元々設定されていたネームサーバーに再帰的にクエリーを投げる、という動作をします。つまりUbuntuデスクトップのDnsmasqと同じ挙動ですね。

resolv.confを書き換える処理は、/etc/init.d/unboundの中の

/sbin/resolvconf -a lo.unbound

が行っています。この直前の条件分岐を見るとわかりますが、interfaceには0.0.0.0もしくは127.0.0.1が指定されていること[5]⁠、もしくはinterfaceが明示的に指定されていないことを期待しています。⁠192.168.1.1のような)任意のIPアドレスが指定されていると、resolv.confが空になってしまうため注意してください。forwardの追加は/etc/resolvconf/update.d/unbound内で行われています。unbound-controlコマンド(後述)を利用して、forwardの追加もしくはoffを行っていることがわかります。

Unboundの設定

このように、Unboundはインストールしただけで、ローカルなキャッシュリゾルバとして動作します。これを家庭内LAN全体から使えるように、またローカルな名前解決のためのレコードを持てるように、いくつかの設定を行いましょう。Unboundの設定ファイルは/etc/unbound/unbound.confです。中身は以下のように、include文が一行書かれているだけです。

/etc/unbound/unbound.conf
# The following line includes additional configuration files from the
# /etc/unbound/unbound.conf.d directory.
include: "/etc/unbound/unbound.conf.d/*.conf"

このことからもわかるように、設定ファイルは/etc/unbound/unbound.conf.d/*.confに分割して置くほうが管理しやすいでしょう。サーバーの基本的な設定は、以下の内容を/etc/unbound/unbound.conf.d/server.confに書くことにしました。0.0.0.0で待ち受け、ローカルホストと家庭内LANからはアクセスを許可するという意味です。

/etc/unbound/unbound.conf.d/server.conf
server:
    interface: 0.0.0.0
    access-control: 127.0.0.1/32 allow
    access-control: 172.16.0.0/16 allow

家庭内サーバーのDNSレコードも、メンテナンスしやすいように別ファイルにまとめましょう。/etc/unbound/unbound.conf.d/local-data.confというファイルを作成し、local-dataを列挙することにしました。local-dataに記述された名前に対するクエリーには、その結果をUnboundが返します。local-dataにない名前、たとえばこの例であれば「www.example.com」へのクエリーは、上位のDNSサーバーへ再帰問い合わせを行います。この挙動(local-dataにない名前は再帰問い合わせを行う=transparent)がUnboundのデフォルトですが、これを変更し、local-dataにない名前には即時NXDOMAINを返したり、エラーを返すことも可能です。詳しくはunbound.confのmanにあるlocal-zoneを参照してください。

/etc/unbound/unbound.conf.d/local-data.conf
server:
    local-data: "hoge.example.com.    IN A 172.16.0.1"
    local-data: "fuga.example.com.    IN A 172.16.0.2"
    local-data: "foo.example.com.     IN A 172.16.0.3"
    local-data: "bar.example.com.     IN A 172.16.0.4"
(……略……)

/usr/share/doc/unbound/examples/unbound.confにコメントつきの完全な設定ファイルのサンプルが用意されているので、こちらをベースに設定ファイルを作成するのもよいでしょう。unbound.confの設定内容はこちらも参考になります。設定が完了したら、Unboundを再起動してください。

Unboundの再起動
$ sudo service unbound restart

LAN内の別のPCから、digで名前解決ができるか試してみましょう。

digで名前解決をテストする
$ dig hoge.example.com @(UnboundサーバーのIPアドレス) +norec

(……略……)
;; ANSWER SECTION:
hoge.example.com.   3600    IN  A    172.16.0.1
(……略……)

ログの設定

サービスのログは設定の確認やトラブル時の問題解決に役立つのはもちろんのこと、DNSのクエリーログはセキュリティ的にも大きな意味を持ちます。ですので、可能であればログを出力する設定をしておきましょう。server.confのserver:ディレクティブに以下の内容を追記してください。ログの出力にはsyslogを利用し、またクエリーログを出力するということを意味します。

use-syslog: yes
log-queries: yes

Unboundがsyslogに出力するログファシリティはdaemonです。Ubuntuのデフォルト設定では、daemon.*ログは個別のログファイルに書かれず/var/log/syslogにだけ書かれてしまいます。そこで/etc/rsyslog.d/50-default.confの以下の行のコメントアウトを解除して、rsyslogを再起動してください。/var/log/daemon.logにログが出力されるようになります。なお、Unboundのログの識別子はunboundです。

daemon.logにログを出力する
daemon.*                        -/var/log/daemon.log
rsyslogの再起動
$ sudo service rsyslog restart

クエリーログはかなりの量になることが予想されるうえ、ある程度の期間保存しておきたいため、ログローテーションの設定を変更します。/etc/logrotate.d/rsyslogを見てみましょう。/var/log/daemon.logは、mail.logやauth.logと同様に、週次4世代ローテーションに設定されています。

/etc/logrotate.d/rsyslog
(……略……)
/var/log/daemon.log
/var/log/kern.log
/var/log/auth.log
(……略……)
{
        rotate 4
        weekly
        missingok
        notifempty
        compress
        delaycompress
        sharedscripts
        postrotate
                reload rsyslog >/dev/null 2>&1 || true
        endscript
}

上記の部分から/var/log/daemon.logの1行を削除したうえで、ファイルの末尾に以下を追加してください。日次で180世代ローテーション、つまり半年分のクエリーログを保存する設定です。もちろん世代数は、ログのサイズやディスクの容量と相談して決めてください。

daemon.logに以下を追記する
/var/log/daemon.log
{
        rotate 180
        daily
        missingok
        notifempty
        delaycompress
        compress
        postrotate
                reload rsyslog >/dev/null 2>&1 || true
        endscript
}

unbound-controlを使う

unbound-controlは、Unboundをリモートから制御するためのCLIツールです。unbound-controlは設定をオンラインで変更できるため[6]⁠新しいサーバーを立てたのでlocal-dataを追加したい」というような場合も、いちいちUnboundを再起動しなくていいのです。

unbound-controlとUnboundが通信するためには、サーバー、クライアント双方にそれぞれ鍵ペアが必要になります。これを生成してくれるのがunbound-control-setupというシェルスクリプトなのですが、Ubuntuの場合、unboundパッケージのインストール後の処理で自動的に実行されるため、手動で行う必要はありません。/etc/unbound以下にunbound_control.key、unbound_control.pem、unbound_server.key、unbound_server.pemの4つのファイルがあるはずです。便利ですね。

次にリモートコントロールを有効にするため、/etc/unbound/unbound.conf.d/remote-control.confというファイルを作成し、以下の内容を記述してください。remote-control:ディレクティブでは、リモートコントロールの有効化とコントロールインターフェース、サーバー、クライアントそれぞれの鍵ファイルを指定しています。

/etc/unbound/unbound.conf.d/remote-control.conf
remote-control:
    control-enable: yes
    control-interface: 127.0.0.1
    server-key-file: "/etc/unbound/unbound_server.key"
    server-cert-file: "/etc/unbound/unbound_server.pem"
    control-key-file: "/etc/unbound/unbound_control.key"
    control-cert-file: "/etc/unbound/unbound_control.pem"

……と言いはしたものの、実はUbuntuに限っては、この設定ファイルは作成しなくても動作します。その理由は以下の通りです。

unbound-controlからUnboundを操作するためには、設定のcontrol-enableを「yes」に設定しなくてはなりません。この値はデフォルトで「no」がセットされているため、設定ファイルにcontrol-enableの設定を明示的に書かない限り、unbound-controlは使えないはずです。しかしresolvconfの節で説明した通り、UbuntuではUnboundの起動時に、unbound-controlを使ってforwardを設定しなければならないという事情があります。そこで明示的な設定がなくてもこの機能が有効になるよう、パッケージ内でソースコードにパッチを当てているのです[7]⁠。control-interfaceや各鍵ファイルは上記の設定値がデフォルト値そのままのため、この設定ファイルは(Ubuntuにおいては)不要、というわけです。

disable_chroot_by_defaultパッチの抜粋
@@ -198,7 +198,7 @@ config_create(void)
        cfg->local_zones_nodefault = NULL;
        cfg->local_data = NULL;
        cfg->python_script = NULL;
-       cfg->remote_control_enable = 0;
+       cfg->remote_control_enable = 1;
        cfg->control_ifs = NULL;
        cfg->control_port = UNBOUND_CONTROL_PORT;
        cfg->minimal_responses = 0;
unbound.confのmanからの抜粋。説明にもパッチが当たっている
control-enable: <yes or no>
            The option is used to enable remote control, default is "yes".  If turned off, the server does not listen for control commands.

長々と説明しましたが、つまりUbuntuでは何もしなくてもunbound-controlコマンドが使えるということです。至れり尽くせりですね!

unbound-controlには様々なサブコマンドが用意されています。たとえばlocal-dataを追加するにはlocal_dataサブコマンドを利用します。詳しくはunbound-controlのmanを参照してください。

unbound-controlの例
$ sudo unbound-control local_data "www.example.com. IN A 172.16.0.10"
ok

$ host www.example.com
www.example.com has address 172.16.0.10

Muninでのモニタリング

Unboundには、MuninやCactiでクエリーの統計を取るためのプラグインが用意されています。せっかくですのでUnboundサーバーにMuninクライアントとプラグインをインストールして、Unboundを「見える化」してみましょう。 今回はApacheとMuninサーバーもUnboundと同じサーバー上に構築しました。Muninサーバー自体の構築方法は第359回を参照してください。

まずUnbound側の設定です。/etc/unbound/unbound.conf.d/statistics.confというファイルを作成し、統計のための設定を追加します。設定が完了したら、Unboundを一度再起動しておいてください。

/etc/unbound/unbound.conf.d/statistics.conf
server:
        statistics-interval: 0
        extended-statistics: yes
        statistics-cumulative: no

次にUnboundのソース[8]を取得、展開してください。contribディレクトリの中にunbound_munin_というMuninプラグインがありますので、これを/usr/local/share以下にディレクトリを作成してコピーします。このプラグインはワイルドカードプラグインなので、名前を変えて複数のシンボリックリンクを/etc/munin/plugins以下に張ることで、それぞれ異なったグラフを生成することができます。

Muninプラグインのインストール
$ sudo apt-get install dpkg-dev
$ apt-get source unbound
$ sudo mkdir -p /usr/local/share/munin/plugins
$ sudo cp unbound-1.4.22/contrib/unbound_munin_ /usr/local/share/munin/plugins/

Muninプラグイン動作するために必要な設定を、/etc/munin/plugin-conf.d/munin-nodeの末尾に追記してください。具体的には以下のように、プラグインが使うテンポラリファイル、unbound.conf、unbound-controlのパスを記述します。

sudo vi /etc/munin/plugin-conf.d/munin-node

[unbound*]
user root
env.statefile /var/lib/munin-node/plugin-state/munin/unbound-state
env.unbound_conf /etc/unbound/unbound.conf
env.unbound_control /usr/sbin/unbound-control

シンボリックリンクを張ってプラグインを有効にします。munin-nodeを再起動した後は、munin-runでプラグインの動作を確認しておきましょう。

プラグインを有効にする
$ cd /etc/munin/plugins
$ for n in unbound_munin_hits unbound_munin_queue unbound_munin_memory unbound_munin_by_type unbound_munin_by_class unbound_munin_by_opcode unbound_munin_by_rcode unbound_munin_by_flags unbound_munin_histogram
do
sudo ln -s /usr/local/share/munin/plugins/unbound_munin_ $n
done
$ sudo service munin-node restart
プラグインの動作確認
$ sudo munin-run unbound_munin_hits
t0_n_queries.value .475341
t_n_queries.value .475341
t_n_chits.value .203717
t_n_prefetch.value 0
n_q_tcp.value 0
n_q_ipv6.value 0
u_queries.value 0
u_replies.value 0

5分ほど待てば、グラフが生成されます。

図1 DNSの応答数をリターンコード別にグラフ化
図1 DNSの応答数をリターンコード別にグラフ化
図2 DNSの応答時間をグラフ化
図2 DNSの応答時間をグラフ化

さいごに

家庭内でちょっとだけ名前解決したいんだけど、hostsをメンテするのは大変だし、BINDはサブドメインのゾーンを作らないといけないし、というような場合には、Unboundをご家庭キャッシュサーバーにすると便利かもしれませんね!

おすすめ記事

記事・ニュース一覧