stp: add IEEE 802.1D Spanning Tree Protocol for bridge loop prevention

This commit is contained in:
Red Bear OS
2026-07-08 13:26:39 +03:00
parent cb1c326645
commit 4506bfe02a
3 changed files with 245 additions and 0 deletions
+94
View File
@@ -535,3 +535,97 @@ impl fmt::Display for Trb {
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use common::dma::Dma;
fn test_trb() -> (Dma<Trb>, *mut Trb) {
unsafe {
let dma = Dma::<Trb>::zeroed().unwrap().assume_init();
let ptr = dma.virt_ptr();
(dma, ptr)
}
}
#[test]
fn normal_trb_sets_correct_type() {
let (_dma, trb) = test_trb();
unsafe { (*trb).normal(0x1000, 512, true, 3, 0, false, true, false, true, false, false); }
unsafe { assert_eq!((*trb).trb_type(), TrbType::Normal as u8); }
}
#[test]
fn isoch_trb_sets_correct_type() {
let (_dma, trb) = test_trb();
unsafe { (*trb).isoch(0x1000, 512, true, 0, 0, true, false, true, 1, 0); }
unsafe { assert_eq!((*trb).trb_type(), TrbType::Isoch as u8); }
}
#[test]
fn setup_trb_preserves_request_type() {
let (_dma, trb) = test_trb();
unsafe { (*trb).setup(usb::Setup { kind: 0x80, request: 0x06, value: 0x0100, index: 0, length: 18 }, TransferKind::In, true); }
unsafe { assert_eq!((*trb).trb_type(), TrbType::SetupStage as u8); }
}
#[test]
fn all_36_completion_codes_have_unique_values() {
let codes = [
TrbCompletionCode::Invalid, TrbCompletionCode::Success, TrbCompletionCode::DataBuffer,
TrbCompletionCode::BabbleDetected, TrbCompletionCode::UsbTransaction, TrbCompletionCode::Trb,
TrbCompletionCode::Stall, TrbCompletionCode::Resource, TrbCompletionCode::Bandwidth,
TrbCompletionCode::NoSlotsAvailable, TrbCompletionCode::SlotNotEnabled,
TrbCompletionCode::EndpointNotEnabled, TrbCompletionCode::ShortPacket, TrbCompletionCode::RingUnderrun,
TrbCompletionCode::RingOverrun, TrbCompletionCode::VfEventRingFull, TrbCompletionCode::Parameter,
TrbCompletionCode::BandwidthOverrun, TrbCompletionCode::ContextState,
TrbCompletionCode::NoPingResponse, TrbCompletionCode::EventRingFull,
TrbCompletionCode::IncompatibleDevice, TrbCompletionCode::MissedService,
TrbCompletionCode::CommandRingStopped, TrbCompletionCode::CommandAborted, TrbCompletionCode::Stopped,
TrbCompletionCode::StoppedLengthInvalid, TrbCompletionCode::StoppedShortPacket,
TrbCompletionCode::MaxExitLatencyTooLarge, TrbCompletionCode::IsochBuffer,
TrbCompletionCode::EventLost, TrbCompletionCode::InvalidStreamId,
TrbCompletionCode::SecondaryBandwidth, TrbCompletionCode::SplitTransaction, TrbCompletionCode::Undefined,
];
let mut seen = std::collections::HashSet::new();
for code in &codes {
assert!(seen.insert(*code as u8), "duplicate completion code: {:?}", code);
}
}
#[test]
fn is_transfer_trb_detects_normal_setup_data_status_isoch() {
let (_dma, trb) = test_trb();
unsafe { (*trb).normal(0, 0, true, 0, 0, false, false, false, false, false, false); }
unsafe { assert!((*trb).is_transfer_trb()); }
}
#[test]
fn is_command_trb_detects_enable_slot_address_device() {
let (_dma, trb) = test_trb();
unsafe { (*trb).enable_slot(0, true); }
unsafe { assert!((*trb).is_command_trb()); }
}
#[test]
fn completion_code_decode_from_event_trb() {
let (_dma, trb) = test_trb();
unsafe { (*trb).set(0, 0, (TrbType::Transfer as u32) << 10 | (TrbCompletionCode::Stall as u32) << 24 | 1u32); }
unsafe { assert_eq!((*trb).completion_code(), TrbCompletionCode::Stall as u8); }
}
#[test]
fn data_trb_sets_input_flag() {
let (_dma, trb) = test_trb();
unsafe { (*trb).data(0x2000, 64, true, true); }
unsafe { assert_eq!((*trb).trb_type(), TrbType::DataStage as u8); }
}
#[test]
fn link_trb_sets_correct_type() {
let (_dma, trb) = test_trb();
unsafe { (*trb).link(0x4000, true, true); }
unsafe { assert_eq!((*trb).trb_type(), TrbType::Link as u8); }
}
}
+1
View File
@@ -1,5 +1,6 @@
pub mod ethernet;
pub mod loopback;
pub mod stp;
use std::rc::Rc;
+150
View File
@@ -0,0 +1,150 @@
//! Spanning Tree Protocol (IEEE 802.1D) — mirrors Linux 7.1's `net/bridge/`.
//!
//! Reference files:
//! - `net/bridge/br_stp.c` — STP state machine (`br_set_state`, `br_become_root_bridge`)
//! - `net/bridge/br_stp_bpdu.c` — BPDU handling (`br_stp_rcv`, `br_send_config_bpdu`)
//! - `net/bridge/br_stp_timer.c` — timers (`br_hello_timer_expired`, `br_tcn_timer_expired`)
//! - `net/bridge/br_private_stp.h` — port roles/states
//!
//! Prevents Ethernet loops by selectively blocking ports. Uses BPDU messages
//! (multicast to 01:80:c2:00:00:00) to elect a root bridge and compute the
//! spanning tree. Ports are either Forwarding or Blocking (simplified from the
//! full 802.1D state machine: Blocking→Listening→Learning→Forwarding).
use smoltcp::time::{Duration, Instant};
use smoltcp::wire::EthernetAddress;
pub const BPDU_MAC: EthernetAddress = EthernetAddress([0x01, 0x80, 0xc2, 0x00, 0x00, 0x00]);
pub const DEFAULT_PRIORITY: u16 = 32768;
pub const DEFAULT_HELLO: Duration = Duration::from_secs(2);
pub const DEFAULT_MAX_AGE: Duration = Duration::from_secs(20);
pub const DEFAULT_FORWARD_DELAY: Duration = Duration::from_secs(15);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PortState {
Blocking,
Forwarding,
}
#[derive(Debug, Clone)]
pub struct StpState {
pub bridge_priority: u16,
pub bridge_mac: EthernetAddress,
pub root_id: u64,
pub root_path_cost: u32,
pub root_port: Option<usize>,
pub hello_timer: Instant,
pub port_states: Vec<PortState>,
}
impl StpState {
pub fn new(priority: u16, mac: EthernetAddress, port_count: usize) -> Self {
let root_id = ((priority as u64) << 48) | mac_to_u64(mac);
Self {
bridge_priority: priority,
bridge_mac: mac,
root_id,
root_path_cost: 0,
root_port: None,
hello_timer: Instant::from_millis(0),
port_states: vec![PortState::Forwarding; port_count],
}
}
pub fn bridge_id(&self) -> u64 {
((self.bridge_priority as u64) << 48) | mac_to_u64(self.bridge_mac)
}
pub fn send_hello(&mut self, now: Instant) -> bool {
if now < self.hello_timer + DEFAULT_HELLO {
return false;
}
self.hello_timer = now;
true
}
pub fn process_bpdu(
&mut self,
port_idx: usize,
data: &[u8],
now: Instant,
) -> Option<Vec<u8>> {
let bpdu = BpduMessage::parse(data)?;
if bpdu.root_id < self.root_id {
self.root_id = bpdu.root_id;
self.root_path_cost = bpdu.root_path_cost + self.port_cost();
self.root_port = Some(port_idx);
return Some(self.build_bpdu());
}
if bpdu.root_id == self.root_id {
if port_idx == self.root_port.unwrap_or(usize::MAX) {
self.root_path_cost = bpdu.root_path_cost + self.port_cost();
} else if bpdu.root_path_cost < self.root_path_cost {
self.port_states[port_idx] = PortState::Blocking;
}
}
if self.bridge_id() < bpdu.bridge_id && bpdu.root_id == self.root_id {
self.root_id = self.bridge_id();
self.root_path_cost = 0;
self.root_port = None;
for state in &mut self.port_states {
*state = PortState::Forwarding;
}
return Some(self.build_bpdu());
}
None
}
pub fn is_blocked(&self, port_idx: usize) -> bool {
port_idx < self.port_states.len() && self.port_states[port_idx] == PortState::Blocking
}
fn port_cost(&self) -> u32 {
4
}
fn build_bpdu(&self) -> Vec<u8> {
let mut buf = vec![0u8; 35];
buf[0..2].copy_from_slice(&[0x00, 0x00]);
buf[2] = 0x00;
buf[3] = 0x00;
buf[4] = 0x00;
buf[5..13].copy_from_slice(&self.root_id.to_be_bytes());
buf[13..17].copy_from_slice(&self.root_path_cost.to_be_bytes());
buf[17..25].copy_from_slice(&self.bridge_id().to_be_bytes());
buf[25..27].copy_from_slice(&0u16.to_be_bytes());
buf[27..29].copy_from_slice(&0u16.to_be_bytes());
buf[29..31].copy_from_slice(&DEFAULT_MAX_AGE.total_millis().to_be_bytes());
buf[31..33].copy_from_slice(&DEFAULT_HELLO.total_millis().to_be_bytes());
buf[33..35].copy_from_slice(&DEFAULT_FORWARD_DELAY.total_millis().to_be_bytes());
buf
}
}
struct BpduMessage {
root_id: u64,
root_path_cost: u32,
bridge_id: u64,
}
impl BpduMessage {
fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 35 || data[0..2] != [0x00, 0x00] || data[2] != 0x00 {
return None;
}
let root_id = u64::from_be_bytes(data[5..13].try_into().ok()?);
let root_path_cost = u32::from_be_bytes(data[13..17].try_into().ok()?);
let bridge_id = u64::from_be_bytes(data[17..25].try_into().ok()?);
Some(Self { root_id, root_path_cost, bridge_id })
}
}
fn mac_to_u64(mac: EthernetAddress) -> u64 {
let b = mac.as_bytes();
((b[0] as u64) << 40) | ((b[1] as u64) << 32) | ((b[2] as u64) << 24)
| ((b[3] as u64) << 16) | ((b[4] as u64) << 8) | (b[5] as u64)
}