IS-IS在广播网络中运行时会通过交互LAN IIH(局域网中间系统到中间系统Hello)来发现邻居并维护邻接关系。与点对点链路不同,广播链路上的每台路由器并不需要两两建立完整邻接,而是通过选举一台指定中间系统(Designated Intermediate System,DIS)来集中管理链路状态数据库的同步。DIS负责周期发送CSNP(完整序列号PDU),帮助其他路由器检测链路状态信息是否缺失;当网络不稳定时,DIS还能更快地触发LSP泛洪。理解DIS选举机制,对排查IS-IS邻居震荡、优化收敛时间都有实用价值。

本文将从IS-IS协议定义的优先级和SNPA(子网连接点地址,通常为接口MAC地址)两个字段入手,使用Ruby语言构建一个简化的选举仿真。我们会先解释广播链路中的DIS选举规则,再给出节点建模和Hello报文封装思路,最后通过可运行代码验证多种场景下的选举结果。
广播链路中的DIS作用与选举依据
IS-IS将网络接口分为点对点和广播两种类型。广播链路通常指以太网等多路访问介质,任何一台路由器发出的报文都能被同网段其他路由器接收。在这种环境中,如果每台路由器都独立向所有邻居发送LSP,会带来大量的重复流量和复杂的一致性维护。DIS的出现解决了这个问题:它在逻辑上充当链路状态信息的汇聚点,通过周期发送CSNP列出自己数据库中所有LSP的摘要,其他路由器对比后仅请求缺失的LSP。这样既能保证数据库快速同步,又避免了广播风暴。
DIS选举只看两个参数:接口优先级和SNPA。优先级字段占8位,范围0到127,默认值为64。优先级为0的路由器不会被选为DIS,但仍参与其他路由器的选举。当多台路由器优先级不同,数值最大的成为DIS;当优先级相同,SNPA(通常是接口MAC地址)更大的路由器获胜。需要注意,IS-IS的DIS选举支持抢占:一旦有更高优先级或更大SNPA的路由器加入,当前DIS会被立即替换。这与OSPF中DR只有在原DR失效后才重新选举的规则明显不同。
此外,IS-IS没有像OSPF那样设置备份指定路由器(BDR)。DIS如果失效,邻居关系会在Hold时间超时后拆除,随后重新触发选举。由于IS-IS中邻居状态机相对简单,重新选举速度一般可以接受。
用Ruby建模节点与Hello信息
要在Ruby中模拟DIS选举,首先需要抽象出路由器节点和Hello报文两个对象。路由器节点至少包含系统ID、接口优先级和SNPA三个属性。系统ID用于在Hello报文中标识自身;优先级和SNPA用于选举比较。为了简化,我们使用一个字符串表示SNPA,例如MAC地址格式“00:1c:42:aa:bb:01”,比较时可以直接按字符串字典序处理,因为相同长度的十六进制MAC地址字典序与数值大小一致。若使用不同格式,则需要先规范化为统一长度。
Hello报文在真实IS-IS中由固定头部和若干TLV(类型-长度-值)组成,本文为了聚焦选举逻辑,仅定义三个字段:发送者系统ID、发送者优先级、发送者SNPA。接收方在邻居表里记录这些信息,并维护自己的状态。下面是一个简单的Ruby类实现,其中HelloPDU负责封装报文,Node类负责构造Hello并处理接收逻辑。
# 定义Hello PDU结构
class HelloPDU
attr_reader :system_id, :priority, :snpa
def initialize(system_id, priority, snpa)
@system_id = system_id
@priority = priority
@snpa = snpa
end
def to_s
"HelloPDU[sysid=#{@system_id}, priority=#{@priority}, snpa=#{@snpa}]"
end
end
# 定义网络节点
class Node
attr_reader :system_id, :priority, :snpa
def initialize(system_id, priority, snpa)
@system_id = system_id
@priority = priority
@snpa = snpa
end
def build_hello
HelloPDU.new(@system_id, @priority, @snpa)
end
def dis_candidate?
@priority > 0
end
end
上述代码中,dis_candidate? 方法返回布尔值,表示优先级是否大于0。这是IS-IS协议中的硬性条件:优先级为0的路由器不参与DIS竞选,即使它的SNPA更大也不能成为DIS。真实环境中,接口默认优先级为64,可以通过配置手动调整,从而影响选举结果。
在实际协议栈中,Hello报文还包含Hold Time、Circuit Type、LAN ID等字段。这些字段对选举结果没有直接影响,但会影响邻居表的建立和老化。仿真时暂不实现这些细节,只保留三个影响选举结果的字段,这样可以更清晰地观察排序逻辑。如果希望扩展,可以在HelloPDU中增加hold_time和circuit_type属性,并在邻居记录中加入时间戳用于超时处理。
实现DIS选举算法
选举算法本质是一个排序问题。所有参与竞选的节点先从邻居表中取出,然后按照优先级降序排列;如果优先级相同,则按照SNPA降序排列。排序后的第一个节点即为DIS。需要特别注意的是,节点自身也包含在邻居表中,因为路由器接收其他路由器Hello的同时,也会以自身参数参与比较。仿真时,我们直接输入一组节点,统一计算选举结果。
下面是完整的选举函数,它接收一个Node数组,过滤掉优先级为0的节点,然后使用Ruby的sort_by方法进行多关键字排序。代码中使用了字符比较来模拟SNPA大小,前提是MAC地址格式统一。
def elect_dis(nodes)
candidates = nodes.select(&:dis_candidate?)
return nil if candidates.empty?
candidates.sort_by { |node| [-node.priority, -node.snpa] }.first
end
# 构建测试节点
nodes = [
Node.new("Router-A", 64, "00:1c:42:aa:bb:01"),
Node.new("Router-B", 100, "00:1c:42:aa:bb:02"),
Node.new("Router-C", 64, "00:1c:42:aa:bb:03"),
Node.new("Router-D", 0, "00:1c:42:aa:bb:04")
]
dis = elect_dis(nodes)
if dis
puts "DIS是:#{dis.system_id},优先级 #{dis.priority},SNPA #{dis.snpa}"
else
puts "没有符合条件的DIS"
end
运行结果会显示Router-B被选为DIS,因为它的优先级100高于Router-A和Router-C的64,Router-D优先级为0被排除。这里使用-nage值作为sort_by的排序键,是为了在升序排列中实现降序效果。这只是Ruby中的一种简洁写法,也可以改用sort方法和自定义比较块。如果SNPA格式统一,字符串比较有效;如果存在大小写不一致或包含分隔符不一致的情况,需要先转换为标准格式。
选举完成后,DIS会周期发送CSNP。其他路由器收到CSNP后,会与本地LSP摘要对比,向DIS请求缺失的报文。DIS还会根据网络状态调整LSP发送行为。本文不深入实现CSNP交互,但选举函数是后续所有同步逻辑的基础。需要注意的是,真实IS-IS中如果DIS本身的某个LSP被确认过期,它也会通过广播链路发送LSP请求,并不因为自己是DIS就免于同步。
验证选举抢占与边界场景
DIS选举支持抢占,意味着网络中一旦出现更高优先级或更大SNPA的节点,DIS会立即发生变化。我们可以通过修改节点参数来验证这一行为。例如,在原有节点基础上增加一台Router-E,优先级设为120,SNPA任意。重新调用elect_dis函数后,DIS将切换为Router-E。这种抢占机制在OSPF中并不存在,因此很多网络工程师在早期学习IS-IS时容易产生混淆。实际上,IS-IS通过抢占可以保证任何时刻都是最优节点担任DIS,但同时也可能因为接口优先级调整导致意外切换,需要在运维时谨慎操作。
另一个容易忽略的边界场景是SNPA完全相同。在真实网络中,同一广播链路上的MAC地址不应重复,但如果仿真环境中出现重复SNPA且优先级相同,排序结果取决于Ruby的稳定性。为避免不确定行为,可以在比较逻辑中加入系统ID作为最终决胜条件。虽然协议没有规定系统ID参与选举,但仿真中可以用它保证结果唯一。下面给出改进后的比较代码。
def elect_dis_with_tiebreak(nodes)
candidates = nodes.select(&:dis_candidate?)
return nil if candidates.empty?
candidates.sort_by do |node|
[-node.priority, -node.snpa, node.system_id]
end.first
end
这段代码在SNPA相同时按照系统ID升序排列,确保不会随机选出DIS。实际网络中不必担心这一点,但作为仿真程序,输出确定性很重要。此外,如果所有节点优先级都为0,elect_dis函数返回nil,表示该广播链路无法选举出DIS。真实协议中这种情况虽然配置上允许,但会导致链路状态同步效率下降,一般不建议将所有接口优先级设置为0。
最后一个测试场景是优先级最大但SNPA较小的节点。例如优先级为100的节点SNPA为00:00:00:00:00:01,另一个优先级为64的节点SNPA为ff:ff:ff:ff:ff:ff。根据规则,优先级先比较,因此优先级为100的节点仍然获胜,即使SNPA较小。说明优先级在选举中具有更高的权重,SNPA只在优先级相同的情况下生效。这个细节在排查IS-IS DIS异常时经常用到。
总结
本文用Ruby实现了一个简化的IS-IS DIS选举过程,覆盖了广播链路的协议背景、节点建模、Hello报文关键字段以及选举排序逻辑。通过多组测试可以观察到优先级优先、SNPA其次、支持抢占、无备份DIS等IS-IS特有行为。这些规则虽然简单,却是理解IS-IS广播网络收敛行为的重要基础。
如果需要进一步扩展,可以加入CSNP发送定时器、邻居超时维护以及TLV编码解析,使仿真更接近真实协议栈。也可以增加图形化输出,直观展示DIS切换前后链路状态数据库的同步过程。希望这个Ruby示例能帮助读者建立对IS-IS DIS选举机制的直观认识。