パンダのメモ帳

技術系のネタをゆるゆると

CentOS 5.4でDNSサーバーの構築

CentOS 5.4にBINDを導入し、DNSサーバーとして動作させる。

1. 要件と仮定

今回の要件は以下の通り。

  • 保有している独自ドメイン用のゾーンサーバーとして動作させる。
  • 同時に、内部ネットワーク用のキャッシュサーバーとしても動作させる。

便宜上、設定等について以下の通り仮定する。

  • 固定IPネットワーク 123.45.67.0/29 を取得済みであるとする。
  • 内部ネットワークは 192.168.0.0/24 であるとする。
  • 保有する独自ドメインは example.com であるとする。

2. インストール

yumからインストールを行う。セキュリティを考慮してchroot環境で動作させたいので、bind-chroot も同時に導入しておく。

[root@localhost ~]# yum -y install bind bind-chroot

2009年12月24現在、yumでインストールした場合のバージョンは 9.3.6 だった。以降の手順の確認は本バージョンで行っており、以前・以降のバージョンでは異なる場合があるので注意してほしい。

3. 設定

まず、bindの設定ファイルである named.conf を作成する。bind-chrootを導入した場合のパスは /var/named/chroot/etc/named.conf だ。設定方法が複雑なため、細かくコメントを入れているが、実際のファイルにコメントを記述する必要はない。
BIND9から実装された view ステートメントにより、リクエスト元のアドレスや受信インターフェースのアドレスなどにより、namedの挙動や返答内容を変えることができる。
view 定義は先頭から順に評価され、最初に全条件にマッチした view が使用される。ここではLAN向けの view と WAN向けの view を定義し、LAN向けには再帰問い合わせを許可するようにしている。また、LANへはローカルIP、WANへはグローバルIPを返すよう、ゾーンファイルをLAN用とWAN用で分けている。

# ネットワーク定義。ローカルネットワークを定義している。
acl "lan" {
	127.0.0.1;
	192.168.0.0/24;
};
# ネットワーク定義。ゾーン転送を許可するホストを定義している。
# 有効なスレーブサーバーのIPを全て指定すればOK。
acl "slaves" {
	192.168.0.3;
};
# デフォルトオプション設定
options {
	# ゾーン情報が更新された場合にスレーブサーバーへ通知する。
	notify	  		yes;
	# バージョン情報を非公開とする。
	version	 		"unknown";
	# ゾーン情報が格納されているディレクトリ。
	# chroot環境の場合、以下の設定で /var/named/chroot/var/named となる。
	directory		"/var/named";
	# ゾーン情報が更新された場合に通知するホストのリスト。
	also-notify		{ slaves; };
	# ゾーン情報のコピー(ゾーン転送)を許可するホストのリスト。
	allow-transfer		{ slaves; };
};
# ログ設定
logging {
	# 通常のログチャンネル
	channel "log_default" {
		# ログファイルの指定とログの最大サイズ、保管世代数の指定
		file		"/var/log/default.log" versions 7 size 10m;
		# ログのレベルの指定
		severity	info;
		# ログの各行に日時を記録する
		print-time	yes;
		# ログの各行にカテゴリ名を記録する
		print-category	yes;
	};
	# セキュリティ関連ログチャンネル
	channel "log_security" {
		file		"/var/log/security.log" versions 7 size 10m;
		severity	info;
		print-time	yes;
		print-category	yes;
	};
	# 通常のログはログチャンネル log_default に記録。
	category "default"	{ log_default; };
	# セキュリティ/クライアント関連のログはログチャンネル log_security に記録。
	category "security"	{ log_security; };
	category "client"	{ log_security; };
};
# LAN用VIEW
view "internal" {
	# 条件:ローカルネットワーク内からのリクエストである事。
	match-clients		{ lan; };
	# 条件:ローカルIPへ送られてきたリクエストである事。
	match-destinations	{ lan; };
	
	# 再帰問い合わせに応答する。
	recursion		yes;
	
	# ルートヒントファイルを読み込む。
	include			"/etc/named.root.hints";
	
	# RFC1912に定義されているローカルエリア等のゾーン情報を読み込む。
	include			"/etc/named.rfc1912.zones";
	
	# LAN向け example.com 正引きゾーン定義
	zone "example.com" {
		type		master;
		file		"example.com.lan.zone";
	};
	
	# IPネットワーク 192.168.0.0/24 逆引きゾーン定義
	zone "0.168.192.in-addr.arpa" {
		type		master;
		file		"lan.rev";
	};
};
# WAN用VIEW
view "external" {
	# 条件:LAN用VIEWにマッチしなかった全ての接続が対象となる。
	match-clients		{ any; };
	match-destinations	{ any; };
	
	# 再帰問い合わせに応答しない。
	recursion		no;
	
	# WAN向け example.com 正引きゾーン定義
	zone "example.com" {
		type		master;
		file		"example.com.wan.zone";
	};
	
	# IPネットワーク 123.123.123.0/29 逆引きゾーン定義
	# ゾーン名はISPから指定された名前を設定すること。
	# また、ISPから逆引き権限が委譲されない場合、設定は不要。
	zone "sub-a.67.45.123.in-addr.arpa" {
		type		master;
		file		"wan.rev"
	}
};

