From d850900382bf09739fcfdd72055f73cc2b8b42d6 Mon Sep 17 00:00:00 2001 From: Sajjad Pourali Date: Tue, 15 Sep 2026 20:35:07 -0700 Subject: [PATCH] Handle device EOF and I/O errors in the main event loop #88 --- src/lib.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index e67072a..45b1018 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -319,7 +319,18 @@ fn run( tokio::spawn(async move { loop { select! { - Ok(n) = device.read(&mut buffer) => { + read_result = device.read(&mut buffer) => { + let n = match read_result { + Ok(0) => { + log::info!("Device EOF, stopping IP stack"); + return Ok(()); + } + Ok(n) => n, + Err(e) => { + log::error!("Device read error: {e}"); + return Err(e.into()); + } + }; if let Err(e) = process_device_read(&buffer[offset..n], &mut sessions, &session_remove_tx, &up_pkt_sender, &config, &accept_sender).await { let io_err: std::io::Error = e.into(); if io_err.kind() == std::io::ErrorKind::ConnectionRefused { @@ -439,3 +450,25 @@ async fn process_upstream_recv( Ok(()) } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use tokio::{io::duplex, time::timeout}; + + use super::*; + + #[tokio::test] + async fn device_eof_closes_accept_channel() { + let (device, peer) = duplex(64); + let mut ip_stack = IpStack::new(IpStackConfig::default(), device); + + drop(peer); + + let accept_result = timeout(Duration::from_secs(1), ip_stack.accept()) + .await + .expect("accept should not hang after device EOF"); + assert!(matches!(accept_result, Err(IpStackError::AcceptError))); + } +}