IPv6 プロトコル

このページの目次

あるデバイスには fe80:: アドレスがあるがインターネットにアクセスできず、別のデバイスにはグローバルアドレスがあるがドメイン名が解決できない。これらは矛盾していない:アドレス、ネクストホップ、DNS、パスMTUは異なるメカニズムから来る。IPv6を理解するには、これらの状態を一つずつ結びつける必要があり、「自動設定が成功した」ことを「すべてのネットワーク機能が同時に準備できた」と誤解してはならない。

確認すべき問題核心的な判断基準
パケットは本当にどれくらい長いのか40バイトの基本ヘッダにPayload Length、拡張ヘッダもペイロード長に含まれる
TCP/UDPヘッダはどこにあるのかNext Header チェーンをたどって解析し、常にオフセット40から読むわけではない
アドレスがあるのになぜインターネットに接続できないのかアドレス状態、ルーティング、ネイバー、DNS、PMTUをそれぞれ確認する
大きなパケットは誰が分割するのか送信元;中間ルータは Packet Too Big をフィードバックする
SLAACではMACアドレスを使う必要があるか必須ではない。安定した不透明IIDと一時アドレスにはそれぞれの用途がある
IPv6は自動的に安全なのかアドレス空間、IPsecの能力、実際のアクセス制御はそれぞれ判断する

アドレス:表記、スコープ、インターフェースをまず区別する

IPv6アドレスは128ビットで、通常8つの16進数グループとして表記される。各グループの先頭のゼロは省略可能で、連続するゼロのグループは一度だけ :: で圧縮できる。ただし、:: は1回しか出現できず、そうでなければ各所で何個のグループが省略されたか特定できない。例えば 2001:0db8:0001:0000:0000:0000:0000:000a と 2001:db8:1::a は同じアドレスを表す。

アドレス/プレフィックス意味これに基づいて推論できないこと
::未指定アドレス。特定の初期化パケットの送信元アドレスとして使用可能通常の到達可能なホストアドレスとして使用できない
::1ループバック他のデバイスに転送できない
fe80::/10リンクローカルユニキャスト範囲ルータを越えてアクセスできない。一般的なアドレス構成では64ビットIIDを使用
ff00::/8マルチキャスト。スコープ情報を含むすべてのマルチキャストが現在のリンクに限定されるわけではない
グローバルユニキャストアドレスリンクを越えたユニキャスト通信のためのアドレスを提供アドレスがあっても、ルーティング公開、ファイアウォールの許可、サービスリスニングが保証されるわけではない

IPv6にはブロードキャストアドレスがなく、ネイバーディスカバリなどは対応するマルチキャストグループを使用する。アナキャストはユニキャストアドレス空間から割り当てられ、同じアドレスが複数の場所でサービスを提供でき、ルーティングによって1つが選択される。「最近」はルーティング上の意味であり、地理的に最近であることを保証するものではない。RFC 4291: アドレスアーキテクチャ

fe80::1%eth0 における %eth0 は、ローカルでスコープインターフェースを指定するために使用される識別子であり、オンラインの128ビットアドレスの一部ではない。2つのネットワークインターフェースが属するリンクの両方に fe80::1 が存在する可能性があるため、アドレスだけを与えると不十分である。通常のイーサネットSLAACでは、/64プレフィックスと64ビットIIDの組み合わせが使用される。これはIPv6ルーティングテーブルが/64のみを許可する意味ではなく、集約ルートとホストルートにはそれぞれ異なる長さがある。

基本ヘッダ:固定されているのは入口であり、すべてのヘッダの合計長ではない

フィールドビット数読み取り方法
Version4値は6
Traffic Class8DSCPとECNの分類、輻輳セマンティクス
Flow Label20ソース/宛先などと一緒にフローを識別するのを補助
Payload Length16基本ヘッダに続くバイト数
Next Header8次の拡張ヘッダまたは上位プロトコルの番号
Hop Limit8ルータ転送ごとにデクリメントされ、尽きると破棄
Source / Destination各128ソースアドレスと宛先アドレス
基本ヘッダは固定40B;それ以降の長さは実際のヘッダチェーンによって決定される IPv6 基本ヘッダ:40 B Next Header = 0 Hop-by-Hop:8 B Next Header = 17 UDP:8 B + データ 本例ではUDPデータ100 B Payload Length = 8 + 8 + 100 = 116 B IPv6パケット全体 = 40 + 116 = 156 B Next Header は直後に続くタイプを示し;Payload Length は拡張ヘッダを含むが、40Bの基本ヘッダは含まない。

