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

IPv6のアドレス表記とヘッダー構造

128ビットの表記と、基本・拡張ヘッダーから通信内容を確認します。

説明

IPv6アドレスは128ビットで、16進数をコロンで区切って表します。文字列の省略規則と、パケット内の16バイトを対応させると、アドレスと通信内容を自分で確認できます。

省略表記とリンクの指定を確認した後、一つのEcho要求をtcpdumpとバイト列で解析します。基本ヘッダーから拡張ヘッダーをたどり、ICMPv6の開始位置を求めます。

1128ビットのアドレス表記

2001:db8:10::aを展開し、16進数1桁が4ビットであることを確かめます。

python3 -cアドレスを展開 'import ipaddress; a=ipaddress.IPv6Address("2001:db8:10::a"); print(a.exploded); print(a.packed.hex(),len(a.packed)); print(a.compressed)'省略形と16バイトの対応

8組×16ビットで128ビット、16バイトです。::は連続する0の組を省略し、一つのアドレスで一度だけ使えます。末尾aは10進数10に対応します。

2リンク内の宛先とインターフェース

同じfe80::1が別のリンクに存在する場合、送出インターフェースの指定が必要です。

python3 -cリンクの識別と宛先種別 'import ipaddress; a=ipaddress.IPv6Address("fe80::1%eth0"); b=ipaddress.IPv6Address("fe80::1%eth1"); print(a.scope_id,b.scope_id,a==b); print(ipaddress.IPv6Address("ff02::1").is_multicast)'zone IDとマルチキャスト

%eth0と%eth1はリンクを特定するzone IDで、IPv6ヘッダーに含める文字列ではありません。ff02::1はリンクローカルスコープの全ノードマルチキャストです。IPv6ではARPのブロードキャストの代わりに、近隣探索でマルチキャスト等を使います。

3一つのパケットを解析する

header.pcapにはDestination Optionsヘッダー付きのICMPv6 Echo要求が入っています。

tcpdump -n -tt -vvvIPv6と後続ヘッダー -r header.pcapEcho要求の記録

送信元2001:db8:10::aから宛先2001:db8:20::14への要求です。IPv6基本ヘッダー、宛先オプション、ICMPv6という順に解析されています。Hop Limitは64で、転送時に減る値です。

4基本ヘッダーの40バイト

ipv6.binはEthernetを除いたIPv6パケットです。

xxd -g 1 -c 16各バイトを表示 ipv6.binIPv6パケット

先頭4バイトにはversion・Traffic Class・Flow Labelが並びます。次の00 18はPayload Length24、3cはNext Header60、40はHop Limit64です。8バイト目から送信元16バイト、その後に宛先16バイトが続き、基本ヘッダーは40バイトです。

5ビット幅に沿って欄を取り出す

先頭32ビットを、4ビット・8ビット・20ビットへ分けます。

python3 -cビット幅ごとの復号 'from pathlib import Path; import struct; p=Path("ipv6.bin").read_bytes(); word,length,nxt,hop=struct.unpack("!IHBB",p[:8]); print(word>>28,(word>>20)&255,hex(word&0xfffff)); print(length,nxt,hop,len(p))'長さと各欄の値

versionは6、Traffic Classは42、Flow Labelは0x12345です。Payload Lengthの24は基本ヘッダーを含まない長さで、パケット全体は64バイトです。IPv4のTotal Lengthはヘッダーを含むため、数える範囲が異なります。IPv6基本ヘッダーにはチェックサム欄がありません。

6Next Headerで後続位置を求める

基本ヘッダーのNext Header60はDestination Optionsです。その先頭2バイトから次の種類と長さを求めます。

python3 -c後続ヘッダーをたどる 'from pathlib import Path; p=Path("ipv6.bin").read_bytes(); size=(p[41]+1)*8; offset=40+size; print(p[6],p[40],size,offset); print(p[offset],p[offset+1],p[offset+8:])'ヘッダー長とICMPv6の開始位置

宛先オプションは8バイトで、そのNext Header58がICMPv6を示します。ICMPv6は48バイト目から始まり、type128・code0のEcho要求とデータIPv6demoが見つかります。拡張ヘッダーがあると、基本ヘッダー直後をTCPやICMPとして読む方法は誤ります。

まとめ

IPv6の基本ヘッダーは40バイトで、Payload Lengthはその後の長さです。

Next Headerと各ヘッダーの長さから、後続プロトコルの開始位置を求めます。

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

シェル / PCAPと通信の記録

1
python3 -c 'import ipaddress; a=ipaddress.IPv6Address("2001:db8:10::a"); print(a.exploded); print(a.packed.hex(),len(a.packed)); print(a.compressed)'
2001:0db8:0010:0000:0000:0000:0000:000a
20010db800100000000000000000000a 16
2001:db8:10::a
2
python3 -c 'import ipaddress; a=ipaddress.IPv6Address("fe80::1%eth0"); b=ipaddress.IPv6Address("fe80::1%eth1"); print(a.scope_id,b.scope_id,a==b); print(ipaddress.IPv6Address("ff02::1").is_multicast)'
eth0 eth1 False
True
3
tcpdump -n -tt -vvv -r header.pcap
reading from file header.pcap, link-type EN10MB (Ethernet)
0.000000 IP6 (class 0x2a, flowlabel 0x12345, hlim 64, next-header unknown (60) payload length: 24) 2001:db8:10::a > 2001:db8:20::14: DSTOPT (padn) [icmp6 sum ok] ICMP6, echo request, id 4660, seq 1
4
xxd -g 1 -c 16 ipv6.bin
00000000: 62 a1 23 45 00 18 3c 40 20 01 0d b8 00 10 00 00  b.#E..<@ .......
00000010: 00 00 00 00 00 00 00 0a 20 01 0d b8 00 20 00 00  ........ .... ..
00000020: 00 00 00 00 00 00 00 14 3a 00 01 04 00 00 00 00  ........:.......
00000030: 80 00 80 64 12 34 00 01 49 50 76 36 64 65 6d 6f  ...d.4..IPv6demo
5
python3 -c 'from pathlib import Path; import struct; p=Path("ipv6.bin").read_bytes(); word,length,nxt,hop=struct.unpack("!IHBB",p[:8]); print(word>>28,(word>>20)&255,hex(word&0xfffff)); print(length,nxt,hop,len(p))'
6 42 0x12345
24 60 64 64
6
python3 -c 'from pathlib import Path; p=Path("ipv6.bin").read_bytes(); size=(p[41]+1)*8; offset=40+size; print(p[6],p[40],size,offset); print(p[offset],p[offset+1],p[offset+8:])'
60 58 8 48
128 0 b'IPv6demo'

演習完了