オフィシャルサイト http://cr.yp.to/djbdns.html
日本語ページ: http://djbdns.qmail.jp/ (前野年紀さん)

djbdns

DNSサーバソフトウェアといえば BIND が有名だが、これから新たに DNSサーバを構築するのなら djbdns が断然お勧めだ。安全なこと。軽量なこと。ドメインネームシステムの規定に忠実に作られ、動作構造にもそれがよく反映されていること... 優位点を挙げればきりがない。しかしそれらにも増してウレシイのは、設定やレコード記述が簡便だという点だ。 djbdns を知るまでは、「BIND のゾーンファイル = Domain Name System」 のように思っていたが、今となっては、あれは DNS と闘っていたのではなく BIND と闘っていたのだとつくづく思う。

参考にしたページ

Table of Contents

インストール

必要なもの

djbdns-1.05.tar.gz djbdns本体
daemontools-0.76.tar.gz サービスを管理するためのユーティリティ群。 無くても動かすことは可能だが、djbdns は、まさに daemontools で管理してくれと言わんばかりのディレクトリ構造でインストールされるので、わざわざ頭をひねって initスクリプトを書き上げたりするのはそうとうの天の邪鬼。Systemd起動にする場合は無用。
ucspi-tcp TCPサーバ/クライアントコネクション制御ユーティリティ群。Systemd起動にする場合は、基本的に無用だが、axfrdnsも動かす場合は要る。パッチ、解説等は別ページにまとめた。
パッチ類
djbdns-1.05.errno.patch DJB御大は「間違ったことをしてるのは今どきのLinuxだ私じゃない」とご立腹だが、errnoヘッダの宣言を変更しないと Linux ではコンパイルできないので仕方がない
daemontools-0.76.errno.patch 同、daemontools用。Systemdで起動する場合は不要。
IPv6 Patch 筆者は今のところ適用していないが、djbdns を IPv6アドレス対応にするパッチ

daemontoolsのインストール

Systemdで起動制御するのならインストール不要。

init/Upstart共通作業

daemontools パッケージはちょっと特異で、実行ファイルが /usr/local/svc -> /command/svc -> /package/admin/command/svc という具合にシンボリックリンクによって配置されるので、ソースの展開は必ず /package へ行い、インストール後も削除してはならない。同様の理由で、展開時には tar コマンドの p オプションが大切。

root# mkdir -p /package
root# chmod 1755 /package
root# cd /package
 
root# tar xzvpf /path/to/daemontools-0.76.tar.gz
root# cd admin/daemontools-0.76
 
root# patch -p1 </path/to/daemontools-0.76.errno.patch
root# package/install

前置きにも書いたとおり、実行ファイル群は /command 下へシンボリックリンクとしてインストールされ、更に、PATH が通って使いやすいよう /usr/local/bin 下へシンボリックリンクが張られる。また、(Linux へのインストールでは) /etc/inittab/command/svscanboot の起動定義が書き加えられ、INITプロセスに HUP シグナルが送られて直ちに起動する (つまりマシンを再起動する必要はない)。

Upstart環境での追加作業

RHEL6/CentOS6/Fedora Core 9 以降では initUpstart に置き換えられているため、上記の作業だけでは svscanboot が起動しない。

まず、/etc/inittab の下記の行をコメントアウトするか削除する;

SV:123456:respawn:/command/svscanboot

/etc/init/ 直下にジョブ設定ファイル svscanboot.conf を作る。SysVinit とは違いファイルパーミッションは 644 に。

svscanboot.conf

# Startup configuration for djb daemontools
 
start on runlevel [2345]
stop on runlevel [S016]
 
respawn
exec /command/svscanboot

マシンをリブートするか、Upstartstart コマンドで起動;

