From 4d009bc8e8221d6865b5f77d8d45e642dd4cb8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Silva?= Date: Tue, 20 Oct 2026 16:11:16 +0200 Subject: [PATCH] stp: give auto-edge back to a port held by its own BPDU stp_loop_hold_peer() takes operEdge away from the port that sent the BPDU we got back, though that port heard nothing itself. When the hold ran out the port stayed non-edge until its next link change, and every later state change on it counted as a topology change. Restore edge when the hold ends with the port still silent and auto-edge on, before the transition is reported, so the recovery itself is not a change. Hellos go out on edge ports too, so a loop that persists is caught again. The console column that shows seconds since the last BPDU is now called age[s]; it never was a receive count. --- doc/stp.md | 9 +++++++++ rtl837x_stp.c | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/doc/stp.md b/doc/stp.md index c3cc2408..543f8928 100644 --- a/doc/stp.md +++ b/doc/stp.md @@ -268,6 +268,15 @@ 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. diff --git a/rtl837x_stp.c b/rtl837x_stp.c index b147c3c2..1e41235f 100644 --- a/rtl837x_stp.c +++ b/rtl837x_stp.c @@ -297,7 +297,7 @@ void stp_status(void) __banked print_string("changes "); print_short(stp_tc_count); write_char('\n'); - print_string("port state role edge tx bpdu\n"); + print_string("port state role edge tx age[s]\n"); for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) { if (!stp_ent_active(stp_i)) continue; @@ -1297,6 +1297,13 @@ void stp_timers(void) __banked print_string("STP: port learning "); print_port_nl(stp_i); } else { + /* A returned BPDU can hold its sending port even though + * that port never received one. Once the hold expires, + * recover auto-edge before the forwarding timer stops. */ + if (stp_loop_held[stp_i] && (stp_pflags[stp_i] & STP_PF_AUTOEDGE) + && !((stp_heard >> stp_i) & 1) + && stp_bpdu_age[stp_i] > STP_EDGE_DELAY) + stp_pflags[stp_i] |= STP_PF_OPEREDGE; stp_loop_held[stp_i] = 0; stp_backup &= ~((uint16_t)1 << stp_i); stp_state_set(stp_i, 0b11);