通常の非jumbogramパケットのPayload Lengthの最大値は65535であり、全体のパケットにはさらに40Bの基本ヘッダが加わる。IPv4のTotal Lengthの意味をそのまま当てはめてはならない。Jumbogramには追加のルールがあり、長さフィールドがゼロだからといって一律に「任意の大きなパケット」や「パーサの検査免除スイッチ」として扱うべきではない。

IPv6には基本ヘッダのチェックサムやIHL(Internet Header Length)がない。オプションは拡張ヘッダで表現される。TCP/UDPなどは独自のチェックルールを持っており、その中で擬似ヘッダにIPv6アドレスなどの情報が含まれるが、チェックサムは暗号学的認証を提供するものではない。IPsecをサポートしていても、すべてのIPv6パケットが暗号化または認証されている意味ではない。RFC 8200: 基本ヘッダおよび上位インターフェース

Flow Label で何ができるか

送信元は同じフローに対して一貫したゼロ以外のラベルを使用することで、ネットワークがトランスポートポートを深く読み取らずともフロー分類や負荷分散を行うのに役立つ。ゼロラベルにも定義がある。20ビットの空間では衝突の可能性があり、デバイス通常はソース/宛先などのフィールドと組み合わせて使用し、ラベルをネットワーク全体で一意な接続IDとして使ってはならない。

ラベルは自動的に帯域幅を予約したり優先度を上げたりするものではない。リソーススケジューリングにはネットワークポリシーが必要である。アプリケーション、カーネルバージョン、設定はすべてラベル生成に影響を与えるため、あるLinuxのデフォルト動作をプロトコルの要件として一般化してはならない。RFC 6437: Flow Label

拡張ヘッダ:タイプごとにデコードしなければならない

基本ヘッダの Next Header=6 は直接TCPに続くことができる。0の場合、まず Hop-by-Hop Options に入り、その後そのヘッダの Next Header によって続ける。上記の図ではUDPは48バイト目から始まるが、パーサが固定で40バイト目からポートを読み取ると、拡張ヘッダのフィールドをポートとして読んでしまう。

ヘッダタイプ番号長さ解析のポイント
Hop-by-Hop / Destination Options0 / 60総長は (Hdr Ext Len+1)×8
Routing43共通ヘッダには長さとルーティングタイプが含まれ、具体的な内容はタイプに応じて解釈
Fragment44固定8Bで、一般的な Hdr Ext Len フィールドはない
AH51独自の Payload Len ルールがあり、単位は4B
ESP50独立したカプセル化形式があり、Next Header はその尾部構造にある
No Next Header59他のプロトコルヘッダは続かない

したがって、すべての拡張ヘッダに対して同じ「2バイト目を読み、8を掛けて、スキップする」というループを実行してはならない。安全な解析には、実際の残存長、許可されたヘッダチェーン、リソース上限をチェックし、解釈できない暗号化ペイロードに遭遇してもポートを推測して続けてはならない。

RFC 8200 は推奨される並べ替え順序を示している。Hop-by-Hop が存在する場合、基本ヘッダに直後に続く必要がある。異なるヘッダの処理ノードは異なり、通常の転送ノードはすべての終点オプションを解釈する必要はない。拡張ヘッダのサポート状況は実際のパスの到達性に影響を与えるため、エンドポイントと中間デバイスを別々に検証する必要があり、単にバイト列がフォーマットに適合していることを証明するだけでは不十分である。RFC 8200: 拡張ヘッダ

UDP はなぜ常に40バイト目ではないのか?

チェーンをタイプ駆動のカーソル移動として考える。Options の2バイト目は長さエンコーディングだが、Fragment では同じ位置にこの公式を適用できない。完全な最初のフラグメントの例を選択した後でも、UDP を読む前に Fragment Header を差し引く必要がある。境界付近で切り捨てられた場合、「まだバイトを取得していない」状態と「すでに次のプロトコルを読んだ」状態を区別する必要がある。

図を準備しています
UDP はなぜ常に40バイト目ではないのか?

Next Header が長さの読み取り方法を決定する。Options と Fragment は長さの公式を共有できない。完全なチェーンの前に、固定オフセットでポートを推測するとオブジェクトを誤って読む。

断片化の計算例:DFビットはなく、送信元でのみ断片化

