1. 从一个真实运维场景说起:为什么BGP属性值得逐条验证
刚接手某跨区域网络运维的时候,我遇到过一个特别典型的问题:两个机房之间的专线明明显示UP,路由表里也能看到对方网段,但业务流量就是时通时断,丢包率在高峰期能飙到30%以上。当时第一反应是物理链路问题,查了光衰、查了接口错包,全都正常。后来抓包看BGP报文才发现,问题出在AS_Path属性上——两边配置的路径预追加策略不一致,导致去程和回程走了完全不同的路径,一条走的是低带宽备份链路,另一条走的是主链路,流量一来一回不对称,TCP重传率自然居高不下。
这件事让我意识到一个很朴素的道理:BGP能通不代表BGP走对了。很多运维同行在配置完BGP邻居之后,看到show ip bgp summary里状态是Established就认为万事大吉,但实际上BGP选路是一个极其复杂的多属性决策过程,任何一个属性的细微差异都可能导致流量走向完全偏离预期。而验证这些属性,恰恰是日常运维中最容易被忽略、出问题时又最难排查的环节。
这篇内容就是围绕BGP各种属性的验证分析展开的。我会把BGP的核心属性逐个拆开,讲清楚每个属性是干什么的、怎么验证它是否按预期生效、验证过程中容易踩哪些坑,以及当多个属性同时作用时怎么判断到底是谁在“做主”。适合已经了解BGP基础概念、但想在排错和验证层面更进一步的网络运维人员阅读。如果你还不太清楚BGP的基本邻居建立流程,建议先补一下基础再来,因为下面的内容会直接进入属性验证的实操层面。
需要提前说明的是,BGP属性验证这件事,光看文档是学不会的。你必须在一个可以随意折腾的实验环境里反复试,把每个属性的效果亲眼看到,才能真正形成肌肉记忆。所以我在讲每个属性的时候,都会给出具体的验证命令和预期输出,你可以直接在自己的实验环境里复现。
2. 验证BGP属性前必须搞清楚的三个底层逻辑
在逐个拆解属性之前,有几个底层逻辑必须先说清楚。如果不理解这些,后面验证的时候很容易得出错误结论。
2.1 BGP选路是逐条比较的,不是打分制
很多人下意识地以为BGP选路像考试一样,每个属性打一个分,最后算总分。实际上BGP的选路过程是逐条属性依次比较的淘汰制。思科体系下的选路顺序大致是:Weight(权重,厂商私有)→ Local Preference(本地优先级)→ 本地始发路由 → AS_Path长度 → Origin(起源)→ MED(多出口鉴别器)→ eBGP优于iBGP → IGP度量值 → 路由年龄等。
这意味着什么呢?意味着如果Weight已经分出了胜负,后面的属性根本不会参与比较。我在验证的时候经常遇到一种情况:明明MED配得好好的,但流量就是不按MED走,原因就是前面的Local Preference已经把结果定死了。所以验证任何一个属性之前,先确认它前面的属性是否已经产生了明确的优选结果,否则你看到的“属性没生效”其实是“轮不到它生效”。
2.2 属性的传递范围决定了你在哪里能看到它
BGP属性分为四类:公认必遵(Well-known Mandatory)、公认自决(Well-known Discretionary)、可选传递(Optional Transitive)、可选非传递(Optional Non-transitive)。这个分类直接决定了属性能不能跨AS传递、能不能被下游看到。
举个例子,Local Preference是公认自决属性,它只在同一个AS内部传递,不会传给eBGP邻居。所以如果你在AS边界路由器上抓包想看Local Preference,是看不到的。而MED是可选非传递属性,它可以传给相邻AS,但相邻AS不会再往下传。这些传递规则直接决定了你的验证位置——你要验证某个属性,必须站在能看到这个属性的路由器上。
2.3 验证的本质是“对比”,不是“查看”
这是我最想强调的一点。很多人验证BGP属性就是敲一条show ip bgp看看属性值,看到数值对了就认为验证通过了。但属性值正确不等于选路结果正确。真正的验证必须是对比:对比配置前后的路由表变化、对比不同邻居学到的同一条路由的属性差异、对比预期路径和实际转发路径是否一致。
我通常的做法是:在验证之前先记录一份基线路由表,然后修改属性配置,再记录一份新的路由表,用diff的方式看变化。如果变化符合预期,说明属性生效了;如果没变化或者变化方向不对,就需要进一步排查。这种对比验证的方法比单纯看属性值可靠得多。
3. 权重与本地优先级:最容易被混淆的两个“本地说了算”属性
Weight和Local Preference是BGP选路中排在最前面的两个属性,它们共同的特点是只在本路由器或本AS内部有效,不影响其他AS的选路。但这两个属性的作用范围和优先级完全不同,实际验证时非常容易搞混。
3.1 Weight:厂商私有但优先级最高
Weight是某厂商私有的属性,取值范围0到65535,值越大越优先。它的作用范围仅限于配置它的那一台路由器,连同一个AS内的其他路由器都不影响。默认情况下,本地始发的路由Weight是32768,从邻居学来的路由Weight是0。
验证Weight的方法很直接:在路由器上对特定邻居或特定路由配置Weight,然后看路由表里这条路由是否被优选。但这里有个坑——Weight只影响本路由器的选路决策,不影响路由的发布。也就是说,你在这台路由器上把某条路由的Weight调高,它自己会优选这条路由,但它向邻居发布路由时,Weight值不会带出去,邻居那边看到的还是原始属性。
我踩过的一个坑是:在AS边界路由器上给某条外部路由配了高Weight,本机确实优选了,但AS内部其他路由器并不知道这个决策,它们还是按照自己的Local Preference和AS_Path来选路,导致AS内部流量走向和边界路由器不一致。所以Weight适合做单路由器的精细控制,不适合做AS级别的流量调度。
验证命令示例:
# 查看某条路由的Weight值 show ip bgp 10.1.1.0/24 # 输出中会显示类似: # BGP routing table entry for 10.1.1.0/24 # from 192.168.1.1 (192.168.1.1) # Origin IGP, metric 0, localpref 100, weight 200, valid, external3.2 Local Preference:AS内部统一的“投票器”
Local Preference是公认自决属性,取值范围0到4294967295,值越大越优先,默认值是100。它的关键特征是只在同一个AS内部传递,eBGP邻居之间不传递这个属性。当路由离开本AS时,Local Preference会被剥离。
验证Local Preference的典型场景是:一个AS有多个出口路由器,希望内部所有路由器都优先从某个出口出去。做法是在出口路由器上对从eBGP邻居学来的路由设置统一的Local Preference值,然后通过iBGP传给AS内所有路由器。
这里有一个验证要点:Local Preference必须在路由进入AS时设置,或者在iBGP邻居上设置。如果你在eBGP邻居的out方向设置Local Preference,是无效的,因为这个属性本来就不会传给eBGP邻居。我见过有同行在eBGP的outbound策略里配Local Preference,然后纳闷为什么对面不按这个走——对面根本收不到这个属性。
验证方法:
# 在AS内部路由器上查看从不同出口学到的同一条路由 show ip bgp 10.1.1.0/24 # 对比两条路径的localpref值 # 路径1: localpref 200(来自出口A) # 路径2: localpref 100(来自出口B) # 优选路径13.3 两者同时存在时谁说了算
Weight的优先级高于Local Preference。如果一台路由器上同时存在Weight和Local Preference的配置,先比较Weight,Weight相同再比较Local Preference。这个顺序在验证时非常重要,因为如果你给某条路由配了高Weight,那么无论Local Preference怎么调,都不会改变选路结果。
我通常建议:Weight用于本路由器的临时性、精细化控制,Local Preference用于AS级别的统一策略。两者不要混用在同一个选路目标上,否则排查起来非常痛苦。
4. AS_Path验证:不只是数长度那么简单
AS_Path是BGP选路中最重要的属性之一,它记录了路由经过的所有AS号。选路时AS_Path越短越优先。但AS_Path的验证远不止“数一数有几个AS号”这么简单,里面有很多细节需要注意。
4.1 AS_Path的三种匹配方式
在验证AS_Path相关策略时,首先要搞清楚你的正则表达式匹配的是什么。AS_Path的匹配有三种常见方式:
- 精确匹配:
^100$,只匹配AS_Path恰好是100的路由 - 包含匹配:
_100_,匹配AS_Path中包含100的路由,不管100在开头、中间还是结尾 - 起始匹配:
^100_,匹配AS_Path以100开头的路由
这三种匹配方式在实际验证中的结果可能完全不同。比如你有一条路由的AS_Path是200 100 300,用^100$匹配不到,用_100_能匹配到,用^100_也匹配不到。我见过有人用^100$想匹配所有经过AS100的路由,结果只匹配到了AS_Path恰好为100的那一条,其他经过AS100的路由全漏了。
4.2 AS_Path预追加的验证陷阱
AS_Path预追加(Prepend)是最常用的流量调度手段之一。通过在AS_Path前面重复添加自己的AS号,人为加长AS_Path,让对面优先走另一条路径。但预追加的验证有几个容易踩的坑。
第一个坑是预追加的方向。预追加是在出方向(outbound)做的,影响的是邻居的选路,不是自己的选路。所以验证的时候必须去邻居路由器上看路由表,在自己这边看是看不出效果的。
第二个坑是预追加的次数。追加一次和追加三次的效果可能完全不同。如果两条路径的AS_Path长度原本只差1,你追加一次可能刚好打平,追加两次才能分出胜负。验证的时候要精确计算追加后的AS_Path长度,而不是凭感觉。
第三个坑是预追加对AS_Path长度比较的影响。BGP比较AS_Path长度时,同样的AS号重复出现是算长度的。比如100 100 100 200的长度是4,100 200的长度是2,前者更长。所以预追加确实能影响选路,但要注意有些厂商在比较时会忽略重复的AS号(这个行为不一致,需要在实际设备上验证)。
验证预追加效果的步骤:
# 在本地路由器上配置预追加 route-map PREPEND permit 10 set as-path prepend 100 100 100 # 在邻居路由器上查看AS_Path show ip bgp 10.1.1.0/24 # 预期看到AS_Path类似:100 100 100 200 # 对比另一条路径的AS_Path长度,判断是否达到预期效果4.3 AS_Path环路检测的验证
BGP通过AS_Path防止环路:如果路由器在AS_Path中看到自己的AS号,就会丢弃这条路由。这个机制在验证时也需要注意。如果你在做预追加的时候不小心把自己的AS号加到了不该加的位置,可能导致邻居丢弃路由。
验证环路检测是否正常工作的方法是:在实验环境中故意构造一个包含自己AS号的路由,看路由器是否拒绝接收。这个测试在排查“为什么邻居不接收我的路由”时非常有用。
5. Origin与MED:最容易被忽略但影响巨大的两个属性
Origin和MED在BGP选路顺序中排在AS_Path之后,平时不太引人注意,但在特定场景下它们会决定流量走向。而且这两个属性的验证有一些非常隐蔽的坑。
5.1 Origin的三种取值与验证方法
Origin属性表示路由的来源,有三种取值:
- IGP(i):路由由BGP的network命令引入,优先级最高
- EGP(e):路由从EGP协议学来,优先级居中
- Incomplete(?):路由通过重分发引入,优先级最低
选路时IGP优于EGP,EGP优于Incomplete。验证Origin的方法很简单,看show ip bgp输出中每条路由的Origin字段即可。但这里有一个容易忽略的点:通过重分发引入的路由,Origin默认是Incomplete。如果你希望它显示为IGP,需要在重分发时用route-map设置Origin。
我遇到过一个案例:两条路径的AS_Path长度完全相同,Local Preference也相同,但流量就是不走其中一条。查了半天才发现,一条路由的Origin是IGP,另一条是Incomplete,选路时IGP胜出。这个属性太容易被忽略了,但在AS_Path打平的情况下,它就是决定性的。
5.2 MED的传递规则与验证位置
MED(Multi-Exit Discriminator)是可选非传递属性,值越小越优先,默认值是0。它的作用是告诉相邻AS:“从我这里进入我的AS,走这个入口更好。”但MED的传递有几个重要限制:
- MED只在相邻AS之间传递,不会继续往下传
- MED的比较只在同一条路由从同一个相邻AS的多个入口学来时才有意义
- 不同相邻AS发来的MED默认不比较(除非配置了
bgp always-compare-med)
验证MED时最常见的错误是在错误的位置看MED。因为MED不跨AS传递,你只能在直接相邻的AS边界路由器上看到MED值。如果你在AS内部路由器上看,MED可能已经被剥离了。
另一个验证要点是MED的比较条件。默认情况下,只有来自同一个相邻AS的路由才会比较MED。如果两条路由来自不同的相邻AS,即使MED值不同,也不会比较MED,而是继续往后比较其他属性。这个行为可以通过配置改变,但验证时要清楚当前设备的行为是什么。
验证MED的典型场景:
# 在AS边界路由器上查看从同一邻居不同入口学到的路由 show ip bgp 10.1.1.0/24 # 对比两条路径的MED值 # 路径1: metric 100(来自邻居的入口A) # 路径2: metric 200(来自邻居的入口B) # 优选路径1(MED值小)5.3 MED与AS_Path的配合验证
在实际流量调度中,MED经常和AS_Path预追加配合使用。比如你希望对面从入口A进来,可以在入口A上设置较低的MED,同时在入口B上做AS_Path预追加。这样对面选路时,如果AS_Path长度不同,优先看AS_Path;如果AS_Path打平,再看MED。
验证这种组合策略时,必须分别验证每个属性的效果,再验证组合效果。先确认单独调MED时对面是否按预期选路,再确认单独做预追加时对面是否按预期选路,最后两个一起上,看最终结果是否符合预期。如果组合效果不对,你就知道是哪个属性的优先级判断出了问题。
6. 属性验证的完整排查链路:一个真实案例的复现
前面讲的都是单个属性的验证方法,但实际工作中遇到的问题往往是多个属性交织在一起。下面我通过一个完整的案例,展示从发现问题到定位根因的完整排查链路。
6.1 问题现象与初步排查
某天收到反馈:从办公区访问某云服务商的延迟突然升高,从原来的20ms涨到了80ms。办公区和云服务商之间通过两条专线连接,一条是主链路(低延迟),一条是备份链路(高延迟)。正常情况下流量应该走主链路。
初步排查步骤:
- 确认物理链路状态:两条专线都UP,无错包
- 确认BGP邻居状态:两个邻居都是Established
- 查看路由表:发现去往云服务商网段的路由确实走了备份链路
到这里可以确定问题是BGP选路结果不符合预期,需要进一步分析是哪个属性导致了选路变化。
6.2 逐属性对比分析
在边界路由器上查看从两个邻居学到的同一条路由的详细属性:
show ip bgp 203.0.113.0/24输出对比:
| 属性 | 主链路邻居 | 备份链路邻居 |
|---|---|---|
| Weight | 0 | 0 |
| Local Preference | 100 | 100 |
| AS_Path | 65001 65002 | 65001 65002 |
| Origin | IGP | IGP |
| MED | 50 | 100 |
| 下一跳 | 192.0.2.1 | 198.51.100.1 |
从表格可以看出,Weight、Local Preference、AS_Path、Origin都相同,MED是主链路更优(50 < 100)。按选路规则,应该优选主链路才对。但实际路由表显示走的是备份链路,说明在MED之前还有某个属性产生了不同的结果。
继续往前排查,检查Weight和Local Preference的实际配置。发现主链路邻居的入方向策略里,Local Preference被设置成了100,而备份链路邻居的入方向策略里Local Preference被设置成了200。问题找到了:备份链路的Local Preference更高,所以在Local Preference比较阶段就胜出了,根本轮不到MED参与比较。
6.3 根因确认与修复验证
进一步排查为什么备份链路的Local Preference是200。查配置历史发现,上周有人为了临时切换流量,在备份链路邻居上设置了Local Preference 200,切换完成后忘记删除。这个残留配置导致所有从备份链路学来的路由都被优选。
修复步骤:
- 删除备份链路邻居入方向的Local Preference 200配置
- 软清除BGP邻居,使策略重新生效
- 验证路由表是否恢复走主链路
# 删除错误配置后 clear ip bgp 198.51.100.1 soft in # 验证路由表 show ip bgp 203.0.113.0/24 # 确认主链路路由被优选修复后再次查看路由表,确认去往云服务商网段的路由已经恢复走主链路,延迟也降回了20ms。
6.4 从这个案例中提炼的验证经验
这个案例给我最大的教训是:验证BGP属性时,必须按照选路顺序逐条检查,不能跳步。如果一上来就盯着MED看,永远找不到问题,因为MED根本没参与比较。
我现在养成了一个习惯:每次排查BGP选路问题,都按照选路顺序列一个检查清单,从Weight开始逐条确认,直到找到产生差异的那个属性为止。这个清单看起来笨,但非常有效,能避免在错误的属性上浪费时间。
另外,配置变更后的清理工作非常重要。临时性的策略调整一定要有记录和回滚机制,否则残留配置会在未来某个时刻突然“发作”,而且因为时间久远,排查起来更加困难。
7. 属性验证中那些文档不会告诉你的实操细节
前面讲的都是方法论层面的东西,这一节分享一些我在实际验证中积累的、文档里通常不会写的细节和经验。
7.1 软清除不是万能的
修改BGP策略后,通常需要用clear ip bgp soft来让策略重新生效。但软清除有一个限制:它只能重新应用入方向或出方向的策略,不能触发完整的路由重新计算。如果你修改的是影响选路的属性(比如Local Preference),软清除后路由表会更新;但如果你修改的是影响路由发布的策略,可能需要硬清除才能完全生效。
硬清除会中断BGP邻居,生产环境慎用。我通常的做法是:先在实验环境验证策略效果,确认无误后再在生产环境用软清除应用。如果软清除后效果不符合预期,再考虑硬清除。
7.2 路由反射器环境下的属性验证
在大型AS内部,通常会部署路由反射器(RR)来减少iBGP全互联的复杂度。但RR环境下验证属性有一个特殊之处:RR反射路由时不会修改大部分属性,但有一些属性在反射过程中可能会被改变。比如Originator ID和Cluster List是RR添加的,验证时要注意区分。
另外,RR环境下从客户端学到的路由和从非客户端学到的路由,反射规则不同。验证属性时如果发现某台路由器学不到某条路由,可能是RR的反射规则导致的,而不是属性本身的问题。
7.3 多厂商环境下的属性行为差异
不同厂商对BGP属性的实现有一些细微差异,这些差异在验证时可能导致困惑。比如:
- 某些厂商默认不比较来自不同AS的MED,某些厂商默认比较
- 某些厂商对AS_Path中重复AS号的计数方式不同
- 某些厂商的Weight默认值不同
在多厂商环境中验证属性时,不要假设所有设备的行为一致。遇到不符合预期的结果时,先查一下当前厂商的默认行为和配置选项,确认是不是厂商差异导致的。
7.4 验证脚本的编写思路
如果经常需要验证BGP属性,可以写一些简单的脚本来自动化对比。比如用Python的netmiko库登录多台路由器,抓取路由表,然后对比同一条路由在不同路由器上的属性差异。
脚本的核心逻辑不复杂:登录设备→执行show命令→解析输出→提取关键属性→对比差异→输出报告。关键是要把解析逻辑写健壮,因为不同厂商、不同版本的输出格式可能有差异。
我自己的做法是先用脚本抓取原始输出保存下来,再用文本对比工具人工分析。这样既提高了效率,又保留了人工判断的灵活性。完全自动化的判断容易漏掉一些需要经验才能发现的异常。
8. 把属性验证变成日常习惯
聊了这么多,最后说点个人体会。BGP属性验证这件事,最大的难点不在于理解每个属性的含义,而在于养成按顺序、按层次验证的习惯。我见过太多同行,配置完BGP之后只看邻居状态和路由表有没有路由,从来不验证选路结果是否符合预期。等到出问题的时候,才发现流量早就走偏了。
我现在的工作流程是:每次BGP策略变更后,必须做三件事。第一,记录变更前的路由表基线;第二,变更后对比路由表变化,确认变化符合预期;第三,在业务侧验证实际转发路径,确认流量走向正确。这三步做完,才算真正验证通过。
另外,实验环境里的验证和生产环境的验证是两回事。实验环境里你可以随便折腾,把每个属性的效果都试一遍;生产环境里你只能做最小化的验证,因为任何操作都可能影响业务。所以我的建议是:在实验环境里把属性验证练熟,形成自己的检查清单和验证脚本,然后在生产环境里快速执行。这样既能保证验证质量,又能控制风险。
BGP属性验证不是什么高深的技术,但它需要耐心和细致。每一个属性都搞清楚它的作用范围、传递规则、比较顺序,然后在实际环境中反复验证,你就能在遇到选路问题时快速定位根因,而不是像无头苍蝇一样到处乱撞。