路由器收到两条到同一前缀的路径,一条 AS_PATH 更短,另一条符合更高的本地优先级,后者可能获选。但如果目的地址还匹配一条更具体的前缀,实际转发又会使用更具体路由。理解路由,首先要区分“为同一前缀选候选路径”和“为一个目的地址查最长前缀”。
| 问题 | 所在层次 |
| 这个前缀有哪些候选 | 协议接收、导入策略和 RIB |
| 同一前缀选哪条路径 | 协议优选及跨协议选择 |
| 下一跳怎样到达 | 递归路由解析、IGP/直连/静态路由 |
| 当前这个包走哪里 | 策略规则、FIB 最长前缀匹配、ECMP |
| 改动多久影响数据 | 检测、计算、传播与 FIB 安装形成的收敛过程 |
RIB 与 FIB:候选信息和转发结果
RIB 是路由信息的逻辑组织,FIB 面向实际转发。BGP 还区分从邻居接收的 Adj-RIB-In、本地选择的 Loc-RIB,以及面向邻居通告的 Adj-RIB-Out。它们是协议模型,不要求每种实现都原样保存三份完全独立的数据结构。RFC 4271:路由信息模型
在 Linux 软件路由场景,FRR 等守护进程可以通过内核接口安装路由;硬件设备还可能需要进一步下发 ASIC。RIB 中有路由,不保证下一跳可解析或安装成功;FIB 中有路由,也不保证出口邻居和回程正常。
最长前缀匹配是语义,trie、压缩层级、硬件表等是实现。不能把 Linux LC-trie 简写为任何规模与访问条件下都恒定 O(1),也不能用一个不断变化的全球前缀总数描述所有路由器的内存需求:邻居数量、地址族、候选路径和策略同样重要。
把最长前缀算清楚
假设同一张表中有默认路由、203.0.113.0/24 经 A、203.0.113.128/25 经 B。目标 .140 同时匹配三条,选 /25 经 B;目标 .20 选 /24 经 A。即使 /24 的 BGP LOCAL_PREF 很高,也不会覆盖已安装 /25 的最长前缀优势。
跨协议优先级通常解决相同前缀的候选竞争,不应该拿某设备的静态路由管理距离与另一条不同长度前缀混算。路由聚合减少条目时还会扩大被覆盖范围,需要考虑缺失更具体路由时是否把流量送入黑洞。
Linux 策略路由:先决定查哪张表
RPDB 按规则优先级由小到大处理,可以依据源地址、fwmark、输入接口等选择动作。规则匹配后执行查表,若没有得到终止结果,可能继续后续规则;因此“第一条匹配规则必定结束”不准确。throw 路由可结束当前表的查找并继续规则链,而 blackhole、unreachable 等具有不同终止语义。ip-rule 手册、ip-route 手册
常见预置规则依次查询 local(255)、main(254)和 default(253)。名为 default 的表不等于默认路由:0.0.0.0/0 或 ::/0 可以位于 main 或其他表。自定义表编号也不局限于 1~252。
例如某源前缀先查表 100,表 100 没有匹配路由,则可继续查 main;若表 100 有默认路由,通常已经给出结果,不会为了寻找 main 中更具体路由而继续。最长前缀比较发生在当前查表上下文中,不是把所有表合并排序。
ip rule show
ip route show table all
ip route get <目标IPv4> from <源IPv4>
ip -6 route get <目标IPv6> from <源IPv6>
更具体的路由,为什么可能根本没被查到?
把 main 表里的匹配范围一直留在图上,再检查查询记录是否真的走到了 main。这样,目的地址命中 /25 却仍经 C 转发的情况,就能由规则终止条件解释,而不必错误地修改最长前缀规则。
正在呈现知识画面
更具体的路由,为什么可能根本没被查到?最长前缀匹配发生在当前表内。前一张表给出默认路由或黑洞终止结果后,不会继续到下一表寻找更长前缀;throw 则继续规则链。
{"id":"routing-table-before-prefix","title":"更具体的路由,为什么可能根本没被查到?","summary":"最长前缀匹配发生在当前表内。前一张表给出默认路由或黑洞终止结果后,不会继续到下一表寻找更长前缀;throw 则继续规则链。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"last\" data-i18n=\"last\"></label><output for=\"last\" id=\"last-value\"></output><input id=\"last\" type=\"range\" min=\"0\" max=\"255\" value=\"140\" step=\"1\"></div><div class=\"presets\" role=\"group\" data-i18n-label=\"policy\"><button data-mode=\"empty\" data-i18n=\"empty\"></button><button data-mode=\"route\" data-i18n=\"route\"></button><button data-mode=\"black\" data-i18n=\"black\"></button><button data-mode=\"throw\" data-i18n=\"throw\"></button></div><div id=\"prefixes\" class=\"intervals\"></div><section class=\"panel\"><h3 data-i18n=\"trace\"></h3><div id=\"trace\"></div></section><div class=\"metrics\"><div><span data-i18n=\"result\"></span><strong id=\"result\"></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:18px}#prefixes{position:relative}#prefixes:after{content:\"\";position:absolute;top:0;bottom:0;left:var(--position);width:2px;background:var(--ink)}","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='route';function draw(){const n=+$('#last').value;set('last-value','203.0.113.'+n);let result,log='rule 100 → table100: '+mode;if(mode==='route')result='C';else if(mode==='black')result='DROP';else{result=n>=128?'B (/25)':'A (/24)';log+='\\ncontinue → main\\n/0 ✓; /24 ✓; /25 '+(n>=128?'✓':'✗')}intervals('prefixes',[{start:0,end:256,label:'main: /24 → A'},{start:128,end:256,label:'main: /25 → B',cls:'gold'}],256);$('#prefixes').style.setProperty('--position',100*n/256+'%');set('trace',log);set('result',result);$('#result').dataset.action=result;pressed(mode);}document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;draw()});$('#last').oninput=draw;draw();","audio":false,"strings":{"black":"blackhole /0","empty":"无匹配路由","heading":"更具体的路由,为什么可能根本没被查到?","intro":"修改目的地址与策略表结果,跟踪规则选择表、表内最长前缀匹配两层决策。","last":"目的地址末字节","policy":"表100内容","reset":"重置","result":"最终动作","route":"默认路由 C","scope":"仅两条已匹配规则:先表100,再main。main有/0、203.0.113.0/24、203.0.113.128/25;不模拟local表、IPv6或递归下一跳。","scopeLabel":"模型范围与约定","throw":"throw /0","trace":"实际查询记录"}}
IGP:把链路代价累加到路径上
OSPF 与 IS-IS 在相应范围内传播链路状态并计算路径;代价表达配置的度量,不必等于测得的延迟。以单区域简化拓扑为例,A→B 代价 10,B→D 为 10,A→C 为 5,C→D 为 30,则经 B 总计 20,优于经 C 的 35。A→B 失效后,经 C 才可能成为新的可用最短路径。RFC 2328:OSPFv2
实际收敛还要经过失效检测、状态传播、SPF 调度和转发表更新;区域结构、路由类型及策略增加更多规则。不能从 Dijkstra 算法本身推导出固定的全网切换毫秒数。IGP 也常用于提供 BGP NEXT_HOP 的域内可达性,两者共同工作。
BGP 会话:TCP 通了只是起点
BGP 使用 TCP 179,随后交换 OPEN、KEEPALIVE、UPDATE 等消息,建立协议状态。OPEN 中的版本为 4,BGP Identifier 是 32 bit 标识,并不要求就是会话端点地址。Hold Time 由双方参数协商,非零值至少为三秒;若协商为零则不运行相应 Hold/Keepalive 定时器,不能一概写成 90/30 秒。
基本 OPEN 的 My Autonomous System 字段仍是两字节;四字节 ASN 通过能力协商及兼容机制支持,不是直接把该字段拉长。RFC 6793:四字节 ASNUPDATE 宣告或撤回前缀,并携带属性;多协议扩展用相应属性承载不同地址族,不能把 IPv4 单播基本布局硬套到全部 MP-BGP 报文。
会话 Established 但没有路由,可能是地址族未启用、导入策略拒绝或对端未导出,而不一定是 TCP 故障。RFC 8212 对 eBGP 缺省导入/导出策略给出限制;实际产品还应核对版本与配置模式。RFC 4271、RFC 8212:显式 eBGP 策略
选路:用候选表解释优先级
本例路径都有效、NEXT_HOP 可解析,且未出现更早的本地属性差异。A 的 LOCAL_PREF=200,高于 B 的 100,因此即使 A 的 AS 序列更长也选 A。把 A 改为 100 后,在其余前序条件相同时才比较路径长度,B 获选。
| 属性/条件 | 典型用途 | 边界 |
| LOCAL_PREF | 在 AS 内表达出口偏好,通常高者优先 | 普通 eBGP 不向外传播,不能要求对方采用本地值 |
| AS_PATH | AS 级路径信息与防环,常偏好较短序列 | 不代表链路延迟、带宽或物理距离 |
| ORIGIN | 路由起源属性的协议分类 | IGP 值不证明路径运行 OSPF,也不是起源认证 |
| MED | 向相邻 AS 提示多入口偏好,常低者优先 | 默认通常限来自同一相邻 AS 的候选比较 |
| NEXT_HOP 的 IGP 代价 | 同等条件下选择域内到出口的成本 | 下一跳首先要可达 |
| 本地 weight、最终破同分项 | 实现或配置增加的选择规则 | 不属于所有厂商统一的固定列表 |
应从正在使用的实现文档核对完整顺序与开关;例如 FRR 的 MED 比较、多路径放宽和 router-ID 相关选项会影响结果。教学中只对明确假设下的候选做推演,避免把 Cisco、Huawei、FRR 的规则混成一份所谓标准算法。FRR BGP 选路文档
路径更短,为什么还是没被选中?
先用较高的 LOCAL_PREF 抵消 A 的较长路径,再把偏好调成相同,才能看到后续条件开始发挥作用。关闭下一跳可达性时,A 会在进入这两个数值比较之前被排除。图的顺序只代表这里明确限定的候选集合。
正在呈现知识画面
路径更短,为什么还是没被选中?候选先要可用,再按明确的优选顺序比较。后面的路径长度不能逆转已经由 LOCAL_PREF 决定的结果。
{"id":"bgp-candidate-order","title":"路径更短,为什么还是没被选中?","summary":"候选先要可用,再按明确的优选顺序比较。后面的路径长度不能逆转已经由 LOCAL_PREF 决定的结果。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"pref\" data-i18n=\"pref\"></label><output for=\"pref\" id=\"pref-value\"></output><input id=\"pref\" type=\"range\" min=\"50\" max=\"200\" value=\"200\" step=\"10\"></div><div class=\"control\"><label for=\"length\" data-i18n=\"length\"></label><output for=\"length\" id=\"length-value\"></output><input id=\"length\" type=\"range\" min=\"1\" max=\"6\" value=\"3\" step=\"1\"></div><label class=\"check\"><input type=\"checkbox\" id=\"reachable\" checked><span data-i18n=\"reachable\"></span></label><section class=\"panel\"><h3 data-i18n=\"candidates\"></h3><div id=\"candidates\"></div></section><div class=\"metrics\"><div><span data-i18n=\"selected\"></span><strong id=\"selected\"></strong></div></div><p id=\"reason\" class=\"message\"></p><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.metrics strong{font-size:20px}","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;}));}function draw(){const p=+$('#pref').value,len=+$('#length').value,ok=$('#reachable').checked,selected=!ok?'B':p!==100?(p>100?'A':'B'):len!==2?(len<2?'A':'B'):'A',reason=!ok?'NEXT_HOP: A unavailable':p!==100?'LOCAL_PREF: '+p+' vs 100':len!==2?'AS_PATH length: '+len+' vs 2':'tie → A';set('pref-value',p);set('length-value',len);table('candidates',[['path','LOCAL_PREF','AS_PATH'],['A',p,len+' AS'],['B',100,'2 AS']]);set('selected',selected);set('reason',reason);$('#selected').dataset.reason=reason;}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"candidates":"候选对照","heading":"路径更短,为什么还是没被选中?","intro":"分别调整两条同前缀路径的 LOCAL_PREF 与 AS_PATH 长度,再撤掉下一跳可达性。","length":"A 的 AS_PATH 长度","pref":"A 的 LOCAL_PREF","reachable":"A 的下一跳可达","reason":"第一个决定性条件","reset":"重置","scope":"两条路径除LOCAL_PREF、AS序列长度和下一跳可达性外条件相同;同分按A显示。不模拟MED、实现特有weight、多路径或收敛。","scopeLabel":"模型范围与约定","selected":"选中路径"}}
防环、iBGP 与路由反射
普通 eBGP 通告会把本地 AS 加入路径,收到含本地 AS 的路径通常排除;iBGP 不为同一 AS 内传播重复增加本地 AS。这个机制抑制 AS 级路由环,但不能替代全部数据平面环路与错误策略检查。
传统 iBGP 从一个 iBGP 邻居学到的路由不会再直接通告给另一个 iBGP 邻居,因此简单互联并不自动形成完整传播。全互联、路由反射与联盟分别解决规模或组织问题;反射器依赖 ORIGINATOR_ID、CLUSTER_LIST 等附加机制防止相应反射环路。RFC 4456:路由反射
iBGP 会话可跨多跳建立,而路由中的 NEXT_HOP 仍必须能递归解析。next-hop-self 是改变下一跳通告的一种策略选择,不是所有部署都必须无条件开启。
Communities 与流量工程:标签需要策略解释
普通 Community 是 32 bit,常写成两个 16 bit 十进制值;Extended 为 8 B,Large 为 12 B,不能认为三者仅显示格式不同。RFC 8092:Large Communities普通标签没有自动动作,双方必须约定怎样匹配和实施策略。
NO_ADVERTISE 限制向任何 BGP 邻居通告;NO_EXPORT 的边界是联盟边界,不能粗略等价成任何形式的 eBGP 会话都禁止传播。联盟内部还需要区分 NO_EXPORT_SUBCONFED 等语义。RFC 1997:标准 Communities
AS_PATH prepend 可以增加某一路径的表观长度,影响远端入站选择,但远端 LOCAL_PREF 可能先决定结果。因此“多 prepend 两次就一定改走另一个出口”不成立。出站偏好由自己的策略直接控制较多,入站通常只能通过通告和约定影响对方。
ECMP:分担的是散列结果,不是精确字节配额
多个符合条件的下一跳可装入 FIB,按地址、端口、Flow Label 或其他所选字段散列。具体字段取决于协议族、内核与配置;Linux 文档提供 fib_multipath_hash_policy,并非永远只使用源/目的 IP。
在稳定成员集与散列策略下,同一流通常保持路径一致以减少乱序。成员增减或路由变化可能重映射流;即使权重 2:1,大流大小不均也会使实际字节量远离 2:1,更不保证单连接获得两条链路之和的吞吐。Linux 多路径散列配置
收敛与起源验证:别只看绿色会话状态
一条路径失效后,设备需要检测、撤回或排除候选、重新优选并安装 FIB,再把变动传播给其他节点。BFD 等可改变检测环节,却不消除整个链条的开销。Graceful Restart 保留旧转发表的假设也要成立,否则保留状态可能延长黑洞。
RPKI 起源验证比较路由前缀、起源 AS 与授权信息,可得到 Valid、Invalid、NotFound。Valid 不认证整个 AS_PATH,也不保证不存在路由泄漏;NotFound 不等于明确错误。是否丢弃或调整优先级需要策略兑现。RFC 6811:起源验证
核查时依次回答:收到什么、被哪条策略处理、为何获选或淘汰、NEXT_HOP 怎样解析、是否安装,以及真实包和回程是否通过。这样可以把“BGP 有路由但不通”拆成具体可验证的问题。
本地推演:前缀与策略先后
from ipaddress import ip_network, ip_address
def lookup(address, entries):
matches = [(ip_network(prefix), hop) for prefix, hop in entries
if ip_address(address) in ip_network(prefix)]
return max(matches,key=lambda item:item[0].prefixlen)[1] if matches else None
main = [('0.0.0.0/0','default'),('203.0.113.0/24','A'),('203.0.113.128/25','B')]
assert lookup('203.0.113.140',main) == 'B'
assert lookup('203.0.113.20',main) == 'A'
policy_table = [('0.0.0.0/0','C')]
assert (lookup('203.0.113.140',policy_table) or lookup('203.0.113.140',main)) == 'C'
paths = [('A',200,[64501,64502,64503]),('B',100,[64504,64503])]
def best(candidates):
return min(candidates,key=lambda x:(-x[1],len(x[2])))[0]
assert best(paths) == 'A'
paths[0] = ('A',100,[64501,64502,64503])
assert best(paths) == 'B'
assert min(10+10,5+30) == 20
print('最长前缀、策略表优先、两项 BGP 候选与路径代价检查通过')
这不是完整 BGP 实现,也不模拟 MED、路由反射或收敛计时。
继续查阅