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:
@@ -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