stp: BPDUs finally reach the wire (CPU tag flags + management VLAN)

Two TX bugs meant our BPDUs NEVER left the switch as valid STP frames -
on the wire they appeared as ethertype 0x8899 (the raw Realtek CPU tag)
and were flooded to all ports instead of directed. Every earlier root
election was a solo act: no other bridge ever saw us. Both are the same
bug classes fixed for LACP earlier:

- rtl_tag.flags was written raw (0x0020); like every other tag field it
  must go through HTONS, otherwise the bits land in the wrong byte
  (0x2000 = EFID), the ASIC fails to parse the tag and floods the frame
  with the 0x8899 header still attached.
- With a management VLAN set, tcpip_output() splices an 802.1Q tag after
  the SA, again shifting the CPU tag out of the parsed position. BPDUs are
  link-local and must egress untagged: suppress the VLAN insert per frame,
  exactly as lacp_send() does.

Hardware note discovered while fixing this: RTL_TAG_KEEP on an LLC/802.3
(length-field) frame makes the ASIC drop it entirely - the same flag works
fine on ethertype frames (LACP). So BPDUs use LEARN_DIS only.

Verified on the wire (tcpdump on the peer): clean "802.3 ... LLC, dsap STP
0x42 ... Rapid STP, bridge-id 8000.<our mac>" at the hello interval, sent
directed (no flood), management HTTP unaffected, LAN at 0% loss throughout.

(cherry picked from commit 4a41a292a9ab88d4fb05a8481ad28f8ffcfd9bc4)
This commit is contained in:
d00f
2026-08-04 03:25:10 +02:00
parent bce1d2bd28
commit fbd19b2b4d
+18 -1
View File
@@ -44,6 +44,7 @@ __xdata uint8_t stp_fdb_i;
extern __xdata struct uip_eth_addr uip_ethaddr; extern __xdata struct uip_eth_addr uip_ethaddr;
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
extern __xdata uint16_t management_vlan; /* owned by rtlplayground.c; suppressed per-frame for BPDUs */
/* CLI tokenizer state + helpers (owned by cmd_parser.c, HOME bank) */ /* CLI tokenizer state + helpers (owned by cmd_parser.c, HOME bank) */
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
@@ -188,7 +189,13 @@ void stp_cnf_send(uint8_t port) __reentrant
STP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); STP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID);
STP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; STP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION;
STP_O->rtl_tag.reason = 0x00; STP_O->rtl_tag.reason = 0x00;
STP_O->rtl_tag.flags = 0x0020; // Disable L2 learning /* Through HTONS like every tag field: raw 0x0020 lands on the wire as
* 0x2000 (EFID), the ASIC fails to parse the tag and floods the frame
* with the 0x8899 header still on it (same bug class as LACP had).
* NOTE: no RTL_TAG_KEEP here - hardware-verified that KEEP on an
* LLC/802.3 (length-field) frame makes the ASIC drop it entirely,
* while the same flag works fine on ethertype frames (LACP). */
STP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS);
STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << port); STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << port);
STP_O->msg_len = HTONS(0x27); STP_O->msg_len = HTONS(0x27);
@@ -229,8 +236,18 @@ void stp_cnf_send(uint8_t port) __reentrant
STP_O->hello = stp_hello_s; STP_O->hello = stp_hello_s;
STP_O->fwd_delay = stp_fwddelay_s; STP_O->fwd_delay = stp_fwddelay_s;
/* BPDUs are link-local and must egress untagged: with a management VLAN
* set, tcpip_output() splices an 802.1Q tag after the SA, shifting the
* in-frame rtl_tag out of the position the ASIC parses - the CPU tag then
* leaks onto the wire as 0x8899 and the BPDU is flooded, not sent.
* Hardware-verified fix, same as lacp_send(). */
{
uint16_t saved_mgmt_vlan = management_vlan;
management_vlan = 0;
uip_len = sizeof(struct stp_pkt); uip_len = sizeof(struct stp_pkt);
tcpip_output(); tcpip_output();
management_vlan = saved_mgmt_vlan;
}
} }