mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
A reserved-multicast trap goes to the port named in EXT_CPU_CTRL, which names no port out of reset, so trapped frames were dropped at ingress. trap_init() points it at the CPU port once at startup, and trap_rma_set() changes only the action field of an RMA register. A trapped frame arrives without the VLAN tag a forwarded one gets, so TRAP_RX_BODY() gives the offset of the payload for both forms. STP now traps its address while it runs instead of steering it with a static L2 entry per VLAN, so turning it on no longer walks the VLAN table and a pvid change no longer needs stp off and on. With STP off the flooding entries are restored as before. A trapped BPDU passes the tagged-only ingress filter that drops a forwarded one, so the stp_setup() warning about such ports goes.
188 lines
8.0 KiB
Markdown
188 lines
8.0 KiB
Markdown
# 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.
|
||
|
||
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.
|
||
|
||
## Link aggregation
|
||
|
||
A LAG (Link Aggregation Group) is handled like an additional port by the STP
|
||
protocol, with its own timers and states. The switch devices support up to 4
|
||
LAGs, which are shown alongside the physical ports in the STP status. A group
|
||
carries its own path cost, priority, edge and guard settings, and gets its own
|
||
row on the Spanning Tree page.
|
||
|
||
```
|
||
stp lag 1 cost 10000 # the group decides, not its members
|
||
stp lag 1 edge off
|
||
```
|
||
|
||
LAGs are configured via the `lag` command. Once a port is a member of a LAG it
|
||
can no longer be configured individually for STP, so `stp port <n>` on a member
|
||
tells you which group to configure instead. Membership is re-read from the
|
||
aggregation registers once a second, so a group changing under LACP is picked
|
||
up without any coordination between the two.
|
||
|
||
The switch hardware does not handle STP state for a LAG as a whole. State
|
||
changes for the members have to be done by the firmware, updating every member
|
||
port, which is possible using a single register write. BPDUs go out through the
|
||
lowest member and carry the group's own port id. Losing one member of a live
|
||
LAG is not a topology change; the logical port only goes down with its last
|
||
link.
|
||
|
||
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 |
|
||
|
||
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.
|
||
|
||
## Topology changes
|
||
|
||
A change on a local non-edge port (the link coming or going, a port promoted
|
||
to forwarding) flushes the addresses learned on it and sets the TC flag in
|
||
our BPDUs for `maxage + fwd` seconds. A TC flag received in a BPDU is passed
|
||
on: the switch flushes the other non-edge ports once and keeps the flag in
|
||
its own BPDUs until one hello after the last flagged frame, so the
|
||
notification crosses the switch instead of dying at it. A legacy TCN is
|
||
acknowledged with TCA and then treated like a local change.
|
||
|
||
## 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.
|
||
|
||
**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.
|
||
|
||
## 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.
|
||
* Topology changes propagate away from the root only: nothing is announced
|
||
on the root port (no TCN and no BPDUs at all), so bridges upstream rely on
|
||
their own detection.
|