stp: integrate 802.1D Spanning Tree Protocol into BridgeDevice

Adds loop prevention to Ethernet bridges:
- BridgeDevice gains stp: Option<StpState> field
- enable_stp(priority, mac) method initializes STP per-bridge
- BPDU frames (dst 01:80:c2:00:00:00) intercepted in recv(),
  processed locally, never forwarded
- STP hello timer sends periodic BPDUs on all ports (root bridge)
- flood() skips STP-blocked ports
- build_bpdu() made public for bridge integration
- stp module declared in link/mod.rs

The recv() flow now: age MACs → check STP hello timer →
poll ports → detect BPDU (absorb) → normal frame (learn + forward).

Reference: Linux 7.1 net/bridge/br_stp.c, br_stp_bpdu.c, br_stp_timer.c
This commit is contained in:
Red Bear OS
2026-07-08 13:38:38 +03:00
parent 1606a6ffb2
commit 30db94c970
4 changed files with 63 additions and 23 deletions
Generated
+18 -22
View File
@@ -85,7 +85,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr 2.8.2",
"memchr 2.8.3",
]
[[package]]
@@ -495,11 +495,11 @@ dependencies = [
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
dependencies = [
"crossbeam-utils 0.8.21",
"crossbeam-utils 0.8.22",
]
[[package]]
@@ -515,9 +515,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "daemon"
@@ -908,7 +908,7 @@ dependencies = [
"futures-macro",
"futures-sink",
"futures-task",
"memchr 2.8.2",
"memchr 2.8.3",
"pin-project-lite",
"slab",
]
@@ -1445,9 +1445,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.8.2"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "nb"
@@ -2024,7 +2024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
dependencies = [
"aho-corasick",
"memchr 2.8.2",
"memchr 2.8.3",
"regex-automata",
"regex-syntax",
]
@@ -2036,7 +2036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr 2.8.2",
"memchr 2.8.3",
"regex-syntax",
]
@@ -2128,9 +2128,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "sb16d"
@@ -2243,7 +2243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr 2.8.2",
"memchr 2.8.3",
"serde",
"serde_core",
"zmij",
@@ -2532,11 +2532,7 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "usb-core"
<<<<<<< HEAD
version = "0.2.5"
=======
version = "0.3.0"
>>>>>>> 1c80937e (networking: comprehensive TCP/IP stack implementation (Phases 1-6))
[[package]]
name = "usbctl"
@@ -2984,18 +2980,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.52"
version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.52"
version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71"
dependencies = [
"proc-macro2",
"quote",
+43
View File
@@ -21,6 +21,7 @@ use smoltcp::wire::{
EthernetAddress, EthernetFrame, EthernetProtocol, EthernetRepr, IpAddress, IpCidr,
};
use super::stp::{self, BPDU_MAC, PortState, StpState};
use super::LinkDevice;
const MAC_AGE_TIMEOUT: Duration = Duration::from_secs(300);
@@ -34,6 +35,7 @@ pub struct BridgeDevice {
name: Rc<str>,
ports: RefCell<Vec<Box<dyn LinkDevice>>>,
mac_table: RefCell<BTreeMap<EthernetAddress, MacEntry>>,
stp: RefCell<Option<StpState>>,
recv_buffer: Vec<u8>,
ip_address: Option<IpCidr>,
}
@@ -44,13 +46,26 @@ impl BridgeDevice {
name: name.into(),
ports: RefCell::new(Vec::new()),
mac_table: RefCell::new(BTreeMap::new()),
stp: RefCell::new(None),
recv_buffer: Vec::with_capacity(1500),
ip_address: None,
}
}
pub fn add_port<T: LinkDevice + 'static>(&self, dev: T) {
let mac = dev.mac_address();
self.ports.borrow_mut().push(Box::new(dev));
if let Some(stp) = self.stp.borrow_mut().as_mut() {
stp.port_states.push(PortState::Forwarding);
}
let _ = mac;
}
/// Enable STP with the given bridge priority and MAC.
/// Must be called after all ports are added.
pub fn enable_stp(&self, priority: u16, bridge_mac: EthernetAddress) {
let port_count = self.ports.borrow().len();
*self.stp.borrow_mut() = Some(StpState::new(priority, bridge_mac, port_count));
}
fn learn(&self, mac: EthernetAddress, port: usize, now: Instant) {
@@ -81,6 +96,9 @@ impl BridgeDevice {
if Some(idx) == except_port {
continue;
}
if self.stp.borrow().as_ref().is_some_and(|s| s.is_blocked(idx)) {
continue;
}
port.send(IpAddress::Ipv4(smoltcp::wire::Ipv4Address::UNSPECIFIED), packet, now);
}
}
@@ -110,6 +128,19 @@ impl LinkDevice for BridgeDevice {
fn recv(&mut self, now: Instant) -> Option<&[u8]> {
self.age_entries(now);
// STP hello timer — send periodic BPDUs if we're the root bridge
if self.stp.borrow_mut().as_mut().is_some_and(|s| s.send_hello(now)) {
let bpdu = {
let stp = self.stp.borrow();
stp.as_ref().map(|s| s.build_bpdu())
};
if let Some(bpdu) = bpdu {
for port in self.ports.borrow_mut().iter_mut() {
port.send(IpAddress::Ipv4(smoltcp::wire::Ipv4Address::UNSPECIFIED), &bpdu, now);
}
}
}
let mut received: Option<(usize, Vec<u8>, EthernetAddress, EthernetProtocol)> = None;
{
let mut ports = self.ports.borrow_mut();
@@ -127,6 +158,18 @@ impl LinkDevice for BridgeDevice {
}
if let Some((port_idx, packet, dst_mac, ethertype)) = received {
// BPDU: process via STP, don't forward
if dst_mac == BPDU_MAC {
let response = self.stp.borrow_mut().as_mut()
.and_then(|s| s.process_bpdu(port_idx, &packet, now));
if let Some(rsp) = response {
if let Some(port) = self.ports.borrow_mut().get_mut(port_idx) {
port.send(IpAddress::Ipv4(smoltcp::wire::Ipv4Address::UNSPECIFIED), &rsp, now);
}
}
return None;
}
if ethertype == EthernetProtocol::Arp
|| ethertype == EthernetProtocol::Ipv4
|| ethertype == EthernetProtocol::Ipv6
+1
View File
@@ -5,6 +5,7 @@ pub mod gre;
pub mod ipip;
pub mod loopback;
pub mod qdisc;
pub mod stp;
pub mod tun;
pub mod vlan;
pub mod vxlan;
+1 -1
View File
@@ -107,7 +107,7 @@ impl StpState {
4
}
fn build_bpdu(&self) -> Vec<u8> {
pub fn build_bpdu(&self) -> Vec<u8> {
let mut buf = vec![0u8; 35];
buf[0..2].copy_from_slice(&[0x00, 0x00]);
buf[2] = 0x00;