生成树协议
Spanning Tree Protocol,一种用于在网络中检测环路并逻辑地阻塞冗余路径,以确保在任意两个节点之间只存在一条路径的技术。生成树机制通过禁止网络设备的相关端口,在有环路的网络中构造出一个总体开销最小的树状拓扑,使得网络在连通的前提下,避免广播风暴。
当网络中出现环路时,该协议可以采用生成树的算法从逻辑上断开其中一条连接,使其成为备份线路。当网络出现断路时,该协议会自动启动上述备份线路,确保网络正常工作。
名称 | 详细信息 | 描述对象 |
---|---|---|
根节点 | Root Switch,生成树网络的根。一个网络中只有一个根节点,网络中ID最小的交换机作为根节点,也即最终网络去环后的树状图的根。 | 整个网络 |
根端口 | Root Port,RP,网络中除了根节点之外,每一个结点有一个根端口。节点在网络中使用根端口连接到根节点,根端口是该节点到根节点路径开销最小的端口。 | 某个节点 |
指定端口 | Designated Port,DP,每一个网段(Segment,直连网络段,不需要经由交换机就能到达的部分,即一跳可达)有且只有一个指定端口。指定端口为该网段所有端口中到根节点开销最小的端口。一方面,构建生成树拓扑时,STP消息通过指定端口发送到该网段内;另一方面,该网段通过指定端口连接其他网段。描述对象 | 某个网段 |
其他端口 | Alternate Port,AP,剩余的端口,不参与构建生成树拓扑,不转发任何消息。 | |
配置消息 | BPDU Config,节点之间通过交换Config消息获取路径及优先级等信息。每个端口独立生成自己的Config消息,包括:自己的节点ID,发送端口ID,自己认为的根节点ID,以及到根节点的路径和开销。 |
配置信息发送方式:基于二层组播,目的MAC地址为01-80-C2-00-00-00
这里简述一下几个典型的组播地址:
01-80-C2-00-00-00(STP协议使用)
01-80-C2-00-00-01(MAC Control的PAUSE帧使用)
01-80-C2-00-00-02(Slow Protocol: 802.3ah OAM/ LACP 协议使用)
01-00-5E-xx-xx-xx(IP组播地址对应的二层组播地址)。
对于01-80-C2-00-00-00该地址,所有参与STP计算的交换机都会监听该
地址,并对目的地址为该地址的数据包进行响应。
Config消息由根节点以Hello Time的周期发出,消息老化时间为Max Age
生成树协议的数据包,从上到下封装为:IEEE 802.3 Ethernet > Logical-
Link Control > Spanning Tree Protocol
802.1D STP的消息格式 | |
---|---|
Proto ID | STP协议标识,为0 |
Version | STP版本号,为0 |
Msg Type | 标识是配置包(0x00)还是拓扑变动包(0x80) |
Flags | 标志位,第1位标识拓扑变更,第8位标志拓扑变更确认 |
Root Switch ID | 该节点认为的根节点ID,前16位为优先级,后48位为MAC地址 |
Root Path Cost | 从该节点该端口到根节点的开销 |
Switch ID | 发送该消息的节点ID,定义方式同Root Switch ID |
Port ID | 发送该消息的端口ID,前8位为优先级,后8位为编号 |
Msg Age | 该消息已存活时间,单位为1/256秒 |
Max Age | 消息最长允许存活时间,单位同上,默认20秒 |
Hello Time | 配置消息发送时间间隔,单位同上,默认2秒 |
Forward Delay | 不同状态间切换时延,单位同上,默认15秒 |
生成树协议原理:
数据结构:
typedef struct stp stp_t;
struct stp {
u64 switch_id; // 节点ID
u64 designated_root; // 节点自己认为的根节点,指定根节点
int root_path_cost; // 从自己到根节点的路径开销
stp_port_t *root_port; // 根端口,起初该指针为空
long long int last_tick; // switch timers
stp_timer_t hello_timer; // hello timer
// ports
int nports; // 端口数
stp_port_t ports[STP_MAX_PORTS]; // 节点的各个端口
pthread_mutex_t lock;
pthread_t timer_thread;
};
struct stp_port {
stp_t *stp; // 指向该端口所在节点
int port_id; // 端口ID
char *port_name;
iface_info_t *iface;
int path_cost; // 通过该端口所在网段的开销,本实验中为1
u64 designated_root; // 端口自己认为的根节点,指定根节点
u64 designated_switch; // 端口所在网段到根节点的上一跳节点的ID,也就是该端口所在网段指定端口所在的节点ID
int designated_port; // 端口所在网段的指定端口
int designated_cost; // 本端口所在网段到根节点的路径开销
};
选择根节点:
-
初始化时,所有节点都认为自己是根节点:
-
节点将自己认定的根节点修改为自己的节点ID
-
遍历每个端口,设置每个端口为指定端口:端口将自己认定的根节点设置为自己所在节点,自己认定到根节点的路径开销设置为0,自己所在网段到根节点的上一跳节点的ID设置为自己所在节点,自己所在网段的指定端口设置为自己
-
-
算法开始后,当节点认为自己是根节点时,周期性的(通过hello定时器)主动向外发送Config信息,直到该节点不再认为自己是根节点为止。如果收到的Config信息中的根节点ID比自己认为的根节点ID小,就将自己认为的根节点更新为消息中的根节点,并转发该Config消息。一直迭代下去,直到所有节点认为的根节点是同一节点。
选择端口状态:
除了根节点,每个节点都有一个根端口,根端口在该节点的所有端口中到根节点的开销最小。每个网段在自己所有的端口中选择一个到根节点开销最小的端口作为指定端口。剩余的端口都称为其他端口。
-
某节点的某端口收到Config消息后,将其与本端口的Config进行优先级比较:
-
如果收到的Config优先级高(收到的Config消息只可能从该端口所在的网段的其他端口发送而来),说明该网段应该通过对方端口连接根节点:
-
首先将本端口的Config按照收到的Config消息更新(修改该端口自己认为的根节点、自己认为的到根节点的路径代价、自己认为的所在网段的上一跳交换机,自己认为的所在网段的指定端口)
-
随后更新节点状态:遍历所有非指定端口,找到优先级最高的非指定端口(并认定该端口为根端口,因为该端口优先级最高,意味着是距离根节点最近的端口):
注:该步的意义在于,接收config的端口信息更新后,可能会取代节点原来的根端口,成为该节点新的根端口-
如果根端口不存在(即不存在非指定端口),说明节点本身就是根节点,就修改节点的根端口为空,节点认为的根节点为节点自身ID,节点认为的到根节点的路径代价为0。
-
如果根端口存在,节点就选择通过根端口连接到根节点
(此时有可能接收Config的端口优先级提高,取代了该节点原来的根端口),更新节点状态:节点根端口修改为刚刚查找到的根端口,节点自己认定的根节点修改为根端口自己认定的根节点,节点自己认为到根节点的路径开销修改为根端口所在网段到根节点的路径开销与通过端口所在网段的路径开销之和
-
-
节点状态更新完后,更新剩余端口的Config:
-
对于所有指定端口,更新该端口认为的根节点为节点认为的根节点,更新该端口所在网段到根节点的路径开销为节点自己到根节点的路径开销
-
对于非指定端口,如果该端口的Config较该端口所在网段内其他端口优先级更高,那么修改该端口为所在网段的指定端口:更新该端口认为的根节点为节点认为的根节点,更新该端口所在网段到根节点的路径开销为节点自己到根节点的路径开销,更新该端口所在网段到根节点的上一跳节点ID为当前节点,更新该端口所在网段的指定端口ID为该端口
-
-
之后,如果节点由根节点变为非根节点,就停止hello计时器
-
最后,每个指定端口将自己的Config信息从端口自身发送出去
-
-
如果收到的Config优先级比接收端口的优先级低,说明接收端口为其所在网段的指定端口,只将该端口本身的Config消息从端口自身发送出去
-
-
当STP收敛后,每个网段内所有端口的配置都相同。需要注意的是,本次实验中,不考虑拓扑变动下的生成树重构(标准STP中,当一个节点感知到链路/端口变化后,通过发送TCN(拓扑变动提醒)数据包告知根节点,根节点确认后再重新构建生成树),也没有考虑如何与MAC学习共存,也没有考虑快速构建生成树
附:优先级标准
- 如果两者认为的根节点ID不同,则根节点ID小的一方优先级高
- 若相同,如果两者到根节点的路径开销不同,则开销小的一方优先级高
- 若相同,如果两者到根节点的上一跳节点ID不同,则上一跳节点ID小的一方优先级高
- 若相同,如果两者到根节点的上一跳端口ID不同,则上一跳端口ID小的一方优先级高
注:可参考,实验报告需注明来源(李昊宸)