restore: networking stack files from reflog (Phases 1-6)
Recovered from reflog commits 1c80937e and d0ecc067 after force-push data loss.
Includes: filter/, icmp_error.rs, slaac.rs, bond.rs, bridge.rs, gre.rs, ipip.rs,
qdisc.rs, tun.rs, vlan.rs, vxlan.rs, netfilter.rs, tun.rs, conntrack.rs, nat.rs,
rule.rs, table.rs, redbear-ufw/, dhcpv6d/, netdiag/ — 39 files total.
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
//! 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::LinkDevice;
|
||||
|
||||
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>>,
|
||||
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()),
|
||||
recv_buffer: Vec::with_capacity(1500),
|
||||
ip_address: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_port<T: LinkDevice + 'static>(&self, dev: T) {
|
||||
self.ports.borrow_mut().push(Box::new(dev));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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 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);
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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" }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user