DNS クライアントを作ってみよう (2)

前へ << DNS クライアントを作ってみよう (1) DNS クライアントを作ってみよう (3) >> 次へ

RFC を読んでみよう

さて DNS クライアントを作る前に、RFC を読んでみましょう。しかし、 RFC を「DNS」で検索 すると、77個もあります (2005/08/15 時点)。 また、「domain name」で検索 すると 43個です。重複分を除いても 90〜100 個はありそうな雰囲気です。

そこから Obsolete になっているものや、現在ではほとんど考慮しなくてもよくなったものを除いて 読むべきなのでしょうが、あいにくまともなリゾルバを作るつもりはありませんので、 当ページ管理者は RFC 1034RFC 1035RFC 1886 だけ読みました。

RFC を読むと照会と応答、いずれも以下のような構成になっているようです (「詳解 TCP/IP Vol.1 プロトコル」にならって、 ここでは「Query」を「照会」、「Response」を「応答」と表記することにします)。

1… …16bit17… …32bit
(1)識別子(2)フラグ
(3)質問数(4)回答 RR 数
(5)権威 RR 数(6)追加 RR 数
(7)質問レコード
(8)回答レコード
(9)権威レコード
(10)追加情報レコード

(1)識別子 (16bit)

任意の 16 ビットのデータ。クライアント側でセットし、サーバ側でそのまま返す。 これによりどの照会に対する応答なのかを識別できる。
(2)フラグ (16bit)
12345678910111213141516
QROPCODEAATCRDRAZRCODE

ビット フィールド 説明
第1bit QR (Query/Response) 0 なら照会、1 なら応答。
つまりクライアントは 0 を、サーバは 1 をセットする。
第2〜5bit OPCODE 0 なら標準照会 (standard query)
1 なら逆照会 (inverse query)
2 ならサーバステータス要求 (server status request)
第6bit AA (Authoritative Answer) 回答に対する権威を持つなら 1。応答時にセットされる。
第7bit TC (TrunCation) 回答が長過ぎて、複数の UDP データグラムに分割されたなら 1
第8bit RD (Recursion Desired) 再帰照会か、反復照会か。普通は 1 (再帰照会)
第9bit RA (Recursion Available) 再帰可能なら 1。応答時にセットされる。
第10〜12bit
3bit とも 0 固定 (将来用にリザーブ)
第13〜16bit RCODE (Response code) 応答結果。応答時にセットされる。
0:エラーなし 1:フォーマットエラー
2:サーバ側エラー 3:ネームエラー
4:未実装 5:拒否

(3)質問数、(4)回答 RR 数、(5)権威 RR 数、(6)追加 RR 数 (各16bit)

照会の場合、(大抵は) 質問数は 1、その他は 0。
応答の場合、(大抵は) 回答数は 1、その他は 0。

(7)質問 (可変長)

1… …16bit17… …32bit
(7.1)照会名 (可変長)
(7.2)照会タイプ (7.3)照会クラス

(7.1)照会名

照会名はラベルとデータが連なったもので、 ラベルはその後に続くデータの長さである。 最後のラベルは 0 である。

(7.2)照会タイプ

照会の際、どの種類の情報を求めているかを表す。主な照会タイプは以下の通り。
照会タイプ数値説明
A 1 ホストアドレス
NS 2 権威あるネームサーバ
CNAME 5 別名 (canonical name)
PTR 12 逆引き用ポインタ
MX 15 メールサーバ
AAAA 28 IPv6 ホストアドレス
ANY 255 任意のタイプ

(7.3)照会クラス

通常はインターネットを表す 1 を指定する。
照会に関する説明は以上です。

パケットキャプチャしてみよう

さて、なんのことだかさっぱりわからんでしょう (こんな説明でわかってもらっちゃ困る)。

そういうときは実際に流れるパケットの中身を見るのが一番です。 tcpdump を使ってもいいのですが、特にバイナリデータの場合は ethereal の GUI が向いています。

