あるデバイスには 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のみを許可する意味ではなく、集約ルートとホストルートにはそれぞれ異なる長さがある。
基本ヘッダ:固定されているのは入口であり、すべてのヘッダの合計長ではない
フィールド ビット数 読み取り方法
Version 4 値は6
Traffic Class 8 DSCPとECNの分類、輻輳セマンティクス
Flow Label 20 ソース/宛先などと一緒にフローを識別するのを補助
Payload Length 16 基本ヘッダに続くバイト数
Next Header 8 次の拡張ヘッダまたは上位プロトコルの番号
Hop Limit 8 ルータ転送ごとにデクリメントされ、尽きると破棄
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 Options 0 / 60 総長は (Hdr Ext Len+1)×8
Routing 43 共通ヘッダには長さとルーティングタイプが含まれ、具体的な内容はタイプに応じて解釈
Fragment 44 固定8Bで、一般的な Hdr Ext Len フィールドはない
AH 51 独自の Payload Len ルールがあり、単位は4B
ESP 50 独立したカプセル化形式があり、Next Header はその尾部構造にある
No Next Header 59 他のプロトコルヘッダは続かない
したがって、すべての拡張ヘッダに対して同じ「2バイト目を読み、8を掛けて、スキップする」というループを実行してはならない。安全な解析には、実際の残存長、許可されたヘッダチェーン、リソース上限をチェックし、解釈できない暗号化ペイロードに遭遇してもポートを推測して続けてはならない。
RFC 8200 は推奨される並べ替え順序を示している。Hop-by-Hop が存在する場合、基本ヘッダに直後に続く必要がある。異なるヘッダの処理ノードは異なり、通常の転送ノードはすべての終点オプションを解釈する必要はない。拡張ヘッダのサポート状況は実際のパスの到達性に影響を与えるため、エンドポイントと中間デバイスを別々に検証する必要があり、単にバイト列がフォーマットに適合していることを証明するだけでは不十分である。RFC 8200: 拡張ヘッダ
UDP はなぜ常に40バイト目ではないのか?
チェーンをタイプ駆動のカーソル移動として考える。Options の2バイト目は長さエンコーディングだが、Fragment では同じ位置にこの公式を適用できない。完全な最初のフラグメントの例を選択した後でも、UDP を読む前に Fragment Header を差し引く必要がある。境界付近で切り捨てられた場合、「まだバイトを取得していない」状態と「すでに次のプロトコルを読んだ」状態を区別する必要がある。
図を準備しています 再読み込み
UDP はなぜ常に40バイト目ではないのか? Next Header が長さの読み取り方法を決定する。Options と Fragment は長さの公式を共有できない。完全なチェーンの前に、固定オフセットでポートを推測するとオブジェクトを誤って読む。
{"id":"ipv6-header-walk","title":"UDP はなぜ常に40バイト目ではないのか?","summary":"Next Header が長さの読み取り方法を決定する。Options と Fragment は長さの公式を共有できない。完全なチェーンの前に、固定オフセットでポートを推測するとオブジェクトを誤って読む。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"chain\"><button data-mode=\"direct\" data-i18n=\"direct\"></button><button data-mode=\"hop\" data-i18n=\"hop\"></button><button data-mode=\"dest\" data-i18n=\"dest\"></button><button data-mode=\"frag\" data-i18n=\"frag\"></button></div><div class=\"control\"><label for=\"available\" data-i18n=\"available\"></label><output for=\"available\" id=\"available-value\"></output><input id=\"available\" type=\"range\" min=\"0\" max=\"64\" value=\"56\" step=\"1\"></div><div id=\"bytes\" class=\"rangegrid\"></div><section class=\"panel\"><h3 data-i18n=\"trace\"></h3><div id=\"trace\"></div></section><div class=\"metrics\"><div><span data-i18n=\"offset\"></span><strong id=\"offset\"></strong></div><div><span data-i18n=\"status\"></span><strong id=\"status\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#trace{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui)}.metrics strong{font-size:17px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let mode='hop';function packet(){const ext=mode==='direct'?0:mode==='dest'?16:8,bytes=new Uint8Array(48+ext);bytes[0]=0x60;bytes[5]=8+ext;bytes[6]=({direct:17,hop:0,dest:60,frag:44})[mode];if(ext){bytes[40]=17;bytes[41]=mode==='dest'?1:0}bytes[40+ext]=0x04;bytes[41+ext]=0xd2;bytes[42+ext]=0;bytes[43+ext]=53;bytes[45+ext]=8;return bytes}function draw(){const data=packet(),n=Math.min(+$('input').value,data.length);$('#available').max=data.length;set('available-value',n);let pos=0,next=null,ok=false,log='';if(n<40)log='need IPv6[0,40)';else{pos=40;next=data[6];log='IPv6: next='+next;while(next!==17){if(next!==44&&n<pos+2){log+='\\nneed length byte at '+(pos+1);break}const len=next===44?8:(data[pos+1]+1)*8;log+='\\n'+(next===44?'Fragment: 8':'Options: ('+data[pos+1]+'+1)×8')+' = '+len;if(n<pos+len){log+='\\nneed ['+pos+','+(pos+len)+')';break}next=data[pos];pos+=len;}if(next===17){ok=n>=pos+8;log+='\\nUDP ['+pos+','+(pos+8)+')'}}cells('bytes',Array.from({length:16},(_,i)=>({text:(i*4)+'…',cls:i*4>=n?'empty':i*4<40?'':i*4<pos?'gold':'on'})));set('trace',log);set('offset',next===17?pos:'—');set('status',t(ok?'ok':'wait'));$('#status').dataset.ready=String(ok);$('#offset').dataset.offset=next===17?pos:'';pressed(mode);}$('#available').oninput=draw;document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;$('#available').max=packet().length;$('#available').value=packet().length;draw()});draw();","audio":false,"strings":{"available":"現在利用可能なバイト数","chain":"ヘッダチェーン","dest":"Destination → UDP","direct":"直接 UDP","frag":"Fragment → UDP","heading":"UDP はなぜ常に40バイト目ではないのか?","hop":"Hop-by-Hop → UDP","intro":"有効なヘッダチェーンを切り替え、切り捨て可能なバイトを観察し、パーサがタイプに応じて長さと次のヘッダを読み取る様子を見る。","offset":"UDP 開始オフセット","ok":"完全な UDP ヘッダ","reset":"リセット","scope":"拡張なし、8B Hop-by-Hop、16B Destination Options、最初のフラグメントの4つのチェーンのみを構築。ESP、AH、不明なヘッダ、または最初のフラグメント以外を処理しない。切り捨ては利用可能なバッファが不足していることを指す。","scopeLabel":"モデル範囲と規約","status":"解析結果","trace":"実際のルールに従って進む","wait":"さらなるバイトを待機"}}
断片化の計算例: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のフラグメントヘッダがあり、ルータは送信元のこのステップを代行しない。
フラグメント データ範囲 Offset M Payload Length 全体長
1 0~1231 0 1 1240 1280
2 1232~2463 154 1 1240 1280
3 2464~2959 308 0 504 544
最後のフラグメントの 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フラグ、適切なプレフィックスおよびローカルIID preferred/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-Lite B4はIPv4をIPv6にカプセル化し、AFTRがカプセル化を解除してNAT44を担当 アクセスネットワークがIPv6を使用して共有IPv4エグジットをどのように運ぶか
NAT64 + DNS64 対応するAAAAを合成し、IPv6トラフィックは翻訳器でIPv4に変換 IPv6クライアントがIPv4のみのサービスにどのようにアクセスするか
464XLAT CLATはステートレス翻訳を行い、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 = 0x BA , 0x 12345
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 == 0x BA
assert word & 0x fffff == 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パケットではない。ネットワークへのデータ送信やインターフェース設定の変更は行わない。
継続的な参照