Files
RTLPlayground/doc/stp.md
T
DrDoof bdca888ecd stp: use the front panel number as the port ID
The port ID in the BPDUs was the chip port plus one, so on most boards
it did not match the port label, neither on the Spanning Tree page nor
on the neighbour. Send the front panel number instead, and 101 and up
for the LAGs, the same numbers the page already uses for the Port
column. A returned BPDU of our own is mapped back to the port by that
number.

Scenario 66 gives one port a front panel number that differs from its
chip port.
2026-10-28 16:05:12 +01:00

292 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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, picks the
cheapest path to it as the root port, blocks ports on which a better bridge is
already designated, promotes ports to forwarding once their listen period
expires, drops information a neighbour stopped sending, 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.
While STP runs, the action is *trap*. A trap delivers to the port named in
`EXT_CPU_CTRL` (0x6724), whose reset value 0xf names no port, and with that
value the trapped frame is dropped at ingress. `trap_init()` sets it to 9, the
port the 8051 sits behind, once at startup. The trap needs no per-VLAN L2
entries, so it does not depend on which VLANs exist or on the PVIDs, and turning
STP on no longer walks the whole VLAN table. BPDUs reach the CPU on a port that
STP holds in blocking with either action: measured on a SWTGW218AS by sending
BPDUs into a blocked port with forward and with trap, both ports stopped being
edge and waited out the forward delay. The chip exempts this address from the
ingress state check; the slow-protocols address used by LACP is not exempt.
A trapped frame reaches the CPU in the format it came in with: unlike a
forwarded one it has no VLAN tag inserted after the RTL tag, and the RTL tag
carries the trap reason. `stp_in()` reads the BPDU from the offset
`TRAP_RX_BODY()` returns, which works for both.
With STP off, the action goes back to *forward* and a static L2 multicast
entry per VLAN in use (`port_l2mc_set()`) targets all ports, restoring the
transparency an unmanaged switch is expected to have, so a surrounding
spanning tree can span *through* this device.
The port's acceptable-frame-type filter treats the two actions differently.
BPDUs are untagged, so a port set to admit tagged frames only
(`ingress <port>t`) drops a forwarded BPDU before it reaches the CPU, while a
trapped one passes. Both were measured on a SWTGW218AS with `ingress 3t`:
LACPDUs, delivered by forward, stopped arriving on port 3, and BPDUs sent into
the same port, delivered by trap, kept arriving. A port that admits tagged
frames only therefore takes part in STP.
Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port:
`00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in
blocking forwards nothing between ports, but it still sends what the CPU
hands it and still passes a received BPDU up to the CPU, which is what lets
loop detection go on working on a port it has already blocked.
## 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 | 1–10 |
| `stp maxage <n>` | 20 | 6–40 |
| `stp fwd <n>` | 15 | 4–30 |
| `stp txhold <n>` | 6 | 1–10 |
802.1D also requires `2 * (fwd - 1) >= maxage >= 2 * (hello + 1)`. A
command typed on the console or sent by the web page that would break it is
refused and the value stays as it was, so change the values in an order that
keeps the rule at every step (for a larger max age raise the forward delay
first). The web page shows the error, and a refused command is not recorded
in the command history, so saving the configuration does not pick it up. Lines of the saved configuration are taken one by one whatever their
order, and `stp on` checks the result: if the rule is broken it warns and
starts with 2/20/15 instead.
The values above are what the switch uses while it is the root. Otherwise it
takes max age and forward delay from the BPDUs on its root port, as 802.1D
requires, and passes them on in its own BPDUs with the message age one second
higher; the hello time stays its own.
A port entering the tree discards for one forward delay, then learns
addresses without forwarding for a second one, and only then forwards (an
edge port skips the wait). In RSTP mode on a point-to-point link
(`p2p auto` or `on`) it can go faster: a designated port that is still
discarding sets the proposal flag, and forwards as soon as the bridge on the
other side answers with an agreement. When the root port receives a proposal,
the switch first puts its other non-edge designated ports back to discarding
(they propose in turn), then forwards the root port and answers with an
agreement; this sync happens once per root port, later proposals are only
answered. An alternate or backup port that receives a proposal does the same
sync once for the information it holds and answers with an agreement in a
BPDU with the alternate role, so the bridge on the other side does not have
to wait out its timers; it stays discarding itself. An agreement is accepted
from a root or an alternate port on the other side. A new root port forwards at once, after every port that was root within
the last forward delay has been put back to discarding; such a recent root
port stays closed, even to an agreement, until that forward delay is over.
A new root port that was a backup port within the last two hello times does
not take the fast path and waits for its timers.
A designated port that hears worse information from a bridge that still
claims to be designated and learning or forwarding on the same link has a
dispute: usually one direction of the link is lost and the other bridge does
not hear this switch's BPDUs. The port goes back to discarding, and every
further such BPDU restarts its listen period, so it does not forward while
the dispute lasts.
With `stp version rstp` each port still talks 802.1D to a neighbour that
does: once a Config BPDU or TCN arrives and the migrate time (3 s since the
port came up or last switched) is over, the port sends Config BPDUs, uses
the 802.1D TC window and reports changes with TCN if it is the root port. An
RST BPDU after the migrate time switches it back, and so does a link that
comes up again or `stp port <n> mcheck`. Any worse information heard on a designated port, disputed or
not, is answered with a BPDU at once instead of at the next hello, so the
other bridge learns sooner that it is not the designated one. Information heard on a port stays
valid for three of the sender's hello times; a BPDU whose message age has
reached its max age is not used at all. When the root port's information
expires the switch moves to the next best port, or reclaims the root role if
there is none.
Among ports offering the same root, the lowest root path cost wins, then the
designated bridge, the designated port ID, and finally this switch's own port
ID. A worse BPDU from a different bridge does not replace what a port holds;
a worse one from the same designated bridge and port does.
The number in a port ID is the one on the front panel, and 101 and up for
the LAGs, so it matches the port labels on both ends of a link.
## Topology changes
A topology change is detected when a non-edge port enters forwarding. That
port, and every other forwarding non-edge port, then carries the TC flag in
its BPDUs for hello time plus one second (max age plus forward delay with
`stp version stp`), and each of the other ports flushes the addresses it
learned. The root port takes part too: while its TC window runs it sends
BPDUs toward the root, so the change reaches the rest of the network. With
`stp version stp` the root port sends TCN instead, until a Config BPDU with
TCA comes back.
A TC flag received on a forwarding root or designated port is passed on the
same way to the other ports, not back to the port it came in on; a port that
is still discarding or learning drops it. A legacy TCN is acknowledged with
TCA and then treated like a received TC. A port that turns
alternate, loses its link, or is blocked for a loop flushes its own addresses
without raising a topology change, and an edge port never raises one.
## 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>
stp pathcost long|short # automatic port costs, 802.1D-2004 (default) or 802.1D-1998
stp bpdu flood|filter # BPDUs while STP is off: pass them on (default) or drop them
```
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.
A port without a configured cost takes it from its link speed. The long
method uses the 802.1D-2004 values (20000 for 1G, 2000 for 10G); the short
one the 802.1D-1998 table for bridges that still use 16-bit costs (100 for
10M, 19 for 100M, 4 for 1G, 2 for 10G, and 3 for 2.5G and 5G, which that
table does not list). All bridges in a network should use the same method.
`stp bpdu filter` keeps BPDUs from being passed between ports while STP is
off, instead of the default flooding that lets a surrounding spanning tree
run through the switch. It takes effect at once when STP is off, otherwise
when STP is turned off.
## 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
stp port <1-9> mcheck # try RSTP again on a port that fell back to STP
```
**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.
**guard** — `bpdu` 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.
## Link aggregation
A LAG (Link Aggregation Group) is one port of the tree, with its own roles,
timers and states. The switch supports up to 4 LAGs; each one takes the place
of its member ports in the STP status and gets its own row on the Spanning
Tree page. A group carries its own path cost, priority, edge, guard and
point-to-point settings, and takes the same commands as a port:
```
stp lag <1-4> on|off|mcheck|edge|cost|prio|guard|filter|p2p ...
stp lag 1 cost 10000 # the group decides, not its members
```
LAGs are configured with the `lag` command. Once a port is a member of a LAG
it can no longer be configured on its own for STP, and `stp port <n>` on a
member names the group to configure instead. Membership is read from the
aggregation registers once a second, so a group changing under LACP is picked
up without any coordination between the two; a port that joins or leaves a
group, and the group itself, start over from discarding. The automatic path
cost of a group is the one of its lowest member that has a link.
The hardware keeps no STP state for a LAG as a whole, so every state change is
written to all member ports, in a single register write. BPDUs go out through
the lowest member that has a link and carry the group's own port id, and a
BPDU received on any member belongs to the group. Losing one member of a live
LAG is not a topology change; the group only goes down with its last link.
## 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 the time since it last
moved, the max age and forward
delay in use (the root's, when this switch is not the root) and, per port, the
live state read from the ASIC, or "No link", together with the configured
options. For a port taking part in the tree it also shows the designated
bridge, port and cost, and whether it talks RSTP or 802.1D to its neighbour;
the Check button runs `stp port <n> mcheck`. The same data is available as
JSON:
```
GET /stp.json
```
The Counters section of the same page (or `/stpcnt.json`) shows per port the
BPDUs received and sent, those of them that carried the topology change flag
(TCNs included), and the time since the last BPDU arrived. The counters start
from zero with `stp on`, and `stp clear` resets them; the topology change
counter is not reset.
The `stp status` command prints the same view on the serial console. A port
is reported with one of the roles Root, Designated, Alternate, Backup or
Disabled; Disabled covers a port with STP turned off on it, one shut down by
BPDU guard and one without link.
The console's `age[s]` column (previously labelled `bpdu`) is the elapsed
time since the last BPDU, capped at 255 seconds; it is not a receive count.
Use the web counters or `/stpcnt.json` for full receive counts.
An own BPDU received back on another port can temporarily hold its sending
port in discarding, even if that sending port has received no BPDUs itself.
When the hold expires, auto-edge is restored if the sending port has remained
silent. A continuing loop keeps the hold active.
BPDUs shorter than their type allows, judged by the 802.3 length field, are
dropped: 7 bytes for a TCN, 38 for a Config BPDU and 39 for an RST BPDU,
LLC header included.
## Limitations
* One spanning-tree instance; no MSTP, no per-VLAN trees.
* A backup port, one that hears the BPDUs of another port of this switch,
is blocked and reported as Backup, but keeps sending BPDUs: loop detection
on that segment relies on them.