mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
stp: full RSTP and a reworked Spanning Tree page
* stp: select the root port by cost and block redundant links * stp: move the protocol to BANK4 and keep the command line in BANK2 * stp: implement the RSTP state machines of 802.1D-2004 clause 17 * stp: counters, more settings and a fuller Spanning Tree page * stp: make a link aggregation group one port of the RSTP tree * web: serve the Spanning Tree JSON from BANK4 as well Kept in BANK1, the larger Spanning Tree page would leave under 1 kB there for the rest of the web server and every page still to come, so send_stp() and send_stp_counters() move to httpd/page_stp.c in BANK4 with the protocol, as banked calls, with their own copies of the few output helpers and of the JSON header, so nothing in BANK4 calls into BANK1 directly or hands it a pointer into its code space. BANK4 takes the free flash between BANK3 and the web files. KP_9000_6XHML_X2: BANK1 44336, BANK2 36969, BANK3 11942, BANK4 20965 bytes of 49152.
This commit is contained in:
1 parent
6c62791d23
commit
aff6ac0923
20 files changed
+3282
-696
No files matched your search
+142
-50
@@ -2,9 +2,11 @@
|
||||
|
||||
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.
|
||||
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:
|
||||
@@ -64,32 +66,6 @@ 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
|
||||
@@ -109,19 +85,80 @@ encodes. All configured values are in seconds:
|
||||
| `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.
|
||||
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.
|
||||
|
||||
## 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.
|
||||
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 root or designated port is passed on the same way to
|
||||
the other ports, not back to the port it came in on. 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
|
||||
|
||||
@@ -129,6 +166,8 @@ acknowledged with TCA and then treated like a local change.
|
||||
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
|
||||
@@ -136,6 +175,17 @@ 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
|
||||
|
||||
```
|
||||
@@ -146,6 +196,7 @@ 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
|
||||
@@ -162,26 +213,67 @@ claims a better priority.
|
||||
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, per port, the live state
|
||||
read from the ASIC together with the configured options. The same data is
|
||||
available as JSON:
|
||||
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 `stp status` command prints the same view on the serial console.
|
||||
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.
|
||||
|
||||
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.
|
||||
* 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.
|
||||
* 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.
|
||||
Reference in new issue
Block a user