DNSキャッシュサーバのServe-Staleについて調べてみた。
2024-03-29
azblob://2024/03/29/eyecatch/2024-03-29-dns-cache-serve-stale-000_1.png

こんにちは、
DNS全くわからない富田です。

前回の記事では、DNSキャッシュサーバ上のキャッシュはTTLが切れると更新されることと、そのTTLは権威サーバで設定できることを書きました。
当然このキャッシュの更新は、キャッシュサーバが権威サーバに問い合わせることで行われるわけですが、
問い合わせる頃に、権威サーバが存在していなかったらどうなるでしょうか?

普通に考えると、TTLが切れた後にキャッシュが無くなるだけで終わりそうですが、そうとも限らないことがわかりました。
つまり、キャッシュサーバがTTL切れのキャッシュを提供し続ける場合があるようです。
今回は、このキャッシュサーバの機能であるServe-Staleについて調べてみました。

Serve-Staleとは?

前述の通り、Serve-Staleとは、DNSキャッシュサーバがTTL切れのキャッシュを一定期間提供し続ける機能です。
なぜそんな機能が存在しているのでしょうか?

それは、DDoS攻撃で権威サーバが応答できなくなった時に備えるためだったようです。
詳しくはBINDを開発・サポートしているISCさんのブログに書いてあります。
ざっくり説明すると2016年秋にDDoS攻撃で権威サーバが応答できなくなり、有名なサービス(Amazon, Netflix, Twitterなど)が何時間もの間使えなくなるという事件があったそうで、
その際にTTLが切れていてもキャッシュサーバが応答できればとりあえずこれらのサービスは使えるとして、このServe-Staleの機能が必要になったそうです。

では、具体的にどういう風に機能するのでしょうか?
ブログによると、TTLが切れてから一定期間内のキャッシュを、権威サーバから返事が得られなかった場合に代わりに返すという感じらしいです。
この一定期間はBINDではmax-stale-ttlと呼ばれており、キャッシュサーバ側で設定する値です。

具体的に書くと下図のタイムラインようなイメージです。

キャッシュが更新されてからTTLが切れるまでの間はキャッシュを返す。TTLが切れて、権威サーバからの返事もない場合はmax-stale-ttlが過ぎていなければキャッシュを返す。maxl-stale-ttlが切れた後も権威サーバから返事が無ければキャッシュを返さない。

左端が最後にキャッシュが更新されたタイミングです。
それからTTLが切れるまでの間に問い合わせがあると、キャッシュサーバはそのキャッシュを返します。
TTLが切れた後に問い合わせを受けると、キャッシュサーバは権威サーバへの問い合わせを試みますが、それに失敗すると、max-stale-ttlが経過していなければTTL切れのキャッシュを返し、max-stale-ttlが経過していればキャッシュを返しません。

このように、一定期間はTTL切れのキャッシュを返し続けるそうです。

実際に使ってみた

どんな感じなのかを見てみたいので、以前の記事で使った環境を再利用して見てみました。
(詳しい構成は前の記事を見てください。)

まず、bind_cache/named.confを以下のように書き換えます。

options {
 directory "/var/cache/bind";
 listen-on    { any; };
 listen-on-v6 { any; };
 forwarders {
   192.168.11.11;
 };
 
 recursion yes;
 dnssec-validation no;
 
 stale-answer-enable yes;
 stale-cache-enable yes;
 max-stale-ttl 60;
 resolver-query-timeout 5;
 stale-refresh-time 0;
};

差分としては、stale-answer-enableの行以下が増えました。
まず、stale-answer-enableをyesにすることで、このserve-staleの機能を有効にしています。
同様の理由でstale-cache-enableも有効にします。
その次でmax-stale-ttlを60秒に設定しています。つまり、TTLが切れた後も60秒間はキャッシュが有効になっています。
resolver-query-timeoutは権威サーバから何秒返事が来なかったら諦めるかという値です。今回は5秒で諦めます。
最後のstale-refresh-timeは、serve-staleを高速化するためのオプションですが、今回は無効にしています。

では、やってみましょう。
まず、環境を立ち上げます。

docker-compose up -d

続いて、observerからbind_cache(DNSキャッシュサーバでアドレスは192.168.11.12)に向けて問い合わせを定期的に送ります。

watch -n 1 docker-compose exec observer watch -n 1 dig example.com @192.168.11.12

こんな感じになります。

キャッシュサーバから返答あり。ANSWER SECTIONの内容は"example.com. 40 IN A 192.168.10.100"

前回は一部しか表示していませんでしたが、全文はこのようになります。
中央下あたりのANSWER SECTIONと書かれているところが問い合わせに対する答えです。
以前の記事で設定したのと同じ192.168.10.100が返ってきているのが分かります。

そして、権威サーバを止めてみます。

docker-compose stop bind_auth

止まりました。右にもう一つ窓を開いて権威サーバに問い合わせると、確かに返ってこないことが分かります。

キャッシュサーバ(左のウィンドウ)は変わらず返答あり。ANSWER SECTIONの内容は"example.com. 28 IN A 192.168.10.100"。一方権威サーバ(右ウィンドウ)からの返答は無し。

TTLが切れるまで待つと、TTLが30で固定になり、まだ192.168.10.100が表示されています。

キャッシュサーバからの返答あり。ANSWER SECTIONの内容は"example.com. 30 IN A 192.168.10.100" また、OPT PSEUDOSECTIONに"EDE: 3 (Stale Answer): (resolver failure)"という行が追加される。

更に、ANSWER SECTIONの2つ上、OPT PSEUDOSECTIONを見てみると、

EDE: 3 (Stale Answer): (resolver failure)

と書かれた行があります。
どうやら、Serve-Staleの機能によって返ってきたようですね!

更にしばらくすると、ANSWER SECTIONが無くなりました。

キャッシュサーバからの返答あり。ANSWER SECTIONが無い。

つまり、時間経過で返って来なくなるようです。

まとめると、権威サーバが止まってもキャッシュサーバ側でServe-Staleが有効になっていると一定時間はキャッシュを返し続けることが確認できました。

まとめ

権威サーバが返答できなくなった場合に、TTL切れのキャッシュを返すServe-Staleという機能があることを調査し、その機能について実際に確認してみました。
実際に権威サーバが障害でダウンした場合には頼もしいですね。

一方で、意図的に権威サーバを落としたい場合はどうすればいいんでしょうか?
サービスを終了したい場合など、もうそのドメインに対してDNSで解決できないようにしたい場合もあるんじゃないでしょうか?
そういう場合に期限切れのキャッシュを返されても困りますよね?

すぐに思いついた解決策は、「AレコードやAAAAレコードを落としてTTLが過ぎてキャッシュサーバに反映されるまで待つ」という方法ですが、
全てのキャッシュサーバがTTLが切れた後にすぐに読みに来てくれるとは限りません。
読みに来てくれなかったとしてもmax-stale-ttlが切れるのを待てばよさそうですが、
max-stale-ttlはキャッシュサーバ側で設定する値なのでどれぐらい待てばいいかわかりません。
なかなか難しいですね。
何かいい方法が見つかったら書いてみようと思います。

参考記事

Serve-Stale Update - ISC