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バイトを超えてしまうから。

$ 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フォールバックが行われる。

$ 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 が利用される)

$ 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 のみが必須。要求と応答ともに同じフォーマットしようされる。

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つに分かれていて、以下のような感じ。

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)