転送出口が現在のIPv6パケットを収容できない場合、ルータはIPv6断片化を行わず、破棄して対応するICMPv6 Packet Too Big(Type 2、Code 0)を生成する。これを単に「DFは常に1」と簡略化すると、フィールド解析を誤解させる。IPv6にはDFビット自体がない。送信元はPMTUDによって送信サイズを調整するか、必要に応じてFragment Headerを生成する。RFC 8201: IPv6 PMTUD

以下は独立した教育用の計算例である:元のパケット3000B。基本ヘッダ40B、断片化可能な部分2960B、他の拡張ヘッダなし。送信元はPMTU=1280を既知とする。各フラグメントにはさらに8Bのフラグメントヘッダが必要で、最後のフラグメント以外のデータ容量は floor((1280−40−8)/8)×8=1232 B である。

送信元断片化:3000B 元パケット、パスMTUは1280B 元パケット:40 B 基本ヘッダ + 2960 B 断片化可能部分 本例では他の拡張ヘッダなし。各フラグメントに8BのFragment Headerが追加 1232 B データ offset=0、M=1 1232 B データ offset=154、M=1 496 B データ offset=308、M=0 全体1280 B 全体1280 B 全体544 B オフセットの単位は8B。各フラグメントには40Bの基本ヘッダと8Bのフラグメントヘッダがあり、ルータは送信元のこのステップを代行しない。
フラグメントデータ範囲OffsetMPayload Length全体長
10~12310112401280
21232~2463154112401280
32464~29593080504544

最後のフラグメントの 308×8+496=2960 は、元の断片化可能部分の長さを示す。再構築は前の範囲がすべて揃うまで待たなければならず、M=0 だけを見てはならない。重複するフラグメントはIPv6のルールに従って全体が再構築時に破棄される。各フラグメントに必ず付随させる必要がある他のヘッダがある場合、容量を再計算して差し引く必要があり、1232を永久に適用してはならない。最初のフラグメントには完全なIPv6ヘッダチェーンと上位ヘッダを含めなければならず、後続のフラグメントに無秩序に切り分けてはならない。RFC 8200: 断片化と再構築

1280BはIPv6の最小リンクMTU要件であり、より小さな物理伝送ユニットにはリンク下層の適応が必要である。これはすべてのパスの実際の最大値ではない。必要なICMPv6をフィルタリングすると、サイズのフィードバックが破壊される。TCP MSSはUDPアプリケーションのすべてのパケット分割問題を解決するものではない。

自動設定:4つの情報がそれぞれ構築される

まず利用可能なリンクローカルアドレスを形成し、ホストはRSによってRAを要求するか、ルータからのアクティブなマルチキャストRAを受信できる。RAの宛先が常にユニキャストとは限らない。以下で最も混同しやすい情報を区別する。RFC 4861: ルーター発見

情報主要なソース寿命または条件
デフォルトルーターRA Router Lifetime など寿命がゼロだからといって、RA内のすべてのプレフィックスが無効になるわけではない
リンク上のプレフィックスPIOのLフラグアドレスの生成とは別に判断
SLAACアドレスPIOのAフラグ、適切なプレフィックスおよびローカルIIDpreferred/valid lifetime に制約される
DNSサーバーRA RDNSS、DHCPv6、または静的設定「アドレスがある」ことから自動的に推論されるものではない

例えば A=1、L=0 の適切なPIOを受信した場合、ホストはアドレスを形成できるが、このPIOだけに基づいてリンク上のすべての宛先が直接ネイバー解決できるとは限らない。Mフラグはステートフルアドレス設定を示し、Oは他の設定を示す。OはDNS専用に指定されたものではなく、Mも「DNSを別途取得する」意味ではない。対応するクライアントを有効にするかどうかは、ホストの実装と管理設定に依存する。

アドレスは通常tentative(暫定)状態にあり、DAD(重複アドレス検出)を通じてのみ対応するルールに従って使用できる。Preferred Lifetime が期限切れになるとアドレスは deprecated(非推奨)となり、新しい通信には通常選ばれないが、Valid Lifetime が期限切れになるまで使用意義がある。有効期限が切れると、有効なアドレスとしては使用されなくなる。これにより、プレフィックスの更新時に接続を段階的に移行でき、すべての古いアドレスを即座に切断する必要がない。RFC 4862: SLAAC

RAのRDNSSオプションは再帰DNSサーバーを直接提供できるため、「SLAACではDNSのためにDHCPv6を実行する必要がある」というのは成立しない。逆に、アドレス設定結果を1つだけ受信したからといって、デフォルトルートとDNSがすでに存在していることを証明するものではない。RFC 8106: RA DNSオプション

