Files
RTLPlayground/doc/stp.md
T
d00f a9af466702 stp: drop the management failsafe
The window could be armed from the serial console but only ever disarmed
by an HTTP request. save_cmd, which gates arming, is cleared only while
execute_config() replays the startup config, so every interactive command
armed it wherever it was typed, while mgmt_alive, which disarms it, was
written in exactly one place, on HTTP traffic. An operator working
entirely on the serial console therefore lost STP 180 seconds after
enabling it however much they typed, which is what makes the mechanism
impossible to test from a console.

The documentation described the behaviour that was intended rather than
the one that was built, and in both directions: it said a command on the
serial console also confirms, and it said a reboot with STP in the
startup config disables it again three minutes later. Neither held. The
replay path never armed the window at all.

Repairing the asymmetry would have kept a mechanism whose premise is
contested anyway. A watchdog that switches the protection off in response
to silence adds a second failure mode on top of the first: where the
network is misconfigured and STP is the thing holding a storm back,
restoring forwarding removes the last reason management still answers.

Gone with it: the stp failsafe command, the fs and fsT fields of
/stp.json, the input and the tripped banner on the Spanning Tree page,
the two persistence patterns in config.js, the documentation section, and
mgmt_alive itself, which had no other reader.

550 bytes back, 145 of BANK1 and 405 of BANK2, and five of xdata, which
is the four counters and mgmt_alive and nothing else. Built for
SWTGW218AS and KP_9000_6XHML_X2 on sdcc 4.5.0.
2026-08-18 23:30:25 +02:00

5.5 KiB
Raw Blame History

Spanning Tree (STP / RSTP)

The switch can take part in a spanning tree (IEEE 802.1D / 802.1w) so that redundant links between bridges are blocked instead of forming a loop. The implementation elects a root bridge from the BPDUs it receives, promotes ports to forwarding once their listen period expires, ages the root out when it goes silent, and blocks a port on which it sees its own BPDU.

STP can be enabled and controlled via the web interface or the command line, as follows:

Quick start

stp on                  # start participating
stp off                 # stop, all ports back to forwarding

Live status is on the Spanning Tree page of the web UI (or /stp.json), and on the serial console via stp status.

With no other bridge around, the switch elects itself root and every port ends up forwarding — you can leave it on safely. Put the settings in the startup config to make them survive a reboot:

stp prio 15
stp port 1 edge on
stp on

Hardware background

BPDUs are addressed to 01:80:C2:00:00:00, a reserved link-local group. The ASIC's Reserved-Multicast action for that address decides what happens to the frame.

Forwarding to the CPU port works normally: the 8051 sits behind an ordinary port of the internal switch and is an ordinary member of a forwarding mask. The trap action does not deliver to it. Its destination is an external CPU attached to a physical port (cpuTag_externalCpuPort_set, EXT_CPU_CTRL in the vendor SDK), which these boards do not populate. The ACL trap and redirect actions do not deliver to the 8051 either.

Delivery therefore uses the forward action, constrained to the CPU port by a static L2 multicast entry (port_l2mc_set()), one per VLAN in use:

  • while STP runs, the entry's member mask is the CPU port only — BPDUs reach the CPU and are not flooded to other ports, as a participating bridge requires;
  • with STP off, the same entries are retargeted to all ports, restoring the transparency an unmanaged switch is expected to have, so a surrounding spanning tree can span through this device.

A BPDU delivered this way is an ordinary frame to the port's ingress logic and passes through its acceptable-frame-type filter. BPDUs are untagged, so a port set to admit tagged frames only (ingress <port>t) never delivers one to the CPU. stp_setup() prints a warning for every STP-enabled port in that state.

Port states live in RTL837X_MSTP_STATES (0x5310), two bits per port: 00 disabled, 01 blocking, 10 learning, 11 forwarding. In the blocking state a port forwards nothing except frames sent by the CPU, and nothing it receives reaches the CPU.

Timers

stp_timers() runs at 50 Hz (the main loop idles on the 200 Hz system tick and STP is called every fourth pass), which is what STP_HZ in rtl837x_stp.h encodes. All configured values are in seconds:

setting default range
stp hello <n> 2 110
stp maxage <n> 20 640
stp fwd <n> 15 430
stp txhold <n> 6 110

A port entering the tree spends fwd seconds in blocking before it forwards (an edge port skips the wait). Root information is discarded after maxage seconds without a BPDU, and the switch then reclaims the root role.

Bridge settings

stp prio <0-15>         # bridge priority = n * 4096, default 8 (32768)
stp version rstp|stp    # RST BPDUs (default) or legacy Config BPDUs
stp hello|maxage|fwd|txhold <seconds>

The bridge with the lowest priority wins the root election; ties are broken by the MAC address. If you do not want this switch to become the root of an existing network, give it a worse priority than the current root — stp prio 15 (61440) is the usual "never me" value.

Per-port settings

stp port <1-9> on|off              # take part in STP, or stay plain forwarding
stp port <1-9> edge on|off|auto    # host-facing port handling (default: auto)
stp port <1-9> cost <0-200000000>  # path cost, 0 = automatic (20000)
stp port <1-9> prio <0-240>        # port priority, steps of 16
stp port <1-9> guard none|bpdu|root
stp port <1-9> filter on|off       # neither send nor accept BPDUs
stp port <1-9> p2p auto|on|off

edge — an edge port forwards immediately and does not trigger a topology change when its link comes and goes; auto promotes a port to edge after three seconds without a BPDU, and demotes it as soon as one arrives. Use edge on for ports where only hosts are attached.

guardbpdu disables a port as soon as a BPDU arrives on it (a host port should never see one); root keeps a port from ever becoming the path to the root, which protects an existing topology from a newly attached bridge that claims a better priority.

filter — the port neither sends nor accepts BPDUs. Useful when the device on the far side reacts badly to them (some unmanaged switches with loop prevention cut the link) but you still want STP on the rest of the ports.

Status

The Spanning Tree page shows the elected root (priority and MAC), the path cost to it, the root port, the topology-change counter and, per port, the live state read from the ASIC together with the configured options. The same data is available as JSON:

GET /stp.json

The stp status command prints the same view on the serial console.

Limitations

  • One spanning-tree instance; no MSTP, no per-VLAN trees.
  • No proposal/agreement handshake — an RST-capable neighbour will still converge, but through the timers rather than the fast transition.
  • Port roles are approximated: the root port and designated ports are distinguished, alternate/backup are not.