権威サーバは変わったのにキャッシュは古い答えを返す
目標
Pythonで権威DNSサーバーとキャッシュサーバーを立ち上げ、digでTTL・CNAME・否定応答を読んだうえで、「ゾーンは変えたのに、キャッシュは古い答えを返す」を自分で再現します。
なぜ重要なのか
DNS障害の大半は、「誰が何をどのくらいの間覚えているのか」の問題です。/etc/hostsはDNSより先に答え、キャッシュは権威サーバーが与えたTTLの分だけ古い答えを返し、存在しない名前でさえ、SOAが定めた時間だけ覚えられます。このラボは、その3つの層をそれぞれ手で触ってみます。用意されているサーバー/opt/app/minidns.pyは、標準ライブラリで作った小さなDNSで、権威モードは問い合わせのたびにゾーンファイルを読み直し、キャッシュモードはTTLを減らしながら答えます。
ステップ
- ゾーンファイル(
/root/dns/zone.txt)を作成してください。lab.internal.のSOA(最後のフィールドであるminimumは60)、www.lab.internal.のA(10.0.0.10、TTL120)、api.lab.internal.のCNAME(www.lab.internal.を指す)が必要です。権威サーバーをpython3 /opt/app/minidns.py auth --zone /root/dns/zone.txt --port 5300で起動し(ログの出力先:/root/dns/auth.log)、dig @127.0.0.1 -p 5300 www.lab.internal A +noall +answerの結果を保存してください(保存先:/root/dns/01-auth.txt)。 /etc/hostsに10.0.0.99 www.lab.internalを追加し、getent hosts www.lab.internalの結果と/etc/nsswitch.confのhosts:行を保存してください(保存先:/root/dns/02-hosts.txt)。- キャッシュサーバーを
python3 /opt/app/minidns.py cache --upstream 127.0.0.1:5300 --port 5301で起動し、www.lab.internalをキャッシュ(5301)に数秒間隔で2回問い合わせて、2つのanswerをどちらも保存してください(保存先:/root/dns/03-ttl.txt)。 - キャッシュに
api.lab.internalのAを問い合わせ、answerを保存してください(保存先:/root/dns/04-cname.txt)。 - 存在しない名前
nope.lab.internalを、キャッシュに+comments +authorityで数秒間隔で2回問い合わせて保存し(保存先:/root/dns/05-nx.txt)、否定応答がキャッシュに残る秒数を数字1つで書いてください(保存先:/root/dns/05-negttl.txt)。 - キャッシュに
wwwを1回問い合わせて埋めたあと、ゾーンファイルのwww.lab.internal.を10.0.0.11に変え、SOAのserialを上げてください。そして120秒以内に権威サーバー(5300)とキャッシュ(5301)に順に問い合わせ、2つのanswerを保存してください(保存先:/root/dns/06-stale.txt)。 - キャッシュサーバーを終了して再び起動したあと、
wwwのanswerを保存してください(保存先:/root/dns/07-after.txt)。キャッシュを空にできなかったとしたら、古い答えが最大で何秒まで残りうるかを、数字1つで書いてください(保存先:/root/dns/07-window.txt)。 dig @127.0.0.1 -p 5300 +search +domain=lab.internal +ndots=5 api.lab Aで短い名前を問い合わせたあと、権威サーバーのログ(/root/dns/auth.log)からapi.labが入ったQUERY行を取り出して保存してください(保存先:/root/dns/08-search.txt)。- まとめを書いてください(保存先:
/root/dns/09-report.md)。/etc/hostsがなぜ先なのか、移行時の最悪の伝播時間(秒)、ネガティブキャッシュの時間(秒)、ndotsが作る追加の問い合わせを含める必要があります。
参考
digの+noall +answerはanswerセクションだけを、+comments +authorityはステータス行とauthorityセクションを表示します。- 権威サーバーはゾーンファイルを問い合わせのたびに読み直します。直したあとに再起動する必要はありません。キャッシュサーバーは、再起動すると空になります。
- ステップ6の「キャッシュは古い答え」は、キャッシュを埋めたあと、TTL(120秒)以内に観察しないと見えません。時間が過ぎていたら、キャッシュを埋め直してもう一度やってください。
- よくある間違い: キャッシュ(5301)ではなく権威サーバー(5300)に2回問い合わせると、TTLは減りません。権威サーバーは、常にゾーンのTTLをそのまま返します。
ゾーンファイルと権威サーバー
/root/dns/zone.txtを作成し、権威サーバーを5300で起動したあと、www.lab.internalのanswerを/root/dns/01-auth.txtに保存してください。
ゾーンファイルは、1行が이름 TTL 타입 값の形です(プレースホルダーは名前とタイプと値です)。名前は、末尾にドットを付けた絶対名で書いてください。SOAの値は주서버 관리자 serial refresh retry expire minimumの7項目で(プレースホルダーはプライマリサーバーと管理者です)、最後のminimumが否定応答のキャッシュ時間です。サーバーはnohup ... > /root/dns/auth.log 2>&1 &でバックグラウンドに置いてください。
/etc/hostsが先
/etc/hostsに10.0.0.99 www.lab.internalを入れ、getent hosts www.lab.internalの結果とnsswitchのhosts:行を/root/dns/02-hosts.txtに保存してください。
getentは、プログラムと同じ経路(nsswitch)で名前を解決します。hosts:行でfilesがdnsの前にあれば、/etc/hostsが勝ちます。grep '^hosts:' /etc/nsswitch.confで、その行だけを取り出せます。
キャッシュはTTLを減らしながら答える
キャッシュサーバーを5301で起動し、www.lab.internalを数秒間隔で2回問い合わせて、2つのanswerを/root/dns/03-ttl.txtに保存してください。
キャッシュモードはminidns.py cache --upstream 127.0.0.1:5300 --port 5301です。最初の問い合わせは上流に問い合わせて保存し、2回目からは残りのTTLを減らして返します。2回目の結果は>>で追記してください。
CNAMEチェーンは1つの答えで返ってくる
キャッシュにapi.lab.internalのAを問い合わせ、answerを/root/dns/04-cname.txtに保存してください。
CNAMEに出会ったリゾルバーは、チェーンの末尾のAまでたどり、2つのレコードを1つのanswerに入れます。1行しか見えないなら、タイプをCNAMEで問い合わせています。Aで問い合わせてください。
存在しない名前も覚えられる
nope.lab.internalを、キャッシュに+comments +authorityで数秒間隔で2回問い合わせて/root/dns/05-nx.txtに保存し、否定応答がキャッシュに残る秒数を/root/dns/05-negttl.txtに書いてください。
NXDOMAINの答えにはanswerがなく、authorityセクションにSOAが来ます。そのSOAのTTLが、この否定応答のキャッシュ時間で、RFC 2308のとおり、SOAのminimumとSOA自体のTTLのうち小さいほうの値です。2回問い合わせて、そのTTLが減っていくところまで残してください。
ゾーンは変わったのに、キャッシュは古い答え
キャッシュにwwwを問い合わせて埋めたあと、ゾーンのwww.lab.internal.を10.0.0.11に変え、serialを上げてください。120秒以内に権威サーバーとキャッシュに順に問い合わせ、2つのanswerを/root/dns/06-stale.txtに保存してください。
権威サーバーはファイルをすぐに読むので新しいアドレスを返し、キャッシュは保存しておいた古い答えを、TTLが尽きるまで返します。順序が重要です。キャッシュを先に埋め、直し、すぐに2か所に問い合わせてください。serialはsedで1桁だけ上げれば十分です。
キャッシュを空にできるとき、できないとき
キャッシュサーバーを終了して再び起動したあと、wwwのanswerを/root/dns/07-after.txtに、キャッシュを空にできないときに古い答えが残りうる最大の秒数を/root/dns/07-window.txtに書いてください。
このキャッシュはメモリにだけ保存するので、プロセスを終了すると空になります(pkill -f 'minidns.py cache')。他人のキャッシュ(顧客のISP)は空にできず、そのとき待つ必要がある時間は、変更前のレコードのTTLです。
ndotsが作る追加の問い合わせ
+search +domain=lab.internal +ndots=5 api.labで問い合わせたあと、権威サーバーのログからapi.labが入ったQUERY行を/root/dns/08-search.txtに保存してください。
ドットがndotsより少ない名前には、検索ドメインが先に付きます。そのため、ログにはapi.lab.lab.internal.の問い合わせが先に、失敗したあとにapi.lab.の問い合わせが出ます。grep 'QUERY api\.lab' /root/dns/auth.logで取り出してください。
まとめのレポート
/root/dns/09-report.mdに、/etc/hostsの優先順位、最悪の伝播時間(秒)、ネガティブキャッシュの時間(秒)、ndotsが作る追加の問い合わせをまとめてください。
このラボで自分で測った数字を書いてください。伝播時間はステップ7、ネガティブキャッシュはステップ5の値です。ndotsは、ステップ8のログで見た問い合わせの順序をそのまま書けば十分です。