安定したアドレスと一時アドレスはそれぞれ何を解決するか

IIDはMACアドレスを埋め込む必要はない。安定したアドレスはアドレスの持続性に有利である。一時アドレスはローテーションによって時間を超えた関連性を低減する。一時アドレスにはpreferred/valid lifetime、代替アドレスの事前生成などの制約があり、具体的な寿命を一律に24時間と固定してはならず、ローテーション後に既存の接続がすべて即座にソースアドレスを変更すると考えるべきではない。

実際のソースアドレスの選択は、宛先スコープ、アドレス状態、ポリシー、アプリケーションバインドの影響を受ける。一時アドレスはアプリケーションアカウント、DNSクエリ、またはその他の追跡信号を隠すものではなく、着信ファイアウォールが無意味になるものでもない。RFC 8981: 一時アドレス

IPv4との共存:カプセル化と翻訳で異なるものが変わる

メカニズムデータパス説明に適した問題
デュアルスタックホストとネットワークが2つのプロトコルを提供特定のアドレスファミリの障害が別のファミリによってどのように隠されるか
DS-LiteB4はIPv4をIPv6にカプセル化し、AFTRがカプセル化を解除してNAT44を担当アクセスネットワークがIPv6を使用して共有IPv4エグジットをどのように運ぶか
NAT64 + DNS64対応するAAAAを合成し、IPv6トラフィックは翻訳器でIPv4に変換IPv6クライアントがIPv4のみのサービスにどのようにアクセスするか
464XLATCLATはステートレス翻訳を行い、PLATはステートフルNAT64を行うIPv4アプリケーションがIPv6アクセスネットワークをどのように通過するか

DS-Liteはトンネル内のIPv6セマンティクスを保持する。NAT64はネットワーク層のアドレスファミリを変更し、これらは同じ「IPv6ヘッダを被せる」ものではない。NAT64の場合、DNS64は名前によるアクセスを支援するが、アプリケーションがIPv4のリテラルをハードコードしている場合、このAAAA合成を経由しない。464XLATは対応するIPv4アプリケーションに接続を提供できる。DS-Lite RFC 6333、NAT64 RFC 6146、464XLAT RFC 6877

デュアルスタックアプリケーションはHappy Eyeballsを使用して候補接続を配置し、特定のアドレスファミリの失敗による待機を低減できる。これは解決、ソート、遅延起動の競合に関与しており、すべてのリクエストが同時に2回完全に送信されるわけではない。成功したフォールバックはIPv6の障害を隠す可能性があり、診断時には両方のファミリのパスと接続結果を別々に見る必要がある。RFC 8305

ローカル再計算:長さ、フィールド、データ範囲

import struct
from ipaddress import IPv6Address

src = IPv6Address('2001:db8:1::a')
dst = IPv6Address('2001:db8:2::b')
assert src.exploded == '2001:0db8:0001:0000:0000:0000:0000:000a'
traffic_class, flow_label = 0xBA, 0x12345
word = (6<<28) | (traffic_class<<20) | flow_label
header = struct.pack('!IHBB16s16s', word, 116, 0, 64, src.packed, dst.packed)
assert len(header) == 40
assert word>>28 == 6 and (word>>20)&255 == 0xBA
assert word&0xfffff == flow_label
assert 40 + struct.unpack('!H', header[4:6])[0] == 156

payload = bytes(i%251 for i in range(2960))
capacity = ((1280-40-8)//8)*8
pieces = []
for start in range(0,len(payload),capacity):
    data = payload[start:start+capacity]
    more = int(start+len(data)<len(payload))
    offset = start//8
    frag_header = struct.pack('!BBHI', 17, 0, (offset<<3)|more, 123456)
    assert len(frag_header) == 8
    pieces.append((offset,more,8+len(data),48+len(data),data))
assert [x[:4] for x in pieces] == [(0,1,1240,1280),(154,1,1240,1280),(308,0,504,544)]
assert b''.join(x[4] for x in pieces) == payload
assert pieces[-1][0]*8 + len(pieces[-1][4]) == 2960
print('IPv6 フィールド、長さ、断片化オフセットと範囲のチェックが通過しました')

例は局所的なエンコーディングと計算関係を検証する。ランダムなペイロードは完全なUDPパケットではない。ネットワークへのデータ送信やインターフェース設定の変更は行わない。

継続的な参照