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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ dig . NS

; <<>> DiG 9.8.3-P1 <<>> . NS
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22440
;; flags: qr rd ra; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 13
:
;; ANSWER SECTION:
. 80275 IN NS k.root-servers.net.
. 80275 IN NS l.root-servers.net.
. 80275 IN NS m.root-servers.net.
. 80275 IN NS a.root-servers.net.
. 80275 IN NS b.root-servers.net.
. 80275 IN NS c.root-servers.net.
. 80275 IN NS d.root-servers.net.
. 80275 IN NS e.root-servers.net.
. 80275 IN NS f.root-servers.net.
. 80275 IN NS g.root-servers.net.
. 80275 IN NS h.root-servers.net.
. 80275 IN NS i.root-servers.net.
. 80275 IN NS j.root-servers.net.

;; ADDITIONAL SECTION:
:
;; MSG SIZE rcvd: 496

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
2
3
4
5
6
7
$ dig @192.36.144.107 se. any
;; Truncated, retrying in TCP mode.
:
;; Query time: 406 msec
;; SERVER: 192.36.144.107#53(192.36.144.107)
;; WHEN: Sat Feb 20 23:30:44 2016
;; MSG SIZE rcvd: 2052

dig を実行すると始めに Truncated, retrying in TCP mode. の出力があり、TCPフォールバックが実行されたことがわかる。その際の tcpdump。

  1. 問い合わせ時のパケット。

    No.1: UDP で問い合わせを行った後、No.6: TCPで再帰問い合わせてしている。

  2. UDP の応答。

    No2: No1のレスポンス

    TCビットが立てられ、応答の数(Answer RRs)は切り詰められて 11 となっている。

  3. TCP の応答。

    No9: No6のレスポンス

    TCPフォールバック時の応答の数は UDP時の 11 から増えて 19

【検証2】EDNS0

dig では、+edns=0 オプションをつけると EDNS0 が利用される。(+busize=+dnssec をつけた際も EDNS0 が利用される)

1
2
3
4
5
6
7
8
9
$ dig @192.36.144.107 se. any +edns=0
:
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
:
;; Query time: 312 msec
;; SERVER: 192.36.144.107#53(192.36.144.107)
;; WHEN: Mon Feb 22 20:49:14 2016
;; MSG SIZE rcvd: 2063
  1. 問い合わせ時の一連のパケット。

    UDP だけで処理が完結している。

  2. UDP の問い合わせ

    No.15: UDP による問い合わせ

    addtional section に OPTレコードがある

  3. UDP の応答

    No.20: UDP による応答

    TCPフォールバックせずに UDP で全ての応答が返ってきている

[補足] DNS のメッセージフォーマット

DNS のメッセージフォーマットは、5つのセクションに分かれており、Header のみが必須。要求と応答ともに同じフォーマットしようされる。

1
2
3
4
5
6
7
8
9
10
11
12
13
Format

+---------------------+
| Header |
+---------------------+
| Question | the question for the name server
+---------------------+
| Answer | RRs answering the question
+---------------------+
| Authority | RRs pointing toward an authority
+---------------------+
| Additional | RRs holding additional information
+---------------------+

Header は 6つに分かれていて、以下のような感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
The 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 |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

  1. ID(16)
  2. フラグ(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: 拒否
  3. 質問の数(16)

  4. 応答の数(16)
  5. オーソリティの数(16)
  6. 追加情報の数(16)
dns