From ab5e213ce9401507e7bc30581e8222c6248d8b96 Mon Sep 17 00:00:00 2001 From: d00f Date: Tue, 4 Aug 2026 02:14:24 +0200 Subject: [PATCH] stp: contain BPDUs to the CPU while STP runs With STP enabled the switch is a participating bridge, so BPDUs must be consumed, not relayed - yet the reserved group 01:80:C2:00:00:00 was flooded across the VLAN just like any multicast, leaking every BPDU to all ports (the same defect class as the LACPDU flood addressed in the LACP branch, PR #299). On stp on, write a CPU-only static L2 multicast entry for the BPDU group per VLAN: BPDUs can arrive VLAN-tagged and classify into the tag's VID, so cover every VLAN present in the VLAN table plus every port's PVID for the untagged case. On stp off the same entries are retargeted to all ports + CPU, restoring the previous flood behaviour: an unmanaged switch is expected to be transparent to BPDUs so the surrounding spanning tree can span through it, and dropping them instead would partition that topology. Note: with STP enabled the ports start out blocking, which also stops egress of CPU-originated LACPDUs, so an active LACP aggregate drops until the ports reach forwarding - a pre-existing interaction, not changed here. --- rtl837x_stp.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/rtl837x_stp.c b/rtl837x_stp.c index 2eb4f92..e90227a 100644 --- a/rtl837x_stp.c +++ b/rtl837x_stp.c @@ -11,11 +11,18 @@ #include "rtl837x_sfr.h" #include "rtl837x_regs.h" #include "rtl837x_stp.h" +#include "rtl837x_port.h" /* port_pvid_get(), port_l2mc_set() */ #include "uip.h" #include "machine.h" extern __code struct machine machine; extern __xdata uint8_t sfr_data[4]; +extern __xdata struct machine_runtime machine_detected; /* owned by rtl837x_port.c */ + +/* Scratch for stp_fdb_update(), in xdata: plain locals would overflow the + * near-full internal-RAM overlay (OSEG), cf. rtl837x_lacp.c. */ +__xdata uint16_t stp_fdb_vid; +__xdata uint8_t stp_fdb_i; extern __xdata struct uip_eth_addr uip_ethaddr; @@ -202,6 +209,39 @@ void stp_timers(void) __banked } +/* + * Steer BPDUs (01:80:C2:00:00:00) while STP runs: one static CPU-only L2 + * multicast entry per PVID in use, so BPDUs reach the CPU without being + * flooded to other ports (a bridge running STP must consume BPDUs, not + * relay them - relaying poisons the neighbours' view of the topology). + * + * With STP off the same entries are retargeted to all ports + CPU, which + * restores the previous flood behaviour ("BPDU transparency"): the + * surrounding spanning tree can keep spanning *through* this switch, which + * unmanaged setups rely on. Same per-PVID/IVL rules as the LACP steering - + * see port_l2mc_set() and rtl837x_lacp.c. NOTE: changing a port's PVID + * while STP runs needs `stp off`/`on` to refresh the entries. + */ +static void stp_fdb_update(__xdata uint16_t pmask) +{ + /* Unlike LACPDUs (always untagged, so per-PVID entries suffice), BPDUs + * can arrive VLAN-tagged and then classify into the tag's VID - cover + * every VLAN that exists in the VLAN table, plus every port's PVID for + * the untagged case. A duplicate VID just overwrites the same slot. */ + for (stp_fdb_vid = 1; stp_fdb_vid < 4095; stp_fdb_vid++) { + if (vlan_get(stp_fdb_vid) < 0) + continue; + if (!(sfr_data[0] & 0x02)) /* bit 1: VLAN table entry valid */ + continue; + port_l2mc_set(0x00, stp_fdb_vid, pmask); + } + for (stp_fdb_i = machine.min_port; stp_fdb_i <= machine.max_port; stp_fdb_i++) { + stp_fdb_vid = port_pvid_get(stp_fdb_i); + port_l2mc_set(0x00, stp_fdb_vid, pmask); + } +} + + void stp_setup(void) __banked { print_string("Enabling STP: "); @@ -222,6 +262,9 @@ void stp_setup(void) __banked root_bridge.prio = 0x80; // This corresponds to 32768 root_bridge.ext = 0x00; memcpy(root_bridge.mac, uip_ethaddr.addr, 6); + + /* Take BPDUs to the CPU only - we are a participating bridge now. */ + stp_fdb_update(PMASK_CPU); } @@ -236,4 +279,7 @@ void stp_off(void) __banked } sfr_data[1] |= 0x0f; // Do not block CPU-Port reg_write_m(RTL837X_MSTP_STATES); + + /* Restore BPDU transparency: flood them again like an unmanaged switch. */ + stp_fdb_update(PMASK_CPU | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6)); }