次に /var/named/chroot/etc/named.root.hints を以下の様に作成する。

zone "." {
	type	hint;
	file	"named.root";
};

続いて /var/named/chroot/etc/named.rfc1912.zones を作成する。/usr/share/doc/bind-9.3.6/sample/etc/named.rfc1912.zones にあるサンプルをそのままコピーすればOK。

[root@localhost ~]# cd /usr/share/doc/bind-9.3.6/sample/etc
[root@localhost etc]# cp named.rfc1912.zones /var/named/chroot/etc/

/var/named/chroot/var/named 以下に必要なファイルをコピー(または作成)する。

[root@localhost ~]# cd /usr/share/doc/bind-9.3.6/sample/var/named
[root@localhost named]# cp local*.zone /var/named/chroot/var/named/
[root@localhost named]# cp named.* /var/named/chroot/var/named/
[root@localhost named]# chown root:named *.*
[root@localhost named]# chmod 644 *.*

ログファイルが存在しないとログの記録が開始されないため、あらかじめ用意しておく。

[root@localhost ~]# mkdir /var/named/chroot/var/log
[root@localhost ~]# touch /var/named/chroot/var/log/default.log
[root@localhost ~]# touch /var/named/chroot/var/log/secuirty.log
[root@localhost ~]# chgrp named /var/named/chroot/var/log/*.log
[root@localhost ~]# chmod 664 /var/named/chroot/var/log/*.log
[root@localhost ~]# ln -s /var/named/chroot/var/log /var/log/named

4. 正引きゾーンファイルの作成

いよいよ自ドメイン用の設定に入る。ドメインからIPを割り出すためのデータファイルは「ゾーンファイル」と呼ばれ、ドメインごとに作る必要がある。ゾーンファイルは「レコード」と呼ばれる情報を列挙する形で記述する。レコードの形式は次のようになっている。

ホスト[	TTL]	クラス	タイプ	データ[	データ[	データ]*]

「ホスト」はその名の通りホスト名を指定する。省略に関するルールが絡むので混乱しやすい。省略しない場合は「hostname.example.com.」の様に ホスト名.ドメイン名. の形となるが、ドメイン部は省略可能なので「hostname」とだけ書いても同じ意味になる。また、「@」とだけ書いた場合は「example.com.」と解釈される。また、レコードの先頭が空白文字で始まる=ホストを省略した場合、直前のレコードと同じホストが指定されたと見なされる。
TTL」はそのレコードがキャッシュされた場合の有効期限だが、レコード毎にTTLを変える事は稀であり、たいてい省略される。省略した場合、$TTL で設定した数値が指定されたと見なされる。
「クラス」は本来ネットワークの種別などを指定するために存在するが、現在ではインターネットを表す "IN" 以外の設定はまず行われない。省略しないこと。
「タイプ」はレコードの種別を表す。使用可能なタイプと簡単な説明は下記の通り。

タイプ 用途
SOA ゾーンファイルに必ず必要となるレコード。ゾーンに関する情報などを指定する。
NS ネームサーバーの指定。
MX メールサーバーの指定。
A ホスト名に対応するIPv4アドレスの指定。
AAAA ホスト名に対応するIPv6アドレスの指定。
CNAME ホストの別名(Canonical Name)の指定。
PTR PoinTeRレコード。主に逆引き設定に使用される。

「データ」はタイプ毎に必要なパラメータを指定する。必要なパラメータの内容や数はタイプによって異なる。データ部にホスト名を指定する場合、ホスト部と同様のルールで省略が可能。

各パラメータは任意の数の空白(半角スペースまたはTab文字)で区切られ、レコードとレコードの間は改行で区切られる。ただし、SOAレコードなどは1行に記述すると見づらいため、括弧でくくる事で複数行の記述も可能だ。

LAN向け正引きデータである /var/named/chroot/var/named/example.com.lan.zone を例に記述方法を解説する。

; デフォルトのTTL(キャッシュ有効期限)。以下の場合1日に設定。
$TTL 86400
; SOAレコード(詳細は後述)
@		IN	SOA	ns1	root (
				2009122401	; Serial
				3H		; Refresh
				1H		; Retry
				1W		; Expire
				1H		; Min-TTL
)
; NSレコード
; データ部にはネームサーバーのホスト名を指定する。
		IN	NS	ns1
		IN	NS	ns2
; MXレコード
; データ部には優先度とホスト名を指定する。
; 優先度が低いサーバーから使用される。
		IN	MX	10	mail
; Aレコード
; ホスト名に対応するIPアドレスを指定する。
		IN	A	192.168.0.2