root# start svscanboot
root# status svscanboot
ちなみに、daemontools は、シェルや Perl などの言語で書いたスクリプトを簡単にデーモン化できる、便利なツールだ。スクリプトに必要な条件は以下;
あとは、dnscachetinydns の runスクリプトや log/run スクリプトを参考にすればいいが、log/main などのディレクトリ作成やパーミッション設定が煩雑なので、それらを整備するためのちょっとしたシェルスクリプト mksvcdir.sh を作成した。例えば、/usr/local/myscript (これはあらかじめ存在している必要あり) 配下に daemontools 連携用の構造を整備するには、root権限で `mksvcdir.sh /usr/local/myscript [LOG_USER ]' という風に実行する。オプション引数 LOG_USER は、multilog によるログを root 以外のユーザで取りたい場合にだけ付ければよい。
 

ucspi-tcpのインストール

tcpserver のページを参照のこと。Systemdで起動制御する場合 tinydnsdnscache には要らないが、axfrdns も使う場合はインストールしておく必要がある。

djbdnsのインストール

daemontools とは違ってソースを残しておく必要はないのでソースの展開先は任意。ここでは /home/hoge/cabinet だと仮定する。

hoge$ cd ~/cabinet
hoge$ tar xzvf djbdns-1.05.tar.gz
hoge$ cd djbdns-1.05
hoge$ patch -p1 </path/to/djbdns-1.05.errno.patch
hoge$ make
hoge$ su
root# make setup check
root# cd ..
root# rm -rf djbdns-1.05

この時点では、ただ道具がインストールされただけで、起動はまだ不可能。

ローカルネットワーク用DNSサーバとDNSキャッシュの構築

ここからの説明では、「DNSサーバ」 は名前解決用レコードを持つ本当の意味でのDNSサーバを指す。 DNSキャッシュと組み合わせた総体としての DNS や、キャッシュなのかDNSサーバなのか断言できない外界の DNS のことは 「DNSシステム」 と呼ぶことにする。 BIND しかさわっていなかった頃の私は「DNSサーバ」と「DNSキャッシュ」との区別がよく分からなかった。しかしふたつは機能的に別々のもの。 djbdns をさわり始めれば、じきにふたつの役割の違いがはっきりと認識できるはずだ。

構築セオリー

DNSマシンも含めたローカルネットワーク上のマシンから参照する DNSシステムを考えると、普通、その DNSシステムは、ローカルネットワーク上のコンピュータの名前解決だけでなく、外部 (つまりインターネット上) のホストの名前解決もできなければならない。しかし、キャッシュも DNS もごた混ぜの BIND とは異なり、 djbdns では、 DNSサーバデーモンと DNSキャッシュデーモンが完全に分離している。前者は tinydns というプログラムであり、後者は dnscache だ。(これは決して悪いことではなく、構造のシンプルさからもセキュリティの面からも好ましいことだ)。そこで、こういった用途の DNSシステムを構築する際には、下図のような仕組みを採ることになる。

ここでは、物理的には 1台の Linuxサーバで、 DNSキャッシュと DNSサーバを動作させることにする。

ローカルネットワーク内の DNSクライアント (このマシン自身も含む) は、それぞれの /etc/resolv.conf
nameserver 192.168.1.1
と書いて運用する。名前解決が必要な局面が発生すると、どのマシンも 192.168.1.1 (DNSキャッシュ) に名前解決を求めるわけだ。ここで、dnscache には 「.hoge.cxm 又は 192.168.1.x については DNSサーバ (tinydns) に訊くべし」という設定しておく。すると、問い合わせが例えば 「dog.hoge.cxm のアドレスは?」だった場合には、 dnscache が、 tinydns に問い合わせをエスカレーションしてその回答をキャッシュしつつクライアントに返す。

かたや、問い合わせが「www.yahoo.co.jp のアドレスは?」であった場合には、 dnscache はその設定で指定してある外部DNSシステムへ問い合わせを行い、やはりその返答をキャッシュしつつクライアントに返す。

dnscachetinydns は同一のアドレスで起動することはできない。そこで、一番手っ取り早いのは、図のように dnscache はそのマシンの持つネットワークインターフェイスの外向きの IPアドレスで待機させ、 tinydns はループバックアドレスで待機させるというやり方だ。また別の方策として、ネットワークインターフェイスに、 tinydns 用に追加の IPアドレス (ネットワークエイリアス) を持たせる方法もある。例えば、本来の eth0 の IPアドレスが 192.168.1.1 で、そのエイリアスインターフェイスを作りたい場合、 /etc/sysconfig/network-scripts/ifcfg-eth0:1 というファイルを作り、そこにもうひとつの IPアドレス (例えば 192.168.1.11) などを書く。

ネットワークエイリアスを利用する際に知っておきたいこと

まず基本的なことだが、エイリアスの設定ファイルには GATEWAY は書いてはいけない。
もうひとつは、DNS を稼働させているサーバ自身で iptables による IPフィルタリングを行っている場合について。通常、 eth0 から入ってきたパケットならば Netfilter によって i=eth0 という情報が捕捉されるわけだが、 eth0:1 エイリアスインターフェイスからパケットが入ってくると、まずは i が空のパケットとして認識され、その後すぐに eth0 へ渡されるという見え方になる。よって、そのパケットは eth0 に対するルールでフィルタリングすることができる。なお、「渡される」といっても ip_forward を on にする必要はない。

dnscacheの設定

dnscache は、クライアントからの再帰型問い合わせ (Recursive Query: リカーシブ・クエリ) にだけ答える DNSリクエスト処理サーバで、RFC1035 で言うところの、キャッシュを備えたリゾルバ (Resolver)、 DNSプロキシ、あるいは再帰問い合わせサーバ (Recursive Server) にあたる。 再帰型問い合わせ (リカーシブ・クエリ) とは、「abc.abcd.com のアドレスを教えてくれ。ただし、結果が分かるか失敗が明らかになるまでそっちで何処へなりと尋ね回って最大限の努力をしなさい。ただし私への返事は結果 だけでよし」 といった問い合わせ。ドメインプロトコルメッセージの面から捉えると、メッセージヘッダの中の RD (Recursion Desired) フィールドに `1' の立ったリクエストメッセージだ (RFC1035)。

