Files
RedBear-OS/netstack/src/link/bridge.rs
T
Red Bear OS 998593f243 stats: add statistics() to bridge, bond, and tun devices
Bridge: statistics() aggregates rx/tx bytes+packets from all member ports.
arp_stats() delegates to each port, showing per-port breakdown.

Bond: statistics() aggregates from all slaves (active+standby).
arp_stats() delegates to each slave.

TUN: statistics() now returns live counters tracked during send()/recv().
rx_bytes, rx_packets, tx_bytes, tx_packets increment per-packet.

Previously these devices returned Stats::default() (all zeros).
Now netcfg /ifaces/*/stats shows real data for bridge, bond, tun.
2026-07-08 16:00:26 +03:00

262 lines
8.9 KiB
Rust

//! 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<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>,
}
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<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) {
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<usize> {
self.mac_table.borrow().get(&mac).map(|e| e.port)
}
fn flood(&self, packet: &[u8], now: Instant, except_port: Option<usize>) {
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<u8>, 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<str> {
&self.name
}
fn can_recv(&self) -> bool {
self.ports.borrow().iter().any(|p| p.can_recv())
}
fn mac_address(&self) -> Option<EthernetAddress> {
None
}
fn set_mac_address(&mut self, _addr: EthernetAddress) {}
fn ip_address(&self) -> Option<IpCidr> {
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
}
}