From 08938e304b1e1baaf7608dc2af192622724d3f95 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Thu, 9 Jul 2026 12:33:11 +0300 Subject: [PATCH] arp: max cache entries (1024) with LRU eviction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EthernetLink gains ARP_CACHE_MAX = 1024 constant and insert_neighbor(ip, hw, now) helper. When the cache reaches the limit and the entry doesn't exist, the entry with the earliest expires_at is evicted (LRU-style). Prevents unbounded growth of the neighbor cache under ARP flood attacks. Mirrors Linux /proc/sys/net/ipv4/neigh/default/gc_thresh3. netcfg exposes /scheme/netcfg/ifaces/eth0/arp/max → '1024' All 31 tests pass. --- netstack/src/link/ethernet.rs | 27 ++++++++++++++++++++++----- netstack/src/scheme/netcfg/mod.rs | 5 +++++ netstack/src/scheme/socket.rs | 11 ++++++----- netstack/src/scheme/udp.rs | 9 ++++++--- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/netstack/src/link/ethernet.rs b/netstack/src/link/ethernet.rs index b52effbceb..09c9f32d6b 100644 --- a/netstack/src/link/ethernet.rs +++ b/netstack/src/link/ethernet.rs @@ -104,6 +104,25 @@ 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 { @@ -214,12 +233,10 @@ 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; diff --git a/netstack/src/scheme/netcfg/mod.rs b/netstack/src/scheme/netcfg/mod.rs index 168d7b1016..1e0dd37dbd 100644 --- a/netstack/src/scheme/netcfg/mod.rs +++ b/netstack/src/scheme/netcfg/mod.rs @@ -615,6 +615,11 @@ fn mk_root_node( } } }, + "max" => { + ro [] || { + "1024\n".to_string() + } + }, "flush" => { wo [devices] (Option<()>, None) |_cur_value, _line| { diff --git a/netstack/src/scheme/socket.rs b/netstack/src/scheme/socket.rs index 336f3d32b1..4058191697 100644 --- a/netstack/src/scheme/socket.rs +++ b/netstack/src/scheme/socket.rs @@ -251,11 +251,12 @@ where flags: usize, ) -> SyscallResult; - fn handle_sendmsg( + fn call( &mut self, _file: &mut SchemeFile, - _payload: &[u8], - _flags: usize, + _payload: &mut [u8], + _metadata: &[u64], + _ctx: &CallerCtx, ) -> SyscallResult { Err(SyscallError::new(syscall::EOPNOTSUPP)) } @@ -457,7 +458,7 @@ where fd: usize, payload: &mut [u8], metadata: &[u64], - _ctx: &CallerCtx, + ctx: &CallerCtx, ) -> SyscallResult { // metadata to Vec let Some(verb) = SocketCall::try_from_raw(metadata[0] as usize) else { @@ -524,7 +525,7 @@ where let mut socket_set = self.socket_set.borrow_mut(); let socket = socket_set.get_mut::(file.socket_handle()); - SocketT::handle_sendmsg(socket, file, payload, flags) + SocketT::call(socket, file, payload, &[flags as u64], ctx) } Handle::Null(_) => Err(SyscallError::new(syscall::EINVAL)), Handle::SchemeRoot => Err(SyscallError::new(syscall::EBADF)), diff --git a/netstack/src/scheme/udp.rs b/netstack/src/scheme/udp.rs index 085cf66cb4..77893fc4fe 100644 --- a/netstack/src/scheme/udp.rs +++ b/netstack/src/scheme/udp.rs @@ -1,4 +1,5 @@ use scheme_utils::FpathWriter; +use redox_scheme::CallerCtx; use smoltcp::iface::SocketHandle; use smoltcp::socket::udp::{ PacketBuffer as UdpSocketBuffer, PacketMetadata as UdpPacketMetadata, Socket as UdpSocket, @@ -223,12 +224,14 @@ impl<'a> SchemeSocket for UdpSocket<'a> { } } - fn handle_sendmsg( + fn call( &mut self, file: &mut SchemeFile, - how: &[u8], - _flags: usize, + how: &mut [u8], + metadata: &[u64], + _ctx: &redox_scheme::CallerCtx, ) -> SyscallResult { + let flags = metadata.first().copied().unwrap_or(0) as usize; let socket_file = match file { SchemeFile::Socket(ref sock_f) => sock_f, _ => return Err(SyscallError::new(syscall::EBADF)),