DNSサーバソフトウェアといえば BIND が有名だが、これから新たに DNSサーバを構築するのなら djbdns が断然お勧めだ。安全なこと。軽量なこと。ドメインネームシステムの規定に忠実に作られ、動作構造にもそれがよく反映されていること... 優位点を挙げればきりがない。しかしそれらにも増してウレシイのは、設定やレコード記述が簡便だという点だ。 djbdns を知るまでは、「BIND のゾーンファイル = Domain Name System」 のように思っていたが、今となっては、あれは DNS と闘っていたのではなく BIND と闘っていたのだとつくづく思う。
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アドレス対応にするパッチ |
Systemdで起動制御するのならインストール不要。
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 シグナルが送られて直ちに起動する (つまりマシンを再起動する必要はない)。
RHEL6/CentOS6/Fedora Core 9 以降では init が Upstart に置き換えられているため、上記の作業だけでは 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
マシンをリブートするか、Upstart の start コマンドで起動;
root# start svscanboot root# status svscanboot
tcpserver のページを参照のこと。Systemdで起動制御する場合 tinydns と dnscache には要らないが、axfrdns も使う場合はインストールしておく必要がある。
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システム」 と呼ぶことにする。 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システムへ問い合わせを行い、やはりその返答をキャッシュしつつクライアントに返す。
dnscache と tinydns は同一のアドレスで起動することはできない。そこで、一番手っ取り早いのは、図のように dnscache はそのマシンの持つネットワークインターフェイスの外向きの IPアドレスで待機させ、 tinydns はループバックアドレスで待機させるというやり方だ。また別の方策として、ネットワークインターフェイスに、 tinydns 用に追加の IPアドレス (ネットワークエイリアス) を持たせる方法もある。例えば、本来の eth0 の IPアドレスが 192.168.1.1 で、そのエイリアスインターフェイスを作りたい場合、 /etc/sysconfig/network-scripts/ifcfg-eth0:1 というファイルを作り、そこにもうひとつの IPアドレス (例えば 192.168.1.11) などを書く。
ネットワークエイリアスを利用する際に知っておきたいことまず基本的なことだが、エイリアスの設定ファイルには GATEWAY は書いてはいけない。 |
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
setuidgid と multilog はともに、 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の用語では 「フォワーディング」 あるいは 「フォワーダを設定する」 と言い、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 プロセスが暴走してメモリサイズが膨らみ始めた時のオサえ設定 (バイト) ※ |
説明を見て分かるとおり、 CACHESIZE は DATALIMIT よりも小さくなければならない。 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 に含まれるユーティリティのひとつ。
まだやることはあるが、とりあえずここまでで稼働させ、動作を確認するとよい。
daemontools の見張っている /service ディレクトリ下へシンボリックリンクを作ることで、 5 秒以内に自動的に稼働状態になる;
root# ln -s /var/dnscache /service root# svstat /service/dnscache
2行目は daemontools の動作確認用コマンドで、 `/service/dnscache: up (pid...' と表示されれば正常だ。
サービスユニットファイル /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
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' するかマシンを再起動すればいい。