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と通信の記録
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
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
tcpdump -n -tt -vvv -r header.pcapreading 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
xxd -g 1 -c 16 ipv6.bin00000000: 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
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
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'
演習完了