use alloc::{collections::BTreeMap, sync::Arc};
use core::sync::atomic::{AtomicU64, Ordering};
use ax_kspin::SpinNoIrq as Mutex;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SwitchPortId {
pub vm_id: usize,
pub generation: usize,
pub device_index: u16,
}
impl SwitchPortId {
pub const fn new(vm_id: usize, generation: usize, device_index: u16) -> Self {
Self {
vm_id,
generation,
device_index,
}
}
}
pub trait SwitchPort: Send + Sync {
fn id(&self) -> SwitchPortId;
fn guest_mac(&self) -> [u8; 6];
fn is_active(&self) -> bool;
fn deliver_ingress(&self, frame: &[u8]) -> bool;
fn notify_ingress(&self);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SwitchDropReason {
Undersize,
SourceMacViolation,
InactiveGeneration,
UnknownUnicast,
}
#[derive(Debug, Default)]
pub struct SwitchStats {
pub host_rx_packets: AtomicU64,
pub host_tx_requested: AtomicU64,
pub unknown_unicast_drop: AtomicU64,
pub broadcast_copies: AtomicU64,
pub multicast_copies: AtomicU64,
pub local_unicast_forwarded: AtomicU64,
pub source_mac_violation: AtomicU64,
pub undersize_drop: AtomicU64,
pub inactive_generation_drop: AtomicU64,
pub duplicate_mac_rejected: AtomicU64,
}
impl SwitchStats {
fn inc(&self, field: &AtomicU64) {
field.fetch_add(1, Ordering::Relaxed);
}
}
#[derive(Default)]
struct SwitchRegistry {
by_id: BTreeMap<SwitchPortId, Arc<dyn SwitchPort>>,
by_mac: BTreeMap<[u8; 6], SwitchPortId>,
}
pub struct VirtualSwitch {
registry: Mutex<SwitchRegistry>,
stats: SwitchStats,
}
impl VirtualSwitch {
pub fn new() -> Arc<Self> {
Arc::new(Self {
registry: Mutex::new(SwitchRegistry::default()),
stats: SwitchStats::default(),
})
}
pub fn stats(&self) -> &SwitchStats {
&self.stats
}
pub fn register_owned(
self: &Arc<Self>,
port: Arc<dyn SwitchPort>,
) -> Result<SwitchPortRegistration, SwitchError> {
let id = port.id();
let mac = port.guest_mac();
let mut registry = self.registry.lock();
if registry.by_id.contains_key(&id) {
self.stats.inc(&self.stats.duplicate_mac_rejected);
return Err(SwitchError::DuplicatePortId(id));
}
if registry.by_mac.contains_key(&mac) {
self.stats.inc(&self.stats.duplicate_mac_rejected);
return Err(SwitchError::DuplicateMac(mac));
}
registry.by_id.insert(id, port);
registry.by_mac.insert(mac, id);
drop(registry);
Ok(SwitchPortRegistration {
switch: Some(self.clone()),
id,
})
}
pub fn unregister(&self, id: SwitchPortId) {
let mut registry = self.registry.lock();
let Some(port) = registry.by_id.remove(&id) else {
return;
};
if registry.by_mac.get(&port.guest_mac()) == Some(&id) {
registry.by_mac.remove(&port.guest_mac());
}
}
pub fn active_port_ids(&self) -> alloc::vec::Vec<SwitchPortId> {
self.registry
.lock()
.by_id
.iter()
.filter(|(_, port)| port.is_active())
.map(|(id, _)| *id)
.collect()
}
pub fn switch_from_port(&self, src_id: SwitchPortId, frame: &[u8]) -> EgressOutcome {
let header = match ethernet_destination(frame) {
Some(header) => header,
None => {
self.stats.inc(&self.stats.undersize_drop);
return EgressOutcome::dropped(SwitchDropReason::Undersize);
}
};
let decision = {
let registry = self.registry.lock();
let Some(src_port) = registry.by_id.get(&src_id) else {
drop(registry);
self.stats.inc(&self.stats.inactive_generation_drop);
return EgressOutcome::dropped(SwitchDropReason::InactiveGeneration);
};
if !src_port.is_active() {
drop(registry);
self.stats.inc(&self.stats.inactive_generation_drop);
return EgressOutcome::dropped(SwitchDropReason::InactiveGeneration);
}
if header.src != src_port.guest_mac() {
drop(registry);
self.stats.inc(&self.stats.source_mac_violation);
return EgressOutcome::dropped(SwitchDropReason::SourceMacViolation);
}
classify_destination(&header.dst, src_id, ®istry)
};
for target in decision.local_targets.iter() {
if target.deliver_ingress(frame) {
target.notify_ingress();
match header.class() {
DestinationClass::Broadcast => {
self.stats.inc(&self.stats.broadcast_copies);
}
DestinationClass::Multicast => {
self.stats.inc(&self.stats.multicast_copies);
}
DestinationClass::Unicast => {
self.stats.inc(&self.stats.local_unicast_forwarded);
}
}
}
}
if decision.uplink {
self.stats.inc(&self.stats.host_tx_requested);
}
EgressOutcome::Forwarded {
uplink: decision.uplink,
}
}
pub fn switch_from_uplink(&self, frame: &[u8]) {
self.stats.inc(&self.stats.host_rx_packets);
let header = match ethernet_destination(frame) {
Some(header) => header,
None => {
self.stats.inc(&self.stats.undersize_drop);
return;
}
};
let targets: alloc::vec::Vec<Arc<dyn SwitchPort>> = {
let registry = self.registry.lock();
match header.class() {
DestinationClass::Unicast => match registry.by_mac.get(&header.dst) {
Some(id) => registry.by_id.get(id).cloned().into_iter().collect(),
None => {
drop(registry);
self.stats.inc(&self.stats.unknown_unicast_drop);
return;
}
},
DestinationClass::Broadcast | DestinationClass::Multicast => registry
.by_id
.values()
.filter(|port| port.is_active())
.cloned()
.collect(),
}
};
for target in targets {
if target.deliver_ingress(frame) {
target.notify_ingress();
match header.class() {
DestinationClass::Broadcast => {
self.stats.inc(&self.stats.broadcast_copies);
}
DestinationClass::Multicast => {
self.stats.inc(&self.stats.multicast_copies);
}
DestinationClass::Unicast => {
self.stats.inc(&self.stats.local_unicast_forwarded);
}
}
}
}
}
}
struct Decision {
local_targets: alloc::vec::Vec<Arc<dyn SwitchPort>>,
uplink: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DestinationClass {
Unicast,
Multicast,
Broadcast,
}
struct EthernetView {
dst: [u8; 6],
src: [u8; 6],
}
impl EthernetView {
fn class(&self) -> DestinationClass {
if self.dst == [0xff; 6] {
DestinationClass::Broadcast
} else if self.dst[0] & 0x01 != 0 {
DestinationClass::Multicast
} else {
DestinationClass::Unicast
}
}
}
const ETHERNET_HEADER_LEN: usize = 14;
fn ethernet_destination(frame: &[u8]) -> Option<EthernetView> {
if frame.len() < ETHERNET_HEADER_LEN {
return None;
}
let mut dst = [0u8; 6];
let mut src = [0u8; 6];
dst.copy_from_slice(&frame[0..6]);
src.copy_from_slice(&frame[6..12]);
Some(EthernetView { dst, src })
}
fn classify_destination(
dst: &[u8; 6],
src_id: SwitchPortId,
registry: &SwitchRegistry,
) -> Decision {
let class = EthernetView {
dst: *dst,
src: [0u8; 6],
}
.class();
match class {
DestinationClass::Unicast => match registry.by_mac.get(dst) {
Some(target_id) if *target_id != src_id => Decision {
local_targets: registry.by_id.get(target_id).into_iter().cloned().collect(),
uplink: false,
},
_ => Decision {
local_targets: alloc::vec::Vec::new(),
uplink: true,
},
},
DestinationClass::Broadcast | DestinationClass::Multicast => {
let local_targets = registry
.by_id
.values()
.filter(|port| port.is_active() && port.id() != src_id)
.cloned()
.collect();
Decision {
local_targets,
uplink: true,
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EgressOutcome {
Forwarded { uplink: bool },
Dropped(SwitchDropReason),
}
impl EgressOutcome {
fn dropped(reason: SwitchDropReason) -> Self {
EgressOutcome::Dropped(reason)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SwitchError {
DuplicatePortId(SwitchPortId),
DuplicateMac([u8; 6]),
}
pub struct SwitchPortRegistration {
switch: Option<Arc<VirtualSwitch>>,
id: SwitchPortId,
}
impl SwitchPortRegistration {
pub fn id(&self) -> SwitchPortId {
self.id
}
pub fn release(mut self) {
if let Some(switch) = self.switch.take() {
switch.unregister(self.id);
}
}
}
impl Drop for SwitchPortRegistration {
fn drop(&mut self) {
if let Some(switch) = self.switch.take() {
switch.unregister(self.id);
}
}
}
impl core::fmt::Debug for SwitchPortRegistration {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SwitchPortRegistration")
.field("id", &self.id)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use super::*;
struct FakePort {
id: SwitchPortId,
mac: [u8; 6],
active: AtomicBool,
delivered: Mutex<alloc::vec::Vec<alloc::vec::Vec<u8>>>,
accept: AtomicUsize,
notifications: AtomicUsize,
}
impl FakePort {
fn new(id: SwitchPortId, mac: [u8; 6]) -> Arc<Self> {
Arc::new(Self {
id,
mac,
active: AtomicBool::new(true),
delivered: Mutex::new(alloc::vec::Vec::new()),
accept: AtomicUsize::new(usize::MAX),
notifications: AtomicUsize::new(0),
})
}
fn set_active(&self, active: bool) {
self.active.store(active, Ordering::Release);
}
fn set_capacity(&self, capacity: usize) {
self.accept.store(capacity, Ordering::Release);
}
fn delivered(&self) -> alloc::vec::Vec<alloc::vec::Vec<u8>> {
self.delivered.lock().clone()
}
fn notifications(&self) -> usize {
self.notifications.load(Ordering::Acquire)
}
}
impl SwitchPort for FakePort {
fn id(&self) -> SwitchPortId {
self.id
}
fn guest_mac(&self) -> [u8; 6] {
self.mac
}
fn is_active(&self) -> bool {
self.active.load(Ordering::Acquire)
}
fn deliver_ingress(&self, frame: &[u8]) -> bool {
if !self.is_active() {
return false;
}
let mut delivered = self.delivered.lock();
if delivered.len() >= self.accept.load(Ordering::Acquire) {
return false;
}
delivered.push(frame.to_vec());
true
}
fn notify_ingress(&self) {
self.notifications.fetch_add(1, Ordering::Release);
}
}
fn frame(dst: [u8; 6], src: [u8; 6]) -> alloc::vec::Vec<u8> {
let mut f = alloc::vec::Vec::with_capacity(14);
f.extend_from_slice(&dst);
f.extend_from_slice(&src);
f.extend_from_slice(&[0x08, 0x00]); f.extend_from_slice(b"payload");
f
}
const MAC_A: [u8; 6] = [0x52, 0x54, 0x00, 0x12, 0x34, 0x56];
const MAC_B: [u8; 6] = [0x52, 0x54, 0x00, 0x12, 0x34, 0x57];
const MAC_HOST: [u8; 6] = [0x52, 0x55, 0x0a, 0x00, 0x02, 0x02];
fn port_id(vm: usize) -> SwitchPortId {
SwitchPortId::new(vm, 1, 0)
}
fn two_port_switch() -> (
Arc<VirtualSwitch>,
Arc<FakePort>,
Arc<FakePort>,
SwitchPortRegistration,
SwitchPortRegistration,
) {
let switch = VirtualSwitch::new();
let a = FakePort::new(port_id(1), MAC_A);
let b = FakePort::new(port_id(2), MAC_B);
let ra = switch.register_owned(a.clone()).unwrap();
let rb = switch.register_owned(b.clone()).unwrap();
(switch, a, b, ra, rb)
}
#[test]
fn registers_two_ports_and_rejects_duplicates() {
let switch = VirtualSwitch::new();
let a = FakePort::new(port_id(1), MAC_A);
let b = FakePort::new(port_id(2), MAC_B);
let ra = switch.register_owned(a.clone()).unwrap();
let rb = switch.register_owned(b.clone()).unwrap();
let a_dup = FakePort::new(SwitchPortId::new(3, 1, 0), MAC_A);
assert_eq!(
switch.register_owned(a_dup).unwrap_err(),
SwitchError::DuplicateMac(MAC_A)
);
let id_dup = FakePort::new(port_id(1), [0x52, 0x54, 0x00, 0x00, 0x00, 0x09]);
assert_eq!(
switch.register_owned(id_dup).unwrap_err(),
SwitchError::DuplicatePortId(port_id(1))
);
drop(ra);
drop(rb);
}
#[test]
fn known_unicast_delivers_only_to_target_without_uplink() {
let (switch, a, b, _ra, _rb) = two_port_switch();
let outcome = switch.switch_from_port(port_id(1), &frame(MAC_B, MAC_A));
assert_eq!(outcome, EgressOutcome::Forwarded { uplink: false });
assert!(a.delivered().is_empty());
assert_eq!(b.delivered().len(), 1);
assert_eq!(a.notifications(), 0);
assert_eq!(b.notifications(), 1);
}
#[test]
fn broadcast_fans_out_to_other_ports_and_uplink() {
let (switch, a, b, _ra, _rb) = two_port_switch();
let outcome = switch.switch_from_port(port_id(1), &frame([0xff; 6], MAC_A));
assert_eq!(outcome, EgressOutcome::Forwarded { uplink: true });
assert!(a.delivered().is_empty()); assert_eq!(b.delivered().len(), 1);
}
#[test]
fn multicast_fans_out_to_other_ports_and_uplink() {
let (switch, a, b, _ra, _rb) = two_port_switch();
let mcast = [0x01, 0x00, 0x5e, 0x00, 0x00, 0x01];
let outcome = switch.switch_from_port(port_id(2), &frame(mcast, MAC_B));
assert_eq!(outcome, EgressOutcome::Forwarded { uplink: true });
assert_eq!(a.delivered().len(), 1);
assert!(b.delivered().is_empty()); }
#[test]
fn unknown_unicast_is_uplinked_only() {
let (switch, a, b, _ra, _rb) = two_port_switch();
let outcome = switch.switch_from_port(port_id(1), &frame(MAC_HOST, MAC_A));
assert_eq!(outcome, EgressOutcome::Forwarded { uplink: true });
assert!(a.delivered().is_empty());
assert!(b.delivered().is_empty());
}
#[test]
fn source_mac_spoof_is_dropped() {
let (switch, a, b, _ra, _rb) = two_port_switch();
let outcome = switch.switch_from_port(port_id(1), &frame(MAC_B, MAC_B));
assert_eq!(
outcome,
EgressOutcome::Dropped(SwitchDropReason::SourceMacViolation)
);
assert!(a.delivered().is_empty());
assert!(b.delivered().is_empty());
assert_eq!(
switch.stats().source_mac_violation.load(Ordering::Relaxed),
1
);
}
#[test]
fn host_rx_known_unicast_targets_one_port() {
let (switch, a, b, _ra, _rb) = two_port_switch();
switch.switch_from_uplink(&frame(MAC_A, MAC_HOST));
assert_eq!(a.delivered().len(), 1);
assert!(b.delivered().is_empty());
}
#[test]
fn host_rx_broadcast_fans_out_to_all() {
let (switch, a, b, _ra, _rb) = two_port_switch();
switch.switch_from_uplink(&frame([0xff; 6], MAC_HOST));
assert_eq!(a.delivered().len(), 1);
assert_eq!(b.delivered().len(), 1);
}
#[test]
fn host_rx_unknown_unicast_is_dropped() {
let (switch, a, b, _ra, _rb) = two_port_switch();
switch.switch_from_uplink(&frame([0x52, 0x54, 0x00, 0x00, 0x00, 0x99], MAC_HOST));
assert!(a.delivered().is_empty());
assert!(b.delivered().is_empty());
assert_eq!(
switch.stats().unknown_unicast_drop.load(Ordering::Relaxed),
1
);
}
#[test]
fn dropping_registration_removes_port_from_table() {
let (switch, a, b, ra, _rb) = two_port_switch();
switch.switch_from_uplink(&frame([0xff; 6], MAC_HOST));
assert_eq!(a.delivered().len(), 1);
drop(ra);
switch.switch_from_uplink(&frame([0xff; 6], MAC_HOST));
assert_eq!(a.delivered().len(), 1); assert_eq!(b.delivered().len(), 2);
}
#[test]
fn one_full_ingress_does_not_block_other_copies_or_uplink() {
let (switch, a, b, _ra, _rb) = two_port_switch();
a.set_capacity(0); let outcome = switch.switch_from_port(port_id(1), &frame([0xff; 6], MAC_A));
assert_eq!(outcome, EgressOutcome::Forwarded { uplink: true });
assert!(a.delivered().is_empty());
assert_eq!(b.delivered().len(), 1);
}
#[test]
fn inactive_port_is_skipped_during_fanout() {
let (switch, a, b, _ra, _rb) = two_port_switch();
b.set_active(false);
let outcome = switch.switch_from_port(port_id(1), &frame([0xff; 6], MAC_A));
assert_eq!(outcome, EgressOutcome::Forwarded { uplink: true });
assert!(a.delivered().is_empty()); assert_eq!(b.delivered().len(), 0); }
#[test]
fn stale_generation_frame_is_dropped() {
let switch = VirtualSwitch::new();
let stale_id = SwitchPortId::new(1, 1, 0);
let outcome = switch.switch_from_port(stale_id, &frame(MAC_B, MAC_A));
assert_eq!(
outcome,
EgressOutcome::Dropped(SwitchDropReason::InactiveGeneration)
);
}
#[test]
fn generation_reuse_after_unregister_succeeds() {
let switch = VirtualSwitch::new();
let mac = MAC_A;
let old = FakePort::new(SwitchPortId::new(1, 1, 0), mac);
let reg = switch.register_owned(old).unwrap();
drop(reg);
let new = FakePort::new(SwitchPortId::new(1, 2, 0), mac);
assert!(switch.register_owned(new).is_ok());
}
}