//! 802.1D MAC Learning Bridge — mirrors Linux 7.1's `net/bridge/`. //! //! Reference files: //! - `net/bridge/br.c` — bridge core (`br_add_bridge`, `br_del_bridge`) //! - `net/bridge/br_fdb.c` — forwarding database (MAC learning + aging) //! - `net/bridge/br_forward.c` — frame forwarding logic //! - `net/bridge/br_input.c` — ingress frame handling //! - `net/bridge/br_device.c` — bridge as a `net_device` //! //! The bridge composes multiple link-layer devices and forwards Ethernet //! frames between them based on a dynamically-learned MAC→port table. //! Unknown destinations are flooded to all other ports, mirroring Linux's //! `br_flood()` in `br_forward.c`. use std::cell::RefCell; use std::collections::BTreeMap; use std::rc::Rc; use smoltcp::time::{Duration, Instant}; use smoltcp::wire::{ EthernetAddress, EthernetFrame, EthernetProtocol, EthernetRepr, IpAddress, IpCidr, }; use super::stp::{self, BPDU_MAC, PortState, StpState}; use super::LinkDevice; use super::Stats; const MAC_AGE_TIMEOUT: Duration = Duration::from_secs(300); struct MacEntry { port: usize, last_seen: Instant, } pub struct BridgeDevice { name: Rc, ports: RefCell>>, mac_table: RefCell>, stp: RefCell>, recv_buffer: Vec, ip_address: Option, } impl BridgeDevice { pub fn new(name: &str) -> Self { Self { 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(&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) { if !mac.is_unicast() { return; } self.mac_table.borrow_mut().insert( mac, MacEntry { port, last_seen: now, }, ); } fn age_entries(&self, now: Instant) { self.mac_table .borrow_mut() .retain(|_, e| now < e.last_seen + MAC_AGE_TIMEOUT); } fn lookup(&self, mac: EthernetAddress) -> Option { self.mac_table.borrow().get(&mac).map(|e| e.port) } fn flood(&self, packet: &[u8], now: Instant, except_port: Option) { for (idx, port) in self.ports.borrow_mut().iter_mut().enumerate() { 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); } } } impl LinkDevice for BridgeDevice { fn send(&mut self, _next_hop: IpAddress, packet: &[u8], now: Instant) { if packet.len() < 14 { return; } let frame = EthernetFrame::new_unchecked(packet); let Ok(repr) = EthernetRepr::parse(&frame) else { return; }; let dst_mac = repr.dst_addr; if repr.dst_addr.is_broadcast() || repr.dst_addr.is_multicast() { self.flood(packet, now, None); } else if let Some(port_idx) = self.lookup(dst_mac) { if self.stp.borrow().as_ref().is_some_and(|s| s.is_blocked(port_idx)) { return; } if let Some(port) = self.ports.borrow_mut().get_mut(port_idx) { port.send(IpAddress::Ipv4(smoltcp::wire::Ipv4Address::UNSPECIFIED), packet, now); } } else { self.flood(packet, now, None); } } 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, EthernetAddress, EthernetProtocol)> = None; { let mut ports = self.ports.borrow_mut(); for (port_idx, port) in ports.iter_mut().enumerate() { if let Some(buf) = port.recv(now) { let frame = EthernetFrame::new_unchecked(buf); let Ok(repr) = EthernetRepr::parse(&frame) else { continue; }; self.learn(repr.src_addr, port_idx, now); received = Some((port_idx, buf.to_vec(), repr.dst_addr, repr.ethertype)); break; } } } 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 { if dst_mac.is_broadcast() || dst_mac.is_multicast() { self.recv_buffer = packet.clone(); self.flood(&self.recv_buffer, now, Some(port_idx)); return Some(&self.recv_buffer); } else if let Some(dst_port_idx) = self.lookup(dst_mac) { if dst_port_idx != port_idx && !self.stp.borrow().as_ref().is_some_and(|s| s.is_blocked(dst_port_idx)) { let mut ports = self.ports.borrow_mut(); if let Some(target) = ports.get_mut(dst_port_idx) { target.send(IpAddress::Ipv4(smoltcp::wire::Ipv4Address::UNSPECIFIED), &packet, now); } return None; } } self.recv_buffer = packet; return Some(&self.recv_buffer); } } None } fn name(&self) -> &Rc { &self.name } fn can_recv(&self) -> bool { self.ports.borrow().iter().any(|p| p.can_recv()) } fn mac_address(&self) -> Option { None } fn set_mac_address(&mut self, _addr: EthernetAddress) {} fn ip_address(&self) -> Option { self.ip_address } fn set_ip_address(&mut self, addr: IpCidr) { self.ip_address = Some(addr); } fn arp_table(&self) -> String { let mut out = String::from("Bridge FDB:\n"); for (mac, entry) in self.mac_table.borrow().iter() { out.push_str(&format!(" {} port={} last_seen={}\n", mac, entry.port, entry.last_seen)); } if self.mac_table.borrow().is_empty() { out.push_str(" (empty)\n"); } out } fn link_state(&self) -> &'static str { if self.ports.borrow().is_empty() { "down" } else { "up" } } fn statistics(&self) -> Stats { let mut total = Stats::default(); for port in self.ports.borrow().iter() { let s = port.statistics(); total.rx_bytes += s.rx_bytes; total.rx_packets += s.rx_packets; total.tx_bytes += s.tx_bytes; total.tx_packets += s.tx_packets; } total } fn arp_stats(&self) -> String { let mut out = String::new(); for (i, port) in self.ports.borrow().iter().enumerate() { let s = port.arp_stats(); if !s.is_empty() { out.push_str(&format!("port{}: {}", i, s)); } } out } }