交换机第一次收到 A 发给 B 的帧时,还不知道 B 在哪里,却可能让 B 顺利收到:它学习 A 的入口,再把未知目的帧复制到允许的其他端口。B 回复后,反向来源也被学习。这个过程解释了 FDB 如何形成,但不能解释环路如何消失;环路控制与隧道扩展还需要各自机制。
问题 本篇入口 核心区分
没学到目的 MAC 为什么还能通信 三帧学习推演 学源地址,查目的地址
VLAN 内有组播却收不到 转发许可与 MDB 泛洪范围、成员、端口状态
Linux bridge 是否必然纯软件 桥接路径 管理接口与硬件卸载
两条冗余线为什么会出环 STP / RSTP 物理拓扑与转发拓扑
VXLAN 要查几张表 VTEP 路径 内层 MAC、远端 VTEP、外层路由与邻居
1500 B 隧道为何发不通 MTU 算例 内层 MTU 与外层 IP 长度
三帧就能看懂学习与转发
假设端口 p1、p2、p3 都属于 VLAN 10 且允许转发,A 在 p1,B 在 p2,C 在 p3,初始 FDB 为空。以下省略本地终结与特殊地址处理:
到达的帧 学到的源位置 对目的查表 出口
A→B,从 p1 进入 (VLAN 10,A)→p1 B 未知 p2、p3 泛洪,B 可收到
B→A,从 p2 进入 (VLAN 10,B)→p2 A 在 p1 仅 p1
A→B,再从 p1 进入 刷新 A 的位置/时间 B 在 p2 仅 p2
动态学习需要端口处于允许学习的状态并通过相应入口规则,不是任何帧到达就无条件更新。FDB 的键还包含 VLAN 等上下文,相同 MAC 在不同 VLAN 可有不同位置。交换设备维护的是各自观察/控制平面得到的局部信息,不能理解为交换机之间自动同步的一张全局哈希表。
如果目的位置就是入口,普通桥接通常过滤这次转发;Hairpin 可允许特定场景返回同一端口。这种入口过滤不等于完整防环,帧从其他链路返回时仍可能被再次转发。
泛洪与老化不等于丢一个包
未知单播一般在同 VLAN、允许泛洪且处于转发状态的相关端口复制,不会无条件发给整台交换机所有端口。广播也受 VLAN、隔离和控制策略限制;组播还可能根据 IGMP/MLD snooping 的 MDB 成员及路由器端口选择出口,保留链路本地组播又有特殊规则。
动态 FDB 老化后,下一帧可能成为未知单播并被泛洪,这通常仍能到达目标,不会天然丢掉恰好一个包 。若未知单播被策略禁止、泛洪路径受限或网络拥塞,才需进一步分析。目标收到后是否很快恢复学习,还取决于它有没有发出可被学习的源帧。
地址移动可通过新入口的源流量更新位置,但持续来回移动也可能反映重复 MAC 或二层环路。静态、永久、控制平面管理或硬件卸载条目具有不同生命周期,不应把所有条目都写成固定 300 s 后删除。
交换机只看到一帧,能学到哪一半?
同一张图保留“当前学习表”和“最近一次转发结果”。先发 A→B,再让 B→A,最后重发 A→B,可以看到定向转发的证据是怎样产生的。清表只影响后续查找,不会重写已经发生的转发;因此清表后画面仍保留上一次出口。
正在呈现知识画面 重新加载
交换机只看到一帧,能学到哪一半? 入口证实源 MAC 的位置,目的 MAC 的位置仍需独立证据。老化目的条目后可以重新泛洪;同名 MAC 的 VLAN 键仍独立。
{"id":"bridge-local-learning","title":"交换机只看到一帧,能学到哪一半?","summary":"入口证实源 MAC 的位置,目的 MAC 的位置仍需独立证据。老化目的条目后可以重新泛洪;同名 MAC 的 VLAN 键仍独立。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"form-field\"><span data-i18n=\"src\"></span><select id=\"src\"><option value=\"A\">A</option><option value=\"B\">B</option><option value=\"C\">C</option></select></label><label class=\"form-field\"><span data-i18n=\"dst\"></span><select id=\"dst\"><option value=\"B\">B</option><option value=\"C\">C</option><option value=\"A\">A</option></select></label><label class=\"form-field\"><span data-i18n=\"vid\"></span><select id=\"vid\"><option value=\"10\">10</option><option value=\"20\">20</option></select></label><div class=\"actions\"><button id=\"send\" data-i18n=\"send\"></button><button id=\"age\" data-i18n=\"age\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><svg id=\"ports\" class=\"diagram\" viewBox=\"0 0 360 180\" role=\"img\" data-i18n-label=\"heading\"></svg><div class=\"metrics\"><div><span data-i18n=\"out\"></span><strong id=\"out\"></strong></div><div><span data-i18n=\"mode\"></span><strong id=\"mode\"></strong></div></div><section class=\"panel\"><h3 data-i18n=\"fdb\"></h3><div id=\"fdb\"></div></section><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#fdb .tr{grid-template-columns:.7fr 1fr 1fr}.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 fdb={},last=null;function draw(){table('fdb',[[t('vid'),'MAC','Port'],...[10,20].flatMap(v=>[...'ABC'].map(n=>[v,n,fdb[v+':'+n]??'∅']))]);set('out',last?fmt(last.ports):'∅');set('mode',t(last?(last.known?'known':'flood'):'pending'));graph('ports',[{name:'SW',x:180,y:100},...[...'ABC'].map((name,i)=>({name,x:[45,180,315][i],y:[35,175,35][i],cls:last?.ports.includes(i+1)?'on':'',sub:'p'+(i+1)}))],[[0,1],[0,2],[0,3]]);$('#ports').setAttribute('viewBox','0 0 360 225');$('#fdb').dataset.entries=JSON.stringify(fdb);$('#out').dataset.ports=last?.ports.join(',')||'';}$('#send').onclick=()=>{const v=$('#vid').value,s=$('#src').value,d=$('#dst').value,p='ABC'.indexOf(s)+1;fdb[v+':'+s]=p;const dest=fdb[v+':'+d];last={known:dest!==undefined,ports:dest===undefined?[1,2,3].filter(n=>n!==p):dest===p?[]:[dest]};draw()};$('#age').onclick=()=>{const v=$('#vid').value;Object.keys(fdb).filter(k=>k.startsWith(v+':')).forEach(k=>delete fdb[k]);draw()};$('#reset').onclick=()=>{fdb={};last=null;draw()};draw();","audio":false,"strings":{"age":"老化此 VLAN 的表项","dst":"目的站","fdb":"当前局部 FDB","flood":"未知:泛洪","heading":"交换机只看到一帧,能学到哪一半?","intro":"任选源、目的与 VLAN 后发送。学习表保留每次观察,比较未知泛洪和已知单播。","known":"已知:定向或同口过滤","mode":"查表结果","out":"最近一次转发出口","pending":"尚未发送","reset":"重置","scope":"三端口均允许学习和转发;VLAN10/20都覆盖三口,A/B/C固定在p1/p2/p3。普通单播,无hairpin、STP、MDB和端口隔离;老化按钮只清所选VLAN的动态项。","scopeLabel":"模型范围与约定","send":"发送所选帧","src":"源站","vid":"VLAN"}}
Linux bridge:软件对象可以管理硬件转发
Linux bridge 提供二层桥接,veth、TAP、物理接口或 VXLAN 设备都可以作为相关端口。宿主机要在该二层网络参与 IP 通信,可通过桥主设备或相应 VLAN 接口配置三层功能;端口被加入 bridge 不会自动等于开启 IP 路由。
Linux 7.2 的 net/bridge/br_private.h 中,FDB 使用含 MAC 和 VLAN ID 的 key,并关联目的端口及更新信息。学习和转发路径可查 br_input.c、br_forward.c。
Linux 还可通过 switchdev 将 FDB 等信息下发硬件,数据平面未必逐帧经过 CPU。因此“Linux bridge 比硬件慢十倍、只能用于 10 Gb/s 以下”没有通用依据;性能应按包大小、CPU/NUMA、驱动、规则和卸载能力测量。Linux switchdev 模型
可先用只读命令检查实际状态,设备名为示例:
ip - d link show br0
bridge link show
bridge vlan show
bridge fdb show br br0
bridge mdb show
传统 bridge sysfs 的 ageing_time 使用 clock_t/USER_HZ 表示,不能把 echo 60 直接称作 60 秒。本地 br_sysfs_br.c 与 br_stp.c 有明确转换,常见 USER_HZ=100 时一分钟对应 6000;更稳妥的是按所用工具文档确认输入单位。Linux bridge 接口文档
桥接过滤与三层过滤不要混用预期
br_netfilter 可让部分桥接流量进入相应三层 netfilter 路径,历史上常用于 iptables 规则;这不表示纯二层转发天然会经过所有 ip/inet 规则。nftables 的 bridge family 可直接处理桥接层,无需为了它一概加载 br_netfilter。nftables 桥接过滤
排查时需要知道报文是在桥上转发、发给本机还是进入路由,再确定相应 hook、规则计数和卸载路径。Hairpin、端口隔离与 VLAN 过滤又是不同设置,不能用一个防火墙开关代替全部转发策略。
STP:用生成树约束没有 TTL 的二层转发
普通以太网帧没有类似 IP TTL 的逐跳递减字段。冗余链路形成环路时,广播/未知单播复制可能持续循环,同时引发源 MAC 在端口间漂移。STP 保留物理冗余,通过端口角色和状态选择无环转发拓扑;它不是靠识别某个重复帧后自动去重。
三角拓扑:物理链路保留,转发拓扑去掉环路
A:Root Bridge
假设 Bridge ID 最小
B:到根成本 10
B—A 为 Root Port
C:到根成本 10
C—A 为 Root Port
成本 10
成本 10
B—C 成本 10
假设 B ID 更小,C 侧端口为备用 / 丢弃
B、C 都优先直达 A;B—C 再按桥 ID 等规则决胜。链路断开后仍需检测、角色变化与 FDB 收敛。
先按 Bridge ID 等规则选根,非根桥比较到根的路径成本及后续决胜字段选择 Root Port;每段再选 Designated Port。图中所有链路成本 10,B、C 直达根成本都是 10,经对方则为 20;在 B—C 段上双方根成本相同,假设 B ID 更小,B 赢得指定角色,C 一侧成为备用/不转发路径。
BPDU 是协议控制报文。经典 STP 使用相应 802.3 长度/LLC 封装,常见 DSAP/SSAP 为 0x42,不能把出现的 0x0026 长度写成 EtherType。STP 配置 BPDU、拓扑变化 BPDU 和 RSTP BPDU 的格式/类型也不完全相同。
RSTP 通过角色、边缘端口及适用点到点链路上的 Proposal/Agreement 加快安全转发转换;不是所有故障都保证 1~3 s。传统 STP 的等待定时器也受拓扑、配置和故障检测影响,不能把某一默认场景收敛时间当作固定性能指标。Cisco:RSTP 机制
BPDU 过滤、边缘端口设置与保护功能应按接入角色设计,错误地隐藏 BPDU 可能保留真实环路。Linux bridge 是否启用 STP、是否由用户态守护进程实现所需快速/多实例协议,都应检查,不能因接口名叫 bridge 就推断防环已开启。
VXLAN:先解决外层可达,再建立内层邻接
VXLAN 用 UDP 承载内层以太网帧,VTEP 是封装/解封装端点。基本头为 8 B,VNI 占 24 bit,I 标志表示 VNI 有效;标准 UDP 目的端口为 4789,部署中仍要明确配置兼容值。VNI 提供约 1677 万种编号空间,不等于硬件保证能容纳相同数量的活动租户。
VXLAN:内层承载租户帧,外层只负责到远端 VTEP
VTEP A
192.0.2.1
IP Underlay
按外层目的 IP 路由
VTEP B
192.0.2.2
外层 IP
IPv4 20 B
UDP
8 B
VXLAN
8 B / VNI
内层 Ethernet
14 B,无 FCS
内层 L3 数据
示例 1500 B
外层 Ethernet 未画;它不属于 Underlay 的 IP MTU
本例外层 IP 长度 = 20 + 8 + 8 + 14 + 1500 = 1550 B
无额外标签、IPv4 选项或其他隧道;外层 IPv6 基本头再增加 20 B。VXLAN 不自带加密认证。
A 端本地桥先确定目的 MAC 应走 VXLAN 端口,VXLAN 转发信息再定位远端 VTEP,外层 IP 路由决定物理出口,最后外层邻居解析得到当前链路下一跳 MAC。外层下一跳可能是中间路由器,不必是远端 VTEP 的物理 MAC。对端剥去外层后,依据 VNI 和本地转发配置处理内层帧。RFC 7348:VXLAN
状态 对应问题
本地 bridge FDB 内层目的 MAC 在本地口还是隧道口
VXLAN FDB / 控制平面映射 该 MAC 或复制项对应哪个远端 VTEP
Underlay 路由 外层 VTEP IP 从哪个出口、经哪个下一跳到达
Underlay 邻居表 当前出口下一跳的链路地址是什么
UDP 源端口可携带流哈希信息,帮助 Underlay ECMP 分散不同内层流;这不自动提供 NAT 穿越协商。防火墙、返回路径、MTU 和允许的 VTEP 都需要相应配置。VXLAN 本身也没有提供租户内容加密与对端认证。
MTU 用外层 IP 长度结算
在无内层 VLAN、无 IPv4 选项的例子中,内层 L3 为 1500 B,加内层 Ethernet 14 B、VXLAN 8 B、UDP 8 B、外层 IPv4 20 B,共 1550 B 外层 IP 包 。内层 FCS通常不封装,外层 Ethernet 又在 IP MTU 之外。若 Underlay IP MTU 只有 1500,相同假设下内层 L3 可用 1450 B;外层 IPv6 基本头为 40 B,则为 1430 B。
额外标签、扩展头或加密隧道继续占预算。不能仅把物理口 MTU 设大就推断所有路径和远端一致;分片、丢弃与错误反馈还受具体协议和实现限制。小 ping 通、大负载不通时,要分别确认内外层长度和路径行为。
BUM 复制与 EVPN:控制平面减少未知信息
广播、未知单播、组播常合称 BUM,但其转发策略不必完全相同。VXLAN 可使用相应 Underlay 组播或入口复制等方案,复制对象是该 VNI 的相关远端成员,而非所有部署的 VTEP。若一个入口需要向三个远端各复制 1 MiB 内层数据,仅复制出的内层数据量就为 3 MiB,还未计外层开销。
EVPN 可通过 BGP 分发 MAC/IP 与相关可达信息,提供比纯数据面学习更明确的控制平面;它与 Open vSwitch 这类交换实现不是同一个概念。ARP/ND suppression 利用可信且有效的绑定在本地回答部分解析请求,但不能消除全部广播,也不能在绑定过期后随意给出旧答案。RFC 8365:EVPN Overlay
MAC 移动、VTEP 故障、复制成员变化和底层路由收敛仍需处理。Overlay 让二层边界可跨三层网络延伸,也可能延伸故障影响范围,不能把“隧道跨得更远”直接当作隔离更强。
本地转发与封装模型
以下只模拟同 VLAN、所有端口允许学习/转发、普通单播且无 Hairpin 的基本逻辑;不模拟真实 bridge 的并发、老化或控制报文。
ports = { 10 : { ' p1' , ' p2' , ' p3' } , 20 : { ' p1' , ' p4' } }
fdb = { }
def forward ( vid , ingress , source , dest ) :
assert ingress in ports [ vid ]
fdb [ ( vid , source ) ] = ingress
out = fdb . get ( ( vid , dest ) )
if out is None :
return ports [ vid ] - { ingress }
return { out } if out != ingress else set ( )
assert forward ( 10 , ' p1' , ' A' , ' B' ) == { ' p2' , ' p3' }
assert forward ( 10 , ' p2' , ' B' , ' A' ) == { ' p1' }
assert forward ( 10 , ' p1' , ' A' , ' B' ) == { ' p2' }
assert forward ( 20 , ' p4' , ' B' , ' A' ) == { ' p1' }
assert fdb [ ( 10 , ' B' ) ] == ' p2' and fdb [ ( 20 , ' B' ) ] == ' p4'
assert forward ( 10 , ' p1' , ' D' , ' A' ) == set ( )
vni = 100
header = bytes ( [ 0x 08, 0 , 0 , 0 ] ) + vni . to_bytes ( 3 , ' big' ) + bytes ( 1 )
assert len ( header ) == 8 and header . hex ( ) == ' 0800000000006400'
assert 20 + 8 + 8 + 14 + 1500 == 1550
assert 1500 - ( 20 + 8 + 8 + 14 ) == 1450
assert 1500 - ( 40 + 8 + 8 + 14 ) == 1430
print ( ' 学习/泛洪/同口过滤、VLAN 独立键、VXLAN 编码及 MTU 计算通过' )
本地检查没有创建网桥、建立 VXLAN 隧道或向生产网络注入二层帧。
继续查阅