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と通信の記録
tcpdump -n -tt -vvv -r ra.pcapreading 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
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::
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
tcpdump -n -tt -vvv -r neighbor.pcapreading 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
tcpdump -n -tt -vvv -r dad-success.pcapreading 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
tcpdump -n -tt -vvv -r dad-conflict.pcapreading 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
cat dad-events.csvCASE,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
python3 -c 'print([(t,t<1800,t<3600,t<7200) for t in (2000,4000)])'[(2000, False, True, True), (4000, False, False, True)]
演習完了