前回は、tinydns を IP アドレス指定せずに起動したいケースについて述べた。 前々回で述べたように、 djbdns はネームサーバ tinydns と、 キャッシュサーバ dnscache が完全に分離した独立のプログラムになっていることが 大きな特徴であるが、 tinydns を IP アドレス指定せずに起動しようとすると、 同じポート番号を使う dnscache を起動することができなくなってしまう。
そもそも論で言えば、 ネームサーバとキャッシュサーバは全く異なるサービスを行なうサーバなのだから、 同じポート番号を使うこと自体が間違っているのである。 もしキャッシュサーバが 53番以外のポートを使うのであれば、 tinydns も dnscache も IP アドレスを指定せずに同じマシンで起動できるし、 これ以上シンプルな解決策はないであろう。
ならば dnscache を 53番とは異なるポートで起動しよう。 ただし、dnscache は 53番ポートを bind(2) するように ハードコーディングされているので、 若干パッチをあてる必要がある。
--- dnscache.c.org Mon Feb 12 06:11:45 2001 +++ dnscache.c Sun Jul 9 06:43:23 2006 @@ -390,6 +390,7 @@ { char *x; unsigned long cachesize; + unsigned long port; x = env_get("IP"); if (!x) @@ -397,16 +398,19 @@ if (!ip4_scan(x,myipincoming)) strerr_die3x(111,FATAL,"unable to parse IP address ",x); + x = env_get("PORT"); + if (x) scan_ulong(x,&port); else port = 53; + udp53 = socket_udp(); if (udp53 == -1) strerr_die2sys(111,FATAL,"unable to create UDP socket: "); - if (socket_bind4_reuse(udp53,myipincoming,53) == -1) + if (socket_bind4_reuse(udp53,myipincoming,port) == -1) strerr_die2sys(111,FATAL,"unable to bind UDP socket: "); tcp53 = socket_tcp(); if (tcp53 == -1) strerr_die2sys(111,FATAL,"unable to create TCP socket: "); - if (socket_bind4_reuse(tcp53,myipincoming,53) == -1) + if (socket_bind4_reuse(tcp53,myipincoming,port) == -1) strerr_die2sys(111,FATAL,"unable to bind TCP socket: "); droproot(FATAL);
すなわち、環境変数「PORT」にポート番号を設定した場合は、 53番ポート番号の代わりに設定されたポート番号を使えるようにする。 例えば 2053番ポートで dnscache を起動する。 もちろんリゾルバは、こんな事情はお構い無しに 53番ポートへ問合わせるので、 それを 2053番ポートへ振り向ける (リダイレクトさせる) 必要がある。
Linux には、 パケット フィルタリング フレームワーク があって、 ネットワーク アドレス変換を自在に行なうことができる。 だから、53番ポートへのアクセスを 2053番ポートへリダイレクトする、 などということは iptables コマンドを実行するだけで手軽に実現できる。
例えば、dnscache と同じマシン上のリゾルバから 53番ポートへの接続を、 2053番ポートへリダイレクトするなら、
iptables -t nat -A OUTPUT -j REDIRECT -p udp --dport 53 --to-port 2053 \ -s 127.0.0.1 -d 127.0.0.1
などと iptables を実行するだけである。 「-p udp --dport 53」の部分で、 UDP 53番ポートへのアクセスが対象であることを指定し、 「--to-port 2053」の部分で、 リダイレクト先が 2053番ポートであることを指定している。
また、「-s 127.0.0.1 -d 127.0.0.1」の部分が、 接続元 (つまりリゾルバ) と接続先 (つまり dnscache) の IP アドレスの指定である。 接続元が LAN 内の他のマシンの場合も許可するなら、 この部分を例えば「-s 192.168.1.0/24 -d 192.168.1.1」に変更し、 「OUTPUT」の代わりに「PREROUTING」チェインを指定して iptables を実行すればよい (後述)。
さて、これだけだと実は問題がある。 dnscache は当然のことながら自ドメインのホスト名を解決するには 同じマシン上の tinydns へアクセスする必要があるのだが、 上記のように iptables を実行すると、 この dnscache から tinydns へのアクセスまでリダイレクトしてしまう。 すなわち dnscache が localhost の 53番ポートへ、 自ドメインに関する問合わせを行なうと、 それが 2053番ポートへリダイレクトされ、 結果 dnscache (つまり自分自身) に届いてしまう。 これでは自ドメインのホスト名の解決ができない。
したがって、dnscache からのアクセスの場合だけは、 リダイレクトが行なわれないようにしなければならない。 これを実現するには、 iptables のパラメータに「-m owner ! --uid-owner Gdnscache」 を追加すればよい。 これは、アクセス元のユーザが「Gdnscache」ならばリダイレクトを行なわない、 という指定であり、 Gdnscache は dnscache を実行しているユーザID である。
以上をまとめると、 次のような sh スクリプトになる。 これをマシンのブート時に実行すればよい。
#!/bin/sh PATH=/sbin:/usr/sbin:/bin:/usr/bin nsredirect="-t nat -j REDIRECT --dport 53 --to-port 2053" iptables -p udp $nsredirect -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 \ -m owner ! --uid-owner Gdnscache iptables -p tcp $nsredirect -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 if [ -n "$INNER_NETWORK" -a -n "$INNER_DNSCACHE" ]; then nsredirect="$nsredirect -A PREROUTING \ -s $INNER_NETWORK/$INNER_NETMASK -d $INNER_DNSCACHE" iptables -p udp $nsredirect iptables -p tcp $nsredirect fi
「$INNER_NETWORK」と「$INNER_NETMASK」に、 LAN のネットワークアドレス (例えば 192.168.1.0) とサブネットマスクを それぞれ指定する。 また、「$INNER_DNSCACHE」は キャッシュサーバ (つまりこのマシン) の IPアドレスである。
なお、Linux のパケット フィルタリングでは、
接続元が同一ホストの場合は OUTPUT チェインが使われるので、
「-s 127.0.0.1」を指定するときは「-A OUTPUT」を指定して
OUTPUT チェインへ追加し、
接続元が LAN 内の他のマシンの場合は PREROUTING チェインが使われるので、
「-s $INNER_NETWORK/$INNER_NETMASK」を指定するときは
「-A PREROUTING」を指定して PREROUTING チェインへ追加している。