BGP作为骨干网和IDC出口最常用的动态路由协议,平时老老实实跑着的时候没人想起它,一旦链路闪断、设备重启或者上游做了策略调整,各种奇奇怪怪的问题就全冒出来了。很多时候业务中断并不是BGP本身down了,而是主备切换瞬间路由收敛行为跟预期不一致,导致流量黑洞或者来回路径不一致。这篇文章我就结合自己实际抓包排障的经历,聊聊BGP主备倒换的那些坑,以及不对称路由到底是怎么产生的、怎么从报文层面去定位。
先说一个我印象特别深的故障。某机房核心出口做了双机冗余,一台主一台备,上行分别对接两个运营商。某天凌晨主设备说要升级内存,我提前把业务切到备机,结果切完之后业务全部超时。当时第一反应是路由没过去,但登到备机上show bgp neighbor一看,邻居状态都是Established,路由表也有。后来在备机的上联口抓包才发现,它一直在往外发BGP Update,但回包全部丢了——对端运营商还在往主设备的物理地址发下一跳报文,备机收到的BGP报文源MAC全是主机的。这就是典型的“路由策略切换了,但转发面并没有真正切换干净”的场景。
这类问题靠看路由表是看不出来的,必须在报文转发的关键路径上抓包对比,才能定位到是哪一跳出了问题。后面我会把抓包拆解的思路、主备倒换的完整过程,以及不对称路由的产生机理和规避方式一条条说清楚。
1. 内容整体设计与思路拆解
1.1 从“冗余”到“冗余陷阱”:主备倒换为什么这么容易出事
网络架构里做BGP主备设计,本质上是想消除单点故障——一台设备挂了,另一台立刻接管流量。这个思路本身没问题,问题出在“接管”这两个字上。很多人理解的主备切换就是把优先级调一下,让备份路由器开始干活,但实际生产环境里,切换涉及的东西远比“多了一条路由”复杂。
先用一个简单拓扑把场景说清楚。核心交换机下挂两台出口路由器,Router A作为主,Router B作为备,两台路由器分别跟两个上游运营商建立EBGP邻居,同时跟核心交换机建立IBGP邻居。正常状态下,Router A通过EBGP学到运营商A的默认路由,Router B通过EBGP学到运营商B的默认路由,两台路由器再把这些路由通过IBGP互相宣告,同时各自向核心交换机下发默认路由,Router A的优先级更高。
这个架构里,主备切换不仅仅是一台设备的事情,它牵涉到三个层面的联动:
- 控制层面:BGP邻居状态要重新收敛,路由要重新计算
- 数据层面:核心交换机的默认路由下一跳要从Router A切到Router B
- 物理层面:如果路由器跟上联运营商之间还有二层透传设备,MAC表项也要跟着刷新
这三层不是同步完成的,时间差就是故障窗口。BGP协议本身收敛时间可以很快,但底层转发表、ARP表、MAC表的刷新速度未必跟得上,于是就会出现“路由已经切换了,但流量还往老路径上送”的尴尬局面。我见过很多次所谓的BGP倒换故障,最后查下来根本不是BGP协议有问题,而是硬件转发表项切换太慢或者根本没触发切换。
这也是为什么我强调要在数据面抓包验证,而不是只看路由表和控制面的状态。控制面说“我切了”,不代表数据面真的切了,报文才是最终的真相。
1.2 不对称路由的成因分类:先分清“天生”和“后天”
不对称路由这个词在BGP场景下特别容易让人迷糊,因为它的成因太杂了。同样表现为来回路径不一致,底层的原因可能完全不同。我习惯把不对称路由分两类:一类是“天生不对称”,一类是“后天不对称”。
天生不对称指的是网络设计上就是往返路径不一样的。比如访问内部服务器的时候,去程走运营商A的线路,回程因为源地址策略路由走运营商B的线路。这种设计在很多做多出口负载均衡的IDC里是故意为之的,本身不算故障。反方向是好的,但如果中间有一台防火墙或者IPS做状态检测,这种设计就会出大事,防火墙上只能看到单向流量,会话直接给你拦掉。
后天不对称指的是正常情况下是对称的,因为某些异常事件变成了不对称。主备倒换就是最常见的诱因。举个例子,正常时候去程和回程都走Router A,某次链路抖动Router A的EBGP邻居闪断又恢复,但这段时间里Router B已经接替了去程流量。等Router A恢复以后,去程因为本地优先级还是走Router A,回程却可能因为Router B学到的是更优的路由而继续走Router B,于是来回路径就岔开了。
搞清楚这两类的区别,对排障思路的影响非常大。天生不对称需要从设计层面调整,后天不对称则要先找到触发事件,再针对性做收敛优化或者路由策略修正。但不管哪一类,定位思路都是一样的——抓包看路径,看报文的进出接口,对比正常基线和异常时刻的差异。
2. 核心细节解析与实操要点
2.1 BGP主备倒换的完整过程拆解
BGP主备倒换不是一蹴而就的,它是一连串事件按照特定顺序推进的过程。我用一次典型的手动主备切换来拆解这个过程,因为手动切换是可控的,每一步都能观测,理解了手动切换,自动故障切换就是一个加速版。
整个切换流程大致是这么走的:
第一步,调整路由优先级。把Router B从备份状态提升为主状态,通常通过修改本地优先级(Local Preference)或者MED值来实现。如果是用路由策略控制,则重新下发新的route-map,让Router B学到的路由优先级高于Router A。这一步做完,控制面的路由选择开始偏向Router B。
第二步,BGP路由重新计算。由于路由优先级变了,Router B上原本处于备选状态的路径变成最优路径,它需要把这条新路径通告给自己的IBGP邻居,也就是核心交换机。核心交换机收到Update报文后,替换原有的最优路径。
第三步,核心交换机更新转发表。这是最关键的一步。控制面收到了新的路由信息,但硬件转发表是否立刻更新,取决于设备的实现机制。大多数情况下会有一个短暂的收敛窗口,这个窗口内可能出现丢包。
第四步,物理链路层面完成切换。Router B开始承担流量转发后,它跟上游运营商之间的链路开始有实际的业务流量通过,如果上游设备此前已经把流量送到Router A,还需要等待MAC地址表老化或者由 gratuitous ARP 来刷新。
这个过程里最容易出问题的就是第三步和第四步之间的空隙,以及第一步之后第二步之前的窗口。前者是本地转发表切换慢,后者是BGP收敛本身的延迟。
在实际操作里,手动切换我还遇到过更隐蔽的问题。Router A和Router B跟核心交换机之间的互联如果是以太网链路,切换后核心交换机的ARP表项可能还指着Router A的接口MAC。Router B发出的报文能到达核心交换机,但核心交换机回包还是发给Router A,于是Router A明明已经不再承担转发任务了,却还在不断收到回程流量,这些流量又因为没有对应的转发条目而被丢弃。这种故障用一句话概括就是:路由层面切了,但二层转发层面没切干净。
2.2 主备倒换的核心参数配置与选型逻辑
不同厂商的设备上,BGP主备倒换的配置方式有差异,但核心参数的概念是通用的。我挑几个最关键的说一下,这些参数决定了倒换的速度和可靠性。
首先是最常用的本地优先级(Local Preference)。这个值只在AS内部传递,默认值是100,越大越优。主备场景下通常把主设备的本地优先级调高,比如主设备设为200,备设备保持100。这样即使两条路径都学到了,流量也一定优选主设备。这里常见的坑是:local-preference只影响本AS内的选路,不会传给EBGP邻居。如果上游有两个运营商,你想让运营商A的流量过来走主设备,光设local-preference是不够的,因为运营商的选路发生在他们的AS内部。
第二个是MED值,这个值会传递给对端AS,用于影响对端进入本AS的选路。主备倒换时如果出现入向流量不切换的情况,多半要调整发给上游的MED值。但注意,MED默认只在相邻AS之间传递,多跳EBGP环境下可能被重置,这又是一个隐藏坑。
第三个是AS Path,通过附加AS号来降低某条路径的优先级。这种方式操作起来直观,但会让路由表变得难看,也增加了排障时读路由的难度,我一般不太推荐在核心链路上用AS Path做主备控制。
然后是BGP定时器。Keepalive默认60秒,Hold time默认180秒,这两个值决定了BGP邻居对链路故障的感知速度。生产环境里很多故障之所以恢复慢,就是因为链路上加了传输设备,物理层断了BGP不一定立刻感知,要等Hold time超时才重新收敛。把Keepalive调到3秒,Hold time调到9秒(即3倍关系)是常见的优化手段,但这属于刀尖舔血的操作。定时器调的太短,网络轻微拥塞就可能误判邻居故障,引发频繁倒换。我见过某机房为了追求快速收敛把Hold time设成6秒,结果一遇上广播风暴,两台路由器疯狂互相判死,业务直接瘫痪。所以调定时器之前,先想清楚你的网络抖动容忍度是多少。
最后还有一个经常被忽略的参数:GTSM(通用TTL安全机制),它通过限制BGP报文的TTL值来防止伪造BGP报文的攻击。主备倒换场景下如果两台设备的互联链路经过了多跳,TTL限制会导致BGP邻居起不来,这属于配置层面很容易踩的坑。
2.3 抓包定位的诊断架构设计
遇到BGP主备倒换类故障,很多人的第一反应是登录设备看日志。日志当然要看,但日志只能告诉你控制面发生了什么,数据面是否真的按预期转发,必须靠抓包来验证。
抓包诊断的第一步是选好抓包点。原则上讲,抓包点应该放在所有关键转发路径上,但现实是IDC的网络拓扑往往比较复杂,不可能每个接口都抓。我一般按优先级从高到低选三个位置:
第一个位置是核心交换机的上行接口(连接Router A和Router B的接口)。这个位置能看到核心交换机往哪台路由器发送流量,以及从哪台路由器接收到流量,是判断主备是否真正切换的最直接证据。
第二个位置是两台出口路由器的上联接口。这里能看到BGP报文和业务报文的进出情况。特别是BGP报文,如果在主设备的上联接口上还能抓到BGP报文,说明邻居关系还在,但业务流量已经不再从这里经过了,需要进一步确认原因。
第三个位置是核心交换机下联到业务侧的接口。如果故障表现为部分业务通、部分业务不通,这个位置的抓包能帮你缩小范围,判断是路由问题还是二层问题。
抓包工具方面,命令行环境下tcpdump是首选,简单可靠,几乎任何Linux设备都能用。关键要掌握几个实用选项:-i指接口,-s 0抓全包(避免截断导致分析困难),-w写文件方便后续分析。带BGP环境的网络里,还建议加上 -s 96 这种精确长度选项,让单个报文只保留足够分析头部的长度,降低磁盘占用。
在Windows环境或者带图形界面的抓包终端上,Wireshark更直观。BGP协议Wireshark支持得非常好,能直接解析出NLRI里的前缀、AS Path、Local Pref等信息,比人肉看十六进制高效太多。但要注意,Wireshark抓包本身是带性能开销的,在核心设备上长时间全量抓包可能引发CPU升高,影响转发性能。所以生产环境里我通常是短时间定向抓包,抓到关键报文就停,然后离线分析。
3. 实操过程与核心环节实现
3.1 模拟主备倒换的典型环境搭建
排障经验的积累离不开复现环境。我自己习惯在eNSP或者GNS3里搭一套BGP主备倒换的实验环境,跑通以后再对照生产环境的抓包结果来分析。虚拟环境做不到100%还原真实硬件的行为差异,但协议交互流程是一模一样的,足够验证大部分逻辑问题。
实验拓扑大概这样:三台路由器R1、R2、R3,R1模拟运营商A,R2模拟运营商B,R3作为核心交换机。两台出口路由器R4和R5分别连接R1和R2,同时通过交换机连接R3,R4和R5之间建立IBGP邻居。网络规划上,R1和R2各宣告一条网段,R4和R5向R3宣告默认路由,R4的优先级高于R5。
配置的时候有几个地方要特别注意。R4和R5之间的IBGP会话要设置loopback地址作为更新源,保证物理链路断了会话还能维持,这是很多BGP实验环境起不来的常见原因。R3上则要配置两条默认路由,分别指向R4和R5,管理距离或者优先级让R4的路径更优。
在这个环境里模拟主备倒换,最常用的手段是直接shutdown R4的上联接口,强制流量切换到R5。这种方式最接近真实的链路故障,也最方便观察整个收敛过程。如果要模拟设备重启,可以在R4上执行reload观察启动后的重新收敛。相比之下,route-map策略切换因为不涉及物理链路变化,表现会更平滑,但实际生产故障很少是这种温和的切换。
3.2 抓包数据的关键特征与逐包解读
在实验环境里跑一次主备倒换,然后在R3连接R4的接口上抓包,会看到非常清晰的几个阶段。
第一阶段是BGP Keepalive报文的周期性出现。正常状态下每隔几秒就有一次,这是邻居关系健康的标志。第二阶段是链路故障瞬间的报文异常,R4的接口shutdown后,Keepalive中断,R3和R4之间的邻居关系开始进入超时倒计时。第三阶段是路由撤销和重计算,Hold time超时后,R4向R3发送BGP Notification报文,宣告会话终止,R3清除从R4学到的路由。第四阶段是路径切换,R3的路由表重新计算,把默认路由的下一跳切到R5,同时开始向R5发送数据流量。
逐包解读的时候,有几个最容易忽略的信号:
- BGP Open报文里的Hold Time字段,它决定了后续的故障感知速度,如果两台设备配置的Hold Time不一致,会以较小值为准
- Update报文里的NLRI字段,主备倒换后新的Update报文的AS Path长度往往更长或更短,这个变化直接反映了路径优先级的调整
- Notification报文的Error Code和Subcode,比如Cease(错误码6)代表会话被人为关闭,如果排障时看到大量的Cease,多半是有人改了配置,而不是链路故障
这里还要提到一种特别迷惑性的抓包现象。主设备已经不再承担转发任务了,但它的上联接口还能抓到流量。原因可能是上游路由器的转发项还没老化,仍然把流量送到主设备;也可能是主设备的接口处于二层透传模式,流量只是穿过去并非目的地。这时候光看图不看接口信息,很容易误判。
我自己的经验是,抓包后第一件事不是看BGP报文,而是先统计一下接口上的流量构成。用tcpdump抓个一分钟的包,大概统计一下BGP报文、ARP报文、ICMP报文、业务TCP报文的比例,再跟正常基线做对比。这个动作看似简单,但能非常快地定位问题大致方向,比钻进BGP报文里逐字节分析高效得多。
3.3 典型案例复盘:双出口主备切换后的流量黑洞
用一个真实案例来把前面的理论串起来。这个案例是我去年处理的,典型的双出口主备架构,主设备Router A通过运营商M上联,备设备Router B通过运营商N上联,核心交换机同时连接Router A和Router B。某天晚上Router A的CPU突然飙高,远程登录已经非常卡顿,我们决定做一次手动主备切换,把流量切到Router B上。
切换动作很快完成了,Router B的BGP邻居全部Established,路由表正常,看起来一切顺利。但业务监控立刻报警,海外客户的访问量断崖式下跌,国内客户倒是没什么影响。
我第一时间在核心交换机的上行口抓包,发现核心交换机已经把去往出口的流量全部从Router A的接口切到了Router B的接口,这部分是正常的。但再看Router B的上联接口抓包,发现报文只出不进——Router B持续往外发送TCP SYN报文和BGP Keepalive报文,但对方的回包全部没有出现。
继续在上联口的入方向抓包,发现了一个非常重要的现象:Router A的接口仍然不断收到来自运营商M的回包,但这些报文的源MAC已经指向了运营商M的出口网关,目的MAC还是Router A的接口MAC。也就是说,运营商M的出口设备根本不知道主备已经切换,它还在把回程流量往Router A的物理地址上送。Router A收到这些回程报文以后,因为自己的路由优先级已经降级,而且默认路由的下一条已经指向了Router B,所以直接丢弃或转发给了核心交换机,但核心交换机认为回程流向已经切换到Router B了,对来自Router A的报文处理策略又跟预期不一致,最终结果就是回程流量黑洞。
这个案例最典型的启示是:主备切换不只是本地设备的事情,对端设备也需要感知到切换的发生。本地可以快速完成路由计算和转发表更新,但上游设备什么时候把流量切过来,取决于它的故障感知能力和收敛速度。如果上游设备没有及时检测到路径变化,流量黑洞就会持续。
解法有几个层面:一是让Router A在切换时主动向上游发送BGP路由撤销,明确告诉运营商M这条路径已经不可用;二是配合BFD,让链路状态变化很快传递到上游;三是如果条件允许,通过向运营商申请BGP会话的附加路径(Add-Path)能力,让两台路由器同时向上游通告路由,由上游自行选路,从机制上消除切换延迟。
4. 常见问题与排查技巧实录
4.1 BGP主备倒换中的典型故障问题速查
踩的坑多了,慢慢就形成了一张问题速查表。遇到主备倒换问题的时候,我一般先对照这张表做初步判断,能省去大量盲目的排查时间。
现象一:切换后全部流量不通。优先检查核心交换机的默认路由下一跳是否已经切换,以及Router B的上联链路是否真的能通。用ping测一下Router B到运营商网关的连通性,再检查Router B是否学到了完整的路由表。有一种情况是Router B的EBGP会话虽然建立了,但收到的路由条目被入口策略过滤掉了,导致路由表不完整。
现象二:部分流量通,部分流量不通。这种大概率是不对称路由加状态检测设备导致的。检查去程路径是否经过防火墙,回程路径是否绕过了防火墙。如果在流量路径里有防火墙,必须确认防火墙上的会话表是否能看到完整的双向流量。从设计角度说,主备切换后所有流量必须保证往返路径经过同一台防火墙,否则会话必断。
现象三:切换后延迟急剧增大,但丢包并不多。这种通常不是BGP收敛问题,而是切换后的路径绕远了。比如Router A本来直连运营商M,Router B的上游运营商N跟目标网络的互联带宽不够,或者出口的RTT天然就高。这种情况需要先确认切换后的真实路径,再决定是否要调整路由策略。
现象四:反复倒换,抖动不停。这是最烦人的一个问题。排除硬件故障后,最常见的原因是BGP定时器设置不合理。Keepalive时间设得太短,链路稍有一点拥塞或者队列延迟抖动,就触发Hold time超时,邻居关系闪断,恢复后另一台设备又因为同样原因闪断,两台设备来回抢主。这种场景下先检查两端设备的BGP计时器配置,再排查链路质量,不要急着改协议参数。
现象五:切换成功,但路由表中出现了不该有的路径。比如Router A和Router B之间通过IBGP交互路由时,可能因为水平分割规则以外的配置问题导致路由环路。查看BGP路由表里的下一跳属性,确认没有指向自己的下一跳。
4.2 从抓包视角的独家排障技巧与心得
最后分享几个我自己总结的独家排障技巧,这些很难从官方文档里学到,全是实战里熬出来的经验。
第一个技巧是抓包前先确认抓包点的端口镜像方向。很多交换机做端口镜像时默认只镜像入方向或者出方向,如果没选对方向,最容易出现的假象是“出方向全是报文,入方向一个包都没有”,然后误判为链路单向不通。我习惯在做镜像配置时同时开入方向和出方向两个会话,分别存文件,分析时对照看。
第二个技巧是善用BGP的tcpdump过滤表达式。抓BGP报文不用把整个链路的流量全抓下来,一条精准的过滤表达式能省下大量的时间和磁盘空间。比如抓跟某个邻居的BGP报文,可以写成:
tcpdump -i eth0 -s 96 -w bgp_capture.pcap tcp port 179 and host 192.0.2.1如果确认是IPv6环境,记得加上 ip6 关键字。还有一种情况是BGP跑在非标准端口上,需要针对性改动端口号。
第三个技巧是Wireshark里用Follow TCP Stream功能分析BGP会话。BGP报文虽然承载在TCP之上,但它的状态机变化非常复杂,用Follow TCP Stream可以快速看到一次会话从Open到Update再到Notification的全过程。特别是看到BGP Notification报文里的错误码,可以立刻定位到问题的具体类型。比如错误码2代表Open消息错误,通常是参数协商不一致;错误码3代表Update消息错误,通常是路由属性有问题。Wireshark对BGP协议的解析深度足够,不需要什么高端功能,基础的过滤和分析就够用了。
第四个技巧是抓包时间基准。分析抓包文件时,要同时打开绝对时间和相对时间两种显示模式。绝对时间用于跟设备日志做时间轴对齐,确认事件顺序;相对时间用于精确计算各阶段耗时,比如从链路中断到路由切换完成一共花了多少毫秒。这种时间分析对优化收敛速度特别有用,可以明确知道瓶颈出在BGP检测阶段、路由计算阶段还是转发表下发阶段。
第五个技巧是别忽略ARP和NDP报文。BGP主备倒换场景下,很多问题其实出在二层地址解析上。切换后核心交换机要刷新ARP表,如果ARP学习失败或者学到的是旧的MAC地址,流量就会一直送给错误的设备。我在任何一次主备倒换排障里,都会顺手看一下抓包里有没有异常的ARP请求风暴或者ARP欺骗报文。
4.3 从排障到预防:主备倒换的运维改进建议
排障排到最后,总归要回到一个问题上:下次怎么避免同样的事情发生。基于几次重大的故障复盘,我总结了一套主备倒换的运维改进清单,分享出来供参考。
第一,建立主备倒换的标准化操作流程。切换不是拍脑袋决定的事,从切换前检查(确认备机状态、确认上游链路冗余、确认业务低谷期)、切换动作执行(按步骤操作、每步确认状态)、切换后验证(业务探测、路径确认、持续观察一段时间),每一步都要有明确的执行标准和责任人。我见过太多次切换失败是因为切换前没检查备机的CPU状态,上去以后备机直接被打满。
第二,引入持续的路由质量监控。只看BGP邻居状态是远远不够的,要监控路由表里的关键前缀是否正常,路由路径的AS Path是否发生了变化,下一跳是否符合预期。现在主流的网络监控平台都已经支持通过BGP监控协议(BMP)采集路由状态,可以做到秒级感知路由变化。如果条件不允许部署BMP,至少要在核心设备上做SNMP告警,监控BGP状态变化和路由表条目数量波动。
第三,定期做主备倒换演练。这个听起来是废话,但真正坚持做的团队少之又少。演练不是随便找个时间切一下就行,要有预案、有观测手段、有回退方案。我建议每季度至少做一次完整的主备切换演练,同时把切换耗时记录在案,对比每次切换的时间差异,如果某次切换耗时明显变长,说明设备状态或者转发性能出现了劣化,需要及时排查。
第四,维护一份完整的报文基线库。在一个稳定运行的网络里,定期抓包保存,作为后续排障的对照基准。这个基线库不一定要很大,每个关键接口存一份代表正常状态的抓包文件就行。当故障发生时,拿故障时的抓包跟基线对比,往往一眼就能看出差异在哪里。这个方法比拿着一张路由表到现场边看边猜靠谱得多。
还有一点值得特别强调:BGP主备倒换不是孤立的网络事件,它跟上层应用的感受有直接关系。TCP长连接对网络中断的容忍度很低,哪怕BGP只断几秒钟,一批TCP连接就会断开重连,业务侧就能感知到。所以在做BGP收敛优化的时候,不要只盯着路由协议的指标,也要从应用角度评估可接受的断流时间,再反推网络侧需要做到多快的收敛。如果业务要求亚秒级切换,那光靠BGP收敛是不够的,可能需要引入BFD联动、设备间会话保持等更高级的手段。
说到底,网络排障的尽头不是把故障修好,而是通过每次故障沉淀出体系化的防御能力。BGP主备倒换本身是件好事,它保证了网络的高可用性,但只有当你真正理解了倒换过程中每一个报文的去向、每一次路由计算的因果、每一个转发层面的时延,你才能在倒换发生时从容应对,而不是被一个接一个的异常报文打得措手不及。这也是为什么我一直强调抓包——协议栈可以调试,配置可以核对,唯独真实转发的报文不会说谎,它记录的才是网络最真实的状态。