【计网实验——prj6】生成树机制实验
实验要求
1. 基于已有代码,实现生成树运行机制,对于给定拓扑(four_node_ring.py),计算输出相应状态下的最小生成树拓扑;
2. 自己构造一个不少于7个节点,冗余链路不少于2条的拓扑,节点和端口的命名规则可参考four_node_ring.py,使用stp程序计算输出最小生成树拓扑;
3. 在four_node_ring.py基础上,添加两个端节点,把第05次实验的交换机转发代码与本实验代码结合,试着构建生成树之后进行转发表学习和数据包转发。
实现方案
生成树运行机制的主要过程为处理config
消息,这个过程实现在文件stp.c
的stp_handle_config_packet
函数中。
1. 实现逻辑
收到config消息后,将其与本端口的config信息进行优先级比较:
1)若收到的config
优先级比本端口高,则说明该网段应该通过对方端口连接根节点,需要进行如下操作:
- 将本端口的
config
替换为收到的config
消息,本端口为非指定端口; - 更新节点状态,更新剩余端口的
config
; - 如果节点由根节点变为非根节点,停止hello定时器;
- 将更新后的
config
从每个指定端口转发出去;
2)若本端口的config
优先级更高,则说明该网段应该通过本端口连接根节点:
- 该端口是指定端口,发送
config
消息。
2. 代码展示
1)更新本端口的config
,将本端口的config
替换为收到的config
消息,本端口为非指定端口:
代码实现如下:
//update config of this port with the config packet recieved
p->designated_root = ntohll(config->root_id);
p->designated_switch = ntohll(config->switch_id);
p->designated_port = ntohs(config->port_id);
p->designated_cost = ntohl(config->root_path_cost);
stp_port_t *non_designated_ports[STP_MAX_PORTS];
2)更新节点状态,节点状态分为根节点与非根节点两种,根据如下条件判断节点是否为根节点:
①先判断是否存在根端口,遍历所有端口,满足如下条件的为根端口(root_port)
- 该端口是非指定端口
- 该端口的优先级要高于所有剩余非指定端口
②若存在根端口,该节点通过根端口连接到根节点,更新节点状态如下:
③否则,该节点更新为根节点:
代码实现如下:
//update switch
int num_non_ports = 0;
for (int i = 0; i < stp->nports; i++) {
if(!stp_port_is_designated(&stp->ports[i])) {
non_designated_ports[num_non_ports] = &stp->ports[i];
num_non_ports ++;
}
}
int judge = 0;
if (num_non_ports == 1) {
stp->root_port = non_designated_ports[0];
}
else {
for(int i = 0; i < num_non_ports - 1; i++) {
if (judge == 0) {
if(!compare_priority(non_designated_ports[i], non_designated_ports[i+1], NULL, 0)) {
stp->root_port = non_designated_ports[i];
judge = 1;
}
}
else {
if(!compare_priority(non_designated_ports[i], stp->root_port, NULL, 0){
stp->root_port = non_designated_ports[i];
}
}
}
}
if (stp->root_port == NULL) {
stp->designated_root = stp->switch_id;
stp->root_path_cost = 0;
}
else {
stp->designated_root = stp->root_port->designated_root;
stp->root_path_cost = stp->root_port->designated_cost + stp->root_port->path_cost;
}
3)更新其他端口的config
,有两种情况下端口的config
需要更新:
- 指定端口→指定端口:端口状态没有改变,但需要更新其认为的根节点和路径开销;
- 非指定端口→指定端口:若一个非指定端口其
config
较网段内其他端口优先级更高,那么该端口更新为指定端口。
代码实现如下:
//update other ports' config
for (int i =0 ; i < stp->nports; i++) {
if(stp_port_is_designated(&stp->ports[i])) {
stp->ports[i].designated_root = stp->designated_root;
stp->ports[i].designated_cost = stp->root_path_cost;
}
}
其中,第②种情况无需在此处添加额外代码即可实现。
4)config
之间的优先级比较
以上过程中涉及到两种config
信息的比较,一种是端口与端口间的config
比较,另一种是端口config
与接收的config
消息之间的比较。
两种比较的区别是第二种由于接收到的包来自于网络抓包,因此收到的包需要先进行字节序的转换才能与本端口的config
进行比较。
两种比较的逻辑相同为:首先比较节点ID,相同则比较到根节点的开销,再相同则比较上⼀跳节点的ID,最后比较端口ID,所有的比较均为小的值优先级更高。因此,将两种比较用同一个函数实现,用一个传入参数transform
区分进行的是哪一种比较,即是否需要将config
包字节序转换后再进行比较:
//compare port config with config packet recieved(tranform==1) or config of another port(transform==0)
int compare_priority(stp_port_t *p, stp_port_t *q, struct stp_config *config, int transform){
u64 root_id, switch_id;
int root_path_cost, port_id;
if(transform){
root_id = ntohll(config->root_id);
switch_id = ntohll(config->switch_id);
root_path_cost = ntohl(config->root_path_cost);
port_id = ntohs(config->port_id);
}
else{
root_id = q->designated_root;
switch_id = q->designated_switch;
root_path_cost = q->designated_cost;
port_id = q->designated_port;
}
if (p->designated_root != root_id) {
return p->designated_root > root_id;
}
else if (p->designated_cost != root_path_cost) {
return p->designated_cost > root_path_cost
}
else if (p->designated_switch != switch_id) {
return p->designated_switch > switch_id;
}
else if (p->designated_port != port_id) {
return p->designated_port > port_id;
}
else {
return 1;
}
}
运行结果
1. 利用网络拓扑进行四节点测试
结果如下:
2. 自己构造七节点并进行测试
reference程序与自己实现的程序运行结果相同:
./stp
./stp-reference
3. 结合上次实验进行转发表学习和数据包转发测试
在拓扑结构中添加两个主机h1和h2,运行拓扑程序,等待一段之间后在xterm输入h1 ping h2
,得到结果如下:
说明h1与h2之间可以进行通信,构建生成树后数据包转发成功。
思考题
1. 调研说明标准生成树协议中,如何处理网络拓扑变动的情况:当节点加入时?当节点离开时?
在交换网络中,交换机依赖MAC地址表转发数据帧。缺省情况下,MAC地址表项的老化时间是300秒。在节点加入或离开拓扑结构时,都将先重新进行STP计算,更新各节点状态与端口的配置信息,产生新的树形拓扑结构。如果生成树拓扑发生变化,交换机转发数据的路径也会随着发生改变,此时MAC地址表中未及时老化掉的表项会导致数据转发错误,因此在拓扑发生变化后需要及时更新MAC地址表项。
拓扑变化过程中,根桥通过TCN BPDU报文(Topology Change Notification)获知生成树拓扑里发生了故障。根桥生成TC用来通知其他交换机加速老化现有的MAC地址表项。拓扑变更以及MAC地址表项更新的具体过程如下:
- 在网络拓扑发生变化后,有端口转为转发状态的下游设备会不间断地向上游设备发送TCN BPDU报文;
- 上游设备收到下游设备发来的TCN BPDU报文后,只有指定端口处理TCN BPDU报文。其它端口也有可能收到TCN BPDU报文,但不会处理;
- 上游设备会把配置BPDU报文中的Flags的TCA位设置1,然后发送给下游设备,告知下游设备停止发送TCN BPDU报文;
- 上游设备复制一份TCN BPDU报文,向根桥方向发送;
- 重复步骤1、2、3、4,直到根桥收到TCN BPDU报文;
- 根桥收到TCN BPDU后,会将下一个要发送的配置BPDU中的TCA位置位,作为对收到的TCN的确认,还会将该配置BPDU报文中的Flags的TC位置1,用于通知所有网桥拓扑发生了变化;
- 根桥在之后的max age+forwarding delay时间内,将发送BPDU中的TC置位的报文,收到该配置BDPU的网桥,会将自身MAC地址老化时间缩短为forwarding delay。
假设下图拓扑发生了变动,将重新进行STP计算,生成新的树形拓扑,并按照以上方式将拓扑改变的信息传递至整个网络,使得交换机上的MAC地址表很快收敛,避免无效流量浪费带宽:
- 图中,经由STP计算,S1为根桥,S4的E1端口被阻塞。
- 当S3的面向PC1的链路断掉之后,网络中的STP将进行重算,S4的E1端口将转变为指定端口,进入转发状态,此时S4会向上游发送TCN消息。
- S2收到S3的TCN消息之后,将下一个配置BPDU中的TCA置位并从端口E3发送给S4,S2也从自己的根端口E1向根发送TCN BPDU。
- S1收到S2发送的TCN消息之后,将下一个配置BPDU中的TCA和TC置位并从指定端口E1发送给S2。此后的(20秒+15秒)时间内,S1均将配置BPDU中的TC置位,各个网桥收到TC置位的BPDU后,将MAC地址的老化时间置为15秒。
2. 调研说明标准生成树协议是如何在构建生成树过程中保持网络连通的?
在构建生成树的过程中,STP会将部分冗余链路强制转化为阻塞状态,其余则链路处于转发状态。当处于转发状态的链路不可用时,STP可以重新配置网络,集合合适的备用链路,恢复部分冗余链路的转发状态来确保网络的连通性。
3. 实验中的生成树机制效率较低,调研说明快速生成树机制的原理。
快速生成树协议RSTP(Rapid Spanning Tree Protocol)在STP基础上实现了快速收敛,并增加了边缘端口的概念及保护功能。
RSTP的端口角色:
RSTP在STP基础上新增加了2种端口角色:Backup端口和边缘端口。通过端口角色的增补,简化了生成树协议的理解及部署。
- Backup端口:由于学习到自己发送的配置BPDU报文而阻塞的端口,指定端口的备份,提供了另外一条从根节点到叶节点的备份通路。
- 边缘端口:如果端口位于整个交换区域边缘,不与任何交换设备连接,这种端口叫做边缘端口。边缘端口一般与用户终端设备直接连接。
RSTP的端口状态:
RSTP的端口状态在STP的基础上进行了改进。由原来的五种缩减为三种。
- 如果不转发用户流量也不学习MAC地址,那么端口状态就是Discarding状态。
- 如果不转发用户流量但是学习MAC地址,那么端口状态就是Learning状态。
- 如果既转发用户流量又学习MAC地址,那么端口状态就是Forwarding状态。
RSTP快速收敛机制:
在STP中,为了避免出现临时环路,端口从启动到进入转发状态默认需要等待30秒的时间;也就是说STP只能依靠计时器被动的收敛。如果缩短端口从启动到转发的等待时间,可能会引起网络的不稳定。RSTP采用P/A(Proposal/agreement)协商机制,无需依靠计时器来保障无环,可以让交换机的互联接口快速进入转发模式。
1)边缘端口机制
在RSTP里面,边缘端口不接收处理配置BPDU,不参与RSTP运算,可以由Disable直接转到Forwarding状态,且不经历时延,就像在端口上将STP禁用。但是一旦边缘端口收到配置BPDU,就丧失了边缘端口属性,成为普通STP端口,并重新进行生成树计算,从而可能引起网络震荡。
2)根端口快速切换机制
如果网络中一个根端口失效,那么网络中最优的Alternate端口将成为根端口,进入Forwarding状态。因为通过这个Alternate端口连接的网段上必然有个指定端口可以通往根桥。
3)Proposal/Agreement机制
当一个端口被选举成为指定端口之后,在STP中,该端口至少要等待一个Forward Delay(Learning)时间才会迁移到Forwarding状态。而在RSTP中,此端口会先进入Discarding状态,再通过Proposal/Agreement机制快速进入Forward状态。其收敛过程如下:
- 初始状态都认为自己是根桥,所有端口为指定端口,处理Discarding状态,发送配置RST BPDU;
- RST BPDU中的Proposal置位,与接收到的对端BPDU对比优先级,如果自己优,则丢弃对端BPDU,将Proposal置位的本地BPDU到对端;
- 使用同步机制来实现角色端口的协商,当收到置位RST BPDU且优先级高于时,交换机设置所有下游端口为Discarding状态,但是Alternat和边缘端口不变;
- 确认下游端口为Discarding状态后,设备发送RST BPDU回复上游设备发送的Proposal消息,在此过程中,端口被确认为根端口,报文中Flags字段里面设置Agreement标记位和根端口角色;
- 上游交换机收到Agreement置位的RST BPDU后指定端口立即进入Forwarding状态,下游网段进行同样的协商端口角色。
如上图,当S1和S2之间新增了一条链路后,P/A机制工作如下: - S1通过端口E1发送Proposal置位的RST BPDU消息给S2;
- S2收到该消息后,意识到E2为根端口,启用同步机制阻塞指定端口E1和E3以避免产生环路,然后将根端口设置为转发状态,并向S1发送Agreement消息;
- S1收到Agreement消息后,指定端口E1马上进入转发状态;
- S2处于同步状态的非边缘指定端口E1和E3发送Proposal报文;
- S3收到S2发送的Proposal报文后,判断E1为根端口,启动同步过程,由于S3下游均为边缘端口,所以已经实现了同步,因此S3直接向S2回复Agreement消息;
- S2收到S3发送的Agreement消息后,端口E1马上进入转发状态;
- S4的处理过程如S3;
- S2收到S4发送的Agreement消息后,端口E3马上进入转发状态;
- P/A过程结束。
RSTP与STP相比,主要有以下几点优势:
- 如果旧的根端口已经进入阻塞状态,而且新根端口连接的对端交换机的指定端口处于Forwarding状态,在新拓扑结构中的根端口可以立刻进入转发状态;
- 网络边缘的端口,即直接与终端相连,而不是和其它网桥相连的端口可以直接进入转发状态,不需要任何延时;
- 增加了网桥之间的协商机制—Proposal/Agreement。指定端口可以通过与相连的网桥进行一次握手,快速进入转发状态。
也正是由于这几点,使得RSTP的收敛速度比STP快很多,大大提高了构建生成树和网络传输的效率。