EIGRP使用DUAL算法维护无环路径,当一条路由的可行后继丢失且没有直接替代者时,路由器会把它置为活动状态,并向所有邻居发送查询报文。如果某个邻居在规定时间内没有回送应答,这条路由就陷入SIA(Stuck In Active)状态。SIA不仅拖累收敛速度,还可能导致邻居关系被重置和路由被剔除。本文用Ruby模拟这一过程,聚焦查询超时检测和路由剔除的实现细节。

EIGRP SIA状态与查询超时机制
在EIGRP中,一条路由从被动状态进入活动状态后,路由器会启动活动定时器。真实设备上该定时器默认值是3分钟,表示路由器最多愿意等待邻居应答的时间。如果定时器到期时仍然没有收到任何应答,这条路由就被判定为SIA。SIA并不意味着路由彻底丢失,而是说明当前网络中存在无法收敛的查询链,必须采取额外动作打断这种僵局。
查询超时的核心作用有两个:一是防止路由长时间停留在活动状态,避免拓扑变化无法收敛;二是通过强制重置邻居关系来清除可能出现的查询环路或黑洞。在实际EIGRP实现中,路由器会先向其他邻居发送SIA查询确认原查询是否仍然有效,如果确认邻居无响应,则重置该邻居并重新计算路由。我们可以在Ruby中把这一过程简化为:超时触发后直接剔除对应路由,并删除超时邻居的状态。
为了模拟路由和邻居,先用两个简单的类来承载基础数据。路由对象记录前缀和状态,邻居对象记录应答情况。
class Route
attr_accessor :prefix, :state, :metric
def initialize(prefix, metric)
@prefix = prefix
@metric = metric
@state = :passive
end
end
class Neighbor
attr_accessor :router_id, :reply_received, :last_reply_at
def initialize(router_id)
@router_id = router_id
@reply_received = false
@last_reply_at = nil
end
end
Ruby实现查询超时计时与状态维护
为了让模拟器能够跟踪每个查询的发送时间,我们使用一个哈希表,键是路由前缀,值又是一个哈希,记录每个邻居的查询开始时间。当程序调用发送查询方法时,会把当前时间存入该结构。当收到某个邻居的应答时,就从查询表中删除对应条目,并更新邻居对象的应答状态。这种设计可以直观地映射EIGRP中每个活动路由的查询状态。
超时检测采用轮询方式。程序在每次循环中检查所有活动查询,计算从发送到当前的时间差。如果时间差超过设定的超时阈值,就认为该邻居未响应,进入SIA处理流程。由于Ruby的Time.now精度足够,模拟环境中可以设置很小的超时值,方便观察触发效果。
下面给出查询管理、应答接收和超时检测的核心实现。注意这里的>符号在代码块中展示为大于号,表示时间差与超时阈值的比较。
class QueryTimeoutSimulator
attr_reader :routes, :neighbors, :timeout
def initialize(timeout)
@timeout = timeout
@routes = {}
@neighbors = {}
@active_queries = {}
end
def add_route(prefix, metric)
@routes[prefix] = Route.new(prefix, metric)
end
def add_neighbor(router_id)
@neighbors[router_id] = Neighbor.new(router_id)
end
def send_query(prefix, neighbor_id)
@active_queries[prefix] ||= {}
@active_queries[prefix][neighbor_id] = Time.now
puts "发送查询:#{prefix} -> #{neighbor_id}"
end
def receive_reply(prefix, neighbor_id)
if @active_queries[prefix] && @active_queries[prefix][neighbor_id]
@active_queries[prefix].delete(neighbor_id)
@neighbors[neighbor_id].reply_received = true
puts "收到应答:#{prefix} 来自 #{neighbor_id}"
end
end
def check_timeout(prefix)
return unless @active_queries[prefix]
@active_queries[prefix].each do |neighbor_id, start_time|
if Time.now - start_time > @timeout
puts "检测到SIA超时:#{prefix} 邻居 #{neighbor_id}"
remove_route(prefix)
reset_neighbor(neighbor_id)
end
end
end
def remove_route(prefix)
@routes.delete(prefix)
@active_queries.delete(prefix)
puts "路由已剔除:#{prefix}"
end
def reset_neighbor(neighbor_id)
@neighbors.delete(neighbor_id)
puts "邻居关系重置:#{neighbor_id}"
end
end
路由剔除逻辑与邻居重置
当检测到某个邻居超时后,模拟器会依次执行两个动作:删除路由前缀,以及删除邻居对象。这个顺序是有意义的。先剔除路由可以立即停止对该前缀的转发或进一步查询,避免使用可能已经失效的路径。然后重置邻居关系,模拟真实EIGRP中邻居表项的清除和后续重建。通过这种方式,网络拓扑中不可用的邻居不会继续参与后续的DUAL计算。
如果一条活动路由同时向多个邻居发送了查询,但只有其中一个邻居超时,我们的简化模型会选择剔除整条路由并重置那个超时邻居。这种策略虽然比真实EIGRP更激进,但能帮助理解SIA超时后最直接的故障隔离思路。实际协议中,路由器可能会先通过SIA查询确认该邻居是否还能响应,而不是立即重置所有关系。模拟程序可以通过增加查询确认步骤来扩展,但核心的超时判断和剔除动作保持一致。
下面演示一个完整场景:添加路由10.0.0.0/8,添加两个邻居,向其中一个邻居发送查询并让其超时,另一个邻居正常应答,然后触发超时检查。
sim = QueryTimeoutSimulator.new(2) # 超时阈值设为2秒
sim.add_route('10.0.0.0/8', 100)
sim.add_neighbor('192.168.1.1')
sim.add_neighbor('192.168.1.2')
sim.send_query('10.0.0.0/8', '192.168.1.1')
sim.send_query('10.0.0.0/8', '192.168.1.2')
# 模拟其中一个邻居正常回复
sim.receive_reply('10.0.0.0/8', '192.168.1.2')
# 等待超过2秒再检查,模拟另一个邻居未回复
sleep 3
sim.check_timeout('10.0.0.0/8')
运行结果与调优建议
上述代码运行后,控制台会依次输出发送查询、收到来自192.168.1.2的应答、检测到192.168.1.1的SIA超时、路由已剔除和邻居关系重置。可以看到,即使有一个邻居正常回复,只要另一个邻居超时,整条路由也会被剔除。这正是EIGRP在活动状态无法收敛时采取的保守策略:宁可暂时丢弃路由,也不允许存在潜在环路的路径继续留在路由表中。
超时阈值的设置非常关键。如果把阈值调得太短,网络轻微抖动就可能误判邻居不可达,导致路由反复剔除和重建,造成额外的CPU和带宽消耗。如果阈值太长,SIA状态持续时间会增加,收敛变慢。在真实EIGRP环境中,默认3分钟的活动定时器已经在可靠性和收敛速度之间做了权衡。模拟场景中可以自由调整阈值,观察不同数值对路由稳定性的影响。
本文的Ruby实现省略了真实EIGRP的DUAL计算细节和SIA查询确认机制,但保留了超时检测与路由剔除的核心流程。理解了这套逻辑后,可以很容易地把它扩展为更完整的路由协议模拟器,或者将超时剔除思想应用到其他需要一致性确认的分布式系统中。
EIGRP SIA查询超时Ruby路由协议模拟路由剔除修改时间:2026-10-03 17:33:56