Exit Softnet when its VM socket disconnects (#193)

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.
This commit is contained in:
Yibo Zhuang 2026-08-14 05:36:32 -07:00 committed by GitHub
parent 28bb29df4a
commit 0c4327dd71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 73 additions and 0 deletions

View File

@ -108,6 +108,11 @@ impl Proxy<'_> {
loop { loop {
let (vm_readable, host_readable, interrupt) = self.poller.wait()?; 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 // Update coarse time for DHCP snooping and flows
coarsetime::Instant::update(); coarsetime::Instant::update();

View File

@ -26,6 +26,14 @@ impl VM {
pub fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> { pub fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> {
self.sock.recv(buf) self.sock.recv(buf)
} }
pub fn is_connected(&self) -> io::Result<bool> {
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<RawFd> { fn duplicate_vm_fd(vm_fd: RawFd) -> Result<RawFd> {
@ -115,10 +123,12 @@ impl AsRawFd for VM {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::VM; use super::VM;
use polling::{Event, Events, PollMode, Poller};
use std::fs::File; use std::fs::File;
use std::net::UdpSocket; use std::net::UdpSocket;
use std::os::fd::AsRawFd; use std::os::fd::AsRawFd;
use std::os::unix::net::{UnixDatagram, UnixStream}; use std::os::unix::net::{UnixDatagram, UnixStream};
use std::time::Duration;
#[test] #[test]
fn test_new_rejects_negative_fd() { fn test_new_rejects_negative_fd() {
@ -187,4 +197,62 @@ mod tests {
assert!(socket_fd_is_open); 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());
}
} }