Core Question
Core Question
Every bridge on a LAN could claim to be the root of the spanning tree. What single comparison decides which one actually is, and why does that comparison run backwards from every other election in this course?
Outcome
Outcome
By the end of this session, the learner should be able to:
- name the exact tuple
elect_root()compares candidates on, in order - state which direction wins the comparison, and say why that direction is the opposite of OSPF's DR election and VRRP's master election
- explain how an unconfigured network can elect its oldest switch as root by accident
- explain why explicit priority configuration exists and what it overrides
Read Order
Read Order
- Read the module docstring at the top of the file
- Read
DEFAULT_PRIORITY's docstring - Read
_mac_key() - Read
BridgeId - Read
bridge_id_lt() - Read
elect_root() - Run
examples/stp/session_01_walkthrough.py
Read It Like Code
Read It Like Code
BridgeId(
priority,
mac,
)Fields That Matter
Fields That Matter
| Field | Why it matters |
|---|---|
priority | The primary ranking key. Defaults to 32768 for every bridge that hasn't been configured otherwise. |
mac | The tiebreak key. Unique per bridge, so it always resolves a tie — and it's the field that turns an unconfigured network into an accident. |
Decision Flow
Decision Flow
bridge_id_lt(a, b):
compare (a.priority, mac_key(a.mac)) < (b.priority, mac_key(b.mac))
lower tuple wins ("a < b" means "a is MORE preferred")
elect_root(bridge_ids):
compare every bridge on (priority, mac_key(mac))
return the minReading Lens
Reading Lens
The important move in this session is to stop reading elect_root() as "just another max-of-a-tuple election" and start asking:
- what is the comparison key, and is it a single value or a tuple?
- which direction wins — the biggest tuple or the smallest?
- what happens when every bridge on the network ties on the first element?
This is the same election-by-comparison shape as OSPF's dr_election.py (ranks on (priority, router_id), takes max()) and VRRP's ha/vrrp_election.py (ranks on (priority, primary_ip), takes max()). STP builds the identical shape — a (priority, id) tuple — and then takes min() instead. There is no protocol reason for the flip; 802.1D simply defined "more preferred" as "numerically smaller" where OSPF and VRRP defined it as "numerically bigger." The module docstring calls this out explicitly as "a classic source of off-by-inversion bugs when porting election logic" between the three — read bridge_id_lt()'s docstring, which says outright that a < b here means "a is MORE preferred," and that this reads backwards next to the other two modules on purpose.
Toy Model Boundary
Toy Model Boundary
Real STP elects a root through BPDUs exchanged and re-compared as the network converges, with hello timers, message age, and max age all governing when a bridge accepts a claim versus falls back to claiming itself. None of that exists here — elect_root() assumes it is handed the full candidate set at once, the same simplification VRRP's elect() makes and OSPF's DR election makes. This module stops at "which BridgeId wins the comparison," not "how bridges agree on the candidate set in the first place."
Code Landmarks
Code Landmarks
The module docstring
States the inversion as the whole thesis of the file: OSPF and VRRP take max() of (priority, id); STP takes min() of the same shape. "Elsewhere 'more preferred' means 'numerically bigger'; in spanning tree it means 'numerically smaller.'" Read this before anything else — every other landmark in this file is downstream of that one sentence.
DEFAULT_PRIORITY's docstring
32768 is 802.1D's factory default. Its docstring names the consequence directly: when every bridge ships with the same default, priority can't break any tie, so MAC is the only thing left to compare — which is exactly how the oldest-switch accident happens.
_mac_key()
Turns "00:00:00:00:00:0a" into (0, 0, 0, 0, 0, 10) — a tuple of ints, not a string. The same reasoning as _ip_key() in VRRP's module: comparing MAC strings lexically would not sort them the way numeric octets do, so the octets are parsed to ints first.
bridge_id_lt()'s docstring
Names itself as "the inversion point" against the other two modules and states plainly that reading a < b as "a is MORE preferred" is disorienting on purpose — it is the whole point of this session.
elect_root()'s docstring — the oldest-switch accident
This is the landmark to sit with the longest. At default priority, the tiebreak falls entirely to MAC, and MAC addresses sort low-to-high in manufacture order within a vendor block — so the bridge that has been running longest tends to have the lowest MAC and becomes root purely by accident, not by design. The docstring states the fix in the same breath: a network operator who wants a specific bridge at the root sets that bridge's priority below DEFAULT_PRIORITY, which wins the comparison before MAC is ever consulted. Explicit priority isn't a nice-to-have — it's how a human overrides an accident that happens by default.
Failure Questions
Failure Questions
Use the source file to answer these:
bridge_id_lt(a, b)builds(a.priority, _mac_key(a.mac)) < (b.priority, _mac_key(b.mac)). If two bridges have the same priority, which element of the tuple decides the comparison, and what does_mac_key()turn the MAC into before that comparison runs?- Two bridges are both left at
DEFAULT_PRIORITY. Structurally, which one becomes root, and why does the docstring call this "by accident" rather than "by design"? - A bridge is configured with
priority=100and every other bridge on the network is atDEFAULT_PRIORITY(32768). Can any other bridge ever winelect_root()against it, regardless of MAC? Why not, structurally? elect_root()callsmin(), notmax(). If someone copy-pasted OSPF'sdr_election.pyelection function and only swapped inBridgeId, what single change would they need to make for the STP election to behave correctly, per the module docstring?elect_root()takes atuple[BridgeId, ...]and returns a singleBridgeId. What would happen if it were called with an empty tuple — is that guarded anywhere in this file?
Walkthrough
Walkthrough
Run this:
PYTHONPATH=src python3 examples/stp/session_01_walkthrough.py
The walkthrough elects a root between two default-priority bridges to show the oldest-switch accident, then shows an explicit low priority overriding a low MAC, exercises bridge_id_lt() in both directions, and runs elect_root() over three bridges.
Done When
Done When
The learner can say all of the following without looking at notes:
- "STP's election is the same
(priority, id)shape as OSPF and VRRP, but it takesmin()where they takemax()— there's no protocol reason, it's just what the standard picked." - "If every bridge is left at default priority, the network elects its oldest switch as root by accident, because MAC is the only thing left to break the tie."
- "Explicit priority configuration exists specifically to let a human override that accident before MAC is ever consulted."
References
References
- IEEE 802.1D (the standard governing STP root bridge election; no RFC governs STP)
Continue