From 78b3971782dd726e45cf619f15c75602c1bca59c Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sun, 16 Aug 2026 11:46:02 +0200 Subject: [PATCH] stp: pass a received topology change through the switch The tree structure already crossed the switch by regeneration, but the topology-change information did not: a received TC flag was ignored and a TCN only acknowledged, so bridges behind this one kept stale entries until normal aging. A TC flag in a received BPDU now flushes the other non-edge ports once and arms the transmit window our BPDUs already copy the flag from, refreshed to hello+1 seconds by every further flagged frame so it ends one hello after the neighbour stops, without shortening the longer window a local change arms. A TCN is acknowledged as before and then treated like a local change on that port. --- doc/stp.md | 13 +++++++++++++ rtl837x_stp.c | 28 +++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/doc/stp.md b/doc/stp.md index 61bb5e5..0ccca7c 100644 --- a/doc/stp.md +++ b/doc/stp.md @@ -80,6 +80,16 @@ 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 ``` @@ -139,3 +149,6 @@ The `stp status` command prints the same view on the serial console. 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. diff --git a/rtl837x_stp.c b/rtl837x_stp.c index 87bfb22..5bafe17 100644 --- a/rtl837x_stp.c +++ b/rtl837x_stp.c @@ -501,12 +501,14 @@ void stp_in(void) __banked if (STP_I->bpdu_type == 0x80) { /* TCN: a downstream bridge reports a topology change. Acknowledge it - * on this port so the sender stops repeating; the change itself is - * counted (and, once implemented, propagated rootward). */ + * on this port so the sender stops repeating, then treat it like a + * change of our own: flush the port and carry the TC flag on the + * designated ports for the full window. No TCN goes towards the + * root, so a legacy root further up keeps its normal aging. */ stp_tx_flags_extra = 0x80; /* Topology Change Acknowledgment */ stp_cnf_send(port); /* transmits internally */ uip_len = 0; /* ...so handle_rx must not TX again */ - stp_tc_count++; + stp_topology_change(port); return; } @@ -563,6 +565,26 @@ void stp_in(void) __banked return; } + /* Topology Change in transit. The flag arms a short window that our + * own BPDUs copy downstream (the TX side already sends TC while + * stp_tc_while runs) and that each further flagged BPDU refreshes, so + * it expires one hello after the neighbour stops - without shortening + * the long window a local change may have armed. The flush runs once, + * on the arming edge: everything learned on the other non-edge ports + * may sit behind the moved link and must be relearned. */ + if (STP_I->flags & 0x01) { + if (!stp_tc_while) { + uint8_t i; + stp_tc_count++; + for (i = machine.min_port; i <= machine.max_port; i++) + if (i != port && (stp_pflags[i] & STP_PF_ENABLED) + && !(stp_pflags[i] & STP_PF_OPEREDGE)) + port_l2_forget_port(i); + } + if (stp_tc_while < ((uint16_t)stp_hello_s + 1) * STP_HZ) + stp_tc_while = ((uint16_t)stp_hello_s + 1) * STP_HZ; + } + stp_record_designated(port); /* Better root than the one we know? The identifier is priority, system