From 3dcfbd813e19c859d820f480187c9c4e313c30c6 Mon Sep 17 00:00:00 2001 From: Jupiter Belic Date: Sun, 25 Oct 2026 15:51:10 +1000 Subject: [PATCH] net: give each bridged NFS host its own MAC The virtual NFS host used one fixed 02:00:DE:AD:BF:53 address, so two bridged instances with different NFS subnets collided at L2 and ARP replies could reach the wrong host. Fold all four address octets into the last two, which keeps the address stable for one host and separates subnets even when only the first two octets differ. The new test fails on the fixed suffix and passes with the derivation. --- src/net/nfs_pcap.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/net/nfs_pcap.rs b/src/net/nfs_pcap.rs index 4d7f426c..4d68298b 100644 --- a/src/net/nfs_pcap.rs +++ b/src/net/nfs_pcap.rs @@ -29,8 +29,13 @@ pub(crate) struct NfsVirtualHost { impl NfsVirtualHost { pub(crate) fn new(ip: Ipv4Addr, nfs_cfg: NfsConfig) -> Self { // Locally administered, distinct from the NAT gateway's, so the guest - // keeps the virtual NFS host as its own ARP entry. (0xBF53 ≈ "BF-NFS".) - let mac = [0x02, 0x00, 0xDE, 0xAD, 0xBF, 0x53]; + // keeps the virtual NFS host as its own ARP entry. Fold all four + // octets into the last two so two bridged instances with different + // NFS addresses never share an L2 address — a fixed suffix collided. + let o = ip.octets(); + let fold = (u16::from(o[0]) << 8 | u16::from(o[1])) + ^ (u16::from(o[2]) << 8 | u16::from(o[3])); + let mac = [0x02, 0x00, 0xDE, 0xAD, (fold >> 8) as u8, fold as u8]; let server = super::nfsudp::NfsServer::new(nfs_cfg.shared_dir.clone(), nfs_cfg.version); Self { ip, mac, server, ip_id: 1, reasm: Reassembler::default() } } @@ -77,6 +82,23 @@ impl NfsVirtualHost { mod tests { use super::*; use crate::net::portmap::{getport_call, PROG_MOUNTD, PROG_NFS}; + /// Two virtual NFS hosts must not share an L2 address: bridged instances + /// with distinct NFS subnets used to collide on one fixed MAC. The + /// derivation is stable for the same address and separates subnets even + /// when only the first two octets differ. + #[test] + fn distinct_nfs_hosts_get_distinct_macs() { + let a = host(Ipv4Addr::new(192, 168, 10, 1)); + let b = host(Ipv4Addr::new(192, 168, 11, 1)); + let c = host(Ipv4Addr::new(10, 0, 1, 1)); + let d = host(Ipv4Addr::new(10, 1, 1, 1)); + assert_ne!(a.mac, b.mac); + assert_ne!(c.mac, d.mac); + assert_eq!(a.mac, host(Ipv4Addr::new(192, 168, 10, 1)).mac, "the same address derives the same MAC"); + assert_eq!(&a.mac[..4], &[0x02, 0x00, 0xDE, 0xAD]); + assert_eq!(a.mac[0] & 0x03, 0x02, "locally administered, unicast"); + } + fn host(ip: Ipv4Addr) -> NfsVirtualHost { NfsVirtualHost::new(ip, NfsConfig { shared_dir: "/nonexistent".into(),