arp: max cache entries (1024) with LRU eviction

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.
This commit is contained in:
Red Bear OS
2026-07-09 12:33:11 +03:00
parent caa6d333e6
commit 08938e304b
4 changed files with 39 additions and 13 deletions
+22 -5
View File
@@ -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;
+5
View File
@@ -615,6 +615,11 @@ fn mk_root_node(
}
}
},
"max" => {
ro [] || {
"1024\n".to_string()
}
},
"flush" => {
wo [devices] (Option<()>, None)
|_cur_value, _line| {
+6 -5
View File
@@ -251,11 +251,12 @@ where
flags: usize,
) -> SyscallResult<usize>;
fn handle_sendmsg(
fn call(
&mut self,
_file: &mut SchemeFile<Self>,
_payload: &[u8],
_flags: usize,
_payload: &mut [u8],
_metadata: &[u64],
_ctx: &CallerCtx,
) -> SyscallResult<usize> {
Err(SyscallError::new(syscall::EOPNOTSUPP))
}
@@ -457,7 +458,7 @@ where
fd: usize,
payload: &mut [u8],
metadata: &[u64],
_ctx: &CallerCtx,
ctx: &CallerCtx,
) -> SyscallResult<usize> {
// metadata to Vec<u8>
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::<SocketT>(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)),
+6 -3
View File
@@ -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<Self>,
how: &[u8],
_flags: usize,
how: &mut [u8],
metadata: &[u64],
_ctx: &redox_scheme::CallerCtx,
) -> SyscallResult<usize> {
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)),