512バイトを超える DNSパケット
glibc の脆弱性 CVE-2015-7547 でも話題になった 512バイトを超える DNS パケットについてのメモ。
DNS では、TCP が使われたり、512 バイト超えるデータが扱われることは知っていたが、詳しい仕組みなど知らなかったので、備忘録のためにまとめておく。
そもそもなぜ 512 バイト?
調べてみると、
インターネットで使われている IP(IPv4)の仕様では 一度に受信可能なデータグラム(ヘッダーを含むパケッ ト)として、
576 バイトを保証しなければならないと定められています。この値は、64バイトのヘッダーと 512バイトの
データブロックを格納可能な大きさとして選択されたものです
refs: https://jprs.jp/related-info/guide/008.pdf
とのこと。
インターネットで使われている IP の仕様では、かならず「1パケットで 512バイトのデータを送れる」ことが保証されるので、DNS では通信コストをさげるため「1パケットで送受信可能なようにデータサイズを 512バイトに制限」したらしい。
(DNS の場合、IPヘッダ+UDPヘッダ+データで、540(20+8+512)バイト使われている)
ちなみに、root DNS が 13 個しかないのも、14 個だと 512バイトを超えてしまうから。
1 | $ dig . NS |
ANSWER: 13
MSG SIZE rcvd: 496
TCPフォールバック
初期の DNSプロトコルでは 512バイトを超えた応答を受け取るためには、TCP で再帰問い合わせ(TCPフォールバック)を
行う必要があった。
サーバは、UDP の問い合わせに対し応答が 512バイトを超える場合、応答するレコードを 512バイト以下に切り詰めた上で、切り詰められたこと示すビット(TC)を立て、UDP の応答を返す。
クライアント側は、その TCビットを確認すると再度、同じ問い合わせをTCPで行い、全てのレコードがつまった応答を受け取る。
(このあたりまでは、RFC 1034/1035/1123/5966 の話)
DNSキャッシュポイズニング対策
1990年台に問題提起された DNSキャッシュポイズニング対応として、DNSSECの標準化が開始された。
しかし、DNSSEC では鍵や署名をデータに乗せるため、512バイトに収まらず TCPフォールバックが発生することでレイテンシの問題が顕在化。
その対策として、UDP で 512バイト以上のデータを応答できる仕組みが検討され、1999年に RFC 26710 として EDNS0 が標準化された。この RFC では、DNSを DNSSEC や IPv6 に対応させる場合、EDNS0 への対応が必須となっている。
EDNS0 (Extension Mechanisms for DNS)
EDNS0 を使用可能なクライアントは、DNS要求の addtional セクションに OPTレコードを記載し、EDNS0 に対応していることをサーバにしめす。サーバ側が EDNS0 に対応していれば正常な応答が返り、対応していなければ、エラーになるか、無視されて TCPフォールバックすることになる。
動作検証
dig を使用して、TCPフォールバックと EDNS0 のパケットを確認してみる。
【検証1】TCPフォールバック
dig で 512バイトを超える応答を受ける場合、標準でTCPフォールバックが行われる。
1 | $ dig @192.36.144.107 se. any |
dig を実行すると始めに Truncated, retrying in TCP mode.
の出力があり、TCPフォールバックが実行されたことがわかる。その際の tcpdump。
問い合わせ時のパケット。
No.1
: UDP で問い合わせを行った後、No.6
: TCPで再帰問い合わせてしている。
UDP の応答。
No2
: No1のレスポンス
TCビットが立てられ、応答の数(Answer RRs)は切り詰められて11
となっている。TCP の応答。
No9
: No6のレスポンス
TCPフォールバック時の応答の数は UDP時の11
から増えて19
。
【検証2】EDNS0
dig では、+edns=0
オプションをつけると EDNS0 が利用される。(+busize=
や +dnssec
をつけた際も EDNS0 が利用される)
1 | $ dig @192.36.144.107 se. any +edns=0 |
問い合わせ時の一連のパケット。
UDP だけで処理が完結している。
UDP の問い合わせ
No.15
: UDP による問い合わせ
addtional section に OPTレコードがあるUDP の応答
No.20
: UDP による応答
TCPフォールバックせずに UDP で全ての応答が返ってきている
[補足] DNS のメッセージフォーマット
DNS のメッセージフォーマットは、5つのセクションに分かれており、Header
のみが必須。要求と応答ともに同じフォーマットしようされる。
1 | Format |
Header は 6つに分かれていて、以下のような感じ。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16The header contains the following fields:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ID |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode |AA|TC|RD|RA| Z | RCODE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QDCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ANCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NSCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ARCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
- ID(16)
フラグ(16)
QR
(1): 問い合わせ/応答- 0: 問い合わせ
- 1: 応答
OPcode
(4): オペレーションコード- 0: 問い合わせ
- 1: 逆問い合わせ
- 2: サーバ状態要求
AA
(1): オーソリティ応答- 0: 反復の結果の応答
- 1: そのネームサーバからの応答
TC
(1): 切り捨て- 0: データサイズ512バイト以下
- 1: 512バイト超
RD
(1): 再帰要望- 0: 再帰問い合わせをサーバに要求しない
- 1: 再帰問い合わせをサーバに要求
RA
(1): 再帰有効- 0: 再帰問い合わせ不可能
- 1: 再帰問い合わせ可能
Z
(3): (予約)- 0: 未使用。すべて 0
RCODE
(4): 戻りコード- 0: エラーなし
- 1: フォーマットエラー
- 2: サーバエラー
- 3: ドメインが存在しない
- 4: 未実装
- 5: 拒否
質問の数(16)
- 応答の数(16)
- オーソリティの数(16)
- 追加情報の数(16)