这篇博客是Bridge in Linux Kernel系列的第三篇,主要介绍Bridge使用的STP协议的原理,包括STP协议解决的问题,STP协议包的格式及主要功能、Bridge的STP的部分实现以及“地址学习”的原理和实现。STP协议很大程度与IEEE 802.1D标准有关,本篇博客是在Undersatand Linux Network Internal的Chapter 15的基础上写成的。
本篇博客耗时56小时。
网桥的本质工作是在链路层转发(IEEE 802.1D叫中继,Relay,我们权且将转发和中继一词等同,转发有时会改变帧)和过滤数据帧,那么就会有一些基本问题有待解决:
STP协议用于解决这些问题。STP,Spanning Tree Protocol,IEEE 802.1D定义,目前已经被RSTP、MSTP协议所更新、兼容,但是Linux系统仅仅实现了STP协议。STP协议定义了一种数据帧结构,用于网桥之间的通信,通过不同网桥之间发送这种数据帧,相互通知、应打,起到解决上述问题的效果,这种数据帧名字时BPDU,Bridge Protocol Data Unit(BPDU不仅仅为STP协议服务,还为网桥的其他协议如RSTP、MSTP协议服务)。
对于网桥的主要功能——链路层数据帧转发,不是在转发时临时在网络里查询应该向哪条路径转发,而是提前将网络拓扑探测好,并记录没条路径的权重、相关网桥、端口等信息,将这些信息存储在一个转发数据库中(类似于路由表),在转发时通过查阅转发数据库来确定转发路径,这一转发数据库叫做FDB,Forward Database。
STP协议,最小生成树协议,用于找到一个图中的最小生成树,关于找最小生成树算法,STP协议使用了这些算法,关于算法,可以参考最小生成树算法;802.1D中的STP协议使用的最小生成树算法并非在本地运行的,而是在网络上运行的。
首先STP协议运行在L2层,也即ISO/OSI的链路层,Data Link Layer,而链路层本身又分为两个子层,介质访问控制子层(MAC)和逻辑链路控制子层(LLC),STP工作于LLC层之上,STP使用的结构叫做BPDU,如下图所示:
STP工作于LLC层之上,这意味着如果要使用STP协议,则sk_buff里要具备MAC头和LLC头,然后才是STP的帧格式BPDU,STP帧的手发都要经过LLC层。下面看一下BPDU的格式,根据BDPU的类型,可以分为两类,分别是Configuration BPDU和TCN BPDU(Topology Change Notification),Configration BPDU用于通知声明和通知root bridge,而TCN BPDU用于通知网络拓扑变化:
Protocol ID为0,代表STP协议,BPDU还支持RSTP和MSTP,通过Protocol Version区别:
BPDU Type分为两种,一种为Configuration BPDU,一种为TCN BPDU:
#define BPDU_TYPE_CONFIG 0
#define BPDU_TYPE_TCN 0x80
接下来是一个标志位,但只有两种情况被使用TC(Topology Change)和TCA(Topology Change Acknowledgment)。
然后是Priority vector,优先级向量,用于描述网桥、端口的优先级,比较重要,用于确定根网桥、根端口、指定端口等。包含四个部分,下文中会详细解释。
Message Age,有点类似与IP协议的TTL,用于描述当前Configuration BPDU已经经过的跳数(hop,当然也可以是实际的时间),经过一个Port就增加该部分,但Linux系统没有使用该方法,而是计算从接收该BPDU到传输BPDU之间所花费的时间,这也是IEEE 802.1D原本的定义。
Max Age,最大生存周期,如果Message Age超过了Max Age,drop即可。
Hello Time,Hello时间,根网桥周期性的发送Configuration BPDU,告知其他Bridge,Hello Time用于描述该周期,默认2s。
Forward Delay,转发时间,默认15s,端口从LISTENING状态转到LEARNING状态,经过Forward Delay Time,从LEARNING状态转到FORWARDING状态,经过Forward Delay Time。
Linux内核中定一个Configuration BPDU的结构体(TCN的不使用结构体):
struct br_config_bpdu
{
unsigned int topology_change : 1;
unsigned int topology_change_ack : 1;
bridge_id root;
int root_path_cost;
bridge_id bridge_id;
port_id port_id;
int message_age;
int max_age;
int hello_time;
int forward_delay;
};
3、STP的几个基本概念
|
为了方便描述和管理网桥,引入几个基本概念:根网桥(Root Bridge)、根端口(Root Port)、指定桥(Designated Bridge)、指定端口(Designated Port)。
根网桥,指连通不同的LAN的网桥中,网桥ID最小的那个,也即抽象为最小生成树后的树根。网桥ID的描述方法如下:
struct bridge_id
{
unsigned char prio[2];
unsigned char addr[6];
};
根端口,一个网桥的所有端口中,到达根网桥的cost最少的那个端口被称为根端口。根端口的在生成树中相当于任意一个节点通往根结点最短的边。这里的cost指从某一网桥的某一个端口到达根网桥任意端口所耗费的时间,可以由管理员手动指定cost。网桥通过Confiuration BPDU的cost字段来判断到达该port的cost。cost是Port的一个属性,cost越低,越应该选择该Port转发数据。一个100Mb的以太网的cost应该比1000Mb的以太网的cost高。Linux内核中关于cost的计算,使用port_cost()函数:
static int port_cost(struct net_device *dev)
{
struct ethtool_cmd ecmd;
if(!__ethtool_get_settings(dev, &ecmd)) {
switch(ethtool_cmd_speed(&ecmd)) {
case SPEED_10000:
return 2;
case SPEED_1000:
return 4;
case SPEED_100:
return 19;
case SPEED_10:
return 100;
}
}
/* Old silly heuristics based on name */
if(!strncmp(dev->name,"lec", 3))
return 7;
if(!strncmp(dev->name,"plip", 4))
return 2500;
return 100;/* assume old 10Mbps */
}
cost是网卡的属性,比较重要,Bridge和LAN很可能是下面这种方式连接的:
转发Configuration BPDU时,必须将自己的cost加上;假设到达Bridge1的cost为orig cost,那么从Bridge1的p1端口出来的BPDU,经过LAN1到达Bridge3的p3端口时,cost为10+orig cost,而Bridge3再通过其p1端口转发时,cost将达到orig cost+12,而不是orig cost+22,也即发送Configuration BPDU时增加cost,接收时,并不增加cost。这样是否合理?感觉cost应该是path的属性,而不是网卡的属性。
指定桥,是一个从LAN的角度看的概念,如果一个LAN连接了几个网桥(通过几个Port),则在连接这个LAN的所有Bridge中,到达根网桥的cost最小的Port被称为LAN的指定端口(选择指定端口时,实际上看的时Priority vector,不仅仅看Root Path cost),指定端口所在的桥成为LAN的指定桥。指定端口的意义在于通往目标LAN的cost最短。
对于任意一个网桥的任意端口,都属于三种角色(role)中的任意一种,根端口、指定端口、普通端口(不是根端口、也不是指定端口,官方文档上并没有这样的定义,只是说起来比较方百年)。任意一个网桥都只有一个根端口,任意一个LAN也只有一个指定端口,根网桥没有根端口,所有端口都是指定端口(虽然根网桥的端口有可能直接连接其他网桥,而不是LAN,但是也将这种情况说成指定端口),一个网桥有可能有几个指定端口。指定端口通常用来转发根网桥的Configuration BPDU,而根端口通常用于向根网桥发送TCN BPDU。
如下图所示,首先这并不是一颗实际的STP协议生成树,而是便于理解根网桥、根端口、指定端口、指定网桥等概念而画的一张示意图,途中方框节点为网桥,圆形节点为LAN,边代表网桥的Port(实际生成树中不考虑LAN节点,只有网桥节点)。很显然,途中紫色节点2号ID最小,为根节点,也即根网桥;图中所有圆形节点,也即LAN节点有且仅有一条实线与网桥节点相连,相连的网桥节点就是该LAN节点的指定网桥;任意一个网桥节点,图中标有红色线条的是其根端口。图中剩余的线条均为虚线,为实际上物理相连、但生成树中不使用的端口,即不是根端口,也不是指定端口,就是所谓的普通端口。这些端口在网络稳定后,通常处于BLOCKING状态,只能接收STP协议包,但并不能发送STP协议包,也不能学习、转发数据:
根据行为方式的不同,将端口划分为五种状态(Port stat):
#define BR_STATE_DISABLED 0
#define BR_STATE_LISTENING 1
#define BR_STATE_LEARNING 2
#define BR_STATE_FORWARDING 3
#define BR_STATE_BLOCKING 4
五种端口状态的功能区别:
这五种状态转换过程如下:
随着STP协议的运行或者管理员的操作,Port在五种状态之间转换。Listening经过一段时间进入Learning状态,Learning状态经过一段时间进入Forwarding状态,这两段时间叫做Forward Delay Time,通过定时器Forward Delay Timer来计时。
既然定义了Root Bridge、Root Port、Designated Port,STP协议必须能够找出他们,首先是根网桥。
任意一个网桥启动后,由于不知道网络拓扑结构,所以它认为自己是根网桥,自己所有端口都认为是Designated Port,cost均为0,对外广播Configuration BPDU数据包,将BPDU的Priority vector填充后发送(也填充其他字段,含义不在这里描述了)。其中Priority vector比较重要,由4个字段组成,分别是Root Bridge ID、Root Path cost、Bridge ID、Port ID,都是字面意思,从哪个Port发送,填充哪个Port的ID。Priority vector不但用于确定根网桥,还用于确定根端口和指定端口。
如果网络中没有其他网桥,则没有回应,该网桥成为根桥,并且以Hello time(默认2s)周期性的向网络广播根桥的Configuration BPDU,以通知其他网桥根桥的配置。
如果网络中已经存在根桥,则网络中其他网桥存储了根网桥的信息,当其他网桥(或根网桥)收到新启动网桥的自认为根桥的Configuration BPDU时,将其Priority vector中的Root Bridge ID与现有根桥的Bridge ID相比较,如果其Root Bridge ID项小于现有根网桥的Bridge ID,则认为新启动网桥为根网桥,并更新本地的根桥配置信息,并转发其Configuration BPDU;如果其Root Bridge ID项不小于现有根网桥的Bridge ID,则将自己知道的根桥信息,生成一个Configuration BPDU,从接收端口发送出去,而后新启动网桥接收到该Configuration BPDU,则获知Root Bridge ID,并更新本地根网桥的配置。
根网桥的发现,用一句话描述,“启动自认根网桥,BPDU来广播,ID小者真根桥,BPDU来纠错”。
当确定根网桥后,根端口的选择就比较简单,当任意网桥的任意端口收到Configuration BPDU时,比较此前的根端口的Priority vector相比较,小的那个就是根端口。
指定桥,对于任意一个Port,并定能够接收到所连接相同LAN的所有Port的Configuration BPDU,那么就可以通过比较这些Priority vector和本地桥其他Port接收Configuration BPDU的Priority vector。,然后再加上相应的cost,就得到了自己的到达该LAN的cost,通过比较这些Priority vector(cost一致的时候比较Bridge ID和Port ID),就选择出最小的那个作为指定端口。
OK,等根桥、所有网桥的根端口、指定桥、指定端口都已经确定后,整个网络拓扑结构中没有环路的时候,我们说这个网络是稳定的。对于新增加的网桥,已经存在的非根桥网桥,只在其指定桥端口转发Configuration BPDU,换言之,只有指定桥处理新增加网桥的Configuration BPDU。
首先,假设已经由Bridge1、Bridge2、Bridge4连接了五个局域网LAN1、LAN2、LAN3、LAN4、LAN5,并且Bridge1的ID为BR1,Bridge2的ID为BR2,Bridge4的ID为BR4,且BR1
其次,假设此时要增加一个Bridge3,用于连接LAN3和LAN5,Bridge3上两个端口,p1连接LAN5,p2连接LAN3,Bridge3的ID为BR3,BR2 再次,当Bridge4和p1端口和Bridge2的p1端口接收到Bridge3的自认为根网桥的Configuration BPDU时,通过与此前存储的Bridge1的Priority vector相比较,得到BR3>BR1的结论,因此仍然认为Bridge1为根网桥,但是Bridge2和Bridge4转发Bridge1的Configuration BPDU给Bridge3,通知其真正的根网桥是Bridge1(注意,Bridge3的BPDU不一定非得到达根桥Bridge1才能得到回复,只要根桥发送的Configuration BPDU的Max Age没有过期,其他非根桥就存储该BPDU,以备转发,默认Max Age为20,而根桥以Hello Time为周期发送Configuration BPDU,通常不会过期): 接着,当Bridge3接收到Bridge2和Bridge4转发过来的BPDU时,通过比对Bridge ID发现Bridge1才是根网桥,则将根网桥Bridge1的相关信息(periority vector),存储起来,并通过比较Priority的Root Path cost,得出自己的p1端口到达LAN5的cost(50),比Bridge4的p1端口到达LAN5的cost更少(110),因此更新自己的p1端口、p2端口的角色(role),分别为指定端口和根端口,并通过p1端口向Bridge4发送Contigration BPDU通知其Root Path cost: 最后,Bridge4收到了Bridge3发送Contigration BPDU,通过比较Root Path cost获知Bridge3的p1端口到达LAN5的cost(50)低于自己的p1端口到达LAN5的cost(110),因此改变自己的p1端口的状态为BLOCKING(BLOCKING状态仍然可以接收BPDU): 通过这个例子,可以看到,Configuration BPDU通常在下列情况下发送: 另外,下文中将会看到,当网络拓扑发生变化时,也会使用到Configuration BPDU: Configuration BPDU用于告知根网桥的配置,以及计算Root Path Cost,TCN BPDU就是用来处理网络拓扑改变的情况。首先,要确认什么时所谓的“拓扑结构改变”,对于图来说,拓扑结构改变意味着,有点或者边的变化,比如添加一个节点,并添加几条边。但是在STP协议中,拓扑结构改变仅仅指根桥变化,或则边的添加或减少: 一旦某网桥发现网络拓扑结构发生变化,则通过TCN BPDU通知根网桥,随后,根网桥通过带有TC标志的Configuration BPDU,让所有的网桥知道这次网络拓扑结构的变化。具体过程如下(仍然使用稳定状态的例子): 收到带有TC标志的Configuration BPDU的网桥,不断重复步骤4,所有网桥均获知网络拓扑变化的小时,同时也都启动了Short Aging Timer用于刷新本地的转发数据库,清除(clean up)过时的信息。关于Short Aging Timer,就使用来清除过时信息的计时器,具体的清除方法是,从Short Aging Timer计时开始到其过期的期间,如果转发数据库的某些信息没有被使用过,则删除这些信息。Short Aging Timer默认5分钟(ULNI中说也可以为Forward Delay,没有在IEEE 802.1D中查到,但Linux是这么实现的,在拓扑变化时为Forward Delay,其他情况为默认5分钟)。 仍然使用稳定状态的例子,在Bridge3经过Forward Delay Time后,将将p1端口和p2端口的状态由LISTENING转为LEARNING,再经过Forward Delay Time,将他们的状态转为FORWARDING,等待端口p1或p2的状态转为FORWARDING后,Bridge3通过其根端口p2,以Hello Time为周期(实际上使用的计时器名字为TCN Timer)向外发送TCN BPDU,直至收到带TCA标志的Configuration BPDU: 接着,Bridge2的p1端口收到了Bridge3的p2端口的TCN BPDU,Bridge2先判断自己的p1端口是否为指定端口(不是指定端口,直接丢弃,什么事情也不做),如果是则通过p1端口发送带有TCA标志的Configuration BPDU,作为响应,同时通过其根端口p3做Bridge3在p2上做的事情,以Hello Time为周期向外发送TCN BPDU,直至收到带TCA标志的Configuration BPDU;当Bridge1通过p3端口收到Bridge2的TCN BPDU时,也做Bridge2在收到Bridge3的根端口时的事情,校验p3是否为指定端口、向Bridge2的p3回复带有TCA标志的Configuration BPDU: 最后,当根网桥Bridge1获知网络拓扑变化后,启动Topology Change Timer(Max Age + Forward Delay,默认35秒),在Topology Change Timer结束前,通过广播带有TC标志的Configuration BPDU将网络拓扑变化的消息通知所有Bridge,任意一个网桥都通过其指定端口转发该消息,并同时回复带有TCA标志的Configuration BPDU,同时内部启动Short Aging Timer准备刷新转发数据库,将拓扑结构更新到转发数据库中: 关于刷新数据库,这里有个疑问。在Short Aging Timer期间(或者叫Aging Timer),如果没有转发的数据包使用该记录,则将其删除。那么就有个方向问题,Bridge4不会将LAN5的数据包转发给LAN4,那么LAN1如果想访问LAN5,那么还是通过Bridge2,Bridge2由于此前一直认为目的MAC为xxx的都要向Brdige4转发,现在还是这么转发,只不过收不到Bridge4的回复了。那么这种情况算不使用转发数据库中的信息吗?下文中将有详细解释。 有个问题,想想有这种可能性吗?一个LAN连接了两个网桥,其中一个新启动的Brige3,一个旧的Bridge4,LAN原先通过Bridge4连到根网桥,即Bridge3为LAN的指定桥,而Bridge3启动后,Root Path cost较小,则LAN选择了Bridge3作为指定桥,同时Bridge4的相关端口将转为BLOCKING状态,但是Bridge3的相关端口此时还是LISTENING状态,并不是FORWARDING,这意味着此时LAN被孤立?是的,确实由这种情况,着决定了Bridge是否可用。 想想吧,怎么才能解决这个问题? 先看发送,STP协议并不允许以太快的速度发送BPDU,任意两个BPDU包的发送时间间隔必须超过Hold Time(默认1s)。对于任意接收的Configuration BPDU的Bridge按照下面的流程对其进行处理(摘自ULNI Chapter15): 上述流程对应Linux内核Bridge代码的br_transmit_config()函数,从函数的字面意思来看,符合上述流程(怀疑就是根据这个函数写的流程,并且这么多年根本没变化过),其中具体的BPDU的赋值过程在br_send_config_bpdu()接口中完成: 该操作对应Linux内核Bridge代码中的br_received_config_bpdu()函数,仅仅从函数的字面意思看,是符合上述流程的(有些细节可能看不到): STP收敛时间,是指对于一次网络拓扑的变化引起的STP协议执行到网络稳定的时间。对于一个复杂的网络,STP可能经过数分钟才能够稳定,那么要计算STP的收敛时间,先看一下STP协议中的各种Timer。STP中的Timer分两类,分别是Bridge的Timer和Port的Timer: Bridge Timers(此处存疑吧,我查阅了IEEE 802.1D的说法,Aging Time的默认值为300,范围为10.0-1,000,000.0s,没提和Forward Delay的关系,但Linux实现为如果拓扑变化时则Aging Time为Forward Delay,其他情况为默认值300s,想想也应该): Port Timers: 根据本文中“一次稳定状态的过程”中的例子,从新添加一个Bridge3,到最终Bridge3的p1变为FORWARDING状态,中间最少要经过2个Forward Delay和一个Short Aging Time,按照默认值计算,这是300+30=330s,按照Short Aging Time等于Forward Delay算,45s过去了。 又假设根网桥发生了变化(本文中没有画这种情况的图),比如根网桥被管理员移除,瞬间网络可能出现分割的情况,而其他网桥并不能瞬间获知这一变化,必须经过Max Age后,发现没有根网桥的Configuration BPDU,那么所有网桥开始认为自己是根网桥(可能不是同时的,但大概差不多),然后经过至少两个Forward Delay以后,才能选出真正的根网桥。那么,这段时间耗费是Max Age + 2*Forward Delay,都按照默认的计算,为20+2*15=50s。 增加节点5分钟起作用,改变根网桥50秒起作用,对于高速网络而言,这是不可接受的,这也引出了RSTP和MSTP。 地址学习很重要,没有地址学习功能,Bridge就没法在L2层上转发数据帧,但相对于STP协议,地址学习属于细节,因此放在后面。 转发数据库,Forward Database,起到转发目的网桥查询功能,即转发每个数据帧时,根据目的MAC地址通过查询FDB中对应的PortID,并通过该Port将数据帧转发出去,FDB使用“地址学习”的方法来增加其条目,通过Shrot Aging Timer来清除过期的条目,下文中也将FDB条目成为FDB entry。看下Linux对FDB的实现: 下面看下“地址学习”的过程和Short Aging Timer的原理,首先是地址学习: Bridge通过一种叫“地址学习”的功能来确定数据帧如何在L2上转发,也即对于任意一个数据帧,从某个Port到达Bridge,Bridge就可以分析其源MAC和目的MAC,Bridge认为,源MAC地址属于接收Port连接的LAN,而目的MAC地址,查阅FDB是否有描述属于哪个LAN(对应的Port),如果査到目的MAC对应的LAN,则能够找到转发Port,转发即可。如果没有査到目的MAC对应的LAN,则对该数据帧进行“洪泛”,也即将其发送给除了源Port以外的所有Port。一但有一个Port有反馈相应MAC地址的数据帧,Bridge认为目的MAC地址属于相关Port得到确认,并将这条记录存在FDB中: 通过上述图片容易理解“地址学习”过程,说白了就两句话:从一个Port收到的数据帧,数据帧的源MAC地址与该PortID相关联;不识别的目的MAC地址,进行洪泛(flooding),即向除了源Port外的所有Port广播该数据帧。但是地址学习的过程也比较容易出现问题: 对于以上三个问题,解答如下,不知到对不对: 看下Linux内核如何实现Address Learning的过程: Bridge提供了一系列函数操作FDB,br_fdb_xxx(),这里仅仅列出部分函数名字,功能如字面含义: 下面来段逆向工程,首先找到fdb_create()为添加、更新FDB entry的原始函数,再看其调用关系: fdb_create()接口直接创建一个FDB entry并插入到net_bridge->hlist里。Bridge代码在几个位置调用了该函数,分别是br_device_ops.ndo_do_ioctl、br_device_ops.ndo_set_mac_address、br_device_ops.ndo_fdb_add以及br_device_notifier.notifier_call()。这些位置均为更改网桥配置导致的FDB的变化,在为网桥增加网卡、改变网桥MAC地址、其他网卡改变MAC地址、或者用户手动添加一条FDB记录,都会触发向FDB里插入一条记录的动作,但这不是真正的“地址学习”的过程。真正的地址“学习”过程是br_handle_frame接口,该接口与网桥设备的rx_handler相挂接,负责当网桥接收到数据帧时,对数据帧进行处理,处理的过程无非就是过滤和转发,在drop掉无需转发的数据帧,通过br_fdb_update()接口来更新FDB条目,此处不讨论他的细节了,但br_handle_frame()会在另外一篇Blog中详细讨论。 Short Aging Timer用于刷新FDB,将那些过期的条目删除掉,这些过期的条目主要由网络拓扑结构变化引起。原理上,关于Aging Timer的启动点: 看下Linux内核对Short Aging Timer的实现。在Linux内核中的Bridge代码实现,Short Aging Timer被实现为gc_timer,是结构体net_bridge的一个成员。gc_timer在网桥这一网络设备open时初始化,在其stop时删除,gc_timer同时在刷新FDB后被重置。gc_timer在增加网桥时被setup_timer()函数注册为到期自动触发br_fdb_cleanup(): gc_time到期后自动触发br_fdb_cleanup(),用于清理过期的条目: 变化: 这篇博客写得并不容易,起初我想自己通过Linux内核代码学习STP的实现,通过提出问题,解决问题的方法来学习,但是发现在很多理论不清楚的情况下,非常费劲,最终不得不退回到ULNI的Chapter14、15、16三章,ULNI写得非常清晰,当时我不清楚ULNI为什么使用4章共112页去描述网桥,读完其中的部分章节后发现,ULNI说的比较清楚,篇幅用来将复杂问题简单化,篇幅等于理解。 其次,写道这我还是有不少不明白的东西,或者疑问,在原文中直接以问题的方式提出,并标记为黄色背景,但由于时间问题,可能会留到后面去具体分析了。本文中还几个问题没有弄清除: 另外画图浪费时间,应该放到最后去做。 由于文章并非一次性写成,因此可能存在上下文的错误,敬请指出。
5、拓扑结构改变
6、收发Configuraton BPDU数据帧的处理流程
void br_transmit_config(struct net_bridge_port *p)
{
struct br_config_bpdu bpdu;
struct net_bridge *br;
if(timer_pending(&p->hold_timer)) {
p->config_pending = 1;
return;
}
br = p->br;
bpdu.topology_change = br->topology_change;
bpdu.topology_change_ack = p->topology_change_ack;
bpdu.root = br->designated_root;
bpdu.root_path_cost = br->root_path_cost;
bpdu.bridge_id = br->bridge_id;
bpdu.port_id = p->port_id;
if(br_is_root_bridge(br))
bpdu.message_age = 0;
else{
struct net_bridge_port *root
= br_get_port(br, br->root_port);
bpdu.message_age = (jiffies - root->designated_age)
+ MESSAGE_AGE_INCR;
}
bpdu.max_age = br->max_age;
bpdu.hello_time = br->hello_time;
bpdu.forward_delay = br->forward_delay;
if(bpdu.message_age < br->max_age) {
br_send_config_bpdu(p, &bpdu);
p->topology_change_ack = 0;
p->config_pending = 0;
mod_timer(&p->hold_timer,
round_jiffies(jiffies + BR_HOLD_TIME));
}
}
再看接收,对于
任意一个接收的
Configuration BPDU数据帧,按照如下流程进行处理,注意各种Timer的使用(有个疑问,priority vector不高,则判断不是指定端口接收的,那么):
void br_received_config_bpdu(struct net_bridge_port *p,
const struct br_config_bpdu *bpdu)
{
struct net_bridge *br;
int was_root;
br = p->br;
was_root = br_is_root_bridge(br);
if(br_supersedes_port_info(p, bpdu)) {
br_record_config_information(p, bpdu);
br_configuration_update(br);
br_port_state_selection(br);
if(!br_is_root_bridge(br) && was_root) {
del_timer(&br->hello_timer);
if(br->topology_change_detected) {
del_timer(&br->topology_change_timer);
br_transmit_tcn(br);
mod_timer(&br->tcn_timer,
jiffies + br->bridge_hello_time);
}
}
if(p->port_no == br->root_port) {
br_record_config_timeout_values(br, bpdu);
br_config_bpdu_generation(br);
if(bpdu->topology_change_ack)
br_topology_change_acknowledged(br);
}
}elseif(br_is_designated_port(p)) {
br_reply(p);
}
}
8、转发数据库操作
struct net_bridge_fdb_entry
{
struct hlist_node hlist;
struct net_bridge_port *dst;
struct rcu_head rcu;
unsigned long updated;
unsigned long used;
mac_addr addr;
unsigned char is_local;
unsigned char is_static;
__u16 vlan_id;
};
void br_fdb_cleanup(unsigned long _data)
{
struct net_bridge *br = (struct net_bridge *)_data;
unsigned long delay = hold_time(br);
unsigned long next_timer = jiffies + br->ageing_time;
int i;
spin_lock(&br->hash_lock);
for(i = 0; i < BR_HASH_SIZE; i++) {
struct net_bridge_fdb_entry *f;
struct hlist_node *n;
hlist_for_each_entry_safe(f, n, &br->hash[i], hlist) {
unsigned long this_timer;
if(f->is_static)
continue;
this_timer = f->updated + delay;
if(time_before_eq(this_timer, jiffies))
fdb_delete(br, f);
elseif(time_before(this_timer, next_timer))
next_timer = this_timer;
}
}
spin_unlock(&br->hash_lock);
mod_timer(&br->gc_timer, round_jiffies_up(next_timer));
}
br->aging_time默认为300s,hold_time代码如下,delay代表Short Aging Timer的时间段,为300s或者15s,根据是否发生网络拓扑
static inline unsigned long hold_time(const struct net_bridge *br)
{
returnbr->topology_change ? br->forward_delay : br->ageing_time;
}
mod_timer重置timer的到期时间,其他代码容易理解不逐条说明了。
总结