root になって ethereal を実行し、メニューから「Capture → Start」とします。

# ethereal
起動直後Capture
→ Start で
キャプチャ開始

するともう一枚ウィンドウが開き、どの種類のパケットがいくつ流れたかが表示されます。

% dig www.jp.freebsd.org
で名前解決を行ってください。 右図のように UDP が 2 パケット流れたことが確認できるはずです。 そしたら「Stop」を押してキャプチャをストップします。


キャプチャを止めると右図のように流れたデータを確認することができます。

画面最下部のダンプは Ethernet 上を流れたフレームデータです。

画面中央部をクリックすることで、対応する画面下部のダンプが反転表示されます。 上図では画面中央の「Domain Name System (query)」をクリックしたので、 画面下部のダンプ表示は DNS の query の部分が反転表示されています。

いろんな部分をポチポチっとクリックしてみると、 Ethernet フレームIP データグラムUDP データグラムDNS query から成り立っていることがわかります。

0000  00 10 38 0a 6b f0 00 90  99 16 bf 0a 08 00 45 00   ..8.k... ..?@..E.
0010  00 40 0f 06 00 00 40 11  ea 36 c0 a8 00 1f c0 a8   .@....@. .6As..As
0020  00 01 04 4e 00 35 00 2c  22 7c 00 02 01 00 00 01   ...N.5., "|......
0030  00 00 00 00 00 00 03 77  77 77 02 6a 70 07 66 72   .......w ww.jp.fr
0040  65 65 62 73 64 03 6f 72  67 00 00 01 00 01         eebsd.or g.....  
また、画面中央部にはデータの解析結果が表示されます。これは便利。
Domain Name System (query)
    Transaction ID: 0x0002 (識別子は 0x00 0x02)
  □Flags: 0x0100 (Standard query) (ここがフラグ)
      0... .... .... .... = Response: Message is a query (QR は 0、つまり照会)
      .000 0... .... .... = Opcode: Standard query (0) (OPCODE は標準照会)
      (第6bit の AA (Authoritative Answer) が省略されているが、照会時には 0 であることがわかる)
      .... ..0. .... .... = Truncated: Message is not truncated (TC は 0。このメッセージは分割されていない)
      .... ...1 .... .... = Recursion desired: Do query recursively (RD は 1、つまり再帰照会)
      (第9bit の RA (Recursion Available) が省略されているが、照会時には 0 であることがわかる)
      .... .... ...0 .... = Non-authenticated data OK: Non-authenticated data is unacceptable
                                     (権威のない回答はいらない)
                                     (って、ここ 0 固定のはずなんだけど、新しい RFC が出たのかな?)
    Questions: 1 (質問数は 1)
    Answer RRs: 0 (回答 RR 数はゼロ)
    Authority RRs: 0 (権威 RR 数はゼロ)
    Additional RRs: 0 (追加 RR 数はゼロ)
  □Queries
    □www.jp.freebsd.org: type A, class inet
        Name: www.jp.freebsd.org (ホスト名は www.jp.freebsd.org)
        Type: Host address (照会タイプはホストアドレス、つまり A レコードが欲しい)
        Class: inet (照会クラスは 1。つまりインターネット)
(7.1) 照会名 については、さきほど
照会名はラベルとデータが連なったもので、ラベルはその後に続くデータの長さである。最後のラベルは 0 である。
と説明しましたが、よくわかりませんね。実際に流れているデータを見てみましょう。 画面中央部の「Name: www.jp.freebsd.org」をクリックすると、反転表示されます。

よーく観察すると、ホスト名をドットで区切って、区切りごとの長さを置いていることがわかります。

3 w w w 2 j p 7 f r e e b s d 3 o r g 0
↑ 3バイト ↑ 2バイト ↑ 7バイト ↑ 3バイト 終端なので 0

X68000.startshop.co.jp ならば以下のようになります。

6 X 6 8 0 0 0 9 s t a r t s h o p 2 c o 2 j p 0
↑ 6バイト ↑ 9バイト ↑ 2バイト ↑ 2バイト 終端なので 0