それに対して、無論、 non-recursive な問い合わせというものもあるわけで、それを扱うのが djbdns のもうひとつの主コンポーネント tinydns。しかしそれについては次の章に預けるとしよう。

ユーザの作成

dnscache の実行ユーザ dnscache と、DNS関係のログを採るユーザ dnslog、それらのプライマリグループである dns グループを作成する。Systemd制御の場合はログユーザは使われないのでdnslogは作成の必要なし。名前や UID/GID は必ずしもこの通りである必要はなく、後述の dnscache-conf コール時に引数をそれに合わせればいいだけのこと。これらユーザにはホームディレクトリは要らないし (useradd-M オプション)、シェルも潰してよい。 passwd ファイルの都合上必要なホームディレクトリパスも、 /var/dns 以外でいっこうに構わない。 dnslog ユーザは tinydns でも使用する (Systemd起動でない場合) ので、先に作った方がきれい。

root# groupadd -g 800 dns
root# useradd -u 800 -g dns -M -d /var/dns -s /sbin/nologin dnslog
root# useradd -u 801 -g dns -M -d /var/dns -s /sbin/nologin dnscache

運用ディレクトリ構造の作成

dnscache-conf コマンドを使って運用ディレクトリ構造を作成する。 1番目と 2番目の引数は、先ほど作成した dnscache 実行ユーザとログユーザの名前。 3番目の引数は運用ディレクトリの基底だが、 /var/dnscache でなければならない理由はない。 D.J.B のオフィシャル説明書では /etc/dnscache になっているが、 /etc の属しているパーティションはあまり大きく確保しないのが通例なので、筆者は /var ファイルシステム上に置くのが好みだ。最後の引数は dnscache がリッスンする IPアドレス (省略すると 127.0.0.1 になるが、今回の実装方針では必須)。リッスンアドレスは /var/dnscache/env/IP ファイルへ書き込まれるだけなので、後からそれを編集すれば変更も可能だ。

root# dnscache-conf dnscache dnslog /var/dnscache 192.168.1.1

基底ディレクトリパスは /var/dnscache/env/ROOT ファイルに書き込まれる。 dnscache プログラムはそこへ chroot して動作することになる。これ以降、このディレクトリ (当設定例では /var/dnscache) を $ROOT と表すことにする。

ロギング定義の修正

上記の例の通りに dnscache-conf を走らせたのなら、デフォルトで作成された $ROOT/log/run は下記のようになっているだろう。Systemd制御の場合このファイルは使わないので気ににしなくてよい。

exec setuidgid dnslog multilog t ./main

setuidgidmultilog はともに、 daemontools パッケージによって提供されるプログラム。この記述は、「dnslog ユーザ (とそのプライマリグループ) 権限で multilog プログラムを実行し、各エントリはタイムスタンプ付き (t) で出力し、 $ROOT/log/main/ 下でローテーション管理せよ」ということを意味している。暗黙のデフォルトローテーションルールでは、ファイルサイズ上限 99999 バイト (つまり 100Kバイト弱) で、10世代のローテーション。しかし 100Kバイトでは小さすぎるので 3M に変え、10世代管理であることも明示することにする。

exec setuidgid dnslog multilog t s3000000 n10 ./main

問い合わせを許可するクライアントアドレス範囲の指定

この DNSキャッシュを使用できるクライアントのアドレスは $ROOT/root/ip/ 下にあるファイルの名前によって規定される。デフォルトでは 127.0.0.1 があるだけだ。当シナリオでは、 DNSキャッシュへの問い合わせを自分のローカルネットワークメンバに許可したいので、そのネットワーク範囲を示すファイルを作ってやる。 192.168.1.x から問い合わせを認めるなら下のような塩梅だ。

