一覧NW-13-1 · DNSの名前解決

DNSの問い合わせ・委任・キャッシュ

DNSの各欄から、次の問い合わせ先と回答の再利用条件を確認します。

説明

DNSの名前解決では、端末から問い合わせを受けたリゾルバーが、必要に応じて委任先をたどり回答を取得します。応答には最終的な答えだけでなく、担当するサーバーやそのIPが入る場合もあります。

www.example.testの問い合わせをバイト列で確認し、委任のNSとglue、最終回答のAをPCAPで追います。名前圧縮とTTLを解析して、回答の位置とキャッシュを再利用できる時間を確認します。

1問い合わせのバイト列

www.example.testのAレコードを問い合わせるDNSメッセージを表示します。UDP/IPヘッダーは含みません。

xxd -g 1 -c 16問い合わせの構造 query.binDNSメッセージ

先頭12バイトがDNSヘッダーです。ID0x1234、flags0x0100、質問数1、その後に長さ付きのラベルが並びます。03 www、07 example、04 test、00が名前の終端で、最後の0001・0001がQTYPE AとQCLASS INです。

2ヘッダーのIDと再帰要求

最初の12バイトを六つの16ビット値として復号します。

python3 -cDNSヘッダーを復号 'from pathlib import Path; import struct; q=Path("query.bin").read_bytes(); values=struct.unpack("!6H",q[:12]); print(values); print(hex(values[0]),bool(values[1]&0x100),values[2:])'ID・RD・各欄の個数

ID0x1234、RDが1、質問1、各応答欄0です。RDは再帰処理を望むフラグであり、この問い合わせが権威サーバーへ直接送られたことを表すものではありません。端末が再帰リゾルバーへ結果の取得を依頼する場面で使います。

3委任をたどる経路

delegation.csvは教材用のDNS階層です。再帰リゾルバーが必要な委任をまだ保持していない条件の流れを確認します。

cat delegation.csv問い合わせ先と次の委任先

ルートからtest、testからexample.testへ進み、最後に担当する権威からwwwのAを得ます。毎回必ずルートから問い合わせるわけではなく、使える委任や回答がキャッシュにあれば途中を省けます。

4委任応答のNSとglue

referral.pcapはexample.testへの委任応答です。

tcpdump -n -tt -vvv応答の三つの欄 -r referral.pcapNSとglueの記録

Answerは0、Authorityにexample.testのNS、Additionalにns.example.testのAがあります。glueは、委任先サーバー名を引くために同じ委任先へ行かなければならない循環を避ける手掛かりです。wwwのIPを答えている欄ではありません。

5再帰リゾルバーからの最終回答

answer.pcapには端末へ返すAレコードの回答があります。

tcpdump -n -tt -vvvID・フラグ・TTL・A -r answer.pcap端末への回答

ID4660は0x1234と同じで、www.example.testのAは192.0.2.80、TTL60秒です。QRは応答、RAは再帰機能の提供を示し、この回答ではAAは0です。リゾルバーが返す回答と、その名前の権威が直接返す回答を区別して読みます。

6名前圧縮の参照先

回答の名前はc00cという2バイトのポインターで、メッセージ先頭から12バイト目の質問名を参照します。

python3 -c圧縮ポインターを復号 'from pathlib import Path; q=Path("query.bin").read_bytes(); a=Path("answer.bin").read_bytes(); pointer=int.from_bytes(a[len(q):len(q)+2],"big"); print(hex(pointer),pointer>>14,pointer&0x3fff,a[pointer&0x3fff])'参照の種別と12バイト目

先頭2ビットが3、残り14ビットが12です。12バイト目はwwwの長さ3で、同じ名前の文字列を繰り返さず参照しています。参照先はIPアドレスの位置ではなく、DNSメッセージ内の名前の位置です。

7TTLの残りと再問い合わせ

取得時刻を0秒、TTL60秒とし、経過20秒と61秒で再利用できるかを計算します。プリフェッチや期限切れ回答の利用は扱わないモデルです。

python3 -c有効期限と再利用の条件 'expires=60; print([(now,max(0,expires-now),"cache" if now<expires else "query authority") for now in (20,61)])'残り秒数と問い合わせ先

20秒後は残り40秒でキャッシュを使い、61秒後にはAの回答を更新する必要があります。その時点でも委任のキャッシュが有効なら、ルートから全部やり直す必要はありません。TTLはIPパケットのホップ制限とは別の値です。

まとめ

Question・Answer・Authority・Additionalには異なる役割の情報が入ります。

委任と回答のキャッシュには期限があり、使える情報によって問い合わせの開始点が変わります。

操作履歴と解説を振り返れます。

シェル / PCAPと通信の記録

1
xxd -g 1 -c 16 query.bin
00000000: 12 34 01 00 00 01 00 00 00 00 00 00 03 77 77 77  .4...........www
00000010: 07 65 78 61 6d 70 6c 65 04 74 65 73 74 00 00 01  .example.test...
00000020: 00 01                                            ..
2
python3 -c 'from pathlib import Path; import struct; q=Path("query.bin").read_bytes(); values=struct.unpack("!6H",q[:12]); print(values); print(hex(values[0]),bool(values[1]&0x100),values[2:])'
(4660, 256, 1, 0, 0, 0)
0x1234 True (1, 0, 0, 0)
3
cat delegation.csv
STEP,ASKED,ANSWER,NEXT
1,root-for-lab,test NS,192.0.2.52
2,test authority,example.test NS and glue,192.0.2.54
3,example.test authority,www.example.test A,192.0.2.80
4
tcpdump -n -tt -vvv -r referral.pcap
reading from file referral.pcap, link-type EN10MB (Ethernet)
0.000000 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto UDP (17), length 134)
    192.0.2.52.53 > 192.0.2.53.53000: [udp sum ok] 4660- q: A? www.example.test. 0/1/1 ns: example.test. [5m] NS ns.example.test. ar: ns.example.test. [5m] A 192.0.2.54 (106)
5
tcpdump -n -tt -vvv -r answer.pcap
reading from file answer.pcap, link-type EN10MB (Ethernet)
0.000000 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto UDP (17), length 78)
    192.0.2.53.53 > 192.0.2.10.53000: [udp sum ok] 4660 q: A? www.example.test. 1/0/0 www.example.test. [1m] A 192.0.2.80 (50)
6
python3 -c 'from pathlib import Path; q=Path("query.bin").read_bytes(); a=Path("answer.bin").read_bytes(); pointer=int.from_bytes(a[len(q):len(q)+2],"big"); print(hex(pointer),pointer>>14,pointer&0x3fff,a[pointer&0x3fff])'
0xc00c 3 12 3
7
python3 -c 'expires=60; print([(now,max(0,expires-now),"cache" if now<expires else "query authority") for now in (20,61)])'
[(20, 40, 'cache'), (61, 0, 'query authority')]

演習完了