照会のまとめ

先に説明したとおり照会と応答の構成 (照会と応答で共通) は下図左のようになっていますが、 照会の場合は下図右のようなデータが送られていたことがわかりました。

照会と応答の構成 (再掲)解析した照会の構成
1… …16bit17… …32bit
識別子フラグ
質問数回答 RR 数
権威 RR 数追加 RR 数
質問レコード
回答レコード
権威レコード
追加情報レコード
     
1… …16bit17… …32bit
識別子 (0x0002)フラグ
質問数 (1)回答 RR 数 (0)
権威 RR 数 (0)追加 RR 数 (0)
質問

応答編

Ethereal 次は応答を見てみましょう。

これまでは照会部分を表示するため画面上部ウィンドウの 「Standard Query」を見ていましたが、その下の「Standard Query Response」を選びます。 その状態が右の画面になります。

全体の構成を見ると、下図右のようになっていることがわかります。応答にも 質問が含まれていることに注意してください。

照会と応答の構成 (再掲)解析した応答の構成
1… …16bit17… …32bit
識別子フラグ
質問数回答 RR 数
権威 RR 数追加 RR 数
質問レコード
回答レコード
権威レコード
追加情報レコード
     
1… …16bit17… …32bit
識別子 (0x0002)フラグ
質問数 (1)回答 RR 数 (1)
権威 RR 数 (3)追加 RR 数 (4)
質問レコード
回答レコード
権威レコード 1/3
権威レコード 2/3
権威レコード 3/3
追加情報レコード 1/4
追加情報レコード 2/4
追加情報レコード 3/4
追加情報レコード 4/4

メッセージ圧縮

Ethereal Ethereal でいろんなところをクリックしていると、右図のような部分に気づくでしょう。 画面中央部には「jp.FreeBSD.org」となっているのに、画面下部ダンプ画面では 「C0 28」となっています。たった 2バイトで「jp.FreeBSD.org」が表せるはずがない。 何か変です。

実はこれはメッセージ圧縮と言われるものです。DNS はその性質上、 1回の問い合わせで同じドメイン名が何度も出てくることが多いです。

例えば以下のような質問を送ると、

質問: ドメイン名: www.jp.freebsd.org
質問: タイプ: 1 (A)
質問: クラス: 1 (INTERNET)
以下のような応答が返ってきます (応答にも質問レコードが含まれていることに注意)。
質問: ドメイン名: www.jp.freebsd.org
質問: タイプ: 1 (A)
質問: クラス: 1 (INTERNET)

回答: ドメイン名: www.jp.freebsd.org
回答: タイプ: 1 (A)
回答: クラス: 1 (INTERNET)
回答: 生存時間(TTL): 3600 (秒)
回答: リソースデータ長: 4 (バイト)
回答: リソースデータ: 203.139.121.132
下線部 (ひとつめふたつめ) が全く同じ (どちらも www.jp.freebsd.org) であることに注意してください。 こういう場合、DNS サーバは 2番目に現れた www.jp.freebsd.org を圧縮します。 ただ、圧縮と言っても、LZ77 とかブロックソートといったコムツカシイ圧縮ではありません。

さきほどの「C0 28」を 2進数に直すと「11000000 00101000」となります。 最上位 2ビットが「11」になっていますが、これが圧縮の印です。

なお、最上位 2ビットが「10」、「01」は将来のためにリザーブされています。
最上位 2ビットが「11」の場合、最上位 2ビットを 0 にした値、 「11000000 00101000」ならば「00000000 00101000」(10進で 40) がオフセットとなります。 この場合は、
「応答の先頭から数えて 40 バイト目から始まるドメインと同じ」
という意味です。「応答の先頭から数えて」というのは「識別子 から数えて」 ということです。識別子の先頭バイトは 0 バイト目と数えます。 圧縮というよりポインタ機能みたいなものですね。

メッセージ圧縮いろいろ

