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:
Generated
+18
-22
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user