小さなパケットは通過できるが、少し大きくなると詰まる;veth にはトラフィックがあるのに物理インターフェースにはない。仮想デバイス、レイヤー 2 ドメイン、ルーティング、カプセル化、キューイングは、同じ実際のパスに沿って階層的に説明する必要がある。
参照目標 読書位置
bridge、VLAN、bond 第 1~2 節
カプセル化サイズとポリシーベースルーティング 第 3~4 節
キューイング、namespace、TUN/TAP 第 5~7 節
同じパケットが複数の仮想デバイスを通過できる
コンテナ / VM
独立したネットワークビュー
veth / TAP
接続エンドポイント
bridge / VLAN
レイヤー 2 ドメインと転送
ルーティング / 出口
レイヤー 3 とキュー
FDB で宛先 MAC を検索;FIB で IP プレフィックスを検索;qdisc がキューを管理
各レイヤーが解決する問題は異なる。インターフェースを作成しただけでは、パス全体が接続されたわけではない。
これは一例の組み合わせであり、コンテナネットワークは直接ルーティング、他のインターフェース、または BPF データパスを採用することもできる。
1. Bridge:FDB はルーティングテーブルではない
bridge はソース MAC アドレスに基づいてポートを学習し、宛先 MAC アドレスに対応する FDB エントリを照会する。既知のユニキャストは対応するポートに転送され、未知のユニキャスト、ブロードキャスト、マルチキャストは設定に応じてフレッド、マルチキャストリスニングなどの処理が行われる。IP の最長プレフィックスに基づいて次のホップを選択することはない。
VM A と B が br0 の 2 つの TAP ポートに接続され、同じレイヤー 2 ドメインにあると仮定する。A はまず ARP/NDP を介して B のリンク層アドレスを取得し、次に宛先 MAC が B のフレームを送信する。bridge の学習と転送には、br0 自体に IP アドレスを持たせる必要はない。br0 に IP を割り当てるのは、ホストもそのレイヤー 3 ネットワークに参加させるためであり、ブリッジ機能を有効にするための必須条件ではない。
レイヤー 2 のループはブロードキャストストームを引き起こす可能性があるため、正しいトポロジと STP などのメカニズムが必要である。VLAN フィルタリングを有効にすると、ポートメンバー、PVID、タグ付き/タグなしの出口ルールが組み合わさって転送ドメインを決定する。VLAN ID を作成しただけでは、すべてのポートでその VLAN が許可されるわけではない。bridge ドキュメント を参照。
2. VLAN と bonding は同じ能力を追加するものではない
VLAN は共有物理リンク上で論理的なレイヤー 2 ドメインを区別する。VLAN サブインターフェースは通常、上位レイヤーに独立した net_device として見える。ハードウェアオフロードでは、タグがメタデータに保存されるか、ハードウェアによって挿入/除去されるため、パケットキャプチャでタグが見えるかどうかは位置による。
bonding は複数の下位リンクを 1 つの論理インターフェースとしてまとめる:
モード 典型的な用途 制限
active-backup リンク切替 単一フローのスループットを複数のリンクの合計に重ね合わせるものではない
balance-xor ハッシュによる分散 相手側の正しい設定が必要。ハッシュフィールドが均衡に影響する
802.3ad LACP による凝集 スイッチのサポート、メンバー条件、ハッシュが一致する必要がある
balance-rr ラウンドロビン方式で送信を割り当て パケットの順序入れ替えを引き起こす可能性があり、一般的な最適解ではない
10 Gbit/s のリンク 2 本で LACP を構成しても、単一の TCP 接続が必ず 20 Gbit/s に達するわけではない。順序保持のため、同じフローは通常 1 つのメンバーリンクにマッピングされる。リンクの検知は、エンドツーエンドのサービス健全性検知とは異なる。bonding ドキュメント を参照。
3. トンネリング:まずカプセル化を計算し、次に MTU を設定する
IPIP は IP を外側 IP に格納する;GRE は GRE ヘッダーを追加し、対応するプロトコルを運ぶことができる;VXLAN は通常、レイヤー 2 フレームを UDP/IP にカプセル化し、VNI が論理ネットワークを識別する。VNI は VLAN ID よりも大きい値を持つが、暗号化やアクセス認証を提供するわけではない。
アンダーレイの IPv4 MTU が 1500 で、VXLAN に追加オプションがないと仮定する:
カプセル化内容 バイト数
外側 IPv4 20
UDP 8
VXLAN 8
内側 Ethernet 14
合計 50
内側 IP パケットに割り当て 1500 − 50 = 1450
外側を IPv6 ベースヘッダーに変更する場合、通常 20 バイトとなり、結果は約 1430 になる。内側の VLAN や追加ヘッダーもさらに計算に含める必要がある。
これは明確なカプセル化に基づいて計算された上限であり、すべての VXLAN の固定 MTU ではない。アンダーレイ MTU はその IP ペイロードの境界によって定義され、上記の計算では外側 Ethernet ヘッダーは再度控除されていない。階層を混在させると、控除しすぎたり控除不足になったりする。
パス内の MTU の不一致や、ICMP Packet Too Big などがフィルタリングされると、小さなパケットは正常だが大きなパケットが詰まるという現象が発生する可能性がある。トンネルのキー/VNI、リモートアドレス、FDB/制御プレーン、および戻りパスも正しく設定する必要がある必要がある。WireGuard などは暗号化トンネルを提供するが、それらのセキュリティ属性は平文の VXLAN とは異なる。VXLAN ドキュメント を参照。
各レイヤーのオーバーヘッドを同じ長さの軸に戻す:外側 IP、UDP、VXLAN、内側 Ethernet ヘッダー、アプリケーションデータはすべて予算を占有する。外側 IPv4/IPv6 や VLAN を変更すると、収容できるデータの上限随之に移動する。
D m a x = MTU outer − H tunnel − H inner IP + TCP
MTU は外側 IP パケットから計算され、H_tunnel には外側 IP/UDP/VXLAN と内側 Ethernet ヘッダーが含まれる。外側 Ethernet ヘッダーは IP MTU には含まれない。
図を準備しています 再読み込み
ヘッダーが 1 層追加されるごとに、内側にはどれくらい収まるか? VXLAN の外側 IP、UDP、VXLAN、内側 Ethernet はすべてアンダーレイ IP MTU を占有する。内側 IP/TCP ヘッダーも内側データ予算をさらに占有する。
{"id":"tunnel-mtu-budget","title":"ヘッダーが 1 層追加されるごとに、内側にはどれくらい収まるか?","summary":"VXLAN の外側 IP、UDP、VXLAN、内側 Ethernet はすべてアンダーレイ IP MTU を占有する。内側 IP/TCP ヘッダーも内側データ予算をさらに占有する。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"mtu\" data-i18n=\"mtu\"></label><output for=\"mtu\" id=\"mtu-value\"></output><input id=\"mtu\" type=\"range\" min=\"1280\" max=\"2000\" value=\"1500\" step=\"10\"></div><div class=\"presets\" role=\"group\" data-i18n-label=\"outer\"><button data-mode=\"4\" data-i18n=\"v4\"></button><button data-mode=\"6\" data-i18n=\"v6\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"vlan\"><span data-i18n=\"vlan\"></span></label><div class=\"control\"><label for=\"payload\" data-i18n=\"payload\"></label><output for=\"payload\" id=\"payload-value\"></output><input id=\"payload\" type=\"range\" min=\"0\" max=\"1950\" value=\"1400\" step=\"10\"></div><p class=\"caption\" data-i18n=\"packet\"></p><div class=\"packet\"><div id=\"packet\"></div><i id=\"mtu-mark\"></i></div><div class=\"axis\"><span>0</span><span>1050</span><span>2100 B</span></div><div class=\"formula\" id=\"parts\"></div><div class=\"metrics\"><div><span data-i18n=\"maxInner\"></span><strong id=\"maxInner\"></strong></div><div><span data-i18n=\"maxData\"></span><strong id=\"maxData\"></strong></div><div><span data-i18n=\"headers\"></span><strong id=\"headers\"></strong></div><div><span data-i18n=\"excess\"></span><strong id=\"excess\"></strong></div></div><div class=\"formula\" id=\"arithmetic\"></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"fit\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"over\"></p></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.packet{position:relative;height:40px;background:var(--surface);margin:18px 0}.packet>div{display:flex;height:100%}.packet b{display:block;background:var(--second);border-right:1px solid var(--paper);height:100%;box-sizing:border-box;transition:width .2s}.packet b:last-child{background:var(--accent);border:0}.packet>i{position:absolute;width:2px;top:-9px;bottom:-9px;background:var(--ink);transition:left .2s}.formula{min-height:6em;white-space:pre-line}","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)));}let outer=4;function draw(){const mtu=+$('#mtu').value,payload=+$('#payload').value,vlan=$('#vlan').checked?4:0,ip=outer===4?20:40,overhead=ip+8+8+14+vlan,total=overhead+40+payload,parts=[ip,8,8,14+vlan,20,20,payload];set('mtu-value',mtu+' B');set('payload-value',payload+' B');$('#packet').replaceChildren(...parts.map(n=>{const e=document.createElement('b');e.style.width=n/21+'%';return e;}));$('#mtu-mark').style.left=mtu/21+'%';set('parts','outer IP '+ip+' + UDP 8 + VXLAN 8 + Ethernet '+(14+vlan)+'\\ninner IPv4 20 + TCP 20 + data '+payload);set('maxInner',mtu-overhead);set('maxData',mtu-overhead-40);set('headers',overhead);set('excess',Math.max(0,total-mtu));set('arithmetic',mtu+' − '+overhead+' − 20 − 20 = '+(mtu-overhead-40)+' B\\npacket = '+total+' B');set('explanation',t(total>mtu?'over':'fit'));pressed(String(outer));}document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{outer=+e.dataset.mode;draw();});$('#mtu').oninput=$('#payload').oninput=$('#vlan').onchange=draw;draw();","audio":false,"strings":{"excess":"MTU 超過分 / B","fit":"パケット長が現在の MTU を超えていない。ヘッダー長は 1 回だけ控除され、各レイヤーに応じて計算される。","headers":"外側カプセル化ヘッダー / B","heading":"ヘッダーが 1 層追加されるごとに、内側にはどれくらい収まるか?","intro":"アンダーレイ IP MTU、外側 IP バージョン、TCP データ長を調整する。縦線は MTU を示し、ストリップはバイト比率で外側 IP パケット全体を構成する。","maxData":"TCP データ上限 / B","maxInner":"内側 IP 上限 / B","mtu":"アンダーレイ IP MTU / B","outer":"外側 IP バージョン","over":"パケット長が MTU 基準線を超える。大きなパケットが継続して失敗する場合は、アプリケーションデータ長だけでなく、PMTU フィードバックと実際のカプセル化を確認する。","packet":"全体パケットのスケール:ヘッダーは金色、TCP データは青色","payload":"TCP データ長 / B","reset":"リセット","scope":"標準的な VXLAN、追加オプションなし;内側は固定 IPv4 20 B + TCP 20 B、オプションなし。外側 Ethernet は IP MTU に含まれない。超えた場合は PMTU/フラグメンテーションなどの処理が必要だが、具体的なポリシーはシミュレートしない。IPv6 ルーターは送信元のためにフラグメンテーションを行わない。図のスケールは固定 2100 B、ストリップの幅は短いヘッダーを誇張しない。","scopeLabel":"モデルの範囲と規約","v4":"IPv4","v6":"IPv6","vlan":"内側 Ethernet に VLAN タグを含む(4 B)"}}
4. ポリシーベースルーティング:まずルールを選択し、次に該当テーブルを照会する
ルーティングポリシーデータベースは、rule の優先度に基づいてソースアドレス、mark、インターフェースなどの条件をチェックし、テーブル参照などのアクションを実行する。選択されたルックアップルールが終了結果を得られない場合、後続のルールが続く可能性がある。最初の一致条件で無条件に停止するわけではない。
同じテーブル内では、ルーティングは通常最長プレフィックスを優先し、次に候補属性に基づいて選択される。10.2.3.9 は 10.0.0.0/8 と 10.2.0.0/16 の両方に一致するが、他の条件が正常であれば、後者の方がより具体的である。IPv4 FIB が trie を使用しているからといって、あらゆる負荷下で厳密な O(log n) を約束するものではない。
from ipaddress import ip_address , ip_network
address = ip_address ( ' 10.2.3.9' )
routes = [ ip_network ( s ) for s in ( ' 0.0.0.0/0' , ' 10.0.0.0/8' , ' 10.2.0.0/16' ) ]
best = max ( ( r for r in routes if address in r ) , key = lambda r : r . prefixlen )
assert str ( best ) == ' 10.2.0.0/16'
print ( best )
この例は 1 つのテーブルでのプレフィックスマッチングのみを示しており、policy rule、ECMP、ルーティングタイプ、およびネイバー解決はシミュレートしていない。ip route get も、問題に関連するソースアドレス、mark、またはインターフェース情報を含めることで、実際の検索条件に近づける。ip-rule を参照。
5. qdisc:キューイング、スケジューリング、シェーピング、AQM
機能 コアアクション
キューイング 送信できない skb を一時的に保存
スケジューリング キューから次のパケットを選択
シェーピング 送信を遅延させてレート制御
ポリシング 条件を超えるパケットの破棄またはアクション
AQM キュー状態に基づいて早期破棄または ECN マーキングを行い、輻輳をフィードバック
これらを組み合わせることができるが、1 つのインターフェースが自動的にすべての機能を提供するわけではない。
fq_codel はフローキューと CoDel を組み合わせ、CAKE はシェーピング、公平性、キュー管理を組み合わせ、HTB は階層型帯域幅割り当てをサポートする。システムのデフォルト qdisc は設定とデバイスによって影響を受けるため、fq_codel が常にデフォルトであると一概に言うことはできない。
キュー位置の制御:ローカル AQM はすべてのリモートバッファを管理できない
実際のボトルネックがモデム/プロバイダのバッカーにある場合、ホストローカルのキューは常に短く、ローカル AQM はリモートキューを直接制御できない。制御可能なシェーピングレートを適切なボトルネックの前に設定することで、キューを管理下の位置で発生させる機会を得られるが、具体的なレートはリンクオーバーヘッドと変化を測定する必要がある。
ingress/clsact は入力フィルタリングの位置を提供するが、通常の長期キューイング可能な受信シェーピングキューではない。キューイングシェーピングが必要な場合は、IFB などの方法と組み合わせて使用する。BQL はドライバ層のオーバードライブバイトを調整し、その役割はフローレベルの輻輳制御や qdisc AQM とは異なる。
6. Namespace、veth、TUN/TAP
network namespace はデバイスリスト、ルーティング、ポートバインドなどのネットワーク状態を分離する。同じカーネルを共有するが、出口、DNS サービス、帯域幅保証を自動的に作成するものではない。通常の net_device はある時点で 1 つの namespace に属し、veth ペアの両端を異なる namespace に配置できる。
veth はペアになったカーネルネットワークエンドポイントを提供する;TUN/TAP はユーザー空間がファイル記述子を介してネットワークデータを読み書きする。TUN はレイヤー 3 パケットを提供し、TAP はレイヤー 2 フレームを提供する。ユーザー空間が fd から読み取ると、カーネルからそのインターフェースへ送られたデータが得られ、fd に書き込むと、受信データをカーネルに注入する。方向はこの視点に従って理解する必要がある。
追加の packet information や virtio-net ヘッダーがデータに含まれるかどうかは flags に依存し、常に最初のバイトが IP/Ethernetヘッダーであるわけではない。VM では TAP が一般的に使用され、一部のユーザー空間 VPN では TUN が使用される。メインラインカーネルの WireGuard は独自に net_device を実装しており、すべての WireGuard が /dev/net/tun に依存すると一概に言うことはできない。TUN/TAP を参照。
7. 完全なパスを参照のみで確認する
ip - d link show
ip rule show
ip route show table all
bridge link show
bridge vlan show
tc - s qdisc show
まずデバイスと namespace を確認し、次にレイヤー 2 ドメイン、アドレス、ルーティングをチェックし、その後 MTU、フィルタルール、キューを確認する。トラフィックが veth にはあるが物理口にはない場合、それが直接パケット損失を意味するわけではない。ローカルで配信されたか、ブリッジ内で転送されたか、別の出口にカプセル化されたか、または事前にポリシーによって拒否された可能性がある。
関連読書:プロトコルスタック概要 、Netfilter と BPF/XDP 。