実際にどのような圧縮がされているのか見てみましょう。

Ethereal 右図 (1) の x68000.startshop.co.jp は先に説明した通り、

6x680009startshop2co2jp0
となっています。

(2) の x68000.startshop.co.jp は 0xC0 0x0C となっています。上位 2ビットを落とした「00 0C」がオフセット (10進数だと 12)、 応答の先頭は紫色の矢印の部分で、ここをゼロと数えると 12 バイト目は ちょうど (1) の部分の開始位置になります。

(3) の www2.startshop.co.jp の部分は、4www20xC0 0x13 となっています。このように最初は普通にドメイン名を示し、途中からポインタに変わるのもアリです。 上位 2ビットを落とした「00 13」がオフセット (10進数だと 19)、応答の先頭をゼロバイトとして 19バイト目は

9startshop2co2jp0
です。よって、
4www20xC0 0x13
4www29startshop2co2jp0
と等価なわけです。

(4) の www2.startshop.co.jp の部分は 0xC0 0x34、 上位 2ビットを落とした「00 34」がオフセット (10進数だと 52)、 応答の先頭をゼロバイトとして 52バイト目は 4www20xC0 0x13、 つまり (3) の位置ですね。ポインタで飛んだ先にポインタがあった場合、 さらにポインタをたどらないといけないわけです。

応答のフラグ

Ethereal 応答のフラグは、右図のようになっています。
  □Flags: 0x0100 (Standard query) (ここがフラグ)
      1... .... .... .... = Response: Message is a query (QR は 1、つまり応答)
      .000 0... .... .... = Opcode: Standard query (0) (OPCODE は標準照会)
      .... .0.. .... .... = Authoritative: Server is not an authority for doman
      .... ..0. .... .... = Truncated: Message is not truncated (TC は 0。このメッセージは分割されていない)
      .... ...1 .... .... = Recursion desired: Do query recursively (RD は 1、つまり再帰照会)
      .... .... 1... .... = Recursion available: Server can do recursive queries(RA は 1。つまり再帰照会可能)
      .... .... .... 0000 = Reply code: No error (0)(RCODE は 0。つまりエラーなし)

リソースレコード

回答レコード、権威レコード、追加情報レコードは、リソースレコードと言われる 共通の形式になっています。リソースデータは全て DNS サーバがセットします。

1… …16bit17… …32bit
ドメイン名 (可変長)
タイプクラス
生存時間 (TTL)
リソースデータ長リソースデータ (可変長)

  • ドメイン名は照会名と同じく 6x680009startshop2co2jp0 のような形式です。メッセージ圧縮もあります。
  • タイプは 照会タイプ と同じく、A や MX・NS・PTR・AAAA などのタイプが入る
  • クラスは 照会クラス と同じ
  • 生存時間 (TTL) は、この情報が有効な秒数
  • リソースデータ長は、その後に続くリソースデータの長さ (バイト数) が入る
  • リソースデータの内容はタイプによって異なる
実際のリソースレコードを見てみましょう。 右図はタイプ A なので、リソースデータには IP アドレスが入っています。 よって、リソースデータ長は 4 になります。

タイプ AAAA の場合は、リソースデータには IPv6 アドレスが入っています。 リソースデータ長は 16 です。

タイプ MX の場合は、リソースデータには優先度 (Preference) が 2バイト、 その後にメールサーバのドメイン名が入っています。 リソースデータ長は「優先度 2バイト+ドメイン名部分の長さ」です。

タイプ NS、タイプ PTR、タイプ CNAME の場合は、 いずれもリソースデータにはドメイン名が入っています。 リソースデータ長は「ドメイン名部分の長さ」です。 ドメイン名は圧縮されている場合があります。

タイプ TXT の場合は、リソースデータにはテキストデータがそのまま入っています。

前へ << DNS クライアントを作ってみよう (1) DNS クライアントを作ってみよう (3) >> 次へ

ご意見・ご指摘は Twitter: @68user までお願いします。