一覧NW-07-2 · IPv6と近隣探索

IPv6の自動設定と重複アドレス検出

RAの値からアドレス候補を作り、使用開始までの状態を確認します。

説明

IPv6の端末は、ルーター広告からプレフィックスや既定ルーターを知ることができます。アドレスを自動生成する場合も、候補を作った時点で設定が完了するわけではありません。

RAのバイト列から期限とフラグを取り出し、アドレス候補を作ります。続いてDADのNS・NAと端末の状態を確認し、最後にアドレスと既定経路の期限が違うことを確かめます。

1ルーターが知らせる情報

RAはRouter Advertisementの略です。ルーターのリンクローカルアドレスから届く広告を読みます。

tcpdump -n -tt -vvvRAの欄とオプション -r ra.pcapルーター広告

送信元fe80::1、宛先ff02::1、Hop Limit255です。Router Lifetime1800秒と、2001:db8:10::/64のプレフィックス情報が含まれます。ルーターの有効期間と、アドレスの有効期間は別の欄です。

2RAとプレフィックス情報を復号

ra.binはICMPv6本体です。先頭16バイトがRAの固定部分で、その後のPrefix Informationを読みます。

python3 -cRAのバイト列を復号 'from pathlib import Path; import struct,ipaddress; b=Path("ra.bin").read_bytes(); print(b[0],struct.unpack("!H",b[6:8])[0]); print(b[18],hex(b[19]),struct.unpack("!II",b[20:28]),ipaddress.IPv6Address(b[32:48]))'広告された期限とプレフィックス

type134、Router Lifetime1800、プレフィックス長64です。flags0xc0はLとAが両方1で、オンリンク判定と自動アドレス設定に使う情報です。Valid Lifetime7200秒、Preferred Lifetime3600秒なので、既定経路の期限1800秒とは一致しません。

3プレフィックスと端末側の識別子

ここでは端末側で選んだ64ビットの識別子を0xaとします。RAがこの値まで配布したわけではありません。

python3 -cアドレス候補を組み立てる 'import ipaddress; prefix=ipaddress.IPv6Network("2001:db8:10::/64"); iid=0xa; address=ipaddress.IPv6Address(int(prefix.network_address)|iid); print(address,address in prefix)'上位64ビットと下位64ビット

2001:db8:10::aになります。SLAACでは広告されたプレフィックスと端末側で生成する識別子を組み合わせます。識別子を必ずMACから作るわけではありません。この段階では、まだ同じアドレスの使用者がいないかを確認します。

4次ホップのMACを問い合わせる

アドレスを使用できる端末が、次ホップfe80::1のMACを解決した別の記録です。

tcpdump -n -tt -vvv通常のNSとNA -r neighbor.pcap次ホップのMAC解決

NSの対象はfe80::1で、NAのリンク層アドレスオプションが末尾01のMACを知らせます。NAのrouter・solicited・overrideフラグも確認できます。この通常の解決と、次に調べる使用開始前のDADでは、NSの送信元とオプションが異なります。

5重複検査の宛先

DADでは候補アドレスの下位24ビットに対応するSolicited-Node multicastへNSを送ります。

tcpdump -n -tt -vvvDADの送信元と対象 -r dad-success.pcap重複検査の要求

送信元は未指定アドレス::、対象は2001:db8:10::a、宛先はff02::1:ff00:aです。候補アドレスをまだ送信元として使用せず、同じ候補への応答がないかを確認しています。通常のMAC解決と異なり、このNSに送信元リンク層アドレスオプションは付けません。

6候補が既に使われていた場合

今度は同じ候補に対するNAが返る記録を読みます。

tcpdump -n -tt -vvvNSとNAの対象を照合 -r dad-conflict.pcap重複が見つかった記録

Neighbor Advertisementが同じ対象アドレスを通知しています。DAD中にこの応答を受けた候補は利用を開始できません。DADは重複の検出であり、通知した相手が正当な所有者かを認証する処理ではありません。

7待機完了と重複検出の状態

パケット記録に加え、端末が残した状態遷移を確認します。この例は一度の検査送信後に1秒待つ設定です。

cat dad-events.csv端末側の検査結果

成功例は待機を終えてtentativeからpreferredへ進みます。失敗例はNA受信でdadfailedです。キャプチャに応答が写っていないだけでは、待機完了や取得漏れの有無まで分からないため、端末側の状態も確認します。

8アドレスと既定経路の期限

広告の再受信がない条件で、経過2000秒と4000秒の状態を計算します。

python3 -c各期限を別々に評価 'print([(t,t<1800,t<3600,t<7200) for t in (2000,4000)])'広告を更新しない場合の状態

