diff --git a/src/lib.rs b/src/lib.rs index 45b1018..f94c88a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -325,6 +325,10 @@ fn run( log::info!("Device EOF, stopping IP stack"); return Ok(()); } + Ok(n) if n <= offset => { + log::warn!("Discarding packet smaller than header offset: received {n} bytes, expected > {offset} bytes"); + continue; + } Ok(n) => n, Err(e) => { log::error!("Device read error: {e}"); @@ -453,12 +457,72 @@ async fn process_upstream_recv( #[cfg(test)] mod tests { - use std::time::Duration; + use std::{ + collections::VecDeque, + pin::Pin, + task::{Context, Poll}, + time::Duration, + }; - use tokio::{io::duplex, time::timeout}; + use tokio::{ + io::{duplex, AsyncRead, AsyncWrite, ReadBuf}, + time::timeout, + }; use super::*; + struct ChunkedDevice { + chunks: VecDeque>, + } + + impl ChunkedDevice { + fn new(chunks: Vec>) -> Self { + Self { + chunks: chunks.into(), + } + } + } + + impl AsyncRead for ChunkedDevice { + fn poll_read( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if let Some(mut chunk) = self.chunks.pop_front() { + let to_copy = buf.remaining().min(chunk.len()); + buf.put_slice(&chunk[..to_copy]); + chunk.drain(..to_copy); + if !chunk.is_empty() { + self.chunks.push_front(chunk); + } + } + + Poll::Ready(Ok(())) + } + } + + impl AsyncWrite for ChunkedDevice { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Poll::Ready(Ok(buf.len())) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + } + #[tokio::test] async fn device_eof_closes_accept_channel() { let (device, peer) = duplex(64); @@ -471,4 +535,23 @@ mod tests { .expect("accept should not hang after device EOF"); assert!(matches!(accept_result, Err(IpStackError::AcceptError))); } + + #[cfg(unix)] + #[tokio::test] + async fn packet_smaller_than_header_offset_is_discarded_without_panic() { + let mut config = IpStackConfig::default(); + config.packet_information(true); + let mut packet_data = vec![0u8; 4]; + let builder = etherparse::PacketBuilder::ipv4([10, 0, 0, 1], [10, 0, 0, 2], 64).udp(1234, 5678); + builder.write(&mut packet_data, &[1, 2, 3, 4]).unwrap(); + + let device = ChunkedDevice::new(vec![vec![0x00, 0x01], packet_data]); + let mut ip_stack = IpStack::new(config, device); + + let accepted = timeout(Duration::from_secs(1), ip_stack.accept()) + .await + .expect("accept timed out") + .expect("accept returned error"); + assert!(matches!(accepted, IpStackStream::Udp(_))); + } }