root# cd /var/dnscache/root
root# touch ip/192.168.1 && chmod 600 ip/192.168.1

ルートサーバまたはフォワーダの設定

dnscache が他の DNSシステムに問い合わせを行う時、その問い合わせ先は、 $ROOT/root/servers/@ というファイルの内容で規定され、デフォルトではインターネットルートサーバの IPアドレスが列挙されている。このまま、ルートサーバからアドレスを辿らせる方法もあるが、自分の加入しているプロバイダの DNSキャッシュへフォワードする方法もある。自宅サーバだったり、クライアントネットワークが小さい場合には、後者の方が適していると筆者は考える。

ルートサーバへ問い合わせを行う場合

こちらの場合 dnscache は、最終的な解が得られるまで (あるいは失敗が明らかになるまで)、必要に応じてルートサーバから NSレコードを辿って階層的に問い合わせをして ()、最終解答に辿り着こうとする。ただしクライアントへ返すのは、最終解答か「調べましたが私の知る限りそんなホストやドメインはありません」という返事 (NXDOMAIN) だけだ。こちらの設定の場合、 @ ファイルは基本的にはそのまま使うが、リストが古くなっているため、 INTERNIC から最新のルートサーバリストをダウンロードして修正してやる必要がある。その際に便利なのが、djbdns 付属ユーティリティの dnsname プログラム。デフォルトの @ ファイルのまま

root# dnsname `cat /var/dnscache/root/servers/\@`

を行って、 x.root-servers.net のような表示にならないルートサーバは、既に引退している可能性が高い (必ずしもそうではない)。

※ 次々にツテを辿って最終解答へ近づこうとするこうした問い合わせを 「反復問い合わせ」 (Iterative Query: イタレーティブ・クエリ) という。イタレーティブ・クエリは、 RD ビットの立っていない (再帰型でない) クエリの繰り返しだ。だだし、過去の問い合わせの最中に分かった 「どのドメインなら何処の DNSサーバに訊けばいいか」 という情報 (つまり NSレコード) はメモリ上にキャッシュされるので、毎回毎回ルートサーバから遡るわけではない。イタレーティブクエリがどんなものかは moin.qmail.jp で日本語で具体的に説明されている。

プロバイダの DNSシステムなどへ問い合わせを行う場合

こちらは DNSの用語では 「フォワーディング」 あるいは 「フォワーダを設定する」 と言い、tinydns の動作が少々異なる。まず、@ ファイルの内容をフォワーダのアドレスだけにする。フォワーダをふたつ書く場合の例;

root# echo -e '202.224.32.1\n202.224.32.2' >root/servers/@

そして、 dnscache プログラムに「@ の中身はフォワーダだ」と教えてやる。そのため、env ディレクトリに FORWARDONLY というファイルを作る。Systemd起動の時はこのファイルでなく、後で面倒を見る。

root# echo 1 >env/FORWARDONLY

これによって、dnscache@ ファイルのリストを 「別の DNSキャッシュ」 だと認識し、(@ に関しては) 通常のクライアントがやるようなリカーシブ・クエリを行うようになる。

(オプション) キャッシュ量の調節

dnscache プログラムは問い合わせの答や NXDOMAIN (「そんなドメインないぞ」) な結果などをキャッシュメモリに貯える (それが仕事だ)。キャッシュメモリは dnscache のスタートアップ時に所定のサイズだけ確保され、そこから減りも増えもしない (BINDとの大きな違いだ)。当然、レコードの TTL を迎えたデータは順次捨てられる他、古いキャッシュデータはトコロテン式に捨てられていく。調節には、ふたつの数値 (ファイル) が関係する。Systemdで起動制御する場合は別のところに書くことになるが、ここで意味合いを知っておいて損はない。

env/CHACHESIZE キャッシュメモリのサイズ (バイト)
env/DATALIMIT 万が一 dnscache プロセスが暴走してメモリサイズが膨らみ始めた時のオサえ設定 (バイト)

説明を見て分かるとおり、 CACHESIZEDATALIMIT よりも小さくなければならない。 CACHESIZE の黄金律はないようだが、 Life with djbdns によると、「64Mラインのインターネット接続でメールと WEB を使っている小規模な企業で、2M もあれば充分だろう」と述べられている。非常に「やっつけ」だが、ここでは 8Mバイトのキャッシュを確保することにする;