mail		IN	A	192.168.0.2
router		IN	A	192.168.0.1
host01		IN	A	192.168.0.2
host02		IN	A	192.168.0.3
host03		IN	A	192.168.0.4
; CNAMEレコード
; ホスト名に対応するホスト名を指定する。
ns1		IN	CNAME	host01
ns2		IN	CNAME	host02
www		IN	CNAME	host01

SOAレコードに記述するデータは

  • NameServer
  • MailAddress
  • Serial
  • Refresh
  • Retry
  • Expire
  • Minimum

と7つもあるため、上記のように括弧を使って複数行に分けるのが通例。それぞれ次の通り設定する。

項目 設定内容
NameServer ns1 ドメインのプライマリネームサーバー、つまりBINDをセットアップ中のサーバーのホスト名を指定する。例の場合 "ns1.example.com" となる。
MailAddress root ゾーンファイルに記述されているドメインについての連絡先。例の場合 "root@example.com" となる。
Serial 2009122401 ゾーン情報の改訂番号。ゾーンファイルを更新した場合に以前より大きい数値を指定する必要がある。YYYYMMDD+2桁の連番とするのが通例。
Refresh 3H スレーブサーバーによる更新確認頻度の指定。例の場合3時間。
Retry 1H スレーブサーバーが更新確認に失敗した場合のリトライ間隔。例の場合1時間。
Expire 1W スレーブサーバーが更新を行わなかった場合のゾーン情報有効期限。指定期間以内に再び更新を行わなかったスレーブサーバーはクライアントからのリクエストに応答できなくなる。
Min-TTL 1D ネガティブキャッシュの有効期限。ネガティブキャッシュについては後述する。

ネガティブキャッシュについて説明するためには、まずDNSサーバーの役割について解説する必要がある。要件でもさらりと書いたが、DNSには大きく分けて2つの機能がある。

ゾーンサーバー
ドメインからIP、IPからドメインへの名前解決を実際に行うサーバー。ゾーン情報を保有する。
キャッシュサーバー
クライアントからの問い合わせに応じて外部のゾーンサーバーに問い合わせを行い、その結果をクライアントに返すと共に、一定期間結果をキャッシュする。

このキャッシュサーバーによる「キャッシュ」は正常な応答をキャッシュするのはもちろん、名前解決に失敗した事も記憶する。この失敗についての記憶が「ネガティブキャッシュ」だ。SOAレコードの「Min-TTL」はこのネガティブキャッシュの有効期限を指定するパラメータとなる。

ゾーンファイルを作成したら、named-checkzone コマンドで間違いがないか確認する。以下のように「OK」とでれば問題なし。

[root@localhost ~]# named-checkzone example.com /var/named/chroot/var/named/example.com.lan.zone 
zone example.com/IN: loaded serial 2009122401
OK

同様に、WAN向けのゾーンファイル example.com.wan.zone も作成し、チェックしておく。

5. 逆引きゾーンファイルの作成

次にLAN向け逆引きデータ /var/named/chroot/var/named/lan.rev を作成する。SOAレコードとNSレコードについては正引きの場合とほぼ一緒でOK。PTRレコードは同じIPについて1レコードしか記述できないので注意。

$TTL 86400
@		IN	SOA	ns1	 root (
			2009122401	; Serial
			10800		; Refresh
			3600		; Retry
			604800		; Expire
			86400		; Minimum
)
; NSレコード
		IN	NS	ns1.example.com.
		IN	NS	ns2.example.com.
; PTRレコード
; IPからドメイン名に変換する逆引き設定。
1		IN	PTR	router.example.com.
2		IN	PTR	host01.example.com.
3		IN	PTR	host02.example.com.
4		IN	PTR	host03.example.com.

こちらも named-checkzone コマンドで確認する。

[root@athena named]# named-checkzone 0.168.192.in-addr.arpa /var/named/chroot/var/named/lan.rev 
zone 0.168.192.in-addr.arpa/IN: loaded serial 2009122401
OK

同様に、WAN向け逆引きデータ wan.rev も作成し、チェックしておく。

6. namedの起動と確認

namedを起動し、次回から自動的に実行されるように設定する。

[root@localhost ~]# service named start
[root@localhost ~]# chkconfig named on

起動したら、dig コマンドで応答を確認する。

[root@localhost ~]# dig @127.0.0.1 www.example.com

; <<>> DiG 9.3.6-P1-RedHat-9.3.6-4.P1.el5_4.1 <<>> @127.0.0.1 www.example.com
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43734
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 2, ADDITIONAL: 0

;; QUESTION SECTION:
;www.example.com.               IN      A

;; ANSWER SECTION:
www.example.com.        86400   IN      CNAME   host01.example.com.
host01.example.com.     86400   IN      A       192.168.0.2

;; AUTHORITY SECTION:
example.com.            86400   IN      NS      ns2.example.com.
example.com.            86400   IN      NS      ns1.example.com.

;; Query time: 1 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Dec 24 18:38:41 2009
;; MSG SIZE  rcvd: 106

こんなカンジに返答が帰ってくればOK。