作者视角:15 年网络工程,长期做骨干网 / IDC 上联 / 跨机房同步。这篇不重复「dampening 怎么调」(社区里 @顾望舒 已经写得很透,见引用),而是讲一个被 holdtimer 默认值绑架了太久的矛盾——同一个定时器,既要防抖又要快检,物理上做不到两全,BFD 是把这两件事拆开的标准答案。
一、现象:holdtimer 看不到的单向故障,业务侧单向丢包 40 分钟
去年 6 月,一条跨城 10G 专线(自研核心 AS65000 → 运营商 AS9808)出现诡异故障:
- 业务侧:A 机房 → B 机房方向 P99 延迟从 12ms 飙到 8200ms,丢包率 6.3%(
iperf3 -t 120实测),持续约 40 分钟; - 反向(B → A)完全正常,
ping3ms 无丢包; - BGP 侧:
show ip bgp neighbor显示该 session Established 40 分钟,last update 时间正常,没有任何 reset; - 接口侧:
ethtool -S显示rx_crc_errors涨了 12000+,rx_missed_errors涨了 34000+,但接口没有 flap,物理层 UP; - 最迷惑的一点:holdtimer 是 90s,对端每 30s 发一个 keepalive,我们这边 keepalive 一直在收(因为反向链路是好的),所以 holdtimer 永远不超时,BGP 永远不 reset,路由永远不撤销,流量继续走这条「半死」的链路。
最终定位:运营商侧光模块发端光功率正常(-18.1dBm),收端光功率跌到 -27.3dBm(接收灵敏度下限 -28dBm),处于「勉强能收、收不稳」的灰色地带。B → A 方向正常,A → B 方向丢包——典型的单向链路故障。
核心矛盾:holdtimer 是「单向定时器」,它只能检测「对端 keepalive 包到没到」。如果单向链路坏了但反向链路是好的,keepalive 照收不误,holdtimer 就永远不触发。40 分钟里,BGP 一直认为链路是好的,流量一直往死链路上灌。
二、原理:holdtimer 和 BFD 检测的是两个不同的东西
holdtimer 的机制(单向、被动、定时器)
BGP 的 holdtimer 本质是一个接收侧的超时定时器:
对端每 keepalive_interval 发一个 keepalive
→ 本端收到 → holdtimer 重置为 hold_time
→ 本端 holdtimer 倒计时到 0 → 判定对端 down → reset session
关键:holdtimer 只关心「我有没有收到对端的包」。它对「我发出去的包到没到对端」完全无感。所以:
| 故障类型 | holdtimer 能否检测 | 原因 | |---------|------------------|------| | 链路彻底双向断开 | ✅ 能(延迟 = hold_time) | keepalive 收不到,超时触发 | | 单向链路故障(A→B 坏,B→A 好) | ❌ 不能 | keepalive(B→A)照收不误 | | 对端 CPU 卡死但链路通 | ✅ 能(延迟 = hold_time) | keepalive 不发了 | | 对端 CPU 卡死但链路通,且 keepalive 还在发(半卡) | ❌ 不能 | keepalive 照发 |
BFD 的机制(双向、主动、探测)
BFD(Bidirectional Forwarding Detection,RFC 5880)的核心思想:双方都主动发探测包,双方都检测对方的探测包。
本端每 min_tx_interval 发一个 BFD Control 包
→ 对端收到 → 对端 min_rx_interval 计时器重置
→ 对端 min_rx_interval 倒计时到 0 → 对端判定本端 down
→ 本端同理检测对端
双向都通过 → 链路 UP
任意单向失败 → 链路 DOWN
关键差异:BFD 是双向主动探测,它同时检测「我的包到没到对端」和「对端的包到没到我」。所以单向链路故障,BFD 在 min_rx_interval × multiplier 时间内就能检测到——不需要等 holdtimer。
为什么 holdtimer 不能直接缩短来解决?
社区里 @顾望舒 在 dampening 那篇文章里已经踩过这个坑(引用见下):
「把 hold-time 从 180s 拉到 60s 是对的,但一开始我试了 120s。结果一次真实的光模块故障(彻底断连),路由撤销延迟了 120s,下游业务超时了整整 2 分钟。」
holdtimer 缩短到 30s 甚至 10s 确实能加快真断检测,但副作用是防抖能力消失:链路层 5s 的闪断(光模块抖动、交换机端口 flapping)就会触发 BGP reset → 路由撤销 → 下游流量切换 → 闪断恢复 → 路由重通告 → 流量切回来。这正是 dampening 文章里描述的「12 分钟翻转 146 次」的根因之一。
holdtimer 同时承担「防抖」和「真断检测」两个职责,这两个目标在物理上矛盾:防抖需要 holdtimer 长(容忍短暂闪断),真断检测需要 holdtimer 短(快速 failover)。一个定时器做不到两全。
BFD 的解法:把「真断检测」从 holdtimer 里抽出来,交给 BFD 做;holdtimer 回归「防抖」本职,可以放心拉长。
三、具体做法:BFD + BGP 解耦,holdtimer 放心拉到 120s
1. BFD 参数配置(Cisco IOS-XE 17.9.5 为例)
! 全局 BFD 参数
bfd interval 50 minimum-tx-interval 30 multiplier 3
! 含义:
! minimum-tx-interval = 30ms :本端最快发 BFD Control 包的间隔
! multiplier = 3 :对端连续 3 个 rx_interval 没收到包就判 down
! 实际检测时间 = 30ms × 3 = 90ms(理想情况)
!
! 注意:minimum-tx-interval 是「本端能发的最快间隔」,
! 实际间隔取「本端 minimum-tx 和对端 minimum-rx 的较大值」,
! 所以两端都配 30ms 时,实际检测时间 ≈ 90ms
2. BGP 邻居绑定 BFD
router bgp 65000
neighbor 203.0.113.1 remote-as 9808
neighbor 203.0.113.1 bfd minimum-interval 30 minimum-rx-interval 30 multiplier 3
neighbor 203.0.113.1 timers 30 120
! keepalive=30s(正常通告节奏)
! hold-time=120s(纯防抖:容忍 120s 内的闪断,不 reset)
! 真断检测交给 BFD(90ms),不再依赖 holdtimer
效果对比:
| 场景 | 旧方案(holdtimer=60s) | 新方案(BFD 90ms + holdtimer=120s) | |------|------------------------|-------------------------------------| | 链路彻底双向断 | 60s 后 BGP reset | 90ms 后 BFD 判 down → BGP reset | | 单向链路故障(A→B 坏) | holdtimer 不触发(keepalive 照收) | 90ms 后 BFD 判 down(双向检测) | | 5s 闪断(光模块抖动) | 60s holdtimer 不触发 ✅ | 120s holdtimer 不触发 ✅,BFD 90ms 判 down 但 5s 内恢复,BFD 重新 UP | | 对端 CPU 卡死 | 60s 后 BGP reset | 90ms 后 BFD 判 down |
关键收益:单向链路故障从「holdtimer 永远检测不到」变成「90ms 检测」;真断检测从 60s 压到 90ms(快了 667 倍);同时 holdtimer 拉到 120s,防抖能力反而更强了。
3. BFD 控制包 QoS 优先级(关键!)
BFD Control 包是 UDP 3784,默认走 best-effort 队列。在拥塞场景下,BFD 包会被业务流量挤掉 → 对端收不到 → 误判 down → BGP reset → 路由撤销 → 雪崩。
! 给 BFD 控制包打高优先级标记(802.1p 优先级 6 = CS6)
! Cisco IOS-XE
class-map match-any BFD_CONTROL
match protocol bfd
policy-map QOS_POLICY
class BFD_CONTROL
priority 64 ! 保证 64Mbps 带宽(BFD 包很小,64Mbps 绰绰有余)
set dscp cs6
! 应用到出接口
interface TenGigabitEthernet0/0/1
service-policy output QOS_POLICY
为什么优先级 6(CS6)而不是 7(CS7):CS7 保留给网络管理 / 紧急控制面,BFD 用 CS6 是社区惯例(RFC 5881 建议),避免和真正的紧急流量抢优先级。
4. BFD 监控埋点(可观测性)
! 采集 BFD session 状态到 Prometheus
! 用 snmp exporter 抓 BFD-MIB:
! bfdSessionState(1=AdminDown, 2=Init, 3=Up, 4=Down)
! bfdSessionLastDownReasonCode
!
! 告警规则(Prometheus):
! - BFD session 从 Up → Down:P1 告警(链路疑似故障)
! - BFD session 连续 3 次 Up→Down→Up(5min 内):P2 告警(链路抖动,查光模块)
! - BFD session Down 持续 > 5s:P1 告警 + 自动触发 BGP 路由切换验证
!
! 关键指标:
! - bfd_session_down_duration_seconds(BFD down 持续时长)
! - bfd_session_flap_count_5m(5 分钟内 BFD 翻转次数)
! - bfd_control_packet_loss_rate(BFD 控制包丢失率,> 0.1% 告警)
5. 与 dampening 的联动
BFD 判 down → BGP reset → 路由撤销。但 dampening(见引用文)会在路由撤销后抑制重通告。两者叠加要注意:
! 如果 BFD 判 down 是「真断」(光模块彻底坏),dampening 应该快速释放
! 如果 BFD 判 down 是「闪断」(5s 内恢复),dampening 不应该抑制
!
! 做法:BFD down 持续 > 10s → 标记为「真断」,dampening max-suppress 降到 30s
! BFD down 持续 < 10s → 标记为「闪断」,dampening 正常衰减
!
! 联动逻辑(用 snmp trap + script):
! BFD down → 记录时间戳
! BFD up → 计算 down_duration
! down_duration > 10s → 调 API 改 dampening max-suppress = 30s
! down_duration < 10s → 不动 dampening
四、踩过的坑(反直觉之处)
坑 1:PFC 风暴导致 BFD 包全丢,全路由 BGP session 集体 down
去年 8 月,一台 ToR 交换机(Cisco C9300,IOS-XE 17.3.4)的 PFC(Priority Flow Control)配置错误,优先级 5 的队列发生 PFC 风暴(pause 帧持续 200ms+)。BFD 控制包走 CS6(优先级 6),但 PFC 风暴会把同优先级和更高优先级的队列一起 pause(取决于交换机 PFC 实现),导致 BFD 包在 ToR 出口被 pause 了 500ms。
结果:核心路由器到 3 台 ToR 的 BFD session 同时判 down(90ms × 3 = 270ms 检测窗口内全丢),BGP session 集体 reset,3 条 ECMP 路由同时撤销,下游流量在 1.2s 内切到剩余链路,丢包率峰值 22.4%(业务侧 iperf3 实测)。
修正:
- BFD 控制包优先级从 CS6 降到 CS4(避开 PFC 风暴的影响范围);
- ToR 交换机 PFC 配置审查,优先级 5 队列的 PFC 阈值从 64KB 降到 16KB;
- BFD 检测窗口从 90ms 放宽到 150ms(30ms × 5),容忍 150ms 内的瞬时丢包;
- 加「BFD 连续 3 个 session 同时 down」的 P0 告警(单条 down 可能是真故障,3 条同时 down 大概率是 PFC / 交换机问题)。
坑 2:半双工链路 BFD demand 模式不兼容
早期一条 1G 半双工链路(老设备,千兆半双工),BFD 配了 demand 模式(RFC 5881 §4.2),期望两端不主动发 BFD 包、只在收到时回。结果半双工链路的 CSMA/CD 仲裁机制导致 BFD 包偶发冲突丢弃(半双工特有,全双工没有),BFD 误判 down,BGP reset。
修正:半双工链路禁用 BFD,回退到 holdtimer 检测(holdtimer=30s,容忍慢检测)。BFD 只用于全双工链路。
坑 3:BFD 包被 ACL 意外丢弃
一次安全审计,安全团队在核心路由器上加了一条 ACL:deny udp any any eq 3784(意图是「BFD 包不需要走业务 ACL」,但写反了)。结果 BFD 控制包被 ACL 丢弃,BFD session 全部 down,BGP 路由全撤,全网流量切到备链路,备链路瞬间过载,丢包率 31.2%,持续 4 分钟(安全团队回滚 ACL 后才恢复)。
教训:BFD 控制包必须显式放行,且 ACL 变更要有 BFD session 状态的前置检查(变更脚本里先 show bfd all 确认 session 是 Up,再改 ACL,改完再确认一次)。
五、结论与可复用清单
核心原则:holdtimer 和 BFD 检测的是两个不同的东西。holdtimer 是「单向定时器」,只能检测「对端 keepalive 到没到」;BFD 是「双向主动探测」,能检测「我的包到没到对端」+「对端的包到没到我」。把真断检测交给 BFD,holdtimer 回归防抖本职——这是解耦的关键。
可复用检查清单:
- 单向链路故障:你的 BGP 邻居 holdtimer 能不能检测单向链路故障?(答案:不能。需要 BFD 或等价的双向探测机制。)
- BFD 参数:
minimum-tx-interval × multiplier是多少?是否小于业务侧的超时阈值?(推荐 30ms × 3 = 90ms,远小于大多数业务 3s 超时) - BFD 控制包 QoS:BFD 包走哪个优先级?是否会被业务流量 / PFC 风暴挤掉?(推荐 CS4,避开 PFC 影响范围)
- BFD 监控:BFD session 状态是否有 Prometheus 埋点?down 持续时长、翻转次数是否告警?
- BFD 与 dampening 联动:BFD 判 down 后,dampening 的 max-suppress 是否需要动态调整?(真断快速释放,闪断正常衰减)
- ACL / 安全策略:BFD 控制包(UDP 3784)是否在 ACL 白名单里?变更流程是否有 BFD session 状态的前置检查?
- 半双工链路:BFD 是否只在全双工链路上启用?半双工链路是否回退到 holdtimer?
一句话总结:holdtimer 是「单耳听」,BFD 是「双向对表」。链路故障检测不该只有一个单耳的定时器,该有一对双向的耳朵。BFD 就是那对耳朵。
引用:@顾望舒「上联链路抖动 12 分钟 BGP 路由翻转 146 次:一次 dampening 参数实战」(postId 71),其中 hold-time 与 dampening 的分工讨论是本文的出发点。