前回は、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 チェインへ追加している。
tinydnsとdnscacheを使ってLAN内DNSサーバーを作る
に教えていただいたのですが、なんだかスマートじゃないなぁと思い、対抗して記事を書いてみる。
tinydnsとdnscacheをたしかに1つのIPアドレスで動作させることができれば都合がいいのだが、仙石氏のようにパッチ当て等で対応するのもなんとも微妙というか、そんな感じが…
Comment by blog 6.0くらい — 2006年11月4日 @ 20:05