各行は経過秒、既定ルーター有効、preferred、validです。2000秒ではルーター期限が切れてもアドレスはpreferred、4000秒ではdeprecatedですがvalidです。アドレスが残っていることだけでは、外部への既定経路があるとは判断できません。

まとめ

RAのL・Aフラグと各期限は、それぞれ異なる用途を持ちます。

DADの完了を端末状態でも確かめてから、候補の利用可否を判断します。

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

シェル / PCAPと通信の記録

1
tcpdump -n -tt -vvv -r ra.pcap
reading from file ra.pcap, link-type EN10MB (Ethernet)
0.000000 IP6 (class 0x2a, flowlabel 0x12345, hlim 255, next-header ICMPv6 (58) payload length: 56) fe80::1 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 56
	hop limit 64, Flags [none], pref medium, router lifetime 1800s, reachable time 0ms, retrans timer 0ms
	  prefix info option (3), length 32 (4): 2001:db8:10::/64, Flags [onlink, auto], valid time 7200s, pref. time 3600s
	    0x0000:  40c0 0000 1c20 0000 0e10 0000 0000 2001
	    0x0010:  0db8 0010 0000 0000 0000 0000 0000
	  source link-address option (1), length 8 (1): 02:00:00:00:00:01
	    0x0000:  0200 0000 0001
2
python3 -c 'from pathlib import Path; import struct,ipaddress; b=Path("ra.bin").read_bytes(); print(b[0],struct.unpack("!H",b[6:8])[0]); print(b[18],hex(b[19]),struct.unpack("!II",b[20:28]),ipaddress.IPv6Address(b[32:48]))'
134 1800
64 0xc0 (7200, 3600) 2001:db8:10::
3
python3 -c 'import ipaddress; prefix=ipaddress.IPv6Network("2001:db8:10::/64"); iid=0xa; address=ipaddress.IPv6Address(int(prefix.network_address)|iid); print(address,address in prefix)'
2001:db8:10::a True
4
tcpdump -n -tt -vvv -r neighbor.pcap
reading from file neighbor.pcap, link-type EN10MB (Ethernet)
0.000000 IP6 (class 0x2a, flowlabel 0x12345, hlim 255, next-header ICMPv6 (58) payload length: 32) 2001:db8:10::a > ff02::1:ff00:1: [icmp6 sum ok] ICMP6, neighbor solicitation, length 32, who has fe80::1
	  source link-address option (1), length 8 (1): 02:00:00:00:00:0a
	    0x0000:  0200 0000 000a
0.500000 IP6 (class 0x2a, flowlabel 0x12345, hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::1 > 2001:db8:10::a: [icmp6 sum ok] ICMP6, neighbor advertisement, length 32, tgt is fe80::1, Flags [router, solicited, override]
	  destination link-address option (2), length 8 (1): 02:00:00:00:00:01
	    0x0000:  0200 0000 0001
5
tcpdump -n -tt -vvv -r dad-success.pcap
reading from file dad-success.pcap, link-type EN10MB (Ethernet)
0.000000 IP6 (class 0x2a, flowlabel 0x12345, hlim 255, next-header ICMPv6 (58) payload length: 24) :: > ff02::1:ff00:a: [icmp6 sum ok] ICMP6, neighbor solicitation, length 24, who has 2001:db8:10::a
6
tcpdump -n -tt -vvv -r dad-conflict.pcap
reading from file dad-conflict.pcap, link-type EN10MB (Ethernet)
0.000000 IP6 (class 0x2a, flowlabel 0x12345, hlim 255, next-header ICMPv6 (58) payload length: 24) :: > ff02::1:ff00:a: [icmp6 sum ok] ICMP6, neighbor solicitation, length 24, who has 2001:db8:10::a
0.500000 IP6 (class 0x2a, flowlabel 0x12345, hlim 255, next-header ICMPv6 (58) payload length: 32) 2001:db8:10::a > ff02::1: [icmp6 sum ok] ICMP6, neighbor advertisement, length 32, tgt is 2001:db8:10::a, Flags [override]
	  destination link-address option (2), length 8 (1): 02:00:00:00:00:14
	    0x0000:  0200 0000 0014
7
cat dad-events.csv
CASE,TIME,EVENT,STATE
success,0,NS sent,tentative
success,1,waiting period completed,preferred
conflict,0,NS sent,tentative
conflict,0.5,NA received,dadfailed
8
python3 -c 'print([(t,t<1800,t<3600,t<7200) for t in (2000,4000)])'
[(2000, False, True, True), (4000, False, False, True)]

演習完了