EIGRP(Enhanced Interior Gateway Routing Protocol)是思科私有的一种混合型路由协议,它依赖Hello报文维持邻居关系。每个Hello报文中都携带Hold Time字段,如果在保持时间内没有收到邻居的任何报文,路由器就会宣告邻居失效并触发路由重计算。保持时间的取值看似简单,实际上对网络稳定性影响很大。本文将围绕如何用Ruby脚本实现EIGRP邻居保持时间的配置优化展开,从协议原理讲到自动化脚本的完整实现。

一、理解保持时间与Hello间隔的关系
EIGRP的邻居维护机制建立在Hello报文和保持时间的配合之上。默认情况下,在带宽较高的链路(比如大于T1速率的链路)上,Hello间隔为5秒,保持时间为15秒,也就是保持时间默认是Hello间隔的三倍。而在低速链路(如帧中继的多点接口)上,Hello间隔会被拉长到60秒,保持时间相应变为180秒。
这里有一个关键点需要理解:保持时间是由邻居在Hello报文中通告给对端的,而不是本地配置生效的。也就是说,本端配置的保持时间,影响的是对端等待本端报文的超时时间。这一点在排错时经常被混淆。如果两端的保持时间配置不一致,EIGRP并不会像OSPF那样要求严格匹配,它会直接使用收到的通告值,因此链路质量不稳定的环境下,合理调整保持时间可以显著减少邻居翻动。
配置保持时间的命令比较简单,在接口下执行ip hold-time eigrp 100 30即可将AS 100的保持时间设置为30秒。问题在于,当网络中设备数量达到几十上百台时,逐台手工配置既低效又容易出错,这正是引入Ruby脚本进行自动化管理的价值所在。
二、用Ruby采集EIGRP邻居状态信息
要优化保持时间,第一步是拿到当前每台设备上EIGRP邻居的实际状态。Ruby的net-ssh库非常适合做这件事,它可以模拟SSH会话执行命令并捕获输出。下面是一段采集邻居信息的示例代码:
require 'net/ssh'
def fetch_eigrp_neighbors(host, user, password)
output = ''
Net::SSH.start(host, user, password: password) do |ssh|
output = ssh.exec!('show ip eigrp neighbors detail')
end
output
end
def parse_neighbors(raw_output)
neighbors = []
raw_output.each_line do |line|
# 匹配邻居地址、保持时间、正常运行时间等字段
if line =~ /^(\d+\.\d+\.\d+\.\d+)\s+(\w+)\s+(\d+)\s+([\d:]+)\s+(\d+)\s+(\d+)\s+(\d+).*?/
neighbors << {
address: Regexp.last_match(1),
interface: Regexp.last_match(2),
hold_time: Regexp.last_match(3).to_i,
uptime: Regexp.last_match(4),
retransmits: Regexp.last_match(6).to_i
}
end
end
neighbors
end
这段代码的核心在于parse_neighbors方法。EIGRP邻居表的输出格式是固定列布局,通过正则表达式可以稳定提取邻居地址、所在接口、当前剩余保持时间、运行时长和重传次数。其中当前剩余保持时间这个字段特别有价值:如果采集到的值总是徘徊在满值附近,说明链路稳定,可以适当缩短保持时间加快故障感知;如果值经常掉到很低甚至伴随邻居重建,说明链路抖动,需要放宽保持时间。
为了得到可靠的判断依据,建议对同一台设备做多次采样,比如每隔5秒采一次,共采10次,然后把结果存入数组做统计。Ruby的Enumerable模块提供了min、max、reduce等方法,计算保持时间的波动区间和均值只需要几行代码。
三、动态计算合理的保持时间
拿到采样数据后,下一步是设计一个计算策略。一个实用的思路是结合保持时间剩余值的最低值和邻居翻动次数来给出建议值。翻动次数可以通过统计邻居的uptime是否小于某个阈值(比如最近一小时内重建过)来近似判断。
def suggest_hold_time(samples, flap_count)
min_hold = samples.min
avg_hold = samples.reduce(0.0) { |s, x| s + x } / samples.size
if flap_count > 3 || min_hold < 5
# 链路不稳定,保持时间放宽到Hello间隔的6倍
30
elsif min_hold < 10
# 轻微抖动,采用默认三倍并留余量
20
else
# 链路非常稳定,可以收紧到两倍加速收敛感知
10
end
end
# 结合接口Hello间隔做校验,保持时间不应小于Hello间隔的两倍
def validate_suggestion(hold, hello_interval)
[hold, hello_interval * 2].max
end
这个算法体现了保守与激进的平衡。保持时间收敛得太紧,遇到偶发的CPU高峰或链路抖动就会出现邻居震荡,路由反复重算反而拖垮整机性能;放得太宽,真实故障发生时要等很久才被感知,切换时间变长。给保持时间设置一个下限(不低于Hello间隔的两倍)是实践中比较稳妥的经验值。
另外需要注意接口类型差异。如果是隧道接口或者经过蜂窝网络、卫星链路的接口,报文延迟天然较大且不稳定,这类接口的建议保持时间应当单独放宽,脚本中可以通过接口名称匹配(比如Tunnel、Cellular关键字)来应用不同的策略分支。
四、批量下发配置并验证
计算出建议值之后,最后一步是把配置推送到设备。推送前务必先备份当前配置,推送后要立即验证邻居关系没有被破坏。完整流程的代码框架如下:
require 'net/ssh'
def apply_hold_time(host, user, password, as_num, interface, hold)
commands = [
'conf t',
"interface #{interface}",
"ip hold-time eigrp #{as_num} #{hold}",
'end',
'write memory'
]
Net::SSH.start(host, user, password: password) do |ssh|
ssh.open_channel do |channel|
channel.request_pty
channel.send_channel_request('shell') do |ch, success|
commands.each { |cmd| ch.send_data(cmd + "\n") }
ch.send_data("exit\n")
end
end
ssh.loop
end
end
def verify_neighbor(host, user, password, neighbor_ip)
raw = fetch_eigrp_neighbors(host, user, password)
parse_neighbors(raw).any? { |n| n[:address] == neighbor_ip }
end
推送配置后,脚本应当休眠一到两个保持时间周期,再重新采集邻居表确认邻居仍然在线且uptime没有重置。如果发现邻居掉线,说明新配置过于激进,脚本可以自动回滚到备份的原配置。这个回滚机制是自动化操作的安全网,生产环境中不可缺少。
从工程角度看,把采集、计算、推送、验证四个步骤拆分成独立的Ruby方法,再通过一个调度入口串联起来,整个工具就可以接入定时任务,实现周期性的保持时间巡检与自适应优化。相比手工登录设备逐条敲命令,这种方式不仅省时,更重要的是所有决策都有采样数据支撑,配置变更也留有记录和回退路径,网络运维的可靠性会得到明显提升。