root# echo 8000000 >env/CACHESIZE
root# echo 9000000 >env/DATALIMIT

DATALIMIT は、正確には dnscache プログラム自体の備える変数ではなく、 run ファイルでの起動コマンドの中で softlimit プログラムの -d 引数として用いられる、プロセスデータセグメント制限値。 softolimit は daemontools に含まれるユーティリティのひとつ。

dnscacheの稼働

まだやることはあるが、とりあえずここまでで稼働させ、動作を確認するとよい。

init/Upstart駆動の場合

daemontools の見張っている /service ディレクトリ下へシンボリックリンクを作ることで、 5 秒以内に自動的に稼働状態になる;

root# ln -s /var/dnscache /service
root# svstat /service/dnscache

2行目は daemontools の動作確認用コマンドで、 `/service/dnscache: up (pid...' と表示されれば正常だ。

Systemd制御とする場合

サービスユニットファイル /etc/systemd/system/dnscache.service [root:root 644] を作成する。

[Unit]
Description=A DNS cache server
Requires=network.target
After=network.target
 
[Service]
Restart=always
EnvironmentFile=/var/dnscache/dnscache.conf
PIDFile=/var/run/dnscache.pid
LimitDATA=9000000
LimitNOFILE=250
ExecStart=/usr/local/bin/dnscache
 
[Install]
WantedBy=multi-user.target

LimitDATA パラメータは、ulimit -d つまり Data Segment Size であり、daemontools利用時の env/DATALIMIT に相当する。LimitNOFILE は、デフォルトで生成される run ファイルにある 'softlimit -o250' を反映させたもので、同時オープンファイルディスクリプタの上限規制である。

上記の EnvironmentFile パラメータで参照している $ROOT/dnscache.conf ファイルは、dnscache プログラムへ環境変数として渡すべき値をエクスポートするためのファイルで、daemontools を利用する通常の起動方式にはないものだ。env/配下のファイルが形を変えたもの、と言うことができる。

ROOT=/var/dnscache/root
UID=dnscache
GID=dns
IP=192.168.1.1
IPSEND=0.0.0.0
CACHESIZE=8000000
FORWARDONLY=1

上から4つは必須パラメータ。あとはオプションで、フォワーダの有効/無効も環境変数渡しの部類なのでここに入ってくる。フォワーディングしない場合は書いてはいけない。

稼働させよう。

root# systemctl daemon-reload
root# systemctl start dnscache.service
root# systemctl enable dnscache.service
root# systemctl status dnscache.service

ログは標準出力を経てjournalに記録される。

root# journalctl -u dnscache -xe

基本動作テスト

dig、 あるいは djbdns 付属の dnsqr プログラムを使って実際に名前解決ができるか試してみる。 dig の場合;

hoge$ dig @192.168.1.1 www.google.co.jp

代わって dnsqr の場合。末尾手前の a は 「Aレコードの検索」という意味;

hoge$ env DNSCACHEIP=192.168.1.1 dnsqr a www.yahoo.co.jp 

ローカルドメイン検索の振り分け設定

構築セオリー」で述べたように、自ドメインとその逆引きに要求対しては、後で構築する tinydns へ問い合わせをエスカレーションするよう設定を施す。ゾーン名をファイル名に持つファイルを $ROOT/root/servers/ に作り、中身に DNSサーバ (tinydns) のアドレスを書いておくだけだ。自ドメインが hoge.cxm、そのアドレス範囲が 192.168.1.0/24、 tinydns (後述) をループバックアドレスで動かすとすれば、下のような操作になる。ゾーン名ファイルのパーミションは @ に倣い root:root の 644 に。なお、FORWARDONLY を有効にした場合でも、ここで設定したエスカレーション先に対してはリカーシブ・クエリが行われるわけではない。

root# cd /var/dnscache/root
root# echo 127.0.0.1 >servers/hoge.cxm
root# echo 127.0.0.1 >servers/1.168.192.in-addr.arpa

設定を反映させるため dnscache を再起動する。

daemontoolsの場合:

root# svc -t /service/dnscache

Systemdの場合:

root# systemctl restart dnscache.service

使わなくなった dnscache サービスを停めておくには [daemontools利用時]

supervise (daemontools) は、サービスディレクトリに down という名前のファイルがあるのを発見すると、そのサービスは起動させない。

root# touch /service/dnscache/down
root# touch /service/dnscache/log/down
root# svc -d -x /service/dnscache

また dnscache を使いたくなったら、down ファイルふたつを削除してから、`svc -u /service/dnscache' するかマシンを再起動すればいい。