BINDのRRL実装のアルゴリズムについて注意点

この記事は古い9.9系当時のメモを引っ張り出してきたものなので注意

(追記・bind-9.10.2で確認。 lib/dns/rrl.c 660行目あたり。)

TL;DR

BINDのRRLはトークンバケットではない。設定レートは十分上げてslip 0は使わない。

アルゴリズムとドキュメントの問題

BIND9.9系に追加されたRRL (Response Rate Limiting)のマニュアルには、レート制限にトークンバケットを利用していると書かれている。

これは(少なくとも導入時点では)間違っていて、実際には負のバケツを導入したペナルティ付きのアルゴリズムになっている。 バケツに定期的にトークンが補充されるのは通常のトークンバケットと同じなのだが、 トークンが枯渇してもクエリが来るとドロップ(またはTC)されるだけでなく、バケツの値はゼロを超えて負の値になる。

トークンが枯渇した後、通常のトークンバケットでは次のトークンが投入された時点で次のリクエストが許可される。 ところが、BINDの実装ではドロップされたクエリまでカウントしてバケツを負にしてしまうため、クライアントが諦めるかトークンの補充レートが追いつかない限り、バケツが正にもどることがなく、RRLによるリミットがかかりっぱなしになってしまう。

大雑把に言うと、一度設定レートより多くのクエリを送ってRRLに引っかかってしまうと超過分はペナルティにカウントされ、超過クエリ数/設定レートの冷却期間クエリを止めないと通常の動作に戻らない。 バーストにペナルティを課すこの挙動はバーストを許容しかつ最低レートを保証するトークンバケットの性質とはほぼ正反対になっている。

TCPフォールバックしないリゾルバの問題

slipが1以上でTruncatedなレスポンスが返る場合、リゾルバはTCPでリトライするためRRL対象外となり問題ないはずだが、攻撃目的クエリの規模が大きい時などにslip 0を指定していたりすると、UDPのドロップ時にTCPでリトライしないリゾルバ実装では名前解決ができなくなる問題を引き起こす。

また、そもそもTCPフォールバックできないアホな実装やネットワークでは、UDPでリトライし続けるため長時間RRLが解除されず名前解決できない。

BINDのRRLをそのまま使い、かつ上手くTCPフォールバックできないクライアントを考慮するなら、設定レートは十分に上げておくべきと思われる。 もっとも、DNS Amp対策としてはRRLの設定レートを上げると効果が無くなってしまうのだけど。