djbdns のもう一方の主軸ソフトウェア tinydns は、正確な意味での DNSサーバで、リカーシブでない (non-recursive) 問い合わせに責任を持って答える。「責任を持って」というのは抽象的な意味ではなく、 RFC1035 でいうところの Authoritative Name Server (オーソリタティブ・ネームサーバ:権威付きネームサーバ、「DNSコンテンツサーバ」とも訳される) を意味し、権限を与えられているドメインの情報については常に AA (Authoritative Answer) ビット付きのレスポンスを返す。 tinydns は RD ビットの立ったドメインリクエストメッセージにも答えるが、その場合でも再帰解決努力はしないし、権限外のドメインに関する問い合わせには返事をしない。
ここは、前章の dnscache とセットで考えており、dnscache と同一のマシン上で動かすことを想定している。 dnscache の設定手順と重複する部分は少々はしょっているので、疑問があれば dnscache での説明を参照していただくといいかもしれない。
DNSサーバの実行ユーザを作成する。 dns グループと、ログユーザ dnslog(Systemd起動にする場合は不要) については dnscache の設定の際に構築済み。ここにおいても、ユーザ名や UID 、ホームディレクトリは下記の通りである必要はなく、後述する tinydns-conf の引数をそれに合わせれば問題ない。ホームディレクトリは実際には作らせず (-M オプション)、シェルも偽シェルにする。
root# useradd -u 802 -g dns -M -d /var/dns -s /sbin/nologin tinydns
tinydns-conf コマンドを使って運用ディレクトリ構造を作成する。 1番目と 2番目の引数は、先ほど作成した実行ユーザと、ログユーザの名前。 3番目は運用ディレクトリの基底パスだが、 /var/tinydns でなければならないという決まりはない。最後の引数は tinydns がリッスンする IPアドレス(省略不可)。リッスンアドレスは、 /var/tinydns/env/IP ファイルを書き換えれば、あとで変更することも可能。
root# tinydns-conf tinydns dnslog /var/tinydns 127.0.0.1
tinydns は基底ディレクトリである /var/tinydns (/var/tinydns/env/ROOT ファイルで定義されている) へ chroot した状態で動作する。以降、この /var/tinydns を $ROOT と表す。
$ROOT/log/run を修正し、最大 3Mバイトを目処にログをローテーションし 10世代を保存していく設定にする。サイズと世代数を省略したままの時の multilog の規定動作は、 99999バイトの 10世代ローテーション。Systemd起動にする場合はこのファイルは使われないので修正不要。
exec setuidgid dnslog multilog t s3000000 n10 ./main
まだ肝心のレコードがないが、とりあえずここで稼働させてしまう。
daemontools の監視する /service ディレクトリ下へシンボリックリンクを張ることで 5秒以内に稼働状態となる。
root# ln -s /var/tinydns /service root# svstat /service/tinydns
2行目は稼働状態の確認。`/service/tinydns: up (pid ...' と表示されれば無事稼働中。
サービスユニットファイル /etc/systemd/system/tinydns.service [root:root 644] を作成する。
[Unit] Description=A DNS contents server Requires=network.target After=network.target [Service] Restart=always EnvironmentFile=/var/tinydns/tinydns.conf PIDFile=/var/run/tinydns.pid LimitDATA=300000 ExecStart=/usr/local/bin/tinydns [Install] WantedBy=multi-user.target
LimitDATA パラメータは、ulimit -d つまり Data Segment Size。tinydns-conf によって生成される run ファイル内の規定値である 'softlimit -d300000' を反映させたもの。
EnvironmentFile パラメータで参照している $ROOT/tinydns.conf ファイルは、tinydns プログラムへ環境変数として渡すべき値をエクスポートするためのファイルで、daemontools 利用時にはないもの。新規に作成する。
ROOT=/var/tinydns/root UID=tinydns GID=dns IP=127.0.0.1
稼働させる。
root# systemctl daemon-reload root# systemctl start tinydns.service root# systemctl enable tinydns.service root# systemctl status tinydns.service
ログは標準出力を経て journalに記録される。
root# journalctl -u tinydns -xe
tinydns は cdb フォーマットのバイナリファイル $ROOT/root/data.cdb から随時レコードデータを読み取る。そして、このファイルを作成するには、まずテキストファイル $ROOT/root/data に内容を書き、それを tinydns-data ユーティリティを使って data.cdb に変換するという手順を踏む。 data.cdb の変更は即座に認識されるので、更新の度に tinydns を再起動する必要はない。
data ファイルは、手作業で書いてもいいが、 tinydns の運用ディレクトリ構造を作成した際に $ROOT/root/ 下にインストールされる add-ns, add-host といったユーティリティ (シェルスクリプト) を使う方法もある。使い方:
./add-{host|alias|mx|ns|childns} domain_or_host IP
ただし、これらのユーティリティは一長一短で、
というわけで、ユーティリティで下地を作ってから手で修正するのが最も妥当と言える。下の表は、 data ファイルの主な書式と、その定型がどのユーティリティで作れるかを示している。
tinydns-dataの主な書式
書式 | 発生するレコード | ユーティリティ | 説明 |
.domain:x.x.x.x:nshost:TTL | SOA + NS + nshost のA | add-ns | |
.domain::nshost:TTL | SOA + NS | add-ns +編集 |
上記のIP部を省略した形。複数のSOA宣言文で同じネームサーバIPが登場しそうになる場合に、ふたつ目以降ではこのようにIP部を省かないと、そのAレコードが重複登録されてしまう。つまり、ひとつのtinydnsで複数のドメインを管理する場合や、逆引き用SOA宣言に使用する |
&domain:x.x.x.x:nshost:TTL | NS + nshost のA | add-childns | サブドメインを他の DNS に委譲する時などに使用 |
=host:x.x.x.x:TTL | A + PTR | add-host | |
+host:x.x.x.x:TTL | A | add-alias | CNAME はできるだけ使わずこれを使用すること |
^host:x.x.x.x:TTL | PTR | 編集 | |
Chost:x.x.x.x:TTL | CNAME | 編集 | なるべく使用しないこと |
@domain:x.x.x.x:mxhost:D:TTL | MX + mxhost のA | add-mx | D 部は距離(優先度)で 0, 5, 10 など。 :D 以降は省略可能 |
上記全てにおいて :TTL 以降は省略することもでき、その場合には tinydns によって常識的な TTL が自動決定される。なお、 add-ns 及び add-mx で作成されたデータ行の nshost 及び mxhost 部は、a とか b といった適当な英字一文字の相対ホスト名になってしまうので、そこも後で ns1.hoge.cxm などといった Aレコードに相応しい名前に手直ししたいところだ。
[Note] BIND と違って、FQDN を表す場合でも頭にドットは要らない。
こういうサーバ群のあるローカルネットワークを想定してみる;
そうすると、 $ROOT/root/data ファイルはこんな具合。通常、ローカルネットワーク用DNS に MXレコードは不要だが、例として入れてある。
.hoge.cxm:192.168.1.1:ns1.hoge.cxm:259200 .1.168.192.in-addr.arpa::ns1.hoge.cxm:259200 .hoge.cxm:192.168.1.2:ns2.hoge.cxm:259200 .1.168.192.in-addr.arpa::ns2.hoge.cxm:259200 @hoge.cxm:192.168.1.3:mail.hoge.cxm:10:86400 @1.168.192.in-addr.arpa::mail.hoge.cxm:10:86400 =dog.hoge.cxm:192.168.1.1:86400 =wolf.hoge.cxm:192.168.1.2:86400 =monkey.hoge.cxm:192.168.1.3:86400 +www.hoge.cxm:192.168.1.2:86400
上記 2行目と 4行目はそれぞれ、逆引き用の SOA を宣言しているのだが、第2パラメータがカラになっている点がポイント。例えば、もしも 2行目を
.1.168.192.in-addr.arpa:192.168.1.1:ns1.hoge.cxm:259200
と書いてしまうと、「ns1.hoge.cxm のアドレスは 192.168.1.1 である」 という Aレコードが 1行目と 2行目で二度生成されてしまう。逆引き用 MX を示す 6行目にも同じことが言える。
data ファイルの用意ができたら、$ROOT/root へ cd した上で、
root# make
を実行する。これによって、 tinydns が実際に読み込む data.cdb ファイルが出力される。
dig や、djbdns 付属の dnsq コマンドを使って問い合わせをしてみる。まだ /etc/resolv.conf に書いてないので、サーバを指定しなくてはならない。 dig なら;
hoge$ dig @127.0.0.1 www.hoge.cxm hoge$ dig @127.0.0.1 ns hoge.cxm
dnsq では;
hoge$ dnsq a www.hoge.cxm 127.0.0.1 hoge$ dnsq ns hoge.cxm 127.0.0.1
DNSキャッシュに対して既にさんざん問い合わせテストしていた場合には、キャッシュを一旦クリアするために dnscache を再起動する;
daemontools制御の時:
root# svc -t /service/dnscache
Systemd制御なら:
root# systemctl restart dnscache
dig を使った問い合わせテスト;
hoge$ dig @192.168.1.1 www.hoge.cxm hoge$ dig @192.168.1.1 ns hoge.cxm hoge$ dig @192.168.1.1 www.yahoo.co.jp
dnsqr を使った問い合わせテスト;
hoge$ env DNSCACHEIP=192.168.1.1 dnsqr www.hoge.cxm hoge$ env DNSCACHEIP=192.168.1.1 dnsqr ns hoge.cxm hoge$ env DNSCACHEIP=192.168.1.1 dnsqr www.google.co.jp
晴れて解答が得られたら、そろそろ /etc/resolv.conf に書いてもいい頃合いだ;
search hoge.cxm nameserver 192.168.1.1
そうしたらもう、 dig も dnsqr も `@192...' や `env DNSCACHEIP=...' 抜きでクエリが成功するはず。
内部 DNSサーバをプライマリとセカンダリのふたつ立て、それらを resolv.conf
に書いた場合、 ひとつめから回答がないと次の DNSキャッシュに問い合わせが行われるわけだが、 Linux では、デフォルトのタイムアウトは
5秒。しかし、重要なネットワークサービスを提供するサーバ (DNS的にはクライアント)
では、万が一プライマリDNSキャッシュが死んだ際に、これでは長すぎて、サーバサービスの滞りが大きいかもしれない。タイムアウトを指定するには、resolv.conf に options timeout:2 といった行を追加しておく。なお、基本中の基本だが、「回答がない」とは、本当に一切の返答がなかった時であり、 NXDOMAIN (「そんなホストやドメインないよ」) も回答のひとつなので、それは切り替わりのきっかけにはならない。 |
supervise (daemontools) は、サービスディレクトリに down という名前のファイルがあるのを発見すると、そのサービスは起動させない。
root# touch /service/tinydns/down root# touch /service/tinydns/log/down root# svc -d -x /service/tinydns
また tinydns を使いたくなったら、down ファイルふたつを削除してから、`svc -u /service/tinydns' するかマシンを再起動すればいい。
プライマリDNSサーバとセカンダリがともに tinydns であれば、BIND のような込み入ったゾーン転送メカニズムは必要ない。 ただ単に $ROOT/root/data.cdb ファイル自体を同期させてやれば、即、データが反映されるからだ。考え方としては、プライマリDNSサーバ上で make を実行した時に、セカンダリサーバへも data.cdb のコピーが出来上がればいいわけで、これは、プライマリ側の $ROOT/root/Makefile を少し工夫するだけで簡単に実現できる。セカンダリが BIND など他の DNSサーバソフトウェアの場合は、axfrdns を使う。
Makefile修正例 (単体テキストファイルはこちら)
TINYROOT = /var/tinydns/root SYNCUSER = dnssync SSH := /usr/bin/ssh -l $(SYNCUSER) -i /home/$(SYNCUSER)/.ssh/id_rsa SECONDARY = wolf.hoge.cxm .PHONY: sync sync: data.cdb -/usr/bin/rsync -t --rsh="$(SSH)" $(TINYROOT)/data \ $(SYNCUSER)@$(SECONDARY):$(TINYROOT)/ && \ $(SSH) $(SECONDARY) "cd $(TINYROOT) && make" data.cdb: data /usr/local/bin/tinydns-data
ここでは、data.cdb そのものを転送するのではなく、元テキストである data ファイルを転送して、相手先で make する形態を採った。「ssh をシェルに使った rsync」 と相まって、様々なメリットが得られる。
※ 相手が BIND など他の DNSサーバソフトウェアである時のために、 djbdns パッケージにはゾーントランスファサーバ axfrdns と、クライアント axfrdns-get が付属している。しかし筆者はまだ使う機会がなく未研究。
※ make は非常に面白いツールだ。プログラマにだけ使わせておくのはもったいない。上記の Makefile の内容が理解できない人は、損はないので `info make' してみるべし。 Co-op さんが日本語訳も公開している。
最後に挙げた公開鍵利用によるパスワードなしの転送を、なるべく安全に実現するひとつの方法。説明のため、プライマリDNS を動かしているマシンを dog、セカンダリDNSマシンを wolf とする。
root# useradd -u 803 dnssync root# passwd dnssync
dnssync$ mkdir .ssh && chmod 700 .ssh
dnssync@wolf$ cd .ssh dnssync@wolf$ cat id_rsa.pub >>authorized_keys dnssync@wolf$ chmod 644 authorized_keys dnssync@wolf$ rm id_rsa.pub
/var/tinydns/root/ (root:dnssync 2775) data (root:dnssync 0664)
root# chgrp dnssync /var/tinydns/root /var/tinydns/root/data root# chmod g+w /var/tinydns/root /var/tinydns/root/data
dnssync@dog$ cd /var/tinydns/root dnssync@dog$ touch data (実際にデータを編集したのなら要らない) dnssync@dog$ make
こうしておけば、プライマリでの make を dnssync ユーザで行っても root で行っても問題なし。なお且つ、上に紹介した Makefile なら、セカンダリへの転送と相手先での make は必ず dnssync ユーザ権限で行われる。
axfrdns は、セカンダリDNSサーバからのAXFRプロトコルによるDNSコンテンツの吸い上げ要求に答え、tinydns のゾーンデータを提供するデーモン。Systemd で管理する方法を示す。データを要求できる相手を限定するために、ucspi-tcp の tcpserver も必要だ。
tinydns と同一のユーザ/グループを使用する。
axfrdns-conf コマンドを使って運用ディレクトリ構造を作成する。 1番目と 2番目の引数は、実行ユーザと、ログユーザの名前。 3番目は運用ディレクトリの基底パス。4番目に、渡すデータを採ってくるために tinydns の運用ディレクトリを指定。最後の引数は axfrdns がリッスンする IPアドレス。実のところ、Systemd 管理の場合、ログユーザは使われないし、3番目の axfrdns 運用ディレクトリ以外は、あとで環境変数ファイルで渡すので、あくまでも便宜的なもの。こういった具合にスクリプト化しておくと使い回せて楽だ。ゾーントランスファーサーバーは TCP で待ち受ける。 dnscache とポートがぶつかるので、その場合はエイリアスインターフェース等を使って別のIPアドレスを用意しておく必要がある。
#!/bin/sh DNSROOT=/var/axfrdns TINYROOT=/var/tinydns LISTENIP=192.168.1.1 DNSUID=tinydns DNSLOGUID=dnslog DNSHARDLIMIT=500000 /usr/local/bin/axfrdns-conf $DNSUID $DNSLOGUID $DNSROOT $TINYROOT $LISTENIP #perl -pi -e 's%\s(\./main)$% s3000000 n10 $1%;' ${DNSROOT}/log/run exit 0
ゾーンデータを要求できる相手を限定する機能は、tcpserver プログラムに依存している。セカンダリDNSのIPが 1.2.3.4 だとすると、次のような /var/axfrdns/tcp テキストファイルを用意する。
# sample line: 1.2.3.4:allow,AXFR="heaven.af.mil/3.2.1.in-addr.arpa" 1.2.3.4:allow :deny
もし tinydns で複数のドメインやサブドメインを管理していてそのうち一部のみを同期許可したければ、1行目のコメントで示したように、アクション(allow)の後に AXFR変数をセットすればよい。複数指定の場合は"/"で連ねる。AXFR変数が設定されていない場合は全ドメインデータが対象となる。続いて、cdbファイルに変換してやる。axfrdns-conf で運用ディレクトリを配備した際に、Makefile が用意されるのだが、少し調整が必要だ。ルールの更新を root 権限で行う時(普通はそうだろう)、/usr/local/bin は root のPATH変数に載っていないディストリビューションが多いので、tcprules をフルパスに直してやる必要がある。diffで示すとこう。
--- Makefile.org 2019-09-06 17:20:52.383936823 +0900 +++ Makefile 2019-09-06 17:19:54.243620388 +0900 @@ -1,2 +1,2 @@ tcp.cdb: tcp - tcprules tcp.cdb tcp.tmp < tcp + /usr/local/bin/tcprules tcp.cdb tcp.tmp < tcp
そうしておけば今後は
root# cd /var/axfrdns root# make
で cdb が更新できる。
サービスユニットファイル /etc/systemd/system/axfrdns.service [root:root 644] を作成する。
[Unit] Description=A DNS zone-transfer server Requires=network.target After=network.target [Service] Restart=always EnvironmentFile=/var/axfrdns/axfrdns.conf PIDFile=/var/run/axfrdns.pid LimitDATA=500000 ExecStart=/usr/local/bin/tcpserver -vDRHl0 -x /var/axfrdns/tcp.cdb -- ${IP} 53 /usr/local/bin/axfrdns [Install] WantedBy=multi-user.target
LimitDATA パラメータは、ulimit -d つまり Data Segment Size。axfrdns-conf によって生成される run ファイル内の 'softlimit -dxxxxx' の代わりとなるもの。
EnvironmentFile パラメータで参照している axfrdns.conf ファイルは、axfrdns プログラムへ環境変数として渡すべき値をエクスポートするためのファイルで、新規に作成する。
ROOT=/var/tinydns/root UID=tinydns GID=dns IP=192.168.1.1
稼働させる。
root# systemctl daemon-reload root# systemctl start axfrdns.service root# systemctl enable axfrdns.service root# systemctl status axfrdns.service
ログは標準出力を経て journalに記録される。
root# journalctl -u axfrdns -xe
dig や、djbdns 付属の axfr-get コマンドを使って問い合わせをしてみる。その前に axfrdns への問い合わせ許可範囲を一時的に緩めてやらなければならない。tcp ファイルを例えばこのように書き換えて make。
# sample line: 1.2.3.4:allow,AXFR="heaven.af.mil/3.2.1.in-addr.arpa" 1.2.3.4:allow 192.168.1.:allow :deny
dig を使う;
hoge$ dig hoge.cxm @192.168.1.1 axfr hoge$ dig 1.192.168.in-addr.arpa @192.168.1.1 axfr
axfr-get を使う;
hoge$ cd /tmp hoge$ tcpclient 192.168.1.1 53 axfr-get hoge.cxm tinydata tinydata.tmp hoge$ tcpclient 192.168.1.1 53 axfr-get 1.168.192.in-addr.arpa tinyptr tinyptr.tmp
dig や nslookup の代替品として使ったり、 DNSサーバやキャッシュをデバグするツール群について、簡単に触れておく。
dnsip FQDN [FQDN ...] |
リカーシブ・クエリを行い、FQDN のアドレスを得る。環境変数 $DNSCACHEIP にセットすれば問い合わせ先の指定も可能。 FQDN は複数与えることもでき、その場合には 1行にひとつずつ出力される。 |
dnsip UDN [UDN ...] |
`wolf' のような部分ホスト名を (可能ならば) FQDN へ変換してからリカーシブ・クエリを行い、FQDN のアドレスを得る。問い合わせ先の指定は環境変数 $DNSCACHEIP によって可能。 UDN を複数与えると 1行ひとつずつ表示。 |
dnsname x.x.x.x [x.x.x.x ...] |
逆引きのリカーシブ・クエリを行い、x.x.x.x の FQDN を得る。問い合わせ先の指定は環境変数 $DNSCACHEIP によって可能。 x.x.x.x を複数与えると 1行ひとつずつ表示。 |
dnsfilter |
stdin から 1行ずつ読み取り、行頭に IPアドレスらしきものがあればリカーシブ・クエリを行い、元の IPアドレスの後に `=FQDN ' を挿入した形にして stdout へ出力する。問い合わせ先の指定は環境変数 $DNSCACHEIP によって可能。 |
dnsmx FQDN |
リカーシブ・クエリを行い、FQDN の MXレコードを得る。問い合わせ先の指定は環境変数 $DNSCACHEIP によって可能。 |
dnstxt FQDN |
リカーシブ・クエリを行い、FQDN の TXTレコードを得る。問い合わせ先の指定は環境変数 $DNSCACHEIP によって可能。 |
dnsq TYPE FQDN SERVER |
非リカーシブ・クエリを SERVER に行い、FQDN の TYPE レコードをデバグ様式で表示する。 |
dnsqr TYPE FQDN |
リカーシブ・クエリを行い、FQDN の TYPE レコードをデバグ様式で表示する。問い合わせ先の指定は環境変数 $DNSCACHEIP によって可能。 |
dnstrace TYPE FQDN ROOT [ROOT ...] |
FQDN の TYPE レコードに対して影響のある全ての DNSサーバを ROOT ルートサーバから辿り、デバグ様式で表示する。遅いサーバや、返答のないサーバ、委譲の異常なサーバ、異常な DNSパケットに行き当たれば、そのことも表示される。基本的には DNSキャッシュが通常行うイタレーティブ・クエリ (反復クエリ) のアルゴリズムを採るが、可能な限りのあらゆる経路を試すので、出力は膨大な量となり、トレースには少なくとも数分かかる。なるべく読みやすくするには、出力を dnstracesort へパイプ渡した上でファイルへリダイレクトするといいらしい。 |
axfr-get DOMAIN OUTFILE OUTFILE.TMP |
DOMAIN に関してゾーン転送プロトコルで受け取った入力を OUTFILE.TMP に書き出し、成功すればそれを OUTFILE にリネームする。書き出されるのは tinydns のデータ形式。処理はあくまでもディスクリプタからの入出力で、自分でネットワーク接続する機能はないので tcpclient と併用する。axfrdnsの動作テスト に例。 |