An interactive introduction to the spanning tree protocol

Lobsters Hottest News

Summary

This article provides an interactive introduction to the spanning tree protocol, explaining how it prevents loops in Ethernet networks through examples and visualizations.

<p><a href="https://lobste.rs/s/ivr1bw/interactive_introduction_spanning_tree">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 08/25/26, 01:43 PM

# An interactive introduction to the spanning tree protocol Source: [https://vincent.bernat.ch/en/blog/2026-spanning-tree](https://vincent.bernat.ch/en/blog/2026-spanning-tree) Warning This post contains interactive examples\. To visualize and interact with them, you need to enable JavaScript\. Imagine you rent office space for a three\-day event\. You quickly set up a few Ethernet switches and tape some cables on the floor to get everyone online\. Unfortunately, Stan, your clumsiest coworker, kicks out a cable every time he gets up for coffee\. You could add extra cables, but then youโ€™d get a broadcast storm: Ethernet packets that loop and multiply until nothing else gets through\. Thatโ€™s where the*spanning tree protocol*\(STP\) comes in\.STPblocks just enough of your spare cables to leave a loop\-free tree\. When Stan strikes again, it rebuilds the tree in a second, leaving some time for Blobby, your one\-person support crew, to reconnect the cable\. See for yourself: the diagram below runs a realSTPimplementation in your browser\! ``` :demo A1 @0,0 prio=4096 A2 @0,1 A3 @0,2 A4 @0,3 B1 @1,0 prio=8192 B2 @1,1 B3 @1,2 B4 @1,3 C1 @2,0 prio=8192 C2 @2,1 C3 @2,2 C4 @2,3 A1 -- A2 hazard=0 A2 -- A3 hazard=0 A3 -- A4 hazard=0 B1 -- B2 B2 -- B3 B3 -- B4 C1 -- C2 hazard=0 C2 -- C3 hazard=0 C3 -- C4 hazard=0 A1 -- B1 cost=10 B1 -- C1 cost=10 A4 -- B4 cost=20 B4 -- C4 cost=20 Leo @-0.3,0.7 proto=none icon=๐Ÿ‘ฆ๐Ÿป Mia @-0.3,1.3 proto=none icon=๐Ÿ‘ง๐Ÿฝ Joy @0.3,0.7 proto=none icon=๐Ÿ‘ฑ๐Ÿปโ€โ™€๏ธ Roy @0.3,1.3 proto=none icon=๐Ÿ‘จ๐Ÿพ A2 -- Leo hazard=0 A2:edge A2 -- Mia hazard=0 A2:edge A2 -- Joy hazard=0 A2:edge A2 -- Roy hazard=0 A2:edge Max @-0.3,1.7 proto=none icon=๐Ÿ‘จ๐Ÿฝ Zoe @-0.3,2.3 proto=none icon=๐Ÿ‘ฉ๐Ÿพ Ada @0.3,1.7 proto=none icon=๐Ÿ‘ต๐Ÿพ Amy @0.3,2.3 proto=none icon=๐Ÿ‘ฉ๐Ÿผ A3 -- Max hazard=0 A3:edge A3 -- Zoe hazard=0 A3:edge A3 -- Ada hazard=0 A3:edge A3 -- Amy hazard=0 A3:edge Eli @0.7,0.7 proto=none icon=๐Ÿ‘ฆ๐Ÿผ Jay @0.7,1.3 proto=none icon=๐Ÿ‘จ๐Ÿป Kai @1.3,0.7 proto=none icon=๐Ÿง‘๐Ÿฝ Ben @1.3,1.3 proto=none icon=๐Ÿ‘ฑ๐Ÿผ B2 -- Eli hazard=0.2 B2:edge B2 -- Jay hazard=0.2 B2:edge B2 -- Kai hazard=0.2 B2:edge B2 -- Ben hazard=0.2 B2:edge Ava @0.7,1.7 proto=none icon=๐Ÿ‘ฉ๐Ÿป Lea @0.7,2.3 proto=none icon=๐Ÿง‘๐Ÿพโ€๐Ÿฆฑ Ivy @1.3,1.7 proto=none icon=๐Ÿง•๐Ÿฝ Rex @1.3,2.3 proto=none icon=๐Ÿ‘ด๐Ÿฟ B3 -- Ava hazard=0.2 B3:edge B3 -- Lea hazard=0.2 B3:edge B3 -- Ivy hazard=0.2 B3:edge B3 -- Rex hazard=0.2 B3:edge Ana @1.7,0.7 proto=none icon=๐Ÿ‘ฉ๐Ÿฟ Eve @1.7,1.3 proto=none icon=๐Ÿ‘ง๐Ÿผ Abe @2.3,0.7 proto=none icon=๐Ÿง“๐Ÿฟ Ian @2.3,1.3 proto=none icon=๐Ÿง”๐Ÿพ C2 -- Ana hazard=0 C2:edge C2 -- Eve hazard=0 C2:edge C2 -- Abe hazard=0 C2:edge C2 -- Ian hazard=0 C2:edge Ned @1.7,1.7 proto=none icon=๐Ÿ‘จ๐Ÿผโ€๐Ÿฆณ Lou @1.7,2.3 proto=none icon=๐Ÿง‘๐Ÿฟ Fay @2.3,1.7 proto=none icon=๐Ÿ‘ง๐Ÿป Sue @2.3,2.3 proto=none icon=๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฐ C3 -- Ned hazard=0 C3:edge C3 -- Lou hazard=0 C3:edge C3 -- Fay hazard=0 C3:edge C3 -- Sue hazard=0 C3:edge ``` Note This article is also available as a[video](https://vincent.bernat.ch/en/blog/2026-spanning-tree-video), but I advise you to keep reading here to try the interactive demonstrations\. - [The basics](https://vincent.bernat.ch/en/blog/2026-spanning-tree#the-basics)- [Historical interlude](https://vincent.bernat.ch/en/blog/2026-spanning-tree#historical-interlude) - [Electing the root bridge](https://vincent.bernat.ch/en/blog/2026-spanning-tree#electing-the-root-bridge) - [Assigning roles to ports](https://vincent.bernat.ch/en/blog/2026-spanning-tree#assigning-roles-to-ports) - [Port state transition](https://vincent.bernat.ch/en/blog/2026-spanning-tree#port-state-transition) - [Topology change notification](https://vincent.bernat.ch/en/blog/2026-spanning-tree#topology-change-notification) - [Security](https://vincent.bernat.ch/en/blog/2026-spanning-tree#security) - [Why RSTP today?](https://vincent.bernat.ch/en/blog/2026-spanning-tree#why-rstp-today)- [How large can a network be?](https://vincent.bernat.ch/en/blog/2026-spanning-tree#how-large-can-a-network-be) - [How fast is RSTP?](https://vincent.bernat.ch/en/blog/2026-spanning-tree#how-fast-is-rstp) - [About MSTP](https://vincent.bernat.ch/en/blog/2026-spanning-tree#about-mstp) - [About the interactive examples](https://vincent.bernat.ch/en/blog/2026-spanning-tree#about-the-interactive-examples) ## The basics[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#the-basics) Designed in the โ€™80s, the*spanning tree protocol*has evolved into a โ€œrapidโ€ flavor \(RSTP\) and a โ€œVLAN\-awareโ€ variation \(MSTP\)\.[1](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-history)Any sound\-minded network engineer knows there are better alternatives, like[BGP EVPN VXLAN](https://vincent.bernat.ch/en/blog/2017-vxlan-bgp-evpn)\. Yet, because any switch speaks it, the venerable spanning tree protocol still fills a niche\. We focus onRSTP: it replaced the original protocol in 2004\. To eliminate network loops,RSTPimplements a complex state machine\. Timers, link state changes, and the link\-local control frames a bridge receives from its neighbors drive its transitions\. These Ethernet frames are the*Bridge Protocol Data Units*\(BPDUs\)\. You can watch them in action below: hit the โ€œStartโ€ button\. ``` :protocol rstp :tx-hold 10 A1 @0,1 C11 @1,0 prio=4096 icon=๐ŸŒณ C12 @1,2 prio=4096 icon=๐ŸŒณ C21 @2,0 prio=4096 icon=๐ŸŒณ C22 @2,2 prio=4096 icon=๐ŸŒณ A2 @3,1 H1 @0,0.2 proto=none icon=๐Ÿ’ป H2 @0,1.8 proto=none icon=๐Ÿ–จ๏ธ H3 @3,0.2 proto=none icon=๐Ÿ“  H4 @3,1.8 proto=none icon=๐Ÿ“บ A1 -- C11 A1 -- C12 A2 -- C21 A2 -- C22 C11 -- C12 C11 -- C21 C11 -- C21 C11 -- C22 C12 -- C21 C12 -- C22 C21 -- C22 A1 -- H1 A1:edge A1 -- H2 A1:edge A2 -- H3 A2:edge A2 -- H4 A2:edge ``` After[some time](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:5,...), the topology converges to a tree: from the root C11, there is a path to each bridge[2](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-bridge)and no loop\. In the upper right corner, the interface displays a tree icon ๐ŸŒณ followed by the time it took to reach this state\.[Cut a link](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:9,C11--C12,...)and see how the protocol finds an alternate path to reach C12 in less than a second\. You can stop the simulation, move it forward step by step, reset it to its initial state, or slow it down with the โ€œsnailโ€ mode ๐ŸŒ\. Donโ€™t worry about all the displayed information: I explain it later\. All examples run in your browser, powered by[MSTPD](https://github.com/mstpd/mstpd)โ€”an open\-source user\-space[3](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-kernel)implementation ofRSTP\.[4](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-incomplete) ## Historical interlude[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#historical-interlude) [Radia Perlman](https://hiddenheroes.netguru.com/radia-perlman), an inductee of the[Internet Hall of Fame](https://www.internethalloffame.org/inductee/radia-perlman/)in 2014, summarized the ancestor ofSTPshe invented atDECwith this poem, later included in a[US patent](https://patents.google.com/patent/US7339900B2/en): > I think that I shall never see A graph more lovely than a tree\. A tree whose crucial property Is loop\-free connectivity\. A tree which must be sure to span So packets can reach every LAN\. First, the root must be selected\. By ID, it is elected\. Least cost paths from root are traced\. In the tree, these paths are placed\. A mesh is made by folks like me, Then bridges find a spanning tree\. โ€•*Radia Perlman*,[Algorhyme](https://hiddenheroes.netguru.com/radia-perlman)\. ## Electing the root bridge[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#electing-the-root-bridge) To build a tree,RSTPfirst elects the bridge with the**lowest bridge identifier**as the**root bridge**\. The bridge identifier combines the priority and the MAC address:`8192\.6e:2b:10:a0:5f:29`\. In the example below, S1 and S2 have priorities of 4,096 and 8,192: S1 becomes root\. S4 has a priority of 12,288, while S3 keeps the default priority of 32,768:[5](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-priority)S4 becomes root\. S5 and S6 donโ€™t have a specific priority, so the lowest MAC address wins and S5 becomes root\. ``` :protocol rstp S1 @0,0 prio=4096 S2 @0,1 prio=8192 S1 -- S2 S3 @1,0 S4 @1,1 prio=12288 S3 -- S4 S5 @2,0 S6 @2,1 S5 -- S6 ``` [Initially](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:2,S2-%3ES1,@), each bridge advertises itself as root:[6](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-wireshark) ``` Spanning Tree Protocol Protocol Identifier: Spanning Tree Protocol (0x0000) Protocol Version Identifier: Rapid Spanning Tree (2) BPDU Type: Rapid/Multiple Spanning Tree (0x02) Root Identifier: 8192.02:00:00:01:00:01 Bridge Identifier: 8192.02:00:00:01:00:01 ``` Once a bridge receives aBPDUadvertising a better root bridge, it[propagates](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:3,S2-%3ES1,@)this new information to its neighbors\. ``` Spanning Tree Protocol Protocol Identifier: Spanning Tree Protocol (0x0000) Protocol Version Identifier: Rapid Spanning Tree (2) BPDU Type: Rapid/Multiple Spanning Tree (0x02) Root Identifier: 4096.02:00:00:00:00:00 Bridge Identifier: 8192.02:00:00:00:00:01 ``` ## Assigning roles to ports[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#assigning-roles-to-ports) The second step is to assign a role to each port\.RSTPdefines five roles, each denoted by a letter: - root \(R\), - designated \(D\), - alternate \(A\), - disabled \(X\), or - backup \(B\)\.[7](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-backup) Each non\-root bridge chooses its**root port**, the one with the lowest\-cost path to the root\. Unless you override it, each bridge derives the link cost from the speed: 20,000 for 1 Gbps\. In case of equality, the lowest port identifier wins\. Each remaining port becomes a**designated port**if theBPDUit sends is โ€œbetterโ€ than theBPDUit receives\. Otherwise, it becomes an**alternate port**\. Later, if the root port goes down, the โ€œbestโ€ alternate port becomes the new root port\. The tiebreakers for the bestBPDUare: 1. the lowest root bridge identifier, 2. the lowest accumulated cost to the root, 3. the lowest bridge identifier, and 4. the lowest port identifier\. ``` :protocol rstp S1 @1,0 prio=4096 icon=๐ŸŒณ S2 @0,1 S3 @2,1 S1 -- S2 S1 -- S3 S1 -- S3 S2 -- S3 ``` In the example above,[after convergence](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:13), S1 is the root bridge because it has a priority of 4,096, while the other bridges have a priority of 32,768\. All its ports are designated ports because the accumulated cost to the root is 0\. S2โ€™s port facing S1 becomes a root port because it has the lowest accumulated cost to the rootโ€”20,000 vs 40,000\. S3 has two ports facing S1, and the one with the lowest port identifier becomes the root portโ€”`0x8000`vs`0x8001`\. The other candidate is an alternate port because the remote port on the link sends a betterBPDU, with an accumulated cost of 0\. On the segment between S2 and S3, S2โ€™s port wins: while both bridges have the same accumulated cost to the root \(20,000\), S2โ€™s bridge identifier is smallerโ€”`32768\.02:00:00:00:00:01`vs`32768\.02:00:00:00:00:02`\. ``` Spanning Tree Protocol Protocol Identifier: Spanning Tree Protocol (0x0000) Protocol Version Identifier: Rapid Spanning Tree (2) BPDU Type: Rapid/Multiple Spanning Tree (0x02) Root Identifier: 4096.02:00:00:00:00:00 Root Path Cost: 20000 Bridge Identifier: 32768.02:00:00:00:00:01 Port identifier: 0x8002 ``` If you[cut the active link between S1 and S3](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:13,S1--S3:1), S3 promotes the โ€œbestโ€ alternate port to root port\. If you also[disable the second link](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:13,S1--S3:1,S1--S3:2), S3 chooses the remaining alternate port as a root port\. But if you[disable the link between S1 and S2](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:13,S1--S2,@,...), S2 needs a bit more work to elect a new root port because it does not have an alternate port\. Unless a specific event happens, designated ports sendBPDUs[every 2 seconds](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:13,S1-%3ES3:2,S2-%3ES3,S1-%3ES2,@)\.[8](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-hello)If a bridge does not receiveBPDUsfrom its neighbor for 3 consecutive hello periods, it considers the neighbor dead and removes the port information\. ## Port state transition[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#port-state-transition) Each port can have one of three states\. The diagram displays a background color for each state: - discarding \(red\), - learning \(yellow\), or - forwarding \(green\)\. A*root port*transitions automatically to the forwarding state\. An*alternate port*stays in the discarding state\. A*designated port*has two options to transition from the discarding state to the forwarding state: - If the port is an**edge port**, either through configuration or because the remote device does not speak any flavor ofSTP, the bridge assumes it wonโ€™t participate in the protocol and cannot create a loop\. In this case, the designated port immediately transitions to the forwarding state\. - Otherwise, it sends a**proposal**to its downstream neighbor\. If the remote bridge agrees that the receivedBPDUis โ€œbetterโ€ than any otherBPDUstored for other ports, it elects the receiving port as its root port and starts the**synchronization**process: it transitions all non\-edge non\-synced designated ports to the discarding state to avoid a loop\. Then, it sends back an**agreement**\. Upon receiving the agreement, the peer designated port transitions to the forwarding state\.[9](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-learning) ``` :protocol rstp S1 @1,0 prio=4096 icon=๐ŸŒณ S2 @1,1 S3 @0,2 S4 @2,2 S5 @0,3 prio=8192 icon=๐Ÿชพ S6 @2,3 H1 @0,1.2 proto=none icon=๐Ÿ–จ๏ธ H2 @2,1.2 proto=none icon=๐Ÿ“  H3 @2.5,1.3 proto=none icon=๐Ÿ“บ H4 @2.5,2.3 proto=none icon=๐Ÿ’ป S1 -- S2 S2 -- S3 S2 -- S4 S3 -- S5 S4 -- S6 S4 -- S3 S5 -- S6 S3 -- H1 S3:edge S4 -- H2 S4:edge S4 -- H3 S4:edge S6 -- H4 S6:edge ``` In the topology above, H1, H2, H3, and H4 are end devices not participating in the protocol\. We configure the ports they connect to as edge ports, so these ports immediately move to the forwarding state\. Use the โ€œstepโ€ button to move the simulation forward\. The clock moves to 1 second\.[Step again](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:2,S1-%3ES2,S2-%3ES1,@)and S1 and S2 send a proposal to each other\. Here is the proposal from S2: ``` Spanning Tree Protocol Protocol Identifier: Spanning Tree Protocol (0x0000) Protocol Version Identifier: Rapid Spanning Tree (2) BPDU Type: Rapid/Multiple Spanning Tree (0x02) BPDU flags: 0x4e, Agreement, Port Role: Designated, Proposal 0... .... = Topology Change Acknowledgment: No .1.. .... = Agreement: Yes ..0. .... = Forwarding: No ...0 .... = Learning: No .... 11.. = Port Role: Designated (3) .... ..1. = Proposal: Yes .... ...0 = Topology Change: No Root Identifier: 32768.02:00:00:00:00:01 Root Path Cost: 0 Bridge Identifier: 32768.02:00:00:00:00:01 Port identifier: 0x8001 ``` S1 ignores it: its own root identifier is lower\. When S2 receives a similar proposal from S1, it accepts S1 as its root bridge\. It also elects the port to S1 as the root port and starts the synchronization process\. The two designated ports are already discarding, so no change here\.[Step again](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:3,S2-%3ES1#2,@)and S2 sends twoBPDUsto S1\. In one of them, the agreement bit is 1 and the proposal bit is 0\. It also shows that S2 accepted S1 as the root bridge and its root port is now in the forwarding state\. When receiving thisBPDU, S1 transitions its own designated port to the forwarding state\. From this point, the link between S1 and S2 forwards user traffic\. ``` Spanning Tree Protocol Protocol Identifier: Spanning Tree Protocol (0x0000) Protocol Version Identifier: Rapid Spanning Tree (2) BPDU Type: Rapid/Multiple Spanning Tree (0x02) BPDU flags: 0x79, Agreement, Forwarding, Learning, Port Role: Root, Topology Change 0... .... = Topology Change Acknowledgment: No .1.. .... = Agreement: Yes ..1. .... = Forwarding: Yes ...1 .... = Learning: Yes .... 10.. = Port Role: Root (2) .... ..0. = Proposal: No .... ...1 = Topology Change: Yes Root Identifier: 4096.02:00:00:00:00:00 Root Path Cost: 20000 Bridge Identifier: 32768.02:00:00:00:00:01 Port identifier: 0x8001 ``` Letโ€™s look at what happened to S5\.[Reset the simulation and step twice](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:2,S5-%3ES3,S3-%3ES5,S5-%3ES6,S6-%3ES5,@)\. S5 exchangesBPDUswith both S3 and S6\. Since S5 has a lower root identifier than S3 and S6, it stays the root bridge, while S3 and S6 accept the proposal and elect their root ports\. S3 and S6 start the synchronization process\. S6โ€™s port to H4 stays up because this is an edge port\.[Move one step](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:3,S3-%3ES5#1,S6-%3ES5#1,@)\. Both S3 and S6 send an agreement back to S5, which transitions both designated ports to the forwarding state\. Yet, the link between S5 and S3 keeps discarding user traffic\! If you look carefully, S3โ€™s port toward S5 is now a designated port, not a root port\. During the[same step](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:3,S2-%3ES3,@), S3 also receives a betterBPDUfrom S2 with S1 as the root bridge\. It elects its port to S2 as the root port and downgrades the port to S5 to a designated port, which stays in the discarding state\. On the[next step](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:4,S3-%3ES5,@), things get a bit tricky\. S3 sends a proposal to S5:[10](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-agreement) ``` Spanning Tree Protocol Protocol Identifier: Spanning Tree Protocol (0x0000) Protocol Version Identifier: Rapid Spanning Tree (2) BPDU Type: Rapid/Multiple Spanning Tree (0x02) BPDU flags: 0x4f, Agreement, Port Role: Designated, Proposal, Topology Change 0... .... = Topology Change Acknowledgment: No .1.. .... = Agreement: Yes ..0. .... = Forwarding: No ...0 .... = Learning: No .... 11.. = Port Role: Designated (3) .... ..1. = Proposal: Yes .... ...1 = Topology Change: Yes Root Identifier: 4096.02:00:00:00:00:00 Root Path Cost: 40000 Bridge Identifier: 32768.02:00:00:00:00:02 Port identifier: 0x8002 ``` S5 elects S1 as its root bridge and the port toward S3 as its root port\. It starts its synchronization process, but the designated port to S6 does*not*move into the discarding state\. Why? That port stays a designated port and its neighbor S6 had already sent an agreement on the link, so the port keeps its synced status\. Now, letโ€™s[step back](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:3,@)to look at what happens to S6\. At this point, S6 believes S5 is the root bridge\.[Step once](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:4,S4-%3ES6#1,@)and S4 sends a new proposal to S6\. S6 accepts the proposal, elects S1 as the root bridge and the port to S4 as its root port\. The role of the port facing S5 changes: from a root port, it becomes a designated port\. Because its peer keeps advertising an inferiorBPDUon the link, this port becomes disputed and moves to the discarding state\. The root port transitions to the forwarding state and the link starts forwarding immediately because S4โ€™s designated port is already in the forwarding state\. If we[step one more time](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:5,S5-%3ES6,S6-%3ES5,@), S5 and S6 exchange twoBPDUs\. The one from S5 is better because of its lower bridge identifier\. S5โ€™s port stays a designated port, while S6 downgrades its own port to an alternate port\. Letโ€™s rewind one last time from the start: cut the link between S1 and S2,[run the simulation until the topology is stable](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:S1--S2,9), stop the simulation, and restore the link between S1 and S2\. During the[first step](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:S1--S2,9,S1--S2,1,S1-%3ES2,S2-%3ES1,@), S1 and S2 exchange proposals\. S2 elects S1 as the root bridge instead of S5 and the port to S1 as the root port\. It downgrades the previous root port to a designated port and moves it into the discarding state\. The other designated port stays synced and keeps its forwarding state\. At the[next step](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:S1--S2,9,S1--S2,2,S2-%3ES1,@), S2 sends an agreement to S1 and the link between them starts forwarding user traffic\. It also[sends a proposal to S3](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:S1--S2,9,S1--S2,2,S2-%3ES3,@), but not to S4\. Instead,[it sends a regularBPDUto S4](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:S1--S2,9,S1--S2,2,S2-%3ES4,@)\. S4 still elects S1 as its root bridge and the port to S2 as its root port\. It demotes its previous root port, the one to S3, to a designated port, which transitions to the discarding state because of the root port change\. The other alternate port, to S6, also becomes a designated port and stays in the discarding state\. The new root port moves to the forwarding state\. On the[next step](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:S1--S2,9,S1--S2,3,S3-%3ES4#1,@), S4โ€™s port to S3 settles as an alternate port after receiving a โ€œbetterโ€BPDUfrom S3\. RSTPis a giant state machine split into smaller ones: bridge detection, port information, port protocol migration, port role selection, port role transitions, port receive, port state transitions, port timers, port transmit, and topology change\. Some of them are per bridge, some per port\. Each bridge runs an instance\. Time, operational port state changes, and theBPDUsit receives from other instances drive the transitions\. Being event\-driven makesRSTPmore efficient but also more difficult to understand\. ![Western Australian Government Railways class Msa Garratt articulated steam locomotive: elevation and plan drawing](https://d2pzklc15kok91.cloudfront.net/images/[email protected]) Placeholder for the*Port Information*state machine extracted from IEEE 802\.1Q\-2005, page 182\. Pending IEEE authorization for reproduction, this is the blueprint for the Western Australian Government Railways class Msa Garratt articulated steam locomotive\.## Topology change notification[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#topology-change-notification) A bridge populates a MAC address table: it associates each source MAC address with the port that last received it\. When forwarding an Ethernet frame, it looks up this table to choose the right port\.[11](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-bum)When a link fails, a connected fridge reachable through one port may become reachable through another one\. The affected bridges should flush the MAC addresses they learned, because these entries may now be wrong\. For this purpose,RSTPimplements*topology change notifications*using a flooding mechanism\. When a non\-edge port transitions to the forwarding state, a bridge generatesBPDUswith the*topology change*\(TC\) bit set\. It sends them to all the non\-edge designated ports and to the root port\. It also flushes the MAC address table on these ports\. When a bridge receives such aBPDU, it propagates the notification to all non\-edge designated ports and the root port, except the one the notification came from\. It also flushes the MAC address table on these ports\. In the examples, theBPDUswith theTCbit set to 1 have a red circle\. ``` :protocol rstp S1 @1,0 prio=4096 icon=๐ŸŒณ S2 @0,1 S3 @1,1 S4 @2,1 S5 @1,2 LPT @0.1,2 proto=none icon=๐Ÿ–จ๏ธ S1 -- S2 S1 -- S3 S1 -- S4 S2 -- S3 S2 -- S5 S4 -- S5 S5 -- LPT S5:edge ``` Start the simulation and wait a[few seconds](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10)for the topology to settle\. Stop the simulation and[disable the link between S2 and S5](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,S2--S5)\. S5 elects the port facing S4 as the root port, which transitions immediately to the forwarding state\.[Step once](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,S2--S5,1,S5-%3ES4,@)and S5 emits aBPDUwith theTCbit set to 1: ``` Spanning Tree Protocol Protocol Identifier: Spanning Tree Protocol (0x0000) Protocol Version Identifier: Rapid Spanning Tree (2) BPDU Type: Rapid/Multiple Spanning Tree (0x02) BPDU flags: 0x79, Agreement, Forwarding, Learning, Port Role: Root, Topology Change 0... .... = Topology Change Acknowledgment: No .1.. .... = Agreement: Yes ..1. .... = Forwarding: Yes ...1 .... = Learning: Yes .... 10.. = Port Role: Root (2) .... ..0. = Proposal: No .... ...1 = Topology Change: Yes Root Identifier: 4096.02:00:00:00:00:00 Root Path Cost: 40000 Bridge Identifier: 32768.02:00:00:00:00:04 Port identifier: 0x8002 ``` S4 receives thisBPDU\. It flushes the MAC address table on the port facing S1: while LPT was previously reachable through this port, it is now reachable through S5 instead\.[Step once](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,S2--S5,2,S4-%3ES1,@)\. S4 sends S1 aBPDUwith theTCbit set to 1\. When S1 receives thisBPDU, it flushes the MAC address table on the ports facing S2 and S3\.[Step once](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,S2--S5,3,S1-%3ES2,S1-%3ES3,@)and S1 sends a notification to S2 and S3\.[Step once again](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,S2--S5,4,S2-%3ES3,@)and S2 sends a notification to S3, while S3 does nothing because the port toward S2 is an*alternate port*\. S3 does not flush any MAC address table: LPT is still reachable through its port to S1\. If you[step a bit more](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,S2--S5,6,@), you will see that some of the periodicBPDUskeep theTCbit set to 1\. Each port runs a timer equal to the hello timer plus one second\.[12](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-timer)The timer starts when the port emits a notification\. Until it expires, the port sets theTCbit to 1 in everyBPDUit sends\. You can also see[some periodicBPDUs](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,S2--S5,7,S1-%3ES4,S4-%3ES5,@)without theTCbit: they originate from a port that only received a notification and therefore did not arm its timer\. ## Security[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#security) RSTPis weak against configuration errors and malicious actors\. A bridge not talkingRSTPcan create a loop\. An attacker can insert themselves into the topology to disrupt the service, spy on the traffic, or alter it\. To mitigate such problems, you need to identify the edge ports\. An edge port connects to an end device, like a PC or a printer\. Such devices do not generateBPDUsand cannot create a loop\.RSTPdefines two related flags: - When true,**AdminEdge**initializes a port as an edge port\. It defaults to false\. - When true,**AutoEdge**lets a port become an edge port when it does not receiveBPDUsfor 3 seconds\. It defaults to true\. If an edge port receives aBPDU, regardless of the values of these two flags, it reverts to a non\-edge port\. ``` R0 @1.5,1.5 prio=8192 # AutoEdge=true, AdminEdge=false, bridge S1 @3,1.58 R0 -- S1 # AutoEdge=true, AdminEdge=false, end device H1 @2.84,2.18 icon=๐Ÿ–จ๏ธ proto=none R0 -- H1 # AutoEdge=true, AdminEdge=true, bridge S2 @2.18,2.84 R0 -- S2 R0:edge # AutoEdge=true, AdminEdge=true, end device H2 @1.58,3 icon=๐Ÿ’ป proto=none R0 -- H2 R0:edge # AutoEdge=false, AdminEdge=true, bridge S3 @0.68,2.76 R0 -- S3 R0:edge R0:no-auto-edge # AutoEdge=false, AdminEdge=true, end device H3 @0.24,2.32 icon=๐Ÿ“  proto=none R0 -- H3 R0:edge R0:no-auto-edge # AutoEdge=false, AdminEdge=false, bridge S4 @0,1.42 R0 -- S4 R0:no-auto-edge # AutoEdge=false, AdminEdge=false, end device H4 @0.16,0.82 icon=๐Ÿ“บ proto=none R0 -- H4 R0:no-auto-edge # Network port, bridge S5 @0.82,0.16 R0 -- S5 R0:network S5:network # Network port, end device H5 @1.42,0 icon=โ˜• proto=none R0 -- H5 R0:network # AdminEdge=true, bpdu-guard=true, bridge S6 @2.32,0.24 R0 -- S6 R0:bpdu-guard R0:edge # AdminEdge=true, bpdu-guard=true, end device H6 @2.76,0.68 icon=๐Ÿ’ก proto=none R0 -- H6 R0:bpdu-guard R0:edge ``` In the topology above, S1, S2, S3, S4, S5, and S6 act as bridges, while H1, H2, H3, H4, H5, and H6 act as end devices: - S1 and H1 are on a port without a specific configuration:*AutoEdge*is true,*AdminEdge*is false, - S2 and H2 are on a port where*AdminEdge*is true, - S3 and H3 are on a port where*AutoEdge*is false and*AdminEdge*is true, - S4 and H4 are on a port where*AutoEdge*is false\. If you start the topology and[wait about 20 seconds](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:24), links to S1, S2, S3, S4, H1, H2, H3, and H4 eventually forward user traffic: none of the flags matter\. But what about the two remaining pairs? S5 and H5 connect to a*network*port\. Such a port enables a non\-standard feature:**bridge assurance**\. The port transmitsBPDUsregardless of its role\. If it does not receiveBPDUsfor 3 consecutive hello periods, it transitions to the discarding state\. On the link between R0 and S5, you can see[BPDUstraveling in both directions](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:24,R0-%3ES5,S5-%3ER0,@), unlike the other links, where only designated ports sendBPDUs\. S6 and H6 connect to a port where*AdminEdge*is true and**BPDUguard**is enabled\. This is another non\-standard feature that shuts down a port if[it receives aBPDU](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:2,S6-%3ER0,@)\. In summary, if you expect a port to be an edge port, you should set*AdminEdge*to true and enable*BPDUguard*\. Otherwise, declare it as a*network*port\. ## WhyRSTPtoday?[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#why-rstp-today) A compelling use case forRSTPtoday is an out\-of\-band network for a datacenter, since you can tolerate an outage of a few seconds\. The configuration is minimal and you can use cheap switches, like a Cisco 2960X\.[13](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-price)You need two switches acting as root bridges, and you build several loops to connectOOBswitches in each cabinet\. This simple design survives one failure on each loop\.[14](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-erps) ``` :protocol rstp :tx-hold 10 # Root bridges R1 @0,1 prio=0 R2 @0,2 prio=4096 R1 -- R2 cost=200 R1:network R2:network R1 -- R2 cost=200 R1:network R2:network # First loop C1 @1,0 icon=๐Ÿ—„๏ธ C4 @2,0 icon=๐Ÿ—„๏ธ C7 @3,0 icon=๐Ÿ—„๏ธ C10 @4,0 icon=๐Ÿ—„๏ธ C12 @5,0 icon=๐Ÿ—„๏ธ C13 @5,3 icon=๐Ÿ—„๏ธ C15 @4,3 icon=๐Ÿ—„๏ธ C18 @3,3 icon=๐Ÿ—„๏ธ C21 @2,3 icon=๐Ÿ—„๏ธ C24 @1,3 icon=๐Ÿ—„๏ธ R1 -- C1 R1:network C1:network C1 -- C4 C1:network C4:network C4 -- C7 C4:network C7:network C7 -- C10 C7:network C10:network C10 -- C12 C10:network C12:network C12 -- C13 C12:network C13:network C13 -- C15 C13:network C15:network C15 -- C18 C15:network C18:network C18 -- C21 C18:network C21:network C21 -- C24 C21:network C24:network C24 -- R2 C24:network R2:network # Second loop C2 @1,0.5 icon=๐Ÿ—„๏ธ C5 @2,0.5 icon=๐Ÿ—„๏ธ C8 @3,0.5 icon=๐Ÿ—„๏ธ C11 @4,0.5 icon=๐Ÿ—„๏ธ C14 @4,2.5 icon=๐Ÿ—„๏ธ C17 @3,2.5 icon=๐Ÿ—„๏ธ C20 @2,2.5 icon=๐Ÿ—„๏ธ C23 @1,2.5 icon=๐Ÿ—„๏ธ R1 -- C2 R1:network C2:network C2 -- C5 C2:network C5:network C5 -- C8 C5:network C8:network C8 -- C11 C8:network C11:network C11 -- C14 C11:network C14:network C14 -- C17 C14:network C17:network C17 -- C20 C17:network C20:network C20 -- C23 C20:network C23:network C23 -- R2 C23:network R2:network # Third loop C3 @1,1 icon=๐Ÿ—„๏ธ C6 @2,1 icon=๐Ÿ—„๏ธ C9 @3,1 icon=๐Ÿ—„๏ธ C16 @3,2 icon=๐Ÿ—„๏ธ C19 @2,2 icon=๐Ÿ—„๏ธ C22 @1,2 icon=๐Ÿ—„๏ธ R1 -- C3 R1:network C3:network C3 -- C6 C3:network C6:network C6 -- C9 C6:network C9:network C9 -- C16 C9:network C16:network C16 -- C19 C16:network C19:network C19 -- C22 C19:network C22:network C22 -- R2 C22:network R2:network ``` This topology converges in about[6 seconds](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:6)\. Each loop should stay small \(around 16 bridges\) to reduce the probability of a double failure and to avoid sharing too much bandwidth\. The design can evolve a bit without adding too much complexity: one VLAN per loop or one bridge domain per loop\. ## How large can a network be?[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#how-large-can-a-network-be) The maximum age, whose default value is 20, governs the maximum distance of a node from the root\. The topology below is too big forBPDUsfrom R1 to reach beyond S20\.[15](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-root-mac) ``` :protocol rstp :tx-hold 10 :max-age 20 R1 @0,0 prio=4096 icon=๐ŸŒณ R2 @0,5 prio=4096 icon=๐Ÿชพ S1 @1,0 S2 @2,0 S3 @3,0 S4 @4,0 S5 @5,0 S6 @6,0 S7 @6,1 S8 @5,1 S9 @4,1 S10 @3,1 S11 @2,1 S12 @1,1 S13 @1,2 S14 @2,2 S15 @3,2 S16 @4,2 S17 @5,2 S18 @6,2 S19 @6,3 S20 @5,3 S21 @4,3 S22 @3,3 S23 @2,3 S24 @1,3 S25 @1,4 S26 @2,4 S27 @3,4 S28 @4,4 S29 @5,4 S30 @6,4 S31 @6,5 S32 @5,5 S33 @4,5 S34 @3,5 S35 @2,5 S36 @1,5 R1 -- S1 S1 -- S2 S2 -- S3 S3 -- S4 S4 -- S5 S5 -- S6 S6 -- S7 S7 -- S8 S8 -- S9 S9 -- S10 S10 -- S11 S11 -- S12 S12 -- S13 S13 -- S14 S14 -- S15 S15 -- S16 S16 -- S17 S17 -- S18 S18 -- S19 S19 -- S20 S20 -- S21 S21 -- S22 S22 -- S23 S23 -- S24 S24 -- S25 S25 -- S26 S26 -- S27 S27 -- S28 S28 -- S29 S29 -- S30 S30 -- S31 S31 -- S32 S32 -- S33 S33 -- S34 S34 -- S35 S35 -- S36 S36 -- R2 R1 -- R2 cost=200 down ``` Once the[topology settles](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:40), part of the network considers R1 the root, while the other votes for R2\. At the boundary, S20 tries to start a synchronization with S21 to move its*designated port*to the forwarding state\. TheBPDUlooks like this: ``` Spanning Tree Protocol Protocol Identifier: Spanning Tree Protocol (0x0000) Protocol Version Identifier: Rapid Spanning Tree (2) BPDU Type: Rapid/Multiple Spanning Tree (0x02) BPDU flags: 0x4e, Agreement, Port Role: Designated, Proposal Root Identifier: 4096.02:00:00:00:00:00 Root Path Cost: 400000 Bridge Identifier: 32768.02:00:00:00:00:15 Port identifier: 0x8002 Message Age: 20 Max Age: 20 ``` S21 rejects it because the message age equals the maximum age\. On the other hand, theBPDUS21 sends to S20 looks like this: ``` Spanning Tree Protocol Protocol Identifier: Spanning Tree Protocol (0x0000) Protocol Version Identifier: Rapid Spanning Tree (2) BPDU Type: Rapid/Multiple Spanning Tree (0x02) BPDU flags: 0x7c, Agreement, Forwarding, Learning, Port Role: Designated Root Identifier: 4096.02:00:00:00:00:01 Root Path Cost: 320000 Bridge Identifier: 32768.02:00:00:00:00:16 Port identifier: 0x8001 Message Age: 16 Max Age: 20 ``` This is not enough to change S20โ€™s*root port*because S20 has a lower*root identifier*โ€”`4096\.02:00:00:00:00:00`vs`4096\.02:00:00:00:00:01`\. [Fixing the link between R1 and R2](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:40,R1--R2,...)resolves the issue\. The maximum message age any packet carries is now 18, below the configured maximum age\. But it only works until another link breaks\. A plausible fix is to increase the maximum age to 40\.[16](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-forward-delay) ## How fast isRSTP?[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#how-fast-is-rstp) RSTPusually converges in a couple of seconds at startup\. It often repairs a tree in less than a second\. Even the[38\-bridge topology](https://vincent.bernat.ch/en/blog/2026-spanning-tree#how-large-can-a-network-be)takes less than 10 seconds to converge\.[17](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-time)Some topologies can take a bit more time to recover when the root bridge becomes unavailable\.[18](https://vincent.bernat.ch/en/blog/2026-spanning-tree#sidenote-slow) ``` :protocol rstp R0 @1,0 prio=0 S1 @1,1 prio=4096 S2 @0,2 prio=8192 S3 @2,2 R0 -- S1 S1 -- S2 S2 -- S3 S3 -- S1 ``` In the topology above, start the simulation,[wait for convergence](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10), hit stop, and[cut the link between R0 and S1](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,R0--S1)\. The topology is already optimal, butRSTPhas a hard time converging again\. First, S1 loses its root port\. It has no more information about R0 and elects itself as the root bridge\. It keeps its ports to S2 and S3 as designated ports in the forwarding state\.[Step once](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,R0--S1,1,S1-%3ES2,S1-%3ES3,@)and it sends aBPDUto both S2 and S3 to let them know about the root change\. When receiving it, S2 accepts S1 as its root because it does not have a better root on another port\. It elects the port to S1 as its root port\. The other port stays a designated port\. Both ports keep forwarding\. When receiving theBPDUfrom S1, S3 behaves differently: it knows R0 as a better root than S1 through its alternate port to S2\. It promotes this port to a root port and demotes the port facing S1 to a designated port, which requires a new agreement\.[Step once](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,R0--S1,2,S3-%3ES1#1,@)and S3 sends a proposal to S1 with R0 as the root bridge\. S1 elects R0 as the root bridge and promotes its port to S3 as a root port\. During the[same step](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,R0--S1,2,S2-%3ES3,@), S3 also receives aBPDUfrom S2 stating that S1 is the root bridge\. Therefore, S3 has no port left with R0 as the root bridge: it elects S1 as the root bridge and its port to S2 as the root port\.[Step once](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,R0--S1,3,S3-%3ES1,@)and its nextBPDUto S1 includes this information: S1 elects itself again as the root bridge\. But during the[same wave](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,R0--S1,3,S1-%3ES2#1,@), S1 sends a proposal to S2 with R0 as the root bridge\. While S1 and S3 agree that S1 is the root bridge, S2 now believes this is R0\![In turn](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,R0--S1,4,@,...), S2 again convinces S3 that R0 is the root bridge, S3 convinces S1, S1 convinces S2, and S2 convinces S3\. This could go on forever, but it does not\. TheBPDUssaying โ€œR0 is rootโ€ eventually age out when the message age goes past the maximum age\. In the example above, at the[eleventh second](https://vincent.bernat.ch/en/blog/2026-spanning-tree#mstp:10,R0--S1,24,S2-%3ES3,@), S2 sends aBPDUto S3 with R0 as root, but S3 drops it because its message age reached the maximum\. With some luck, the topology can also converge faster if a port stops transmitting newBPDUsafter tripping the transmit hold count, whose default value is 6 per second\. ## AboutMSTP[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#about-mstp) MSTPis the โ€œVLAN\-awareโ€ version ofRSTP: it runs several instances ofRSTPand lets the administrator map each VLAN to a specific instance\. For example, you can map VLANs 100 to 200 to a first instance, and 300 to 400 to a second instance\. The remaining VLANs map to a special instance named the Internal Spanning Tree \(IST\)\.MSTPadds its own complexity, but the gist is that you have several logical topologies acting independently\. If you want to dig deeper, have a look at โ€œ[MSTPTutorial Part I: Inside a Region](https://ine.com/blog/2008-07-27-mstp-tutorial-part-i-inside-a-region)\.โ€ ## About the interactive examples[\#](https://vincent.bernat.ch/en/blog/2026-spanning-tree#about-the-interactive-examples) The interactive examples run[MSTPD](https://github.com/mstpd/mstpd)directly in your browser, compiled to[WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly)with[emscripten](https://emscripten.org/)\. A C API replaces the code talking to the Linux kernel: it manages bridges and ports, exports state as JSON, and drives time deterministically\. A JavaScript wrapper makes it more user\-friendly: ``` import { loadMSTPD } from "./dist/mstpd.mjs"; const mstp = await loadMSTPD(); // Create 3 bridges const a = mstp.createBridge("A", { priority: 4096 }); const b = mstp.createBridge("B", { priority: 8192 }); const c = mstp.createBridge("C"); // Each bridge has two ports const a1 = a.addPort("a-b", { portno: 1 }); const a2 = a.addPort("a-c", { portno: 2 }); const b1 = b.addPort("b-a", { portno: 1 }); const b2 = b.addPort("b-c", { portno: 2 }); const c1 = c.addPort("c-a", { portno: 1 }); const c2 = c.addPort("c-b", { portno: 2 }); // Build a triangle topology mstp.link(a1, b1); mstp.link(a2, c1); mstp.link(b2, c2); // Enable all bridges and ports for (const br of [a, b, c]) br.enable(); for (const p of [a1, a2, b1, b2, c1, c2]) p.enable(); // Execute 40 seconds' worth of wall clock and display the topology mstp.step(40); console.log("Topology:", mstp.topology()); ``` Several dozen unit tests explore the features of MSTPD and check that they work correctly in this environment: ``` $ node --test *.test.mjs โœ” two bridges: lower priority becomes root (41.657342ms) โœ” triangle loop: exactly one port blocks and all agree on the root (5.832ms) โœ” breaking the active link reconverges and restoring recovers (18.730753ms) [โ€ฆ] โ„น tests 40 โ„น pass 40 โ„น fail 0 [โ€ฆ] โ„น duration_ms 396.190897 ``` Additional JavaScript code looks for specific`<pre\>`blocks containing a topology definition and turns them into the interactive widget\. You can inspect and modify the definition by hitting the โ€œeditโ€ button\. There is also a cool trick to tell whether the topology has converged\. After each step, we save a snapshot of the simulation memory, play 50 secondsโ€™ worth of simulation to check if the topology is stable, and travel back in time by restoring that snapshot\. ๐Ÿ•ฐ๏ธ The complete code lives on[GitHub](https://github.com/vincentbernat/mstpd/tree/feature/wasm/wasm)\. I am happy with the result\. It can be difficult to follow everything happening during a single step, but stepping forward and backward helps\. I plan to use the same approach in future blog posts about networking features\. Note [Michael Lynch](https://mtlynch.io/)reviewed a first draft of this article\. He authored โ€œ[Refactoring English](https://refactoringenglish.com/),โ€ a book to sharpen your writing for blog posts, documentation, commit messages, and tutorials\. Any errors are still mine\!

Similar Articles

Building a passive Ethernet tap

Lobsters Hottest

A blog post describing how to build a passive Ethernet tap using RJ45 breakout boards and a breadboard to monitor network traffic without injecting data.

Task-centered iproute2 user guide

Hacker News Top

A task-centered guide to iproute2, the Linux networking toolkit, covering ip, bridge, and ss commands with practical usage examples.