base: apply Red Bear patches on latest upstream/main

251 files: init, acpid, ipcd, netcfg, ihdgd, virtio-gpud, scheme-utils,
inputd, block driver, ptyd, ramfs, randd, initfs bootstrap, path deps,
version +rb0.3.1, author attribution
This commit is contained in:
Red Bear OS
2026-07-11 11:39:24 +03:00
parent 1b17b3fc24
commit bd595851e2
251 changed files with 24641 additions and 5993 deletions
+194
View File
@@ -0,0 +1,194 @@
//! Bonding (Link Aggregation) — mirrors Linux 7.1's `drivers/net/bonding/`.
//!
//! Reference files:
//! - `drivers/net/bonding/bond_main.c:1589` — `bond_should_deliver` (frame handling)
//! - `drivers/net/bonding/bond_main.c:328` — mode selection (round-robin vs active-backup)
//! - `drivers/net/bonding/bond_3ad.c` — LACP (not yet implemented)
//!
//! Two modes are supported:
//! - **ActiveBackup**: one slave is active, others standby. If active fails,
//! the next standby takes over. Mirrors `BOND_MODE_ACTIVEBACKUP`.
//! - **RoundRobin**: packets are distributed across slaves in sequence.
//! Mirrors `BOND_MODE_ROUNDROBIN`.
use std::cell::RefCell;
use std::rc::Rc;
use smoltcp::time::Instant;
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
use super::LinkDevice;
use super::Stats;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BondMode {
ActiveBackup,
RoundRobin,
}
struct Slave {
dev: Box<dyn LinkDevice>,
up: bool,
}
pub struct BondDevice {
name: Rc<str>,
slaves: RefCell<Vec<Slave>>,
active_slave: RefCell<usize>,
mode: BondMode,
rr_counter: RefCell<usize>,
recv_buffer: Vec<u8>,
ip_address: Option<IpCidr>,
}
impl BondDevice {
pub fn new(name: &str, mode: BondMode) -> Self {
Self {
name: name.into(),
slaves: RefCell::new(Vec::new()),
active_slave: RefCell::new(0),
mode,
rr_counter: RefCell::new(0),
recv_buffer: Vec::with_capacity(1500),
ip_address: None,
}
}
pub fn add_slave<T: LinkDevice + 'static>(&self, dev: T) {
self.slaves.borrow_mut().push(Slave {
dev: Box::new(dev),
up: true,
});
}
fn select_slave(&self) -> Option<usize> {
let slaves = self.slaves.borrow();
match self.mode {
BondMode::ActiveBackup => {
let active = *self.active_slave.borrow();
if active < slaves.len() && slaves[active].up {
Some(active)
} else {
for (idx, slave) in slaves.iter().enumerate() {
if slave.up {
*self.active_slave.borrow_mut() = idx;
return Some(idx);
}
}
None
}
}
BondMode::RoundRobin => {
let count = slaves.len();
if count == 0 {
return None;
}
let idx = *self.rr_counter.borrow() % count;
*self.rr_counter.borrow_mut() = idx.wrapping_add(1);
if !slaves[idx].up {
for (i, slave) in slaves.iter().enumerate() {
if slave.up {
return Some(i);
}
}
return None;
}
Some(idx)
}
}
}
}
impl LinkDevice for BondDevice {
fn send(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant) {
if let Some(idx) = self.select_slave() {
let mut slaves = self.slaves.borrow_mut();
if let Some(slave) = slaves.get_mut(idx) {
slave.dev.send(next_hop, packet, now);
}
}
}
fn recv(&mut self, now: Instant) -> Option<&[u8]> {
let mut slaves = self.slaves.borrow_mut();
for slave in slaves.iter_mut() {
if !slave.up {
continue;
}
if let Some(buf) = slave.dev.recv(now) {
self.recv_buffer.clear();
self.recv_buffer.extend_from_slice(buf);
return Some(&self.recv_buffer);
}
}
None
}
fn name(&self) -> &Rc<str> {
&self.name
}
fn can_recv(&self) -> bool {
self.slaves.borrow().iter().any(|s| s.up && s.dev.can_recv())
}
fn mac_address(&self) -> Option<EthernetAddress> {
self.slaves.borrow().first().and_then(|s| s.dev.mac_address())
}
fn set_mac_address(&mut self, addr: EthernetAddress) {
for slave in self.slaves.borrow_mut().iter_mut() {
slave.dev.set_mac_address(addr);
}
}
fn ip_address(&self) -> Option<IpCidr> {
self.ip_address
}
fn set_ip_address(&mut self, addr: IpCidr) {
self.ip_address = Some(addr);
}
fn link_state(&self) -> &'static str {
let active = *self.active_slave.borrow();
let slaves = self.slaves.borrow();
if slaves.is_empty() { return "down"; }
if active < slaves.len() && slaves[active].up { "up" } else { "degraded" }
}
fn arp_table(&self) -> String {
let slaves = self.slaves.borrow();
let active = *self.active_slave.borrow();
let mut out = format!("Bond mode: {:?}, active slave: {}\n",
self.mode, if active < slaves.len() { slaves[active].dev.name().as_ref() } else { "none" });
for (i, s) in slaves.iter().enumerate() {
out.push_str(&format!(" slave {}: {} ({})\n", i, s.dev.name(), if s.up { "up" } else { "down" }));
}
out
}
fn statistics(&self) -> Stats {
let mut total = Stats::default();
for s in self.slaves.borrow().iter() {
let stats = s.dev.statistics();
total.rx_bytes += stats.rx_bytes;
total.rx_packets += stats.rx_packets;
total.tx_bytes += stats.tx_bytes;
total.tx_packets += stats.tx_packets;
}
total
}
fn arp_stats(&self) -> String {
let mut out = String::new();
let slaves = self.slaves.borrow();
for (i, s) in slaves.iter().enumerate() {
let stats = s.dev.arp_stats();
if !stats.is_empty() {
out.push_str(&format!("slave{}: {}", i, stats));
}
}
out
}
}
+320
View File
@@ -0,0 +1,320 @@
//! 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
}
}
#[cfg(test)]
mod tests {
use super::*;
use smoltcp::wire::EthernetAddress;
fn mac(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8) -> EthernetAddress {
EthernetAddress([a, b, c, d, e, f])
}
#[test]
fn learn_stores_unicast_mapping() {
let b = BridgeDevice::new("br0");
let m = mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55);
b.learn(m, 2, Instant::from_secs(0));
assert_eq!(b.lookup(m), Some(2),
"Learned MAC must be found on port 2");
}
#[test]
fn learn_ignores_multicast() {
let b = BridgeDevice::new("br0");
let multicast = EthernetAddress::BROADCAST;
b.learn(multicast, 1, Instant::from_secs(0));
assert_eq!(b.lookup(multicast), None,
"Multicast MACs must not be learned");
}
#[test]
fn learn_replaces_existing_entry_on_new_port() {
let b = BridgeDevice::new("br0");
let m = mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55);
b.learn(m, 1, Instant::from_secs(0));
assert_eq!(b.lookup(m), Some(1));
b.learn(m, 2, Instant::from_secs(10));
assert_eq!(b.lookup(m), Some(2),
"MAC move from port 1 to port 2 must update FDB");
}
#[test]
fn age_entries_removes_expired_macs() {
let b = BridgeDevice::new("br0");
let m1 = mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55);
let m2 = mac(0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE);
b.learn(m1, 1, Instant::from_secs(0));
b.learn(m2, 2, Instant::from_secs(MAC_AGE_TIMEOUT.secs() as i64)); // Age boundary
// Age just past the 300s timeout
b.age_entries(Instant::from_secs(MAC_AGE_TIMEOUT.secs() as i64 + 1));
assert_eq!(b.lookup(m1), None, "m1 (learned at 0) must be aged out");
assert_eq!(b.lookup(m2), Some(2),
"m2 (learned at 300s) must still be valid");
}
#[test]
fn lookup_returns_none_for_unknown_mac() {
let b = BridgeDevice::new("br0");
assert_eq!(b.lookup(mac(0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01)), None);
}
}
+649 -47
View File
@@ -4,14 +4,19 @@ use std::fs::File;
use std::io::{ErrorKind, Read, Write};
use std::rc::Rc;
use smoltcp::phy::ChecksumCapabilities;
use smoltcp::storage::PacketMetadata;
use smoltcp::time::{Duration, Instant};
use smoltcp::wire::{
ArpOperation, ArpPacket, ArpRepr, EthernetAddress, EthernetFrame, EthernetProtocol,
EthernetRepr, IpAddress, IpCidr, Ipv4Address, Ipv4Cidr,
EthernetRepr, Icmpv6Packet, Icmpv6Repr, IpAddress, IpCidr, IpProtocol, Ipv4Address,
Ipv6Address, Ipv6Cidr, Ipv6Packet, NdiscNeighborFlags, NdiscPrefixInfoFlags, NdiscRepr,
RawHardwareAddress,
};
use super::LinkDevice;
use crate::link::qdisc::QdiscConfig;
use super::{LinkDevice, Stats};
use crate::slaac;
struct Neighbor {
hardware_address: EthernetAddress,
@@ -29,20 +34,66 @@ enum ArpState {
},
}
#[derive(Debug, Default)]
enum NdpState {
#[default]
Discovered,
Discovering {
target: Ipv6Address,
tries: u32,
silent_until: Instant,
},
}
type PacketBuffer = smoltcp::storage::PacketBuffer<'static, IpAddress>;
const EMPTY_MAC: EthernetAddress = EthernetAddress([0; 6]);
const NDP_MAX_TRIES: u32 = 3;
#[derive(Debug, Default)]
enum SlaacState {
#[default]
Idle,
LinkLocalOnly,
RsSent {
silent_until: Instant,
},
Configured,
}
fn solicited_node_multicast(target: &Ipv6Address) -> Ipv6Address {
let o = target.octets();
Ipv6Address::new(
0xff02, 0, 0, 0, 0, 1, 0xff00 | (o[13] as u16), ((o[14] as u16) << 8) | (o[15] as u16),
)
}
fn multicast_mac(addr: &Ipv6Address) -> EthernetAddress {
let o = addr.octets();
EthernetAddress([0x33, 0x33, o[12], o[13], o[14], o[15]])
}
pub struct EthernetLink {
name: Rc<str>,
neighbor_cache: BTreeMap<IpAddress, Neighbor>,
arp_state: ArpState,
ndp_state: NdpState,
slaac_state: SlaacState,
waiting_packets: PacketBuffer,
input_buffer: Vec<u8>,
output_buffer: Vec<u8>,
network_file: File,
hardware_address: Option<EthernetAddress>,
ip_address: Option<Ipv4Cidr>,
ip_address: Option<IpCidr>,
qdisc_config: QdiscConfig,
mtu: usize,
enabled: bool,
promiscuous: bool,
stats: Stats,
arp_requests: u64,
arp_replies: u64,
arp_cache_hits: u64,
arp_cache_misses: u64,
}
impl EthernetLink {
@@ -53,6 +104,26 @@ impl EthernetLink {
const NEIGHBOR_LIVE_TIME: Duration = Duration::from_secs(60);
const ARP_SILENCE_TIME: Duration = Duration::from_secs(1);
const ARP_CACHE_MAX: usize = 1024;
fn insert_neighbor(&mut self, ip: IpAddress, hw: EthernetAddress, now: Instant) {
if self.neighbor_cache.len() >= Self::ARP_CACHE_MAX
&& !self.neighbor_cache.contains_key(&ip)
{
// Remove the entry with the earliest expiration time (LRU-style).
if let Some((&oldest_ip, _)) = self.neighbor_cache
.iter()
.min_by_key(|(_, n)| n.expires_at)
{
self.neighbor_cache.remove(&oldest_ip);
}
}
self.neighbor_cache.insert(ip, Neighbor {
hardware_address: hw,
expires_at: now + Self::NEIGHBOR_LIVE_TIME,
});
}
const NDP_SILENCE_TIME: Duration = Duration::from_secs(1);
pub fn new(name: &str, network_file: File) -> Self {
let waiting_packets = PacketBuffer::new(
@@ -69,7 +140,18 @@ impl EthernetLink {
input_buffer: vec![0u8; Self::MTU],
output_buffer: Vec::with_capacity(Self::MTU),
arp_state: Default::default(),
ndp_state: Default::default(),
slaac_state: Default::default(),
qdisc_config: QdiscConfig::default(),
mtu: Self::MTU,
enabled: true,
promiscuous: false,
stats: Stats::default(),
neighbor_cache: Default::default(),
arp_requests: 0,
arp_replies: 0,
arp_cache_hits: 0,
arp_cache_misses: 0,
}
}
@@ -95,6 +177,7 @@ impl EthernetLink {
f(frame.payload_mut());
if let Err(_) = self.network_file.write_all(&self.output_buffer) {
self.stats.tx_errors += 1;
error!(
"Dropped outboud packet on {} (failed to write to network file)",
self.name
@@ -107,13 +190,14 @@ impl EthernetLink {
return;
};
let Some(ip_addr) = self.ip_address else {
let Some(IpCidr::Ipv4(ip_addr)) = self.ip_address else {
return;
};
let Ok(repr) = ArpPacket::new_checked(packet).and_then(|packet| ArpRepr::parse(&packet))
else {
debug!("Dropped incomming arp packet on {} (Malformed)", self.name);
self.stats.rx_errors += 1;
return;
};
@@ -149,13 +233,12 @@ impl EthernetLink {
return;
}
self.neighbor_cache.insert(
self.insert_neighbor(
IpAddress::Ipv4(source_protocol_addr),
Neighbor {
hardware_address: source_hardware_addr,
expires_at: now + Self::NEIGHBOR_LIVE_TIME,
},
source_hardware_addr,
now,
);
self.arp_replies += 1;
if let ArpOperation::Request = operation {
let response = ArpRepr::EthernetIpv4 {
@@ -194,6 +277,10 @@ impl EthernetLink {
self.send_arp(now);
break;
}
Ok((IpAddress::Ipv6(_), _)) => {
let _ = waiting_packets.dequeue();
continue;
}
Err(_) => {
self.arp_state = ArpState::Discovered;
break;
@@ -204,7 +291,7 @@ impl EthernetLink {
self.send_to(
mac,
packet.len(),
|buf| buf.copy_from_slice(packet),
|buf| buf.copy_from_slice(&packet),
EthernetProtocol::Ipv4,
);
}
@@ -227,6 +314,10 @@ impl EthernetLink {
return;
}
Ok((IpAddress::Ipv6(_), _)) => {
let _ = self.waiting_packets.dequeue();
continue;
}
Err(_) => {
self.arp_state = ArpState::Discovered;
return;
@@ -234,6 +325,7 @@ impl EthernetLink {
}
let _ = self.waiting_packets.dequeue();
self.stats.rx_dropped += 1;
debug!(
"Dropped packet on {} because neighbor was not found",
self.name
@@ -241,8 +333,76 @@ impl EthernetLink {
}
}
fn check_waiting_packets_v6(&mut self, ip: Ipv6Address, mac: EthernetAddress) {
let mut waiting_packets =
std::mem::replace(&mut self.waiting_packets, PacketBuffer::new(vec![], vec![]));
loop {
match waiting_packets.peek() {
Ok((IpAddress::Ipv6(dst), _)) if *dst == ip => {}
Ok((IpAddress::Ipv6(dst), _)) => {
self.ndp_state = NdpState::Discovering {
target: *dst,
tries: 0,
silent_until: Instant::ZERO,
};
self.send_ndp_solicit(Instant::ZERO);
break;
}
Ok((IpAddress::Ipv4(_), _)) => {
let _ = waiting_packets.dequeue();
continue;
}
Err(_) => {
self.ndp_state = NdpState::Discovered;
break;
}
}
let (_, packet) = waiting_packets.dequeue().unwrap();
self.send_to(
mac,
packet.len(),
|buf| buf.copy_from_slice(&packet),
EthernetProtocol::Ipv6,
);
}
self.waiting_packets = waiting_packets;
}
fn drop_waiting_packets_v6(&mut self, ip: Ipv6Address) {
loop {
match self.waiting_packets.peek() {
Ok((IpAddress::Ipv6(dst), _)) if *dst == ip => {}
Ok((IpAddress::Ipv6(dst), _)) => {
self.ndp_state = NdpState::Discovering {
target: *dst,
tries: 0,
silent_until: Instant::ZERO,
};
self.send_ndp_solicit(Instant::ZERO);
return;
}
Ok((IpAddress::Ipv4(_), _)) => {
let _ = self.waiting_packets.dequeue();
self.stats.rx_dropped += 1;
continue;
}
Err(_) => {
self.ndp_state = NdpState::Discovered;
return;
}
}
let _ = self.waiting_packets.dequeue();
self.stats.rx_dropped += 1;
debug!("Dropped packet on {} because IPv6 neighbor was not found", self.name);
}
}
fn handle_missing_neighbor(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant) {
let Ok(buf) = self.waiting_packets.enqueue(packet.len(), next_hop) else {
self.stats.rx_dropped += 1;
warn!(
"Dropped packet on {} because waiting queue was full",
self.name
@@ -251,24 +411,37 @@ impl EthernetLink {
};
buf.copy_from_slice(packet);
let IpAddress::Ipv4(next_hop) = next_hop;
if let ArpState::Discovered = self.arp_state {
self.arp_state = ArpState::Discovering {
target: next_hop,
tries: 0,
silent_until: Instant::ZERO,
};
self.send_arp(now)
match next_hop {
IpAddress::Ipv4(v4) => {
if let ArpState::Discovered = self.arp_state {
self.arp_state = ArpState::Discovering {
target: v4,
tries: 0,
silent_until: Instant::ZERO,
};
self.send_arp(now)
}
}
IpAddress::Ipv6(v6) => {
if let NdpState::Discovered = self.ndp_state {
self.ndp_state = NdpState::Discovering {
target: v6,
tries: 0,
silent_until: Instant::ZERO,
};
self.send_ndp_solicit(now)
}
}
}
}
fn send_arp(&mut self, now: Instant) {
self.arp_requests += 1;
let Some(hardware_address) = self.hardware_address else {
return;
};
let Some(ip_address) = self.ip_address else {
let Some(IpCidr::Ipv4(ip_address)) = self.ip_address else {
return;
};
@@ -303,38 +476,321 @@ impl EthernetLink {
}
}
}
fn send_ndp_solicit(&mut self, now: Instant) {
let Some(hardware_address) = self.hardware_address else {
return;
};
let Some(IpCidr::Ipv6(_)) = self.ip_address else {
return;
};
// Extract target. Do NOT destructure tries/silent_until by value
// — the recursive call into drop_waiting_packets_v6 can modify
// self.ndp_state and a later write-back would clobber that
// update. We read tries/silent_until from the live state right
// before we write it back.
let target = match self.ndp_state {
NdpState::Discovered => return,
NdpState::Discovering { target, .. } => target,
};
{
let (tries, silent_until) = match self.ndp_state {
NdpState::Discovering { tries, silent_until, .. } => (tries, silent_until),
NdpState::Discovered => return,
};
if silent_until > now {
return;
}
if tries >= NDP_MAX_TRIES {
self.drop_waiting_packets_v6(target);
self.ndp_state = NdpState::Discovered;
return;
}
}
let snmc = solicited_node_multicast(&target);
let dst_mac = multicast_mac(&snmc);
let ndisc = NdiscRepr::NeighborSolicit {
target_addr: target,
lladdr: Some(RawHardwareAddress::from(hardware_address)),
};
let icmp = Icmpv6Repr::Ndisc(ndisc);
let mut buf = [0u8; 128];
let mut icmp_pkt = Icmpv6Packet::new_unchecked(&mut buf);
let src_addr = Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 0);
let dst_addr = snmc;
icmp.emit(&src_addr, &dst_addr, &mut icmp_pkt, &ChecksumCapabilities::ignored());
let icmp_len = icmp.buffer_len();
// Read the current state (which may have been updated by a
// recursive call into drop_waiting_packets_v6) and update
// incrementally. This avoids overwriting a recursive update.
let (mut tries, mut silent_until) = match self.ndp_state {
NdpState::Discovering { tries, silent_until, .. } => (tries, silent_until),
NdpState::Discovered => return,
};
tries += 1;
silent_until = now + Self::NDP_SILENCE_TIME;
self.ndp_state = NdpState::Discovering {
target,
tries,
silent_until,
};
self.send_to(
dst_mac,
icmp_len,
|out| out[..icmp_len].copy_from_slice(&buf[..icmp_len]),
EthernetProtocol::Ipv6,
);
}
fn send_router_solicitation(&mut self, now: Instant) {
let Some(hardware_address) = self.hardware_address else {
return;
};
let Some(IpCidr::Ipv6(our_addr)) = self.ip_address else {
return;
};
let addr_octets = our_addr.address().octets();
if addr_octets[0] != 0xfe || addr_octets[1] & 0xc0 != 0x80 {
return;
}
if let SlaacState::RsSent { silent_until } = self.slaac_state {
if silent_until > now {
return;
}
}
let dst_mac = EthernetAddress([0x33, 0x33, 0, 0, 0, 2]);
let ndisc = NdiscRepr::RouterSolicit {
lladdr: Some(RawHardwareAddress::from(hardware_address)),
};
let icmp = Icmpv6Repr::Ndisc(ndisc);
let mut buf = [0u8; 128];
let mut icmp_pkt = Icmpv6Packet::new_unchecked(&mut buf);
icmp.emit(
&our_addr.address(),
&slaac::ALL_ROUTERS_MULTICAST,
&mut icmp_pkt,
&ChecksumCapabilities::ignored(),
);
let icmp_len = icmp.buffer_len();
self.slaac_state = SlaacState::RsSent {
silent_until: now + Self::NDP_SILENCE_TIME,
};
self.send_to(
dst_mac,
icmp_len,
|out| out[..icmp_len].copy_from_slice(&buf[..icmp_len]),
EthernetProtocol::Ipv6,
);
}
fn process_ndp(&mut self, packet: &[u8], now: Instant) {
let Some(hardware_address) = self.hardware_address else {
return;
};
let Some(IpCidr::Ipv6(our_addr)) = self.ip_address else {
return;
};
let Ok(ipv6_pkt) = Ipv6Packet::new_checked(packet) else {
return;
};
if ipv6_pkt.next_header() != IpProtocol::Icmpv6 {
return;
}
let Ok(icmp_pkt) = Icmpv6Packet::new_checked(ipv6_pkt.payload()) else {
return;
};
let Ok(icmp_repr) = Icmpv6Repr::parse(
&our_addr.address(),
&ipv6_pkt.src_addr(),
&icmp_pkt,
&ChecksumCapabilities::ignored(),
) else {
return;
};
let Icmpv6Repr::Ndisc(ndisc) = icmp_repr else {
return;
};
match ndisc {
NdiscRepr::NeighborSolicit { target_addr, lladdr } => {
if target_addr != our_addr.address() {
return;
}
let src_addr = ipv6_pkt.src_addr();
let Some(src_mac) = lladdr.map(|a| EthernetAddress::from_bytes(a.as_bytes()))
else {
return;
};
self.neighbor_cache.insert(
IpAddress::Ipv6(src_addr),
Neighbor {
hardware_address: src_mac,
expires_at: now + Self::NEIGHBOR_LIVE_TIME,
},
);
let response = NdiscRepr::NeighborAdvert {
flags: NdiscNeighborFlags::SOLICITED | NdiscNeighborFlags::OVERRIDE,
target_addr: our_addr.address(),
lladdr: Some(RawHardwareAddress::from(hardware_address)),
};
let icmp_resp = Icmpv6Repr::Ndisc(response);
let mut buf = [0u8; 128];
let mut icmp_pkt = Icmpv6Packet::new_unchecked(&mut buf);
icmp_resp.emit(
&our_addr.address(),
&src_addr,
&mut icmp_pkt,
&ChecksumCapabilities::ignored(),
);
let resp_len = icmp_resp.buffer_len();
self.send_to(
src_mac,
resp_len,
|out| out[..resp_len].copy_from_slice(&buf[..resp_len]),
EthernetProtocol::Ipv6,
);
}
NdiscRepr::NeighborAdvert { target_addr, lladdr, .. } => {
let Some(mac) = lladdr.map(|a| EthernetAddress::from_bytes(a.as_bytes())) else {
return;
};
self.neighbor_cache.insert(
IpAddress::Ipv6(target_addr),
Neighbor {
hardware_address: mac,
expires_at: now + Self::NEIGHBOR_LIVE_TIME,
},
);
self.check_waiting_packets_v6(target_addr, mac);
}
NdiscRepr::RouterSolicit { lladdr } => {
if let Some(ll) = lladdr {
self.neighbor_cache.insert(
IpAddress::Ipv6(ipv6_pkt.src_addr()),
Neighbor {
hardware_address: EthernetAddress::from_bytes(ll.as_bytes()),
expires_at: now + Self::NEIGHBOR_LIVE_TIME,
},
);
}
}
NdiscRepr::RouterAdvert {
hop_limit,
router_lifetime,
reachable_time,
retrans_time,
lladdr,
mtu,
prefix_info,
..
} => {
self.slaac_state = SlaacState::Configured;
if let Some(ll) = lladdr {
self.neighbor_cache.insert(
IpAddress::Ipv6(ipv6_pkt.src_addr()),
Neighbor {
hardware_address: EthernetAddress::from_bytes(ll.as_bytes()),
expires_at: now + Self::NEIGHBOR_LIVE_TIME,
},
);
}
if let Some(pi) = prefix_info {
if pi.flags.contains(NdiscPrefixInfoFlags::ADDRCONF) {
let valid_lft = pi.valid_lifetime;
if valid_lft > Duration::ZERO {
let slaac_addr = slaac::form_slaac_addr(
Ipv6Cidr::new(pi.prefix, pi.prefix_len),
hardware_address,
);
self.ip_address = Some(IpCidr::Ipv6(Ipv6Cidr::new(
slaac_addr,
pi.prefix_len,
)));
info!(
"SLAAC: configured {} on {} (via RA from {})",
slaac_addr,
self.name,
ipv6_pkt.src_addr()
);
}
}
}
}
_ => {}
}
}
}
impl LinkDevice for EthernetLink {
fn send(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant) {
let local_broadcast = match self.ip_address.and_then(|cidr| cidr.broadcast()) {
Some(addr) => IpAddress::Ipv4(addr) == next_hop,
None => false,
let eth_proto = if !packet.is_empty() && packet[0] >> 4 == 6 {
EthernetProtocol::Ipv6
} else {
EthernetProtocol::Ipv4
};
let owned = self.qdisc_config.enqueue_and_dequeue(packet.to_vec(), now);
let Some(packet) = owned else {
self.stats.tx_dropped += 1;
return;
};
self.stats.tx_packets += 1;
self.stats.tx_bytes += packet.len() as u64;
let local_broadcast = match self.ip_address {
Some(IpCidr::Ipv4(cidr)) => cidr
.broadcast()
.map(|addr| IpAddress::Ipv4(addr) == next_hop)
.unwrap_or(false),
_ => false,
};
if local_broadcast || next_hop.is_broadcast() {
self.send_to(
EthernetAddress::BROADCAST,
packet.len(),
|buf| buf.copy_from_slice(packet),
EthernetProtocol::Ipv4,
|buf| buf.copy_from_slice(&packet),
eth_proto,
);
return;
}
match self.neighbor_cache.entry(next_hop) {
Entry::Vacant(_) => self.handle_missing_neighbor(next_hop, packet, now),
Entry::Vacant(_) => {
self.arp_cache_misses += 1;
self.handle_missing_neighbor(next_hop, &packet, now)
}
Entry::Occupied(e) => {
if e.get().expires_at < now {
e.remove();
self.handle_missing_neighbor(next_hop, packet, now)
self.arp_cache_misses += 1;
self.handle_missing_neighbor(next_hop, &packet, now)
} else {
self.arp_cache_hits += 1;
let mac = e.get().hardware_address;
self.send_to(
mac,
packet.len(),
|buf| buf.copy_from_slice(packet),
EthernetProtocol::Ipv4,
|buf| buf.copy_from_slice(&packet),
eth_proto,
)
}
}
@@ -352,33 +808,59 @@ impl LinkDevice for EthernetLink {
if e.kind() != ErrorKind::WouldBlock {
error!("Failed to read ethernet device on link {}", self.name);
} else {
// No packet to read but we check if we have arp to send
// No packet to read but we check if we have neighbor discovery to send
self.send_arp(now);
self.send_ndp_solicit(now);
self.send_router_solicitation(now);
}
self.input_buffer = input_buffer;
return None;
}
let packet = EthernetFrame::new_unchecked(&input_buffer[..]);
let Ok(repr) = EthernetRepr::parse(&packet) else {
debug!("Dropped incomming frame on {} (Malformed)", self.name);
continue;
let (is_for_us, ethertype) = {
let packet = EthernetFrame::new_unchecked(&input_buffer[..]);
let Ok(repr) = EthernetRepr::parse(&packet) else {
debug!("Dropped incomming frame on {} (Malformed)", self.name);
self.stats.rx_errors += 1;
continue;
};
let is_for_us = repr.dst_addr.is_broadcast()
|| repr.dst_addr == EMPTY_MAC
|| repr.dst_addr == hardware_address;
(is_for_us, repr.ethertype)
};
// We let EMPTY_MAC pass because somehow this is the mac used when net=redir is used
if !repr.dst_addr.is_broadcast()
&& repr.dst_addr != EMPTY_MAC
&& repr.dst_addr != hardware_address
{
// Drop packets which are not for us
if !is_for_us && !self.promiscuous {
continue;
}
match repr.ethertype {
EthernetProtocol::Ipv4 => {
match ethertype {
EthernetProtocol::Ipv4 | EthernetProtocol::Ipv6 => {
let payload_start = EthernetFrame::<&[u8]>::header_len();
let payload_len = input_buffer.len().saturating_sub(payload_start);
let next_header_byte = if payload_len >= 8 {
input_buffer[payload_start + 6]
} else {
0
};
let is_ndp = ethertype == EthernetProtocol::Ipv6
&& payload_len >= 8
&& next_header_byte == u8::from(IpProtocol::Icmpv6);
if is_ndp {
let ndp_packet = input_buffer[payload_start..].to_vec();
self.process_ndp(&ndp_packet, now);
continue;
}
self.input_buffer = input_buffer;
return Some(EthernetFrame::new_unchecked(&self.input_buffer[..]).payload());
self.stats.rx_packets += 1;
self.stats.rx_bytes += self.input_buffer[payload_start..].len() as u64;
return Some(&self.input_buffer[payload_start..]);
}
EthernetProtocol::Arp => {
let packet = EthernetFrame::new_unchecked(&input_buffer[..]);
self.process_arp(packet.payload(), now);
}
EthernetProtocol::Arp => self.process_arp(packet.payload(), now),
_ => continue,
}
}
@@ -398,15 +880,135 @@ impl LinkDevice for EthernetLink {
}
fn set_mac_address(&mut self, addr: EthernetAddress) {
self.hardware_address = Some(addr)
self.hardware_address = Some(addr);
if self.ip_address.is_none() {
let link_local = slaac::form_link_local(addr);
self.ip_address = Some(IpCidr::Ipv6(link_local));
info!("SLAAC: configured link-local {} on {}", link_local.address(), self.name);
}
}
fn ip_address(&self) -> Option<IpCidr> {
Some(IpCidr::Ipv4(self.ip_address?))
self.ip_address
}
fn set_ip_address(&mut self, addr: IpCidr) {
let IpCidr::Ipv4(addr) = addr;
self.ip_address = Some(addr);
}
fn arp_table(&self) -> String {
let mut out = String::new();
for (ip, neighbor) in &self.neighbor_cache {
out.push_str(&format!("{} {}\n", ip, neighbor.hardware_address));
}
out
}
fn flush_arp(&mut self) {
self.neighbor_cache.clear();
}
fn arp_stats(&self) -> String {
format!(
"requests={} replies={} hits={} misses={} entries={}\n",
self.arp_requests,
self.arp_replies,
self.arp_cache_hits,
self.arp_cache_misses,
self.neighbor_cache.len(),
)
}
fn statistics(&self) -> Stats {
self.stats
}
fn link_state(&self) -> &'static str {
if !self.enabled {
"down"
} else if self.hardware_address.is_some() {
"up"
} else {
"down"
}
}
fn qdisc_info(&self) -> String {
match &self.qdisc_config {
QdiscConfig::None => "none\n".to_string(),
QdiscConfig::TokenBucket(tb) => {
format!("token_bucket rate={} burst={} tokens={}\n", tb.rate(), tb.burst(), tb.tokens())
}
QdiscConfig::PriorityQueue(pq) => {
format!("priority_queue len={} max={}\n", pq.len(), pq.max_len())
}
}
}
fn set_qdisc(&mut self, kind: &str) -> Result<(), String> {
match kind.trim() {
"none" => {
self.qdisc_config = QdiscConfig::None;
Ok(())
}
"token_bucket" | "tbf" => {
self.qdisc_config = QdiscConfig::TokenBucket(
crate::link::qdisc::TokenBucket::new(1_000_000, 1500)
);
Ok(())
}
"priority_queue" | "pfifo_fast" => {
self.qdisc_config = QdiscConfig::PriorityQueue(
crate::link::qdisc::PriorityQueue::new(1000)
);
Ok(())
}
_ => Err(format!("unknown qdisc type: {}\n", kind)),
}
}
fn mtu(&self) -> usize {
self.mtu
}
fn set_mtu(&mut self, mtu: usize) {
self.mtu = mtu.clamp(576, 9000);
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn add_static_neighbor(&mut self, ip: IpAddress, mac: [u8; 6]) -> Result<(), String> {
let hw_addr = EthernetAddress(mac);
if !hw_addr.is_unicast() {
return Err(format!("{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} is not a unicast MAC\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]));
}
self.neighbor_cache.insert(
ip,
Neighbor {
hardware_address: hw_addr,
expires_at: Instant::from_millis(i64::MAX / 2),
},
);
Ok(())
}
fn remove_neighbor(&mut self, ip: IpAddress) -> bool {
self.neighbor_cache.remove(&ip).is_some()
}
fn is_promiscuous(&self) -> bool {
self.promiscuous
}
fn set_promiscuous(&mut self, enabled: bool) {
self.promiscuous = enabled;
}
}
+198
View File
@@ -0,0 +1,198 @@
//! GRE Tunnel (Generic Routing Encapsulation) — mirrors Linux 7.1's
//! `net/ipv4/ip_gre.c`.
//!
//! Reference files:
//! - `net/ipv4/ip_gre.c:601` — `ipgre_tunnel_xmit()` — GRE header prepend
//! - `net/ipv4/ip_gre.c:430` — `ipgre_rcv()` — GRE decapsulation
//! - `include/uapi/linux/if_tunnel.h` — GRE header format
//! - `include/net/gre.h` — GRE protocol constants
//!
//! GRE encapsulates an inner packet in an outer IP header + GRE header
//! (4+ bytes) for tunneling through an IP network. Supports optional
//! GRE key for multiplexing multiple tunnels over the same endpoints.
//!
//! GRE Header (minimum 4 bytes):
//! [Flags 2B][Protocol 2B]
//! Flags: C=checksum, K=key, S=sequence
//! Protocol: 0x0800 = IPv4, 0x86DD = IPv6
//!
//! With GRE key (8 bytes):
//! [Flags 2B][Protocol 2B][Key 4B]
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use smoltcp::time::Instant;
use smoltcp::wire::{
EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv4Packet, Ipv4Repr, IpProtocol,
};
use super::{DeviceList, LinkDevice};
const GRE_PROTO_IPV4: u16 = 0x0800;
const GRE_FLAG_KEY: u16 = 0x2000;
pub struct GreDevice {
name: Rc<str>,
parent_name: Rc<str>,
devices: Rc<RefCell<DeviceList>>,
local_ip: Ipv4Address,
remote_ip: Ipv4Address,
gre_key: Option<u32>,
gre_header: [u8; 8],
gre_header_len: usize,
send_buffer: Vec<u8>,
recv_buffer: Vec<u8>,
recv_queue: VecDeque<Vec<u8>>,
ip_address: Option<IpCidr>,
}
impl GreDevice {
pub fn new(
name: &str,
parent_name: &str,
local_ip: Ipv4Address,
remote_ip: Ipv4Address,
gre_key: Option<u32>,
devices: Rc<RefCell<DeviceList>>,
) -> Self {
let mut header = [0u8; 8];
let header_len = if let Some(key) = gre_key {
header[0] = (GRE_FLAG_KEY >> 8) as u8;
header[1] = (GRE_FLAG_KEY & 0xff) as u8;
header[2] = (GRE_PROTO_IPV4 >> 8) as u8;
header[3] = (GRE_PROTO_IPV4 & 0xff) as u8;
header[4] = (key >> 24) as u8;
header[5] = ((key >> 16) & 0xff) as u8;
header[6] = ((key >> 8) & 0xff) as u8;
header[7] = (key & 0xff) as u8;
8
} else {
header[2] = (GRE_PROTO_IPV4 >> 8) as u8;
header[3] = (GRE_PROTO_IPV4 & 0xff) as u8;
4
};
Self {
name: name.into(),
parent_name: parent_name.into(),
devices,
local_ip,
remote_ip,
gre_key,
gre_header: header,
gre_header_len: header_len,
send_buffer: Vec::with_capacity(1500),
recv_buffer: Vec::with_capacity(1500),
recv_queue: VecDeque::new(),
ip_address: None,
}
}
pub fn push_received(&mut self, packet: Vec<u8>) {
self.recv_queue.push_back(packet);
}
fn build_encapsulated(&mut self, inner_packet: &[u8]) -> &[u8] {
self.send_buffer.clear();
let outer_header = Ipv4Repr {
src_addr: self.local_ip,
dst_addr: self.remote_ip,
next_header: IpProtocol::from(47u8),
payload_len: self.gre_header_len + inner_packet.len(),
hop_limit: 64,
};
let ip_hdr_len = outer_header.buffer_len();
let total_len = ip_hdr_len + self.gre_header_len + inner_packet.len();
self.send_buffer.resize(total_len, 0);
let mut ip_pkt = Ipv4Packet::new_unchecked(&mut self.send_buffer);
outer_header.emit(&mut ip_pkt, &smoltcp::phy::ChecksumCapabilities::ignored());
let gre_start = ip_hdr_len;
self.send_buffer[gre_start..gre_start + self.gre_header_len]
.copy_from_slice(&self.gre_header[..self.gre_header_len]);
self.send_buffer[gre_start + self.gre_header_len..]
.copy_from_slice(inner_packet);
&self.send_buffer
}
fn matches_endpoint(&self, outer_packet: &[u8]) -> bool {
if outer_packet.len() < 24 {
return false;
}
let Ok(ipv4) = Ipv4Packet::new_checked(outer_packet) else {
return false;
};
if u8::from(ipv4.next_header()) != 47 || ipv4.dst_addr() != self.local_ip {
return false;
}
let ip_header_len = 20;
let gre = &outer_packet[ip_header_len..];
if gre.len() < 4 {
return false;
}
let flags = u16::from_be_bytes([gre[0], gre[1]]);
let proto = u16::from_be_bytes([gre[2], gre[3]]);
if proto != GRE_PROTO_IPV4 {
return false;
}
if let Some(expected_key) = self.gre_key {
if flags & GRE_FLAG_KEY == 0 || gre.len() < 8 {
return false;
}
let actual_key = u32::from_be_bytes([gre[4], gre[5], gre[6], gre[7]]);
if actual_key != expected_key {
return false;
}
}
true
}
}
impl LinkDevice for GreDevice {
fn send(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant) {
let encapsulated = self.build_encapsulated(packet).to_vec();
if let Some(parent) = self.devices.borrow_mut().get_mut(&self.parent_name) {
parent.send(next_hop, &encapsulated, now);
} else {
log::debug!("gre: parent {} not found, dropping {} byte frame",
self.parent_name, packet.len());
}
}
fn recv(&mut self, _now: Instant) -> Option<&[u8]> {
let packet = self.recv_queue.pop_front()?;
if !self.matches_endpoint(&packet) {
return None;
}
let ip_header_len = 20;
let gre_header_len = if self.gre_key.is_some() { 8 } else { 4 };
let payload_start = ip_header_len + gre_header_len;
let payload = &packet[payload_start..];
self.recv_buffer.clear();
self.recv_buffer.extend_from_slice(payload);
Some(&self.recv_buffer)
}
fn name(&self) -> &Rc<str> {
&self.name
}
fn can_recv(&self) -> bool {
!self.recv_queue.is_empty()
}
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);
}
}
+132
View File
@@ -0,0 +1,132 @@
//! IPIP Tunnel (IP-in-IP) — mirrors Linux 7.1's `net/ipv4/ipip.c`.
//!
//! Reference files:
//! - `net/ipv4/ipip.c:184` — `ipip_tunnel_xmit()` — build outer header, transmit
//! - `net/ipv4/ip_tunnel.c:326` — `ip_tunnel_rcv()` — receive and decapsulate
//! - `include/uapi/linux/in.h:30` — `IPPROTO_IPIP = 4`
//!
//! The IPIP tunnel wraps an inner IP packet in an outer IP header, routing
//! it through a parent link-layer device to a remote endpoint. On reception,
//! the outer header is stripped and the inner packet is delivered to the
//! network stack.
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use smoltcp::time::Instant;
use smoltcp::wire::{
EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv4Packet, Ipv4Repr, IpProtocol,
};
use super::{DeviceList, LinkDevice};
const IPIP_PROTO: u8 = 4;
pub struct IpipDevice {
name: Rc<str>,
parent_name: Rc<str>,
devices: Rc<RefCell<DeviceList>>,
local_ip: Ipv4Address,
remote_ip: Ipv4Address,
send_buffer: Vec<u8>,
recv_buffer: Vec<u8>,
recv_queue: VecDeque<Vec<u8>>,
ip_address: Option<IpCidr>,
}
impl IpipDevice {
pub fn new(name: &str, parent_name: &str, local_ip: Ipv4Address, remote_ip: Ipv4Address, devices: Rc<RefCell<DeviceList>>) -> Self {
Self {
name: name.into(),
parent_name: parent_name.into(),
devices,
local_ip,
remote_ip,
send_buffer: Vec::with_capacity(1500),
recv_buffer: Vec::with_capacity(1500),
recv_queue: VecDeque::new(),
ip_address: None,
}
}
pub fn push_received(&mut self, packet: Vec<u8>) {
self.recv_queue.push_back(packet);
}
fn build_outer_header(&mut self, inner_packet: &[u8]) -> &[u8] {
self.send_buffer.clear();
let outer_header = Ipv4Repr {
src_addr: self.local_ip,
dst_addr: self.remote_ip,
next_header: IpProtocol::from(IPIP_PROTO),
payload_len: inner_packet.len(),
hop_limit: 64,
};
let header_len = outer_header.buffer_len();
self.send_buffer.resize(header_len + inner_packet.len(), 0);
let mut ip_pkt = Ipv4Packet::new_unchecked(&mut self.send_buffer);
outer_header.emit(&mut ip_pkt, &smoltcp::phy::ChecksumCapabilities::ignored());
self.send_buffer[header_len..].copy_from_slice(inner_packet);
&self.send_buffer
}
fn matches_endpoint(&self, outer_packet: &[u8]) -> bool {
if outer_packet.len() < 20 {
return false;
}
let Ok(ipv4) = Ipv4Packet::new_checked(outer_packet) else {
return false;
};
u8::from(ipv4.next_header()) == IPIP_PROTO && ipv4.dst_addr() == self.local_ip
}
}
impl LinkDevice for IpipDevice {
fn send(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant) {
let encapsulated = self.build_outer_header(packet).to_vec();
if let Some(parent) = self.devices.borrow_mut().get_mut(&self.parent_name) {
parent.send(next_hop, &encapsulated, now);
} else {
log::debug!("ipip: parent {} not found, dropping {} byte frame",
self.parent_name, packet.len());
}
}
fn recv(&mut self, _now: Instant) -> Option<&[u8]> {
let packet = self.recv_queue.pop_front()?;
if !self.matches_endpoint(&packet) {
return None;
}
let Ok(ipv4) = Ipv4Packet::new_checked(&packet) else {
return None;
};
let header_len = 20;
let payload = &packet[header_len..];
self.recv_buffer.clear();
self.recv_buffer.extend_from_slice(payload);
Some(&self.recv_buffer)
}
fn name(&self) -> &Rc<str> {
&self.name
}
fn can_recv(&self) -> bool {
!self.recv_queue.is_empty()
}
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);
}
}
+29 -2
View File
@@ -12,6 +12,8 @@ pub type PacketBuffer = smoltcp::storage::PacketBuffer<'static, ()>;
pub struct LoopbackDevice {
name: Rc<str>,
buffer: PacketBuffer,
enabled: bool,
promiscuous: bool,
}
impl Default for LoopbackDevice {
@@ -23,6 +25,8 @@ impl Default for LoopbackDevice {
LoopbackDevice {
name: "loopback".into(),
buffer,
enabled: true,
promiscuous: false,
}
}
}
@@ -57,7 +61,30 @@ impl LinkDevice for LoopbackDevice {
Some("127.0.0.1/8".parse().unwrap())
}
fn set_ip_address(&mut self, _addr: smoltcp::wire::IpCidr) {
todo!()
fn set_ip_address(&mut self, _addr: smoltcp::wire::IpCidr) {}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn is_promiscuous(&self) -> bool {
self.promiscuous
}
fn set_promiscuous(&mut self, enabled: bool) {
self.promiscuous = enabled;
}
fn mtu(&self) -> usize {
1500
}
fn set_mtu(&mut self, _mtu: usize) {
// Loopback's packet buffer is fixed at 1500 bytes at construction;
// reallocation is not supported. The new value is ignored.
}
}
+73
View File
@@ -1,5 +1,14 @@
pub mod bond;
pub mod bridge;
pub mod ethernet;
pub mod gre;
pub mod ipip;
pub mod loopback;
pub mod qdisc;
pub mod stp;
pub mod tun;
pub mod vlan;
pub mod vxlan;
use std::rc::Rc;
@@ -29,6 +38,70 @@ pub trait LinkDevice {
fn ip_address(&self) -> Option<IpCidr>;
fn set_ip_address(&mut self, addr: IpCidr);
fn arp_table(&self) -> String {
String::new()
}
fn flush_arp(&mut self) {}
fn arp_stats(&self) -> String {
String::new()
}
fn qdisc_info(&self) -> String {
"none\n".to_string()
}
fn set_qdisc(&mut self, _kind: &str) -> Result<(), String> {
Err("qdisc configuration not supported on this device\n".to_string())
}
fn add_static_neighbor(&mut self, _ip: IpAddress, _mac: [u8; 6]) -> Result<(), String> {
Err("static ARP entry not supported on this device\n".to_string())
}
fn remove_neighbor(&mut self, _ip: IpAddress) -> bool {
false
}
fn is_promiscuous(&self) -> bool {
false
}
fn set_promiscuous(&mut self, _enabled: bool) {}
fn mtu(&self) -> usize {
1500
}
fn set_mtu(&mut self, _mtu: usize) {}
fn is_enabled(&self) -> bool {
true
}
fn set_enabled(&mut self, _enabled: bool) {}
fn link_state(&self) -> &'static str {
"unknown"
}
fn statistics(&self) -> Stats {
Stats::default()
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Stats {
pub rx_bytes: u64,
pub rx_packets: u64,
pub tx_bytes: u64,
pub tx_packets: u64,
pub rx_errors: u64,
pub tx_errors: u64,
pub rx_dropped: u64,
pub tx_dropped: u64,
}
#[derive(Default)]
+155
View File
@@ -0,0 +1,155 @@
//! Traffic Control (qdisc) — mirrors Linux 7.1's `net/sched/`.
//!
//! Reference files:
//! - `net/sched/sch_tbf.c` — Token Bucket Filter (`tbf_enqueue`, `tbf_dequeue`)
//! - `net/sched/sch_prio.c` — Priority queue (`prio_enqueue`, `prio_dequeue`)
//! - `net/sched/sch_api.c` — Qdisc registration and API
//!
//! Two qdiscs are implemented:
//! - **TokenBucket**: rate-limits outgoing packets using a token bucket.
//! Packets exceeding the rate are dropped. Mirrors `TBF`.
//! - **PriorityQueue**: three FIFO bands prioritized by IP TOS field.
//! Mirrors `pfifo_fast` (the default Linux qdisc).
use std::collections::VecDeque;
use smoltcp::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct TokenBucket {
rate: u64,
burst: u64,
tokens: u64,
last_update: Instant,
}
impl TokenBucket {
pub fn new(rate_bps: u64, burst_bytes: u64) -> Self {
Self { rate: rate_bps, burst: burst_bytes.max(1500), tokens: burst_bytes.max(1500), last_update: Instant::from_millis(0) }
}
pub fn rate(&self) -> u64 { self.rate }
pub fn burst(&self) -> u64 { self.burst }
pub fn tokens(&self) -> u64 { self.tokens }
pub fn set_rate(&mut self, rate_bps: u64) {
self.rate = rate_bps;
}
pub fn set_burst(&mut self, burst_bytes: u64) {
self.burst = burst_bytes.max(1500);
if self.tokens > self.burst {
self.tokens = self.burst;
}
}
pub fn consume(&mut self, bytes: u64, now: Instant) -> bool {
let elapsed = now - self.last_update;
if elapsed > Duration::ZERO {
// Saturating mul: rate (bytes/s) * elapsed (ms) / 8000.
// saturating_mul prevents overflow on long elapsed durations.
let token_add = self.rate
.saturating_mul(elapsed.total_millis() as u64)
/ 8000;
self.tokens = (self.tokens + token_add).min(self.burst);
self.last_update = now;
}
if self.tokens >= bytes {
self.tokens -= bytes;
true
} else {
false
}
}
}
#[derive(Debug, Clone)]
pub struct PriorityQueue {
bands: [VecDeque<Vec<u8>>; 3],
max_len: usize,
}
impl PriorityQueue {
pub fn new(max_len: usize) -> Self { Self { bands: [VecDeque::new(), VecDeque::new(), VecDeque::new()], max_len } }
pub fn max_len(&self) -> usize { self.max_len }
fn classify(packet: &[u8]) -> usize {
if packet.len() < 2 || packet[0] >> 4 != 4 {
return 1;
}
let tos = if packet.len() > 1 { packet[1] } else { 0 };
let precedence = tos >> 5;
if precedence >= 6 {
0
} else if precedence >= 4 {
1
} else {
2
}
}
pub fn enqueue(&mut self, packet: Vec<u8>) -> bool {
let band = Self::classify(&packet);
if self.bands[band].len() >= self.max_len {
return false;
}
self.bands[band].push_back(packet);
true
}
pub fn dequeue(&mut self) -> Option<Vec<u8>> {
for band in 0..3 {
if let Some(packet) = self.bands[band].pop_front() {
return Some(packet);
}
}
None
}
pub fn len(&self) -> usize {
self.bands[0].len() + self.bands[1].len() + self.bands[2].len()
}
pub fn flush(&mut self) -> Vec<Vec<u8>> {
let mut result = Vec::new();
while let Some(pkt) = self.dequeue() {
result.push(pkt);
}
result
}
}
#[derive(Debug, Clone)]
pub enum QdiscConfig {
None,
TokenBucket(TokenBucket),
PriorityQueue(PriorityQueue),
}
impl Default for QdiscConfig {
fn default() -> Self {
Self::None
}
}
impl QdiscConfig {
pub fn enqueue_and_dequeue(
&mut self,
packet: Vec<u8>,
now: Instant,
) -> Option<Vec<u8>> {
match self {
QdiscConfig::None => Some(packet),
QdiscConfig::TokenBucket(tb) => {
if tb.consume(packet.len() as u64, now) {
Some(packet)
} else {
None
}
}
QdiscConfig::PriorityQueue(pq) => {
pq.enqueue(packet);
pq.dequeue()
}
}
}
}
+207
View File
@@ -0,0 +1,207 @@
//! 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
}
pub 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());
// IEEE 802.1D timer fields are in units of 1/256 second.
// 1 second = 256 ticks. So seconds * 256 = ticks.
let to_ticks = |d: Duration| -> u16 {
// total_millis returns i64; convert to u16 ticks (1s = 256 ticks).
// Clamp to u16::MAX to avoid overflow.
((d.total_millis() as u64).wrapping_mul(256).wrapping_div(1000) as u16)
};
buf[29..31].copy_from_slice(&to_ticks(DEFAULT_MAX_AGE).to_be_bytes());
buf[31..33].copy_from_slice(&to_ticks(DEFAULT_HELLO).to_be_bytes());
buf[33..35].copy_from_slice(&to_ticks(DEFAULT_FORWARD_DELAY).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)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_minimal_bpdu() -> Vec<u8> {
// Minimal config BPDU: protocol=0x0000, version=0, type=0, flags=0,
// root_id(8) + cost(4) + bridge_id(8) + port(2) + age(2) + max_age(2) +
// hello(2) + fwd(2) = 35 bytes.
let mut p = vec![0u8; 35];
p[0] = 0x00; p[1] = 0x00; // protocol id
p[2] = 0x00; // version
p[3] = 0x00; // type = config
p
}
#[test]
fn bpdu_minimal_parses() {
let p = make_minimal_bpdu();
let m = BpduMessage::parse(&p);
assert!(m.is_some(), "Valid config BPDU should parse");
}
#[test]
fn bpdu_short_returns_none() {
let p = vec![0u8; 10];
assert!(BpduMessage::parse(&p).is_none());
}
#[test]
fn bpdu_wrong_protocol_returns_none() {
let mut p = make_minimal_bpdu();
p[0] = 0x80;
assert!(BpduMessage::parse(&p).is_none());
}
#[test]
fn build_bpdu_does_not_panic() {
// Regression test: build_bpdu used to panic because
// Duration::total_millis() returns i64 (8 bytes) but the
// destination buffer was only 2 bytes.
let stp = StpState::new(32768, EthernetAddress([0,0,0,0,0,0]), 1);
let buf = stp.build_bpdu();
// Must produce 35 bytes
assert_eq!(buf.len(), 35);
// And must be a valid BPDU
assert_eq!(&buf[0..2], &[0x00, 0x00]);
assert_eq!(buf[2], 0x00); // version
assert_eq!(buf[3], 0x00); // type
}
}
+84
View File
@@ -0,0 +1,84 @@
//! TUN virtual network device — mirrors Linux 7.1's `drivers/net/tun.c`.
//!
//! The TUN device operates at layer 3 (IP), providing a virtual network
//! interface that exchanges raw IP packets with a userspace program via
//! the `scheme:tun` interface. This enables VPN software, custom routing
//! daemons, and container networking.
//!
//! Linux reference: `tun_net_xmit()` (kernel→userspace) and
//! `tun_get_user()` (userspace→kernel) in `drivers/net/tun.c`.
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use smoltcp::time::Instant;
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
use super::LinkDevice;
use super::Stats;
pub type PacketQueue = Rc<RefCell<VecDeque<Vec<u8>>>>;
pub struct TunDevice {
name: Rc<str>,
rx_queue: PacketQueue,
tx_queue: PacketQueue,
recv_buffer: Vec<u8>,
ip_address: Option<IpCidr>,
stats: Stats,
}
impl TunDevice {
pub fn new(name: &str, rx: PacketQueue, tx: PacketQueue) -> Self {
Self {
name: name.into(),
rx_queue: rx,
tx_queue: tx,
recv_buffer: Vec::new(),
ip_address: None,
stats: Stats::default(),
}
}
}
impl LinkDevice for TunDevice {
fn send(&mut self, _next_hop: IpAddress, packet: &[u8], _now: Instant) {
self.stats.tx_bytes += packet.len() as u64;
self.stats.tx_packets += 1;
self.tx_queue.borrow_mut().push_back(packet.to_vec());
}
fn recv(&mut self, _now: Instant) -> Option<&[u8]> {
self.recv_buffer = self.rx_queue.borrow_mut().pop_front()?;
self.stats.rx_bytes += self.recv_buffer.len() as u64;
self.stats.rx_packets += 1;
Some(&self.recv_buffer)
}
fn name(&self) -> &Rc<str> {
&self.name
}
fn can_recv(&self) -> bool {
!self.rx_queue.borrow().is_empty()
}
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 statistics(&self) -> Stats {
self.stats
}
}
+174
View File
@@ -0,0 +1,174 @@
//! 802.1Q VLAN device — mirrors Linux 7.1's `net/8021q/`.
//!
//! Reference files:
//! - `net/8021q/vlan_dev.c` — `vlan_dev_hard_start_xmit()` (send path)
//! - `include/linux/if_vlan.h:411` — `__vlan_insert_tag()` (tag insertion)
//! - `include/uapi/linux/if_ether.h:44` — `ETH_P_8021Q = 0x8100`
//!
//! The VLAN device wraps a parent link-layer device and inserts/strips
//! the 4-byte 802.1Q tag (TPID 0x8100 + TCI) before/after the Ethernet
//! header on each frame.
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use smoltcp::time::Instant;
use smoltcp::wire::{
EthernetAddress, EthernetFrame, EthernetProtocol, IpAddress, IpCidr,
};
use super::{DeviceList, LinkDevice};
const TPID_8021Q: u16 = 0x8100;
pub struct VlanDevice {
name: Rc<str>,
parent_name: Rc<str>,
devices: Rc<RefCell<DeviceList>>,
vlan_id: u16,
priority: u8,
tag: [u8; 4],
send_buffer: Vec<u8>,
recv_buffer: Vec<u8>,
recv_queue: VecDeque<Vec<u8>>,
mac_address: Option<EthernetAddress>,
ip_address: Option<IpCidr>,
}
impl VlanDevice {
pub fn new(name: &str, parent_name: &str, vlan_id: u16, devices: Rc<RefCell<DeviceList>>) -> Self {
let tci: u16 = vlan_id & 0x0fff;
let tag: [u8; 4] = [
(TPID_8021Q >> 8) as u8,
(TPID_8021Q & 0xff) as u8,
(tci >> 8) as u8,
(tci & 0xff) as u8,
];
Self {
name: name.into(),
parent_name: parent_name.into(),
devices,
vlan_id,
priority: 0,
tag,
send_buffer: Vec::with_capacity(1522),
recv_buffer: Vec::with_capacity(1522),
recv_queue: VecDeque::new(),
mac_address: None,
ip_address: None,
}
}
pub fn vlan_id(&self) -> u16 {
self.vlan_id
}
pub fn parent_name(&self) -> &str {
&self.parent_name
}
pub fn set_priority(&mut self, pcp: u8) {
self.priority = pcp & 0x07;
let tci: u16 = ((self.priority as u16) << 13) | (self.vlan_id & 0x0fff);
self.tag[2] = (tci >> 8) as u8;
self.tag[3] = (tci & 0xff) as u8;
}
pub fn push_received(&mut self, packet: Vec<u8>) {
self.recv_queue.push_back(packet);
}
fn insert_tag(&mut self, packet: &[u8]) -> &[u8] {
self.send_buffer.clear();
if packet.len() < 14 {
self.send_buffer.extend_from_slice(packet);
return &self.send_buffer;
}
self.send_buffer.extend_from_slice(&packet[..12]);
self.send_buffer.extend_from_slice(&self.tag);
self.send_buffer.extend_from_slice(&packet[12..]);
&self.send_buffer
}
fn strip_tag(&mut self, packet: &[u8]) -> Option<&[u8]> {
if packet.len() < 18 {
return None;
}
let tpid = u16::from_be_bytes([packet[12], packet[13]]);
if tpid != TPID_8021Q {
return None;
}
self.recv_buffer.clear();
self.recv_buffer.extend_from_slice(&packet[..12]);
self.recv_buffer.extend_from_slice(&packet[16..]);
Some(&self.recv_buffer)
}
pub fn matches_tag(&self, packet: &[u8]) -> bool {
if packet.len() < 18 {
return false;
}
let tpid = u16::from_be_bytes([packet[12], packet[13]]);
if tpid != TPID_8021Q {
return false;
}
let tci = u16::from_be_bytes([packet[14], packet[15]]);
let vid = tci & 0x0fff;
vid == self.vlan_id
}
}
impl LinkDevice for VlanDevice {
fn send(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant) {
let tagged = self.insert_tag(packet).to_vec();
// Forward the tagged frame to the parent device by looking it
// up in the shared device list. If the parent doesn't exist or
// isn't ready, drop the packet with a debug log.
if let Some(parent) = self.devices.borrow_mut().get_mut(&self.parent_name) {
parent.send(next_hop, &tagged, now);
} else {
log::debug!("vlan: parent {} not found, dropping {} byte frame",
self.parent_name, packet.len());
}
}
fn recv(&mut self, _now: Instant) -> Option<&[u8]> {
let packet = self.recv_queue.pop_front()?;
if packet.len() < 18 {
return None;
}
let tpid = u16::from_be_bytes([packet[12], packet[13]]);
if tpid != TPID_8021Q {
return None;
}
self.recv_buffer.clear();
self.recv_buffer.extend_from_slice(&packet[..12]);
self.recv_buffer.extend_from_slice(&packet[16..]);
Some(&self.recv_buffer)
}
fn name(&self) -> &Rc<str> {
&self.name
}
fn can_recv(&self) -> bool {
!self.recv_queue.is_empty()
}
fn mac_address(&self) -> Option<EthernetAddress> {
self.mac_address
}
fn set_mac_address(&mut self, addr: EthernetAddress) {
self.mac_address = Some(addr);
}
fn ip_address(&self) -> Option<IpCidr> {
self.ip_address
}
fn set_ip_address(&mut self, addr: IpCidr) {
self.ip_address = Some(addr);
}
}
+221
View File
@@ -0,0 +1,221 @@
//! VXLAN (Virtual eXtensible LAN) — mirrors Linux 7.1's `drivers/net/vxlan/`.
//!
//! Reference files:
//! - `drivers/net/vxlan/vxlan_core.c:1855` — `vxlan_xmit()` — encapsulation
//! - `drivers/net/vxlan/vxlan_core.c:1366` — `vxlan_rcv()` — decapsulation
//! - `include/net/vxlan.h` — VXLAN header format, VNI, default port 4789
//!
//! VXLAN encapsulates L2 Ethernet frames in UDP for overlay networking.
//! Each overlay network is identified by a 24-bit VNI (Virtual Network
//! Identifier). The default UDP destination port is 4789 (IANA-assigned).
//!
//! Packet structure:
//! [Outer Eth][Outer IP][UDP:4789][VXLAN 8B][Inner Eth][Inner IP][Payload]
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use smoltcp::time::Instant;
use smoltcp::wire::{
EthernetAddress, EthernetFrame, EthernetProtocol, EthernetRepr, IpAddress, IpCidr,
Ipv4Address, Ipv4Packet, Ipv4Repr, IpProtocol, UdpPacket, UdpRepr,
};
use super::{DeviceList, LinkDevice};
const VXLAN_PORT: u16 = 4789;
const VXLAN_FLAGS: u8 = 0x08;
pub struct VxlanDevice {
name: Rc<str>,
parent_name: Rc<str>,
devices: Rc<RefCell<DeviceList>>,
local_ip: Ipv4Address,
remote_ip: Ipv4Address,
vni: u32,
vxlan_header: [u8; 8],
send_buffer: Vec<u8>,
recv_buffer: Vec<u8>,
recv_queue: VecDeque<Vec<u8>>,
virtual_mac: EthernetAddress,
ip_address: Option<IpCidr>,
}
impl VxlanDevice {
pub fn new(
name: &str,
parent_name: &str,
local_ip: Ipv4Address,
remote_ip: Ipv4Address,
vni: u32,
devices: Rc<RefCell<DeviceList>>,
) -> Self {
let vni_bytes = vni.to_be_bytes();
Self {
name: name.into(),
parent_name: parent_name.into(),
devices,
local_ip,
remote_ip,
vni: vni & 0x00ffffff,
vxlan_header: [
VXLAN_FLAGS, 0, 0, 0,
vni_bytes[1], vni_bytes[2], vni_bytes[3], 0,
],
send_buffer: Vec::with_capacity(1550),
recv_buffer: Vec::with_capacity(1550),
recv_queue: VecDeque::new(),
virtual_mac: EthernetAddress([0x00, 0x00, 0x5e, 0x00, 0x01, 0x01]),
ip_address: None,
}
}
pub fn push_received(&mut self, packet: Vec<u8>) {
self.recv_queue.push_back(packet);
}
fn build_encapsulated(&mut self, inner_packet: &[u8]) -> &[u8] {
self.send_buffer.clear();
let inner_eth = {
let mut buf = [0u8; 14];
buf[..6].copy_from_slice(&EthernetAddress::BROADCAST.0);
buf[6..12].copy_from_slice(&self.virtual_mac.0);
let ethtype = if !inner_packet.is_empty() && inner_packet[0] >> 4 == 6 {
EthernetProtocol::Ipv6
} else {
EthernetProtocol::Ipv4
};
let proto_bytes: [u8; 2] = match ethtype {
EthernetProtocol::Ipv4 => [0x08, 0x00],
EthernetProtocol::Ipv6 => [0x86, 0xDD],
_ => [0x08, 0x00],
};
buf[12..14].copy_from_slice(&proto_bytes);
buf
};
let inner_frame_len = inner_eth.len() + inner_packet.len();
let udp_repr = UdpRepr {
src_port: VXLAN_PORT,
dst_port: VXLAN_PORT,
};
let udp_payload_len = 8 + inner_frame_len;
let outer_ip_repr = Ipv4Repr {
src_addr: self.local_ip,
dst_addr: self.remote_ip,
next_header: IpProtocol::Udp,
payload_len: 8 + udp_payload_len,
hop_limit: 64,
};
let total_len = outer_ip_repr.buffer_len() + 8 + 8 + inner_frame_len;
self.send_buffer.resize(total_len, 0);
let mut ip = Ipv4Packet::new_unchecked(&mut self.send_buffer);
outer_ip_repr.emit(&mut ip, &smoltcp::phy::ChecksumCapabilities::ignored());
let ip_hdr_len = outer_ip_repr.buffer_len();
let mut udp = UdpPacket::new_unchecked(&mut self.send_buffer[ip_hdr_len..]);
udp_repr.emit(
&mut udp,
&IpAddress::Ipv4(self.local_ip),
&IpAddress::Ipv4(self.remote_ip),
udp_payload_len,
|buf| {
buf[..8].copy_from_slice(&self.vxlan_header);
buf[8..8 + inner_eth.len()].copy_from_slice(&inner_eth);
buf[8 + inner_eth.len()..].copy_from_slice(inner_packet);
},
&smoltcp::phy::ChecksumCapabilities::ignored(),
);
&self.send_buffer
}
fn matches_endpoint(&self, outer_packet: &[u8]) -> bool {
if outer_packet.len() < 50 {
return false;
}
let Ok(ipv4) = Ipv4Packet::new_checked(outer_packet) else {
return false;
};
if u8::from(ipv4.next_header()) != 17 || ipv4.dst_addr() != self.local_ip {
return false;
}
let ip_hdr_len = 20;
let udp = &outer_packet[ip_hdr_len..];
if udp.len() < 18 {
return false;
}
let dst_port = u16::from_be_bytes([udp[2], udp[3]]);
if dst_port != VXLAN_PORT {
return false;
}
if udp[8] != VXLAN_FLAGS {
return false;
}
let pkt_vni = u32::from_be_bytes([0, udp[12], udp[13], udp[14]]) & 0x00ffffff;
pkt_vni == self.vni
}
fn decapsulate(&mut self, outer_packet: &[u8]) -> Option<&[u8]> {
if !self.matches_endpoint(outer_packet) {
return None;
}
let inner_frame_start = 20 + 8 + 8;
let inner_frame = &outer_packet[inner_frame_start..];
if inner_frame.len() < 14 {
return None;
}
let eth = EthernetFrame::new_unchecked(inner_frame);
let Ok(repr) = EthernetRepr::parse(&eth) else {
return None;
};
if repr.ethertype != EthernetProtocol::Ipv4 && repr.ethertype != EthernetProtocol::Ipv6 {
return None;
}
self.recv_buffer.clear();
self.recv_buffer.extend_from_slice(eth.payload());
Some(&self.recv_buffer)
}
}
impl LinkDevice for VxlanDevice {
fn send(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant) {
let enc = self.build_encapsulated(packet).to_vec();
if let Some(parent) = self.devices.borrow_mut().get_mut(&self.parent_name) {
parent.send(next_hop, &enc, now);
} else {
log::debug!("vxlan: parent {} not found, dropping {} byte frame",
self.parent_name, packet.len());
}
}
fn recv(&mut self, _now: Instant) -> Option<&[u8]> {
let packet = self.recv_queue.pop_front()?;
self.decapsulate(&packet)
}
fn name(&self) -> &Rc<str> {
&self.name
}
fn can_recv(&self) -> bool {
!self.recv_queue.is_empty()
}
fn mac_address(&self) -> Option<EthernetAddress> {
Some(self.virtual_mac)
}
fn set_mac_address(&mut self, addr: EthernetAddress) {
self.virtual_mac = addr;
}
fn ip_address(&self) -> Option<IpCidr> {
self.ip_address
}
fn set_ip_address(&mut self, addr: IpCidr) {
self.ip_address = Some(addr);
}
}