From 01f696463c407b566202d1c6bdfe1597f161b23e Mon Sep 17 00:00:00 2001 From: Yibo Zhuang Date: Thu, 13 Aug 2026 18:12:16 -0700 Subject: [PATCH] Exit Softnet when its VM socket disconnects Check the VM socket on poll timeouts because macOS kqueue does not report Unix datagram peer disconnects. This prevents orphaned Softnet processes from blocking VM cleanup. --- lib/proxy/mod.rs | 5 ++++ lib/vm.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/lib/proxy/mod.rs b/lib/proxy/mod.rs index 0a59dbc..5e0324e 100644 --- a/lib/proxy/mod.rs +++ b/lib/proxy/mod.rs @@ -108,6 +108,11 @@ impl Proxy<'_> { loop { let (vm_readable, host_readable, interrupt) = self.poller.wait()?; + // kqueue does not report peer disconnects for Unix datagram sockets. + if !self.vm.is_connected()? { + return Ok(()); + } + // Update coarse time for DHCP snooping and flows coarsetime::Instant::update(); diff --git a/lib/vm.rs b/lib/vm.rs index d2682f1..cadab07 100644 --- a/lib/vm.rs +++ b/lib/vm.rs @@ -26,6 +26,14 @@ impl VM { pub fn read(&self, buf: &mut [u8]) -> std::io::Result { self.sock.recv(buf) } + + pub fn is_connected(&self) -> io::Result { + match self.sock.peer_addr() { + Ok(_) => Ok(true), + Err(error) if error.kind() == io::ErrorKind::NotConnected => Ok(false), + Err(error) => Err(error), + } + } } fn duplicate_vm_fd(vm_fd: RawFd) -> Result { @@ -115,10 +123,12 @@ impl AsRawFd for VM { #[cfg(test)] mod tests { use super::VM; + use polling::{Event, Events, PollMode, Poller}; use std::fs::File; use std::net::UdpSocket; use std::os::fd::AsRawFd; use std::os::unix::net::{UnixDatagram, UnixStream}; + use std::time::Duration; #[test] fn test_new_rejects_negative_fd() { @@ -187,4 +197,62 @@ mod tests { assert!(socket_fd_is_open); } + + #[test] + fn test_connected_socket_has_peer() { + let (socket, _peer) = UnixDatagram::pair().unwrap(); + let vm = VM::new(socket.as_raw_fd()).unwrap(); + + assert!(vm.is_connected().unwrap()); + } + + #[test] + fn test_disconnected_peer_is_detected_without_kqueue_event() { + let (socket, peer) = UnixDatagram::pair().unwrap(); + let vm = VM::new(socket.as_raw_fd()).unwrap(); + let poller = Poller::new().unwrap(); + let mut events = Events::new(); + + unsafe { + poller + .add_with_mode(vm.as_raw_fd(), Event::readable(0), PollMode::Edge) + .unwrap(); + } + drop(peer); + + poller + .wait(&mut events, Some(Duration::from_millis(20))) + .unwrap(); + + assert!(events.is_empty()); + assert!(!vm.is_connected().unwrap()); + } + + #[test] + fn test_disconnected_peer_is_detected_when_another_socket_wakes_kqueue() { + let (socket, peer) = UnixDatagram::pair().unwrap(); + let (host, host_peer) = UnixDatagram::pair().unwrap(); + let vm = VM::new(socket.as_raw_fd()).unwrap(); + let poller = Poller::new().unwrap(); + let mut events = Events::new(); + + unsafe { + poller + .add_with_mode(vm.as_raw_fd(), Event::readable(0), PollMode::Edge) + .unwrap(); + poller + .add_with_mode(host.as_raw_fd(), Event::readable(1), PollMode::Edge) + .unwrap(); + } + drop(peer); + host_peer.send(&[1]).unwrap(); + + poller + .wait(&mut events, Some(Duration::from_millis(20))) + .unwrap(); + + assert!(events.iter().any(|event| event.key == 1)); + assert!(!events.iter().any(|event| event.key == 0)); + assert!(!vm.is_connected().unwrap()); + } }