From 82b54b1c124e80d2dcc3e076cc09c36fe91d5986 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Sun, 24 Mar 2024 22:06:49 +0100 Subject: [PATCH 01/14] Add initial ip defrag implementation --- Cargo.toml | 1 + etherparse-defrag/Cargo.toml | 17 + etherparse-defrag/README.md | 4 + etherparse-defrag/src/ip_defrag_buf.rs | 377 +++++++++++++++++++++++ etherparse-defrag/src/ip_defrag_error.rs | 101 ++++++ etherparse-defrag/src/ip_defrag_pool.rs | 35 +++ etherparse-defrag/src/ip_frag_id.rs | 33 ++ etherparse-defrag/src/ip_frag_range.rs | 108 +++++++ etherparse-defrag/src/lib.rs | 21 ++ 9 files changed, 697 insertions(+) create mode 100644 etherparse-defrag/Cargo.toml create mode 100644 etherparse-defrag/README.md create mode 100644 etherparse-defrag/src/ip_defrag_buf.rs create mode 100644 etherparse-defrag/src/ip_defrag_error.rs create mode 100644 etherparse-defrag/src/ip_defrag_pool.rs create mode 100644 etherparse-defrag/src/ip_frag_id.rs create mode 100644 etherparse-defrag/src/ip_frag_range.rs create mode 100644 etherparse-defrag/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index a4afc263..4583f52d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,6 @@ resolver = "2" members = [ "etherparse", + "etherparse-defrag", "etherparse_proptest_generators", ] diff --git a/etherparse-defrag/Cargo.toml b/etherparse-defrag/Cargo.toml new file mode 100644 index 00000000..17265b6f --- /dev/null +++ b/etherparse-defrag/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "etherparse-defrag" +version = "0.14.2" +authors = ["Julian Schmid "] +edition = "2021" +repository = "https://github.com/JulianSchmid/etherparse" +description = "Crate to reconstruct fragmented IP packets." +categories = ["network-programming", "parser-implementations"] +keywords = ["ipv4", "ipv6", "vlan", "udp", "tcp"] +license = "MIT OR Apache-2.0" +readme = "README.md" +exclude = [ + ".gitignore" +] + +[dependencies] +etherparse = { version = "0.14.2", path = "../etherparse" } diff --git a/etherparse-defrag/README.md b/etherparse-defrag/README.md new file mode 100644 index 00000000..2c44b26c --- /dev/null +++ b/etherparse-defrag/README.md @@ -0,0 +1,4 @@ +# etherparse-defrag + +Allows reconstruction of fragmented IP packets. + diff --git a/etherparse-defrag/src/ip_defrag_buf.rs b/etherparse-defrag/src/ip_defrag_buf.rs new file mode 100644 index 00000000..4c40dcd4 --- /dev/null +++ b/etherparse-defrag/src/ip_defrag_buf.rs @@ -0,0 +1,377 @@ +use crate::*; +use etherparse::*; + +/// Buffer to reconstruct a single fragmented IP packet. +#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct IpDefragBuf { + /// IP number identifying the type of payload. + ip_number: IpNumber, + + /// Data buffer that should contain the SOMEIP header + reconstructed payload in the end. + data: Vec, + + /// Contains the ranges filled with data. + sections: Vec, + + /// End length of the defragmented packet (set if a packet with ) + end: Option, +} + +impl IpDefragBuf { + pub fn new(ip_number: IpNumber, mut data: Vec, mut sections: Vec) -> IpDefragBuf { + IpDefragBuf { + ip_number, + data: { + data.clear(); + data + }, + sections: { + sections.clear(); + sections + }, + end: None, + } + } + + /// Return the ip number of the payload data that gets restored. + #[inline] + pub fn ip_number(&self) -> IpNumber { + self.ip_number + } + + /// Data buffer in which data packet is reconstructed. + #[inline] + pub fn data(&self) -> &Vec { + &self.data + } + + /// Sections completed of the packet. + #[inline] + pub fn sections(&self) -> &Vec { + &self.sections + } + + /// Sections completed of the packet. + #[inline] + pub fn end(&self) -> Option { + self.end + } + + /// Add a IPv4 slice + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + pub fn add( + &mut self, + offset: IpFragOffset, + more_fragments: bool, + payload: &[u8] + ) -> Result<(), IpDefragError> { + use IpDefragError::*; + + // validate lengths + let Ok(len_u16) = u16::try_from(payload.len()) else { + return Err(SegmentTooBig { + offset, + payload_len: payload.len(), + max: MAX_IP_DEFRAG_LEN_U16, + }); + }; + + let Some(end) = offset.value().checked_add(len_u16) else { + return Err(SegmentTooBig { + offset, + payload_len: payload.len(), + max: MAX_IP_DEFRAG_LEN_U16, + }); + }; + + // validate that the payload len is a multiple of 16 in case it is not the end + if more_fragments && 0 != payload.len() & 0b1111 { + return Err(UnalignedFragmentPayloadLen { + offset, + payload_len: payload.len(), + }); + } + + // check the section is not already ended + if let Some(previous_end) = self.end { + // either the end is after the current position + if previous_end < end || ((false == more_fragments) && end != previous_end) { + return Err(ConflictingEnd { + previous_end, + conflicting_end: end, + }); + } + } + + // get enough memory to store the de-fragmented + let required_len = usize::from(end); + if self.data.len() < required_len { + if self.data.capacity() < required_len + && self + .data + .try_reserve(required_len - self.data.len()) + .is_err() + { + return Err(AllocationFailure { len: required_len }); + } + unsafe { + self.data.set_len(required_len); + } + } + + // insert new data + let data_offset = usize::from(offset.value()); + self.data[data_offset..data_offset + payload.len()].copy_from_slice(payload); + + // update sections + let mut new_section = IpFragRange { + start: offset.value(), + end, + }; + + // merge overlapping section into new section and remove them + self.sections.retain(|it| -> bool { + if let Some(merged) = new_section.merge(*it) { + new_section = merged; + false + } else { + true + } + }); + self.sections.push(new_section); + + // set end + if false == more_fragments { + self.end = Some(end); + // restrict the length based on the length + unsafe { + // SAFETY: Safe as the length has previously been checked to be at least "end" long + self.data.set_len(usize::from(end)); + } + } + + Ok(()) + } + + /// Returns true if the fragmented data is completed. + pub fn is_complete(&self) -> bool { + self.end.is_some() && 1 == self.sections.len() && 0 == self.sections[0].start + } + + /// Consume the [`IpDefragBuf`] and return the buffers. + #[inline] + pub fn take_bufs(self) -> (Vec, Vec) { + (self.data, self.sections) + } +} + +#[cfg(test)] +mod test { + use etherparse::*; + use crate::*; + + #[test] + fn debug_clone_eq() { + let buf = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); + let _ = format!("{:?}", buf); + assert_eq!(buf, buf.clone()); + assert_eq!(buf.cmp(&buf), core::cmp::Ordering::Equal); + assert_eq!(buf.partial_cmp(&buf), Some(core::cmp::Ordering::Equal)); + + use core::hash::{Hash, Hasher}; + use std::collections::hash_map::DefaultHasher; + let h1 = { + let mut h = DefaultHasher::new(); + buf.hash(&mut h); + h.finish() + }; + let h2 = { + let mut h = DefaultHasher::new(); + buf.clone().hash(&mut h); + h.finish() + }; + assert_eq!(h1, h2); + } + + #[test] + fn new() { + let actual = IpDefragBuf::new(IpNumber::UDP, vec![1], vec![IpFragRange{start: 0, end: 1}]); + assert_eq!(actual.ip_number(), IpNumber::UDP); + assert!(actual.data().is_empty()); + assert!(actual.sections().is_empty()); + assert!(actual.end().is_none()); + } + + /// Returns a u8 vec counting up from "start" until len is reached (truncating bits greater then u8). + fn sequence(start: usize, len: usize) -> Vec { + let mut result = Vec::with_capacity(len); + for i in start..start + len { + result.push((i & 0xff) as u8); + } + result + } + + #[rustfmt::skip] + #[test] + fn add() { + use IpDefragError::*; + + // normal reconstruction + { + let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); + + let actions = [ + (false, (0, true, &sequence(0,16))), + (false, (16, true, &sequence(16,32))), + (true, (48, false, &sequence(48,16))), + ]; + for a in actions { + buffer.add( + IpFragOffset::try_new(a.1.0).unwrap(), + a.1.1, + a.1.2 + ).unwrap(); + assert_eq!(a.0, buffer.is_complete()); + } + let (payload, _) = buffer.take_bufs(); + assert_eq!(&payload, &sequence(0,16*4)); + } + + // overlapping reconstruction + { + let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); + + let actions = [ + (false, (0, true, sequence(0,16))), + // will be overwritten + (false, (32, true, sequence(0,16))), + // overwrites + (false, (32, false, sequence(32,16))), + // completes + (true, (16, true, sequence(16,16))), + ]; + for a in actions { + buffer.add( + IpFragOffset::try_new(a.1.0).unwrap(), + a.1.1, + &a.1.2 + ).unwrap(); + assert_eq!(a.0, buffer.is_complete()); + } + let (payload, _) = buffer.take_bufs(); + assert_eq!(&payload, &sequence(0,16*3)); + } + + // reverse order + { + let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); + + let actions = [ + (false, (48, false, &sequence(48,16))), + (false, (16, true, &sequence(16,32))), + (true, (0, true, &sequence(0,16))), + ]; + for a in actions { + buffer.add( + IpFragOffset::try_new(a.1.0).unwrap(), + a.1.1, + &a.1.2 + ).unwrap(); + assert_eq!(a.0, buffer.is_complete()); + } + let (payload, _) = buffer.take_bufs(); + assert_eq!(&payload, &sequence(0,16*4)); + } + + // error packet bigger then max (payload len only) + { + let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); + let payload_len = usize::from(u16::MAX) + 1; + assert_eq!( + SegmentTooBig { offset: IpFragOffset::try_new(0).unwrap(), payload_len, max: u16::MAX }, + buffer.add( + IpFragOffset::try_new(0).unwrap(), + true, + &sequence(0, payload_len) + ).unwrap_err() + ); + } + + // error packet bigger then max (offset + payload len) + { + let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); + let payload_len = usize::from(u16::MAX) - 32 - 16 + 1; + assert_eq!( + SegmentTooBig { offset: IpFragOffset::try_new(32 + 16).unwrap(), payload_len, max: u16::MAX }, + buffer.add( + IpFragOffset::try_new(32 + 16).unwrap(), + true, + &sequence(0,payload_len) + ).unwrap_err() + ); + } + + // check packets that fill exactly to the max work + { + let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); + + let payload_len = usize::from(u16::MAX - 16); + assert_eq!( + Ok(()), + buffer.add( + IpFragOffset::try_new(16).unwrap(), + false, + &sequence(0, payload_len) + ) + ); + } + + // packets conflicting with previously seen end + for bad_offset in 1..16 { + let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); + assert_eq!( + UnalignedFragmentPayloadLen { + offset: IpFragOffset::try_new(48).unwrap(), + payload_len: bad_offset + }, + buffer.add( + IpFragOffset::try_new(48).unwrap(), + true, + &sequence(0, bad_offset) + ).unwrap_err() + ); + } + + // test that conflicting ends trigger errors (received a different end) + { + let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); + + // setup an end (aka no more segements) + buffer.add( + IpFragOffset::try_new(32).unwrap(), + false, + &sequence(32,16) + ).unwrap(); + + // test that a "non end" going over the end package triggers an error + assert_eq!( + ConflictingEnd { previous_end: 32 + 16, conflicting_end: 48 + 16 }, + buffer.add( + IpFragOffset::try_new(48).unwrap(), + true, + &sequence(48,16) + ).unwrap_err() + ); + + // test that a new end at an earlier position triggers an error + assert_eq!( + ConflictingEnd { previous_end: 32 + 16, conflicting_end: 16 + 16 }, + buffer.add( + IpFragOffset::try_new(16).unwrap(), + false, + &sequence(16,16) + ).unwrap_err() + ); + } + } +} diff --git a/etherparse-defrag/src/ip_defrag_error.rs b/etherparse-defrag/src/ip_defrag_error.rs new file mode 100644 index 00000000..3e2a66cb --- /dev/null +++ b/etherparse-defrag/src/ip_defrag_error.rs @@ -0,0 +1,101 @@ +use etherparse::IpFragOffset; + +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum IpDefragError { + /// Error if a payload lenght of a IP Fragment packet is not a multiple of 16 + /// and the "more fragments" flag is set. + UnalignedFragmentPayloadLen { offset: IpFragOffset, payload_len: usize }, + + /// Error if a segment is bigger then the maximum allowed size. + SegmentTooBig { + offset: IpFragOffset, + payload_len: usize, + max: u16, + }, + + /// Error if multiple TP segments were received with the "more segment" + /// unset and differing end points. + ConflictingEnd { + /// Offset + tp_payload.len() of the previous package with "more segment" unset. + previous_end: u16, + + /// Offset + tp_payload.len() of the current package. + conflicting_end: u16, + }, + + /// Error if not enough memory could be allocated to store the TP payload. + AllocationFailure { len: usize }, +} + +impl core::fmt::Display for IpDefragError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use IpDefragError::*; + match self { + UnalignedFragmentPayloadLen{ offset, payload_len } => write!(f, "Payload length {payload_len} of IP fragment (offset {offset}) is not a multiple of 8. This is only allowed for the last fragment packet."), + SegmentTooBig{ offset, payload_len, max } => write!(f, "Overall length of IP fragment (offset {offset}, payload len: {payload_len}) bigger then the maximum allowed size of {max}."), + ConflictingEnd { previous_end, conflicting_end } => write!(f, "Received a IP fragment (offset + len: {conflicting_end}) which conflicts a package that previously set the end to {previous_end}."), + AllocationFailure { len } => write!(f, "Failed to allocate {len} bytes of memory to reconstruct the fragmented IP packets."), + } + } +} + +impl std::error::Error for IpDefragError {} + +#[cfg(test)] +mod tests { + use etherparse::IpFragOffset; + + use super::IpDefragError::*; + + #[test] + fn debug() { + let err = UnalignedFragmentPayloadLen { offset: IpFragOffset::try_new(0).unwrap(), payload_len: 16 }; + let _ = format!("{err:?}"); + } + + #[test] + fn clone_eq_hash_ord() { + use core::cmp::Ordering; + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let err = UnalignedFragmentPayloadLen { offset: IpFragOffset::try_new(0).unwrap(), payload_len: 16 }; + assert_eq!(err, err.clone()); + let hash_a = { + let mut hasher = DefaultHasher::new(); + err.hash(&mut hasher); + hasher.finish() + }; + let hash_b = { + let mut hasher = DefaultHasher::new(); + err.clone().hash(&mut hasher); + hasher.finish() + }; + assert_eq!(hash_a, hash_b); + assert_eq!(Ordering::Equal, err.cmp(&err)); + assert_eq!(Some(Ordering::Equal), err.partial_cmp(&err)); + } + + #[test] + fn fmt() { + let tests = [ + (UnalignedFragmentPayloadLen { offset: IpFragOffset::try_new(0).unwrap(), payload_len: 2 }, "Payload length 2 of IP fragment (offset 0) is not a multiple of 8. This is only allowed for the last fragment packet."), + (SegmentTooBig { offset: IpFragOffset::try_new(0).unwrap(), payload_len: 2, max: 3, }, "Overall length of IP fragment (offset 0, payload len: 2) bigger then the maximum allowed size of 3."), + (ConflictingEnd { previous_end: 2, conflicting_end: 1 }, "Received a IP fragment (offset + len: 1) which conflicts a package that previously set the end to 2."), + (AllocationFailure { len: 0 }, "Failed to allocate 0 bytes of memory to reconstruct the fragmented IP packets."), + ]; + for test in tests { + assert_eq!(format!("{}", test.0), test.1); + } + } + + #[test] + fn source() { + use std::error::Error; + assert!( + UnalignedFragmentPayloadLen { offset: IpFragOffset::try_new(0).unwrap(), payload_len: 16 } + .source() + .is_none() + ); + } +} diff --git a/etherparse-defrag/src/ip_defrag_pool.rs b/etherparse-defrag/src/ip_defrag_pool.rs new file mode 100644 index 00000000..3fd2a0b6 --- /dev/null +++ b/etherparse-defrag/src/ip_defrag_pool.rs @@ -0,0 +1,35 @@ +use crate::*; +use std::collections::HashMap; + +/// Pool of buffers to reconstruct multiple fragmented IP packets in +/// parallel (re-uses buffers to minimize allocations). +/// +/// # This implementation is NOT safe against "Out of Memory" attacks +/// +/// If you use the [`DefragPool`] in an untrusted environment an attacker could +/// cause an "out of memory error" by opening up multiple parallel TP streams, +/// never ending them and filling them up with as much data as possible. +/// +/// Mitigations will hopefully be offered in future versions but if you have +/// take care right now you can still use [`IpDefragBuf`] directly and implement the +/// connection handling and mitigation yourself. +#[derive(Debug, Clone)] +pub struct IpDefragPool +where + Timestamp: Sized + core::fmt::Debug + Clone, + CustomChannelId: Sized + core::fmt::Debug + Clone + core::hash::Hash + Eq + PartialEq, +{ + /// Currently reconstructing TP streams. + active: HashMap, (IpDefragBuf, Timestamp)>, + + /// Buffers that have finished receiving data and can be re-used. + finished: Vec, +} + +impl IpDefragPool +where + Timestamp: Sized + core::fmt::Debug + Clone, + CustomChannelId: Sized + core::fmt::Debug + Clone + core::hash::Hash + Eq + PartialEq, +{ + +} diff --git a/etherparse-defrag/src/ip_frag_id.rs b/etherparse-defrag/src/ip_frag_id.rs new file mode 100644 index 00000000..bde364b1 --- /dev/null +++ b/etherparse-defrag/src/ip_frag_id.rs @@ -0,0 +1,33 @@ +use etherparse::*; + +/// Values identifying a fragmented packet. +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct IpFragId +where + CustomChannelId: core::hash::Hash + Eq + PartialEq + Clone + Sized, +{ + /// First VLAN id of the fragmented packets. + pub outer_vlan_id: Option, + + /// Second VLAN id of the fragmented packets. + pub inner_vlan_id: Option, + + /// IP source & destination address. + pub ip_src_dst: IpSrcDst, + + /// Identifier in the IP header for the fragmented packet. + pub identifier: u32, + + /// Custom user defined channel identifier (can be used to differentiate packet + /// sources if the normal ethernet packets identifier are not enough). + pub channel_id: CustomChannelId, +} + +/// Source & destionation IP address. +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub enum IpSrcDst { + /// IPv4 source & destination pair. + Ipv4([u8; 4], [u8; 4]), + /// IPv6 source & destination pair. + Ipv6([u8; 16], [u8; 16]), +} diff --git a/etherparse-defrag/src/ip_frag_range.rs b/etherparse-defrag/src/ip_frag_range.rs new file mode 100644 index 00000000..b9db9fea --- /dev/null +++ b/etherparse-defrag/src/ip_frag_range.rs @@ -0,0 +1,108 @@ + +/// Describing the range of reconstructed data. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Ord, PartialOrd)] +pub struct IpFragRange { + /// Offset of section + pub start: u16, + /// Offset + length of section + pub end: u16, +} + +impl IpFragRange { + /// Return if the value is contained within the section. + fn is_value_connected(&self, value: u16) -> bool { + self.start <= value && self.end >= value + } + + /// Combine both sections if possible. + pub fn merge(&self, other: IpFragRange) -> Option { + if self.is_value_connected(other.start) + || self.is_value_connected(other.end) + || other.is_value_connected(self.start) + || other.is_value_connected(self.end) + { + Some(IpFragRange { + start: core::cmp::min(self.start, other.start), + end: core::cmp::max(self.end, other.end), + }) + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn debug_clone_eq() { + let section = IpFragRange { start: 1, end: 2 }; + let _ = format!("{:?}", section); + assert_eq!(section, section.clone()); + assert_eq!(section.cmp(§ion), core::cmp::Ordering::Equal); + assert_eq!( + section.partial_cmp(§ion), + Some(core::cmp::Ordering::Equal) + ); + + use core::hash::{Hash, Hasher}; + use std::collections::hash_map::DefaultHasher; + let h1 = { + let mut h = DefaultHasher::new(); + section.hash(&mut h); + h.finish() + }; + let h2 = { + let mut h = DefaultHasher::new(); + section.clone().hash(&mut h); + h.finish() + }; + assert_eq!(h1, h2); + } + + #[test] + fn is_value_connected() { + let s = IpFragRange { start: 5, end: 9 }; + assert_eq!(false, s.is_value_connected(3)); + assert_eq!(false, s.is_value_connected(4)); + assert!(s.is_value_connected(5)); + assert!(s.is_value_connected(6)); + assert!(s.is_value_connected(7)); + assert!(s.is_value_connected(8)); + assert!(s.is_value_connected(9)); + assert_eq!(false, s.is_value_connected(10)); + assert_eq!(false, s.is_value_connected(11)); + } + + #[test] + fn merge() { + let tests = [ + ((0, 1), (1, 2), Some((0, 2))), + ((0, 1), (2, 3), None), + ((3, 7), (1, 2), None), + ((3, 7), (1, 3), Some((1, 7))), + ((3, 7), (1, 4), Some((1, 7))), + ((3, 7), (1, 5), Some((1, 7))), + ((3, 7), (1, 6), Some((1, 7))), + ((3, 7), (1, 7), Some((1, 7))), + ((3, 7), (1, 8), Some((1, 8))), + ]; + for t in tests { + let a = IpFragRange { + start: t.0 .0, + end: t.0 .1, + }; + let b = IpFragRange { + start: t.1 .0, + end: t.1 .1, + }; + let expected = t.2.map(|v| IpFragRange { + start: v.0, + end: v.1, + }); + assert_eq!(a.merge(b), expected); + assert_eq!(b.merge(a), expected); + } + } +} diff --git a/etherparse-defrag/src/lib.rs b/etherparse-defrag/src/lib.rs new file mode 100644 index 00000000..f4f83389 --- /dev/null +++ b/etherparse-defrag/src/lib.rs @@ -0,0 +1,21 @@ + +mod ip_defrag_buf; +pub use ip_defrag_buf::*; + +mod ip_defrag_error; +pub use ip_defrag_error::*; + +mod ip_defrag_pool; +pub use ip_defrag_pool::*; + +mod ip_frag_id; +pub use ip_frag_id::*; + +mod ip_frag_range; +pub use ip_frag_range::*; + +/// Maximum length of a defragmented packet as [`u16`]. +pub const MAX_IP_DEFRAG_LEN_U16: u16 = u16::MAX; + +/// Maximum length of a defragmented packet as [`usize`]. +pub const MAX_IP_DEFRAG_LEN: usize = MAX_IP_DEFRAG_LEN_U16 as usize; From d274e7d1a6231ec4bdaca8f991cc1d64d4efae38 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Sun, 24 Mar 2024 22:09:11 +0100 Subject: [PATCH 02/14] Update etherparse-defrag Cargo.toml --- etherparse-defrag/Cargo.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/etherparse-defrag/Cargo.toml b/etherparse-defrag/Cargo.toml index 17265b6f..e8984f25 100644 --- a/etherparse-defrag/Cargo.toml +++ b/etherparse-defrag/Cargo.toml @@ -1,17 +1,14 @@ [package] name = "etherparse-defrag" -version = "0.14.2" +version = "0.14.0" authors = ["Julian Schmid "] edition = "2021" repository = "https://github.com/JulianSchmid/etherparse" description = "Crate to reconstruct fragmented IP packets." categories = ["network-programming", "parser-implementations"] -keywords = ["ipv4", "ipv6", "vlan", "udp", "tcp"] +keywords = ["ipv4", "ipv6"] license = "MIT OR Apache-2.0" readme = "README.md" -exclude = [ - ".gitignore" -] [dependencies] etherparse = { version = "0.14.2", path = "../etherparse" } From 294fac0cbf2ae1f3d797d235e3a5b84e8dfca0f0 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Mon, 1 Apr 2024 07:02:19 +0200 Subject: [PATCH 03/14] Further iteration on defragmentation implementation --- .../src/ip_defrag_payload_vec.rs | 67 ++++++++++ etherparse-defrag/src/ip_defrag_pool.rs | 116 +++++++++++++++++- etherparse-defrag/src/ip_frag_id.rs | 18 +-- .../src/ip_frag_version_spec_id.rs | 16 +++ etherparse-defrag/src/lib.rs | 22 ++++ 5 files changed, 222 insertions(+), 17 deletions(-) create mode 100644 etherparse-defrag/src/ip_defrag_payload_vec.rs create mode 100644 etherparse-defrag/src/ip_frag_version_spec_id.rs diff --git a/etherparse-defrag/src/ip_defrag_payload_vec.rs b/etherparse-defrag/src/ip_defrag_payload_vec.rs new file mode 100644 index 00000000..8970a28e --- /dev/null +++ b/etherparse-defrag/src/ip_defrag_payload_vec.rs @@ -0,0 +1,67 @@ +use etherparse::*; + +/// Payload of an IP packet. +#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] +pub struct IpDefragPayloadVec { + /// Identifying content of the payload. + pub ip_number: IpNumber, + + /// Length field that was used to determine the length + /// of the payload (e.g. IPv6 "payload_length" field). + pub len_source: LenSource, + + /// Payload + pub payload: Vec, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn debug() { + let s = IpDefragPayloadVec { + ip_number: IpNumber::UDP, + len_source: LenSource::Slice, + payload: vec![], + }; + assert_eq!( + format!( + "IpDefragPayloadVec {{ ip_number: {:?}, len_source: {:?}, payload: {:?} }}", + s.ip_number, + s.len_source, + s.payload + ), + format!("{:?}", s) + ); + } + + #[test] + fn clone_eq_hash_ord() { + let s = IpDefragPayloadVec { + ip_number: IpNumber::UDP, + len_source: LenSource::Slice, + payload: vec![], + }; + assert_eq!(s.clone(), s); + + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let a_hash = { + let mut hasher = DefaultHasher::new(); + s.hash(&mut hasher); + hasher.finish() + }; + let b_hash = { + let mut hasher = DefaultHasher::new(); + s.clone().hash(&mut hasher); + hasher.finish() + }; + assert_eq!(a_hash, b_hash); + + use std::cmp::Ordering; + assert_eq!(s.clone().cmp(&s), Ordering::Equal); + assert_eq!(s.clone().partial_cmp(&s), Some(Ordering::Equal)); + } +} diff --git a/etherparse-defrag/src/ip_defrag_pool.rs b/etherparse-defrag/src/ip_defrag_pool.rs index 3fd2a0b6..b6f79fc6 100644 --- a/etherparse-defrag/src/ip_defrag_pool.rs +++ b/etherparse-defrag/src/ip_defrag_pool.rs @@ -1,4 +1,5 @@ use crate::*; +use etherparse::*; use std::collections::HashMap; /// Pool of buffers to reconstruct multiple fragmented IP packets in @@ -19,11 +20,14 @@ where Timestamp: Sized + core::fmt::Debug + Clone, CustomChannelId: Sized + core::fmt::Debug + Clone + core::hash::Hash + Eq + PartialEq, { - /// Currently reconstructing TP streams. + /// Currently reconstructing IP packets. active: HashMap, (IpDefragBuf, Timestamp)>, - /// Buffers that have finished receiving data and can be re-used. - finished: Vec, + /// Data buffers that have finished receiving data and can be re-used. + finished_data_bufs: Vec>, + + /// Section buffers that have finished receiving data and can be re-used. + finished_section_bufs: Vec>, } impl IpDefragPool @@ -31,5 +35,111 @@ where Timestamp: Sized + core::fmt::Debug + Clone, CustomChannelId: Sized + core::fmt::Debug + Clone + core::hash::Hash + Eq + PartialEq, { + /// Add data from a sliced packet. + pub fn process_sliced_packet(&mut self, slice: &SlicedPacket, channel_id: CustomChannelId) -> Result<(), IpDefragError> { + + // extract the fragment related data and skip non-fragmented packets + let (frag_id, offset, more_fragments, payload) = match &slice.net { + Some(NetSlice::Ipv4(ipv4)) => { + let header = ipv4.header(); + if false == header.is_fragmenting_payload() { + // nothing to defragment here, skip packet + return Ok(()); + } + + let (outer_vlan_id, inner_vlan_id) = match &slice.vlan { + Some(VlanSlice::SingleVlan(s)) => (Some(s.vlan_identifier()), None), + Some(VlanSlice::DoubleVlan(d)) => (Some(d.outer().vlan_identifier()), Some(d.inner().vlan_identifier())), + None => (None, None) + }; + + ( + IpFragId { + outer_vlan_id, + inner_vlan_id, + ip: IpFragVersionSpecId::Ipv4{ + source: header.source(), + destination: header.destination(), + identification: header.identification(), + }, + channel_id, + }, + header.fragments_offset(), + header.more_fragments(), + ipv4.payload() + ) + } + Some(NetSlice::Ipv6(ipv6)) => { + // skip unfragmented packets + if false == ipv6.is_payload_fragmented() { + // nothing to defragment here, skip packet + return Ok(()); + } + + // get fragmentation header + let frag = { + let mut f = None; + for ext in ipv6.extensions().clone().into_iter() { + use Ipv6ExtensionSlice::*; + if let Fragment(frag_it) = ext { + f = Some(frag_it); + break; + } + } + if let Some(f) = f { + f.to_header() + } else { + // nothing to defragment here, skip packet + return Ok(()); + } + }; + + let (outer_vlan_id, inner_vlan_id) = match &slice.vlan { + Some(VlanSlice::SingleVlan(s)) => (Some(s.vlan_identifier()), None), + Some(VlanSlice::DoubleVlan(d)) => (Some(d.outer().vlan_identifier()), Some(d.inner().vlan_identifier())), + None => (None, None) + }; + + // calculate frag id + ( + IpFragId { + outer_vlan_id, + inner_vlan_id, + ip: IpFragVersionSpecId::Ipv6{ + source: ipv6.header().source(), + destination: ipv6.header().destination(), + identification: frag.identification, + }, + channel_id, + }, + frag.fragment_offset, + frag.more_fragments, + ipv6.payload() + ) + } + None => { + // nothing to defragment here, skip packet + return Ok(()); + } + }; + + // get the reconstruction buffer + use std::collections::hash_map::Entry; + match self.active.entry(frag_id) { + Entry::Occupied(mut entry) => { + let buf = entry.get_mut(); + + } + Entry::Vacant(mut entry) => { + + } + } + + //header. + //slice. + + + Ok(()) + } } diff --git a/etherparse-defrag/src/ip_frag_id.rs b/etherparse-defrag/src/ip_frag_id.rs index bde364b1..1edb9d40 100644 --- a/etherparse-defrag/src/ip_frag_id.rs +++ b/etherparse-defrag/src/ip_frag_id.rs @@ -1,5 +1,7 @@ use etherparse::*; +use crate::IpFragVersionSpecId; + /// Values identifying a fragmented packet. #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct IpFragId @@ -12,22 +14,10 @@ where /// Second VLAN id of the fragmented packets. pub inner_vlan_id: Option, - /// IP source & destination address. - pub ip_src_dst: IpSrcDst, - - /// Identifier in the IP header for the fragmented packet. - pub identifier: u32, + /// IP source & destination address & identifaction field. + pub ip: IpFragVersionSpecId, /// Custom user defined channel identifier (can be used to differentiate packet /// sources if the normal ethernet packets identifier are not enough). pub channel_id: CustomChannelId, } - -/// Source & destionation IP address. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum IpSrcDst { - /// IPv4 source & destination pair. - Ipv4([u8; 4], [u8; 4]), - /// IPv6 source & destination pair. - Ipv6([u8; 16], [u8; 16]), -} diff --git a/etherparse-defrag/src/ip_frag_version_spec_id.rs b/etherparse-defrag/src/ip_frag_version_spec_id.rs new file mode 100644 index 00000000..de33a06b --- /dev/null +++ b/etherparse-defrag/src/ip_frag_version_spec_id.rs @@ -0,0 +1,16 @@ +/// IPv4 & IPv6 specific fragment identifying information. +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub enum IpFragVersionSpecId { + /// IPv4 specific data. + Ipv4{ + source: [u8; 4], + destination: [u8; 4], + identification: u16, + }, + /// IPv6 specific data. + Ipv6{ + source: [u8; 16], + destination: [u8; 16], + identification: u32, + }, +} diff --git a/etherparse-defrag/src/lib.rs b/etherparse-defrag/src/lib.rs index f4f83389..f5ad568d 100644 --- a/etherparse-defrag/src/lib.rs +++ b/etherparse-defrag/src/lib.rs @@ -1,10 +1,29 @@ +// # Reason for 'bool_comparison' disable: +// +// Clippy triggers triggers errors like the following if the warning stays enabled: +// +// warning: equality checks against false can be replaced by a negation +// --> src/packet_decoder.rs:131:20 +// | +// 131 | if false == fragmented { +// | ^^^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!fragmented` +// +// +// I prefer to write `false == value` instead of `!value` as it +// is more visually striking and is not as easy to overlook as the single +// character '!'. +#![allow(clippy::bool_comparison)] + mod ip_defrag_buf; pub use ip_defrag_buf::*; mod ip_defrag_error; pub use ip_defrag_error::*; +mod ip_defrag_payload_vec; +pub use ip_defrag_payload_vec::*; + mod ip_defrag_pool; pub use ip_defrag_pool::*; @@ -14,6 +33,9 @@ pub use ip_frag_id::*; mod ip_frag_range; pub use ip_frag_range::*; +mod ip_frag_version_spec_id; +pub use ip_frag_version_spec_id::*; + /// Maximum length of a defragmented packet as [`u16`]. pub const MAX_IP_DEFRAG_LEN_U16: u16 = u16::MAX; From 7e15b8ab6e668f40ad26a7be608732d0813367f4 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Tue, 2 Apr 2024 11:47:25 +0200 Subject: [PATCH 04/14] Moved defrag crate into etherparse as module --- Cargo.toml | 1 - etherparse-defrag/Cargo.toml | 14 ------ etherparse-defrag/README.md | 4 -- etherparse-defrag/src/lib.rs | 43 ------------------- .../src/defrag}/ip_defrag_buf.rs | 8 ++-- .../src/defrag}/ip_defrag_error.rs | 6 +-- .../src/defrag}/ip_defrag_payload_vec.rs | 4 +- .../src/defrag}/ip_defrag_pool.rs | 4 +- .../src/defrag}/ip_frag_id.rs | 4 +- .../src/defrag}/ip_frag_range.rs | 1 + .../src/defrag}/ip_frag_version_spec_id.rs | 0 etherparse/src/defrag/mod.rs | 26 +++++++++++ etherparse/src/lib.rs | 5 +++ 13 files changed, 45 insertions(+), 75 deletions(-) delete mode 100644 etherparse-defrag/Cargo.toml delete mode 100644 etherparse-defrag/README.md delete mode 100644 etherparse-defrag/src/lib.rs rename {etherparse-defrag/src => etherparse/src/defrag}/ip_defrag_buf.rs (99%) rename {etherparse-defrag/src => etherparse/src/defrag}/ip_defrag_error.rs (98%) rename {etherparse-defrag/src => etherparse/src/defrag}/ip_defrag_payload_vec.rs (96%) rename {etherparse-defrag/src => etherparse/src/defrag}/ip_defrag_pool.rs (99%) rename {etherparse-defrag/src => etherparse/src/defrag}/ip_frag_id.rs (92%) rename {etherparse-defrag/src => etherparse/src/defrag}/ip_frag_range.rs (99%) rename {etherparse-defrag/src => etherparse/src/defrag}/ip_frag_version_spec_id.rs (100%) create mode 100644 etherparse/src/defrag/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 4583f52d..a4afc263 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,5 @@ resolver = "2" members = [ "etherparse", - "etherparse-defrag", "etherparse_proptest_generators", ] diff --git a/etherparse-defrag/Cargo.toml b/etherparse-defrag/Cargo.toml deleted file mode 100644 index e8984f25..00000000 --- a/etherparse-defrag/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "etherparse-defrag" -version = "0.14.0" -authors = ["Julian Schmid "] -edition = "2021" -repository = "https://github.com/JulianSchmid/etherparse" -description = "Crate to reconstruct fragmented IP packets." -categories = ["network-programming", "parser-implementations"] -keywords = ["ipv4", "ipv6"] -license = "MIT OR Apache-2.0" -readme = "README.md" - -[dependencies] -etherparse = { version = "0.14.2", path = "../etherparse" } diff --git a/etherparse-defrag/README.md b/etherparse-defrag/README.md deleted file mode 100644 index 2c44b26c..00000000 --- a/etherparse-defrag/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# etherparse-defrag - -Allows reconstruction of fragmented IP packets. - diff --git a/etherparse-defrag/src/lib.rs b/etherparse-defrag/src/lib.rs deleted file mode 100644 index f5ad568d..00000000 --- a/etherparse-defrag/src/lib.rs +++ /dev/null @@ -1,43 +0,0 @@ - -// # Reason for 'bool_comparison' disable: -// -// Clippy triggers triggers errors like the following if the warning stays enabled: -// -// warning: equality checks against false can be replaced by a negation -// --> src/packet_decoder.rs:131:20 -// | -// 131 | if false == fragmented { -// | ^^^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!fragmented` -// -// -// I prefer to write `false == value` instead of `!value` as it -// is more visually striking and is not as easy to overlook as the single -// character '!'. -#![allow(clippy::bool_comparison)] - -mod ip_defrag_buf; -pub use ip_defrag_buf::*; - -mod ip_defrag_error; -pub use ip_defrag_error::*; - -mod ip_defrag_payload_vec; -pub use ip_defrag_payload_vec::*; - -mod ip_defrag_pool; -pub use ip_defrag_pool::*; - -mod ip_frag_id; -pub use ip_frag_id::*; - -mod ip_frag_range; -pub use ip_frag_range::*; - -mod ip_frag_version_spec_id; -pub use ip_frag_version_spec_id::*; - -/// Maximum length of a defragmented packet as [`u16`]. -pub const MAX_IP_DEFRAG_LEN_U16: u16 = u16::MAX; - -/// Maximum length of a defragmented packet as [`usize`]. -pub const MAX_IP_DEFRAG_LEN: usize = MAX_IP_DEFRAG_LEN_U16 as usize; diff --git a/etherparse-defrag/src/ip_defrag_buf.rs b/etherparse/src/defrag/ip_defrag_buf.rs similarity index 99% rename from etherparse-defrag/src/ip_defrag_buf.rs rename to etherparse/src/defrag/ip_defrag_buf.rs index 4c40dcd4..bdae4885 100644 --- a/etherparse-defrag/src/ip_defrag_buf.rs +++ b/etherparse/src/defrag/ip_defrag_buf.rs @@ -1,5 +1,5 @@ -use crate::*; -use etherparse::*; +use crate::{*, defrag::*}; +use std::vec::Vec; /// Buffer to reconstruct a single fragmented IP packet. #[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] @@ -167,8 +167,8 @@ impl IpDefragBuf { #[cfg(test)] mod test { - use etherparse::*; - use crate::*; + use super::*; + use std::{format, vec}; #[test] fn debug_clone_eq() { diff --git a/etherparse-defrag/src/ip_defrag_error.rs b/etherparse/src/defrag/ip_defrag_error.rs similarity index 98% rename from etherparse-defrag/src/ip_defrag_error.rs rename to etherparse/src/defrag/ip_defrag_error.rs index 3e2a66cb..7f314dac 100644 --- a/etherparse-defrag/src/ip_defrag_error.rs +++ b/etherparse/src/defrag/ip_defrag_error.rs @@ -1,4 +1,4 @@ -use etherparse::IpFragOffset; +use crate::*; #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum IpDefragError { @@ -43,9 +43,9 @@ impl std::error::Error for IpDefragError {} #[cfg(test)] mod tests { - use etherparse::IpFragOffset; - + use super::*; use super::IpDefragError::*; + use std::format; #[test] fn debug() { diff --git a/etherparse-defrag/src/ip_defrag_payload_vec.rs b/etherparse/src/defrag/ip_defrag_payload_vec.rs similarity index 96% rename from etherparse-defrag/src/ip_defrag_payload_vec.rs rename to etherparse/src/defrag/ip_defrag_payload_vec.rs index 8970a28e..5c95b278 100644 --- a/etherparse-defrag/src/ip_defrag_payload_vec.rs +++ b/etherparse/src/defrag/ip_defrag_payload_vec.rs @@ -1,4 +1,5 @@ -use etherparse::*; +use crate::*; +use std::vec::Vec; /// Payload of an IP packet. #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] @@ -17,6 +18,7 @@ pub struct IpDefragPayloadVec { #[cfg(test)] mod test { use super::*; + use std::{format, vec}; #[test] fn debug() { diff --git a/etherparse-defrag/src/ip_defrag_pool.rs b/etherparse/src/defrag/ip_defrag_pool.rs similarity index 99% rename from etherparse-defrag/src/ip_defrag_pool.rs rename to etherparse/src/defrag/ip_defrag_pool.rs index b6f79fc6..caf3f148 100644 --- a/etherparse-defrag/src/ip_defrag_pool.rs +++ b/etherparse/src/defrag/ip_defrag_pool.rs @@ -1,6 +1,6 @@ -use crate::*; -use etherparse::*; +use crate::{*, defrag::*}; use std::collections::HashMap; +use std::vec::Vec; /// Pool of buffers to reconstruct multiple fragmented IP packets in /// parallel (re-uses buffers to minimize allocations). diff --git a/etherparse-defrag/src/ip_frag_id.rs b/etherparse/src/defrag/ip_frag_id.rs similarity index 92% rename from etherparse-defrag/src/ip_frag_id.rs rename to etherparse/src/defrag/ip_frag_id.rs index 1edb9d40..c2463fd7 100644 --- a/etherparse-defrag/src/ip_frag_id.rs +++ b/etherparse/src/defrag/ip_frag_id.rs @@ -1,6 +1,4 @@ -use etherparse::*; - -use crate::IpFragVersionSpecId; +use crate::{*, defrag::*}; /// Values identifying a fragmented packet. #[derive(Debug, Clone, Hash, Eq, PartialEq)] diff --git a/etherparse-defrag/src/ip_frag_range.rs b/etherparse/src/defrag/ip_frag_range.rs similarity index 99% rename from etherparse-defrag/src/ip_frag_range.rs rename to etherparse/src/defrag/ip_frag_range.rs index b9db9fea..b241f5e8 100644 --- a/etherparse-defrag/src/ip_frag_range.rs +++ b/etherparse/src/defrag/ip_frag_range.rs @@ -34,6 +34,7 @@ impl IpFragRange { #[cfg(test)] mod test { use super::*; + use alloc::format; #[test] fn debug_clone_eq() { diff --git a/etherparse-defrag/src/ip_frag_version_spec_id.rs b/etherparse/src/defrag/ip_frag_version_spec_id.rs similarity index 100% rename from etherparse-defrag/src/ip_frag_version_spec_id.rs rename to etherparse/src/defrag/ip_frag_version_spec_id.rs diff --git a/etherparse/src/defrag/mod.rs b/etherparse/src/defrag/mod.rs new file mode 100644 index 00000000..2285f908 --- /dev/null +++ b/etherparse/src/defrag/mod.rs @@ -0,0 +1,26 @@ +mod ip_defrag_buf; +pub use ip_defrag_buf::*; + +mod ip_defrag_error; +pub use ip_defrag_error::*; + +mod ip_defrag_payload_vec; +pub use ip_defrag_payload_vec::*; + +mod ip_defrag_pool; +pub use ip_defrag_pool::*; + +mod ip_frag_id; +pub use ip_frag_id::*; + +mod ip_frag_range; +pub use ip_frag_range::*; + +mod ip_frag_version_spec_id; +pub use ip_frag_version_spec_id::*; + +/// Maximum length of a defragmented packet as [`u16`]. +pub const MAX_IP_DEFRAG_LEN_U16: u16 = u16::MAX; + +/// Maximum length of a defragmented packet as [`usize`]. +pub const MAX_IP_DEFRAG_LEN: usize = MAX_IP_DEFRAG_LEN_U16 as usize; diff --git a/etherparse/src/lib.rs b/etherparse/src/lib.rs index 8176e2a9..8863133c 100644 --- a/etherparse/src/lib.rs +++ b/etherparse/src/lib.rs @@ -302,6 +302,11 @@ extern crate std; /// Module containing error types that can be triggered. pub mod err; +/// Module containing helpers to re-assemble fragmented packets (contains allocations). +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod defrag; + mod link; pub use crate::link::arp_hardware_id::*; pub use crate::link::double_vlan_header::*; From 4792c2b3de6318b687e6ca563df482cf7a2cbe7c Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Tue, 2 Apr 2024 13:09:23 +0200 Subject: [PATCH 05/14] Adapt readme to defrag module --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8e621795..67bbc7fd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # etherparse -A zero allocation library for parsing & writing a bunch of packet based protocols (EthernetII, IPv4, IPv6, UDP, TCP ...). +A mostly zero allocation library for parsing & writing a bunch of packet based protocols (EthernetII, IPv4, IPv6, UDP, TCP ...). Currently supported are: * Ethernet II @@ -32,7 +32,7 @@ Etherparse is intended to provide the basic network parsing functions that allow Some key points are: * It is completely written in Rust and thoroughly tested. -* Special attention has been paid to not use allocations or syscalls. +* Special attention has been paid to not use allocations or syscalls except in the "defragmentation" code. * The package is still in development and can & will still change. * The current focus of development is on the most popular protocols in the internet & transport layer. From a9e2bf66851cf35b2ef9fced5e110a9c32295a0f Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Thu, 11 Apr 2024 08:12:07 +0200 Subject: [PATCH 06/14] Further work on defragmentation --- etherparse/src/defrag/ip_defrag_pool.rs | 100 ++++++++++++++++++++---- etherparse/src/defrag/ip_frag_id.rs | 3 + 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/etherparse/src/defrag/ip_defrag_pool.rs b/etherparse/src/defrag/ip_defrag_pool.rs index caf3f148..3a025458 100644 --- a/etherparse/src/defrag/ip_defrag_pool.rs +++ b/etherparse/src/defrag/ip_defrag_pool.rs @@ -35,16 +35,25 @@ where Timestamp: Sized + core::fmt::Debug + Clone, CustomChannelId: Sized + core::fmt::Debug + Clone + core::hash::Hash + Eq + PartialEq, { + + pub fn new() -> IpDefragPool { + IpDefragPool { + active: HashMap::new(), + finished_data_bufs: Vec::new(), + finished_section_bufs: Vec::new(), + } + } + /// Add data from a sliced packet. - pub fn process_sliced_packet(&mut self, slice: &SlicedPacket, channel_id: CustomChannelId) -> Result<(), IpDefragError> { + pub fn process_sliced_packet(&mut self, slice: &SlicedPacket, timestamp: Timestamp, channel_id: CustomChannelId) -> Result, IpDefragError> { // extract the fragment related data and skip non-fragmented packets - let (frag_id, offset, more_fragments, payload) = match &slice.net { + let (frag_id, offset, more_fragments, payload, is_ipv4) = match &slice.net { Some(NetSlice::Ipv4(ipv4)) => { let header = ipv4.header(); if false == header.is_fragmenting_payload() { // nothing to defragment here, skip packet - return Ok(()); + return Ok(None); } let (outer_vlan_id, inner_vlan_id) = match &slice.vlan { @@ -62,18 +71,20 @@ where destination: header.destination(), identification: header.identification(), }, + payload_ip_number: ipv4.payload().ip_number, channel_id, }, header.fragments_offset(), header.more_fragments(), - ipv4.payload() + ipv4.payload(), + true ) } Some(NetSlice::Ipv6(ipv6)) => { // skip unfragmented packets if false == ipv6.is_payload_fragmented() { // nothing to defragment here, skip packet - return Ok(()); + return Ok(None); } // get fragmentation header @@ -90,7 +101,7 @@ where f.to_header() } else { // nothing to defragment here, skip packet - return Ok(()); + return Ok(None); } }; @@ -110,16 +121,18 @@ where destination: ipv6.header().destination(), identification: frag.identification, }, + payload_ip_number: ipv6.payload().ip_number, channel_id, }, frag.fragment_offset, frag.more_fragments, - ipv6.payload() + ipv6.payload(), + false ) } None => { // nothing to defragment here, skip packet - return Ok(()); + return Ok(None); } }; @@ -128,18 +141,75 @@ where match self.active.entry(frag_id) { Entry::Occupied(mut entry) => { let buf = entry.get_mut(); - + buf.0.add(offset, more_fragments, payload.payload)?; + buf.1 = timestamp; + if buf.0.is_complete() { + let (defraged_payload, sections) = entry.remove().0.take_bufs(); + self.finished_section_bufs.push(sections); + Ok(Some(IpDefragPayloadVec{ + ip_number: payload.ip_number, + len_source: if is_ipv4 { + LenSource::Ipv4HeaderTotalLen + } else { + LenSource::Ipv6HeaderPayloadLen + }, + payload: defraged_payload, + })) + } else { + Ok(None) + } } - Entry::Vacant(mut entry) => { + Entry::Vacant(entry) => { + let data_buf = if let Some(mut d) = self.finished_data_bufs.pop() { + d.clear(); + d + } else { + Vec::with_capacity(payload.payload.len()*2) + }; + let sections = if let Some(mut s) = self.finished_section_bufs.pop() { + s.clear(); + s + } else { + Vec::with_capacity(4) + }; + let mut defrag_buf = IpDefragBuf::new(payload.ip_number, data_buf, sections); + match defrag_buf.add(offset, more_fragments, payload.payload) { + Ok(()) => { + if defrag_buf.is_complete() { + let (defraged_payload, sections) = defrag_buf.take_bufs(); + self.finished_section_bufs.push(sections); + Ok(Some(IpDefragPayloadVec{ + ip_number: payload.ip_number, + len_source: if is_ipv4 { + LenSource::Ipv4HeaderTotalLen + } else { + LenSource::Ipv6HeaderPayloadLen + }, + payload: defraged_payload, + })) + } else { + entry.insert((defrag_buf, timestamp)); + Ok(None) + } + }, + Err(err) => { + // return the buffers + let (data_buf, sections) = defrag_buf.take_bufs(); + self.finished_data_bufs.push(data_buf); + self.finished_section_bufs.push(sections); + Err(err) + }, + } } } + } +} + + +#[cfg(test)] +mod test { - //header. - //slice. - - Ok(()) - } } diff --git a/etherparse/src/defrag/ip_frag_id.rs b/etherparse/src/defrag/ip_frag_id.rs index c2463fd7..a75e4dbc 100644 --- a/etherparse/src/defrag/ip_frag_id.rs +++ b/etherparse/src/defrag/ip_frag_id.rs @@ -15,6 +15,9 @@ where /// IP source & destination address & identifaction field. pub ip: IpFragVersionSpecId, + /// IP number of the payload. + pub payload_ip_number: IpNumber, + /// Custom user defined channel identifier (can be used to differentiate packet /// sources if the normal ethernet packets identifier are not enough). pub channel_id: CustomChannelId, From e48e5cabc1eca235a947a7add66fbc2c3f2de744 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Thu, 23 May 2024 07:25:17 +0200 Subject: [PATCH 07/14] Update proptest and mark some tests as not relevant for miri --- etherparse/Cargo.toml | 2 +- etherparse/src/checksum.rs | 1 + etherparse/src/compositions_tests.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/etherparse/Cargo.toml b/etherparse/Cargo.toml index 275fa583..0385e40e 100644 --- a/etherparse/Cargo.toml +++ b/etherparse/Cargo.toml @@ -26,7 +26,7 @@ std = ["arrayvec/std"] arrayvec = { version = "0.7.2", default-features = false } [dev-dependencies] -proptest = "1.0.0" +proptest = "1.4.0" [package.metadata.docs.rs] all-features = true diff --git a/etherparse/src/checksum.rs b/etherparse/src/checksum.rs index 1fdc91af..d9e0551d 100644 --- a/etherparse/src/checksum.rs +++ b/etherparse/src/checksum.rs @@ -901,6 +901,7 @@ pub mod u64_16bit_word { proptest! { #[test] + #[cfg_attr(miri, ignore)] // vec allocation reduces miri runspeed too much fn u32_u16_comparison( data in proptest::collection::vec(any::(), 0..0xfffusize) ) { diff --git a/etherparse/src/compositions_tests.rs b/etherparse/src/compositions_tests.rs index d85f8705..fd04b690 100644 --- a/etherparse/src/compositions_tests.rs +++ b/etherparse/src/compositions_tests.rs @@ -597,6 +597,7 @@ impl ComponentTest { proptest! { ///Test that all known packet compositions are parsed correctly. #[test] + #[cfg_attr(miri, ignore)] // vec allocation reduces miri runspeed too much fn test_compositions(ref eth in ethernet_2_unknown(), ref vlan_outer in vlan_single_unknown(), ref vlan_inner in vlan_single_unknown(), From 0a10f3e60ddbfdcced506536a3be0d4108a79e16 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Thu, 12 Sep 2024 10:09:33 +0200 Subject: [PATCH 08/14] Applying rust fmt & add return_buf to ip defrag pool --- etherparse/src/defrag/ip_defrag_buf.rs | 22 +++++--- etherparse/src/defrag/ip_defrag_error.rs | 28 +++++++--- .../src/defrag/ip_defrag_payload_vec.rs | 4 +- etherparse/src/defrag/ip_defrag_pool.rs | 55 +++++++++++-------- etherparse/src/defrag/ip_frag_id.rs | 2 +- etherparse/src/defrag/ip_frag_range.rs | 1 - .../src/defrag/ip_frag_version_spec_id.rs | 8 +-- 7 files changed, 72 insertions(+), 48 deletions(-) diff --git a/etherparse/src/defrag/ip_defrag_buf.rs b/etherparse/src/defrag/ip_defrag_buf.rs index bdae4885..9e5553c4 100644 --- a/etherparse/src/defrag/ip_defrag_buf.rs +++ b/etherparse/src/defrag/ip_defrag_buf.rs @@ -1,4 +1,4 @@ -use crate::{*, defrag::*}; +use crate::{defrag::*, *}; use std::vec::Vec; /// Buffer to reconstruct a single fragmented IP packet. @@ -18,7 +18,11 @@ pub struct IpDefragBuf { } impl IpDefragBuf { - pub fn new(ip_number: IpNumber, mut data: Vec, mut sections: Vec) -> IpDefragBuf { + pub fn new( + ip_number: IpNumber, + mut data: Vec, + mut sections: Vec, + ) -> IpDefragBuf { IpDefragBuf { ip_number, data: { @@ -57,16 +61,16 @@ impl IpDefragBuf { self.end } - /// Add a IPv4 slice + /// Add a IPv4 slice #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] pub fn add( &mut self, offset: IpFragOffset, more_fragments: bool, - payload: &[u8] + payload: &[u8], ) -> Result<(), IpDefragError> { use IpDefragError::*; - + // validate lengths let Ok(len_u16) = u16::try_from(payload.len()) else { return Err(SegmentTooBig { @@ -103,7 +107,7 @@ impl IpDefragBuf { } } - // get enough memory to store the de-fragmented + // get enough memory to store the de-fragmented let required_len = usize::from(end); if self.data.len() < required_len { if self.data.capacity() < required_len @@ -195,7 +199,11 @@ mod test { #[test] fn new() { - let actual = IpDefragBuf::new(IpNumber::UDP, vec![1], vec![IpFragRange{start: 0, end: 1}]); + let actual = IpDefragBuf::new( + IpNumber::UDP, + vec![1], + vec![IpFragRange { start: 0, end: 1 }], + ); assert_eq!(actual.ip_number(), IpNumber::UDP); assert!(actual.data().is_empty()); assert!(actual.sections().is_empty()); diff --git a/etherparse/src/defrag/ip_defrag_error.rs b/etherparse/src/defrag/ip_defrag_error.rs index 7f314dac..d5bc2b77 100644 --- a/etherparse/src/defrag/ip_defrag_error.rs +++ b/etherparse/src/defrag/ip_defrag_error.rs @@ -4,7 +4,10 @@ use crate::*; pub enum IpDefragError { /// Error if a payload lenght of a IP Fragment packet is not a multiple of 16 /// and the "more fragments" flag is set. - UnalignedFragmentPayloadLen { offset: IpFragOffset, payload_len: usize }, + UnalignedFragmentPayloadLen { + offset: IpFragOffset, + payload_len: usize, + }, /// Error if a segment is bigger then the maximum allowed size. SegmentTooBig { @@ -43,13 +46,16 @@ impl std::error::Error for IpDefragError {} #[cfg(test)] mod tests { - use super::*; use super::IpDefragError::*; + use super::*; use std::format; #[test] fn debug() { - let err = UnalignedFragmentPayloadLen { offset: IpFragOffset::try_new(0).unwrap(), payload_len: 16 }; + let err = UnalignedFragmentPayloadLen { + offset: IpFragOffset::try_new(0).unwrap(), + payload_len: 16, + }; let _ = format!("{err:?}"); } @@ -59,7 +65,10 @@ mod tests { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; - let err = UnalignedFragmentPayloadLen { offset: IpFragOffset::try_new(0).unwrap(), payload_len: 16 }; + let err = UnalignedFragmentPayloadLen { + offset: IpFragOffset::try_new(0).unwrap(), + payload_len: 16, + }; assert_eq!(err, err.clone()); let hash_a = { let mut hasher = DefaultHasher::new(); @@ -92,10 +101,11 @@ mod tests { #[test] fn source() { use std::error::Error; - assert!( - UnalignedFragmentPayloadLen { offset: IpFragOffset::try_new(0).unwrap(), payload_len: 16 } - .source() - .is_none() - ); + assert!(UnalignedFragmentPayloadLen { + offset: IpFragOffset::try_new(0).unwrap(), + payload_len: 16 + } + .source() + .is_none()); } } diff --git a/etherparse/src/defrag/ip_defrag_payload_vec.rs b/etherparse/src/defrag/ip_defrag_payload_vec.rs index 5c95b278..c94984d1 100644 --- a/etherparse/src/defrag/ip_defrag_payload_vec.rs +++ b/etherparse/src/defrag/ip_defrag_payload_vec.rs @@ -30,9 +30,7 @@ mod test { assert_eq!( format!( "IpDefragPayloadVec {{ ip_number: {:?}, len_source: {:?}, payload: {:?} }}", - s.ip_number, - s.len_source, - s.payload + s.ip_number, s.len_source, s.payload ), format!("{:?}", s) ); diff --git a/etherparse/src/defrag/ip_defrag_pool.rs b/etherparse/src/defrag/ip_defrag_pool.rs index 3a025458..28b886bf 100644 --- a/etherparse/src/defrag/ip_defrag_pool.rs +++ b/etherparse/src/defrag/ip_defrag_pool.rs @@ -1,4 +1,4 @@ -use crate::{*, defrag::*}; +use crate::{defrag::*, *}; use std::collections::HashMap; use std::vec::Vec; @@ -35,7 +35,6 @@ where Timestamp: Sized + core::fmt::Debug + Clone, CustomChannelId: Sized + core::fmt::Debug + Clone + core::hash::Hash + Eq + PartialEq, { - pub fn new() -> IpDefragPool { IpDefragPool { active: HashMap::new(), @@ -45,8 +44,12 @@ where } /// Add data from a sliced packet. - pub fn process_sliced_packet(&mut self, slice: &SlicedPacket, timestamp: Timestamp, channel_id: CustomChannelId) -> Result, IpDefragError> { - + pub fn process_sliced_packet( + &mut self, + slice: &SlicedPacket, + timestamp: Timestamp, + channel_id: CustomChannelId, + ) -> Result, IpDefragError> { // extract the fragment related data and skip non-fragmented packets let (frag_id, offset, more_fragments, payload, is_ipv4) = match &slice.net { Some(NetSlice::Ipv4(ipv4)) => { @@ -58,15 +61,18 @@ where let (outer_vlan_id, inner_vlan_id) = match &slice.vlan { Some(VlanSlice::SingleVlan(s)) => (Some(s.vlan_identifier()), None), - Some(VlanSlice::DoubleVlan(d)) => (Some(d.outer().vlan_identifier()), Some(d.inner().vlan_identifier())), - None => (None, None) + Some(VlanSlice::DoubleVlan(d)) => ( + Some(d.outer().vlan_identifier()), + Some(d.inner().vlan_identifier()), + ), + None => (None, None), }; ( IpFragId { outer_vlan_id, inner_vlan_id, - ip: IpFragVersionSpecId::Ipv4{ + ip: IpFragVersionSpecId::Ipv4 { source: header.source(), destination: header.destination(), identification: header.identification(), @@ -77,7 +83,7 @@ where header.fragments_offset(), header.more_fragments(), ipv4.payload(), - true + true, ) } Some(NetSlice::Ipv6(ipv6)) => { @@ -107,8 +113,11 @@ where let (outer_vlan_id, inner_vlan_id) = match &slice.vlan { Some(VlanSlice::SingleVlan(s)) => (Some(s.vlan_identifier()), None), - Some(VlanSlice::DoubleVlan(d)) => (Some(d.outer().vlan_identifier()), Some(d.inner().vlan_identifier())), - None => (None, None) + Some(VlanSlice::DoubleVlan(d)) => ( + Some(d.outer().vlan_identifier()), + Some(d.inner().vlan_identifier()), + ), + None => (None, None), }; // calculate frag id @@ -116,7 +125,7 @@ where IpFragId { outer_vlan_id, inner_vlan_id, - ip: IpFragVersionSpecId::Ipv6{ + ip: IpFragVersionSpecId::Ipv6 { source: ipv6.header().source(), destination: ipv6.header().destination(), identification: frag.identification, @@ -127,7 +136,7 @@ where frag.fragment_offset, frag.more_fragments, ipv6.payload(), - false + false, ) } None => { @@ -146,7 +155,7 @@ where if buf.0.is_complete() { let (defraged_payload, sections) = entry.remove().0.take_bufs(); self.finished_section_bufs.push(sections); - Ok(Some(IpDefragPayloadVec{ + Ok(Some(IpDefragPayloadVec { ip_number: payload.ip_number, len_source: if is_ipv4 { LenSource::Ipv4HeaderTotalLen @@ -164,7 +173,7 @@ where d.clear(); d } else { - Vec::with_capacity(payload.payload.len()*2) + Vec::with_capacity(payload.payload.len() * 2) }; let sections = if let Some(mut s) = self.finished_section_bufs.pop() { s.clear(); @@ -179,7 +188,7 @@ where if defrag_buf.is_complete() { let (defraged_payload, sections) = defrag_buf.take_bufs(); self.finished_section_bufs.push(sections); - Ok(Some(IpDefragPayloadVec{ + Ok(Some(IpDefragPayloadVec { ip_number: payload.ip_number, len_source: if is_ipv4 { LenSource::Ipv4HeaderTotalLen @@ -192,24 +201,24 @@ where entry.insert((defrag_buf, timestamp)); Ok(None) } - }, + } Err(err) => { // return the buffers let (data_buf, sections) = defrag_buf.take_bufs(); self.finished_data_bufs.push(data_buf); self.finished_section_bufs.push(sections); Err(err) - }, + } } } } } -} + /// Returns a buffer to the pool so it can be re-used. + pub fn return_buf(&mut self, buf: IpDefragPayloadVec) { + self.finished_data_bufs.push(buf.payload); + } +} #[cfg(test)] -mod test { - - - -} +mod test {} diff --git a/etherparse/src/defrag/ip_frag_id.rs b/etherparse/src/defrag/ip_frag_id.rs index a75e4dbc..865d3f66 100644 --- a/etherparse/src/defrag/ip_frag_id.rs +++ b/etherparse/src/defrag/ip_frag_id.rs @@ -1,4 +1,4 @@ -use crate::{*, defrag::*}; +use crate::{defrag::*, *}; /// Values identifying a fragmented packet. #[derive(Debug, Clone, Hash, Eq, PartialEq)] diff --git a/etherparse/src/defrag/ip_frag_range.rs b/etherparse/src/defrag/ip_frag_range.rs index b241f5e8..eb9e56fc 100644 --- a/etherparse/src/defrag/ip_frag_range.rs +++ b/etherparse/src/defrag/ip_frag_range.rs @@ -1,4 +1,3 @@ - /// Describing the range of reconstructed data. #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Ord, PartialOrd)] pub struct IpFragRange { diff --git a/etherparse/src/defrag/ip_frag_version_spec_id.rs b/etherparse/src/defrag/ip_frag_version_spec_id.rs index de33a06b..03729045 100644 --- a/etherparse/src/defrag/ip_frag_version_spec_id.rs +++ b/etherparse/src/defrag/ip_frag_version_spec_id.rs @@ -2,14 +2,14 @@ #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub enum IpFragVersionSpecId { /// IPv4 specific data. - Ipv4{ - source: [u8; 4], + Ipv4 { + source: [u8; 4], destination: [u8; 4], identification: u16, }, /// IPv6 specific data. - Ipv6{ - source: [u8; 16], + Ipv6 { + source: [u8; 16], destination: [u8; 16], identification: u32, }, From d298cb4ded7beec58e464673e50f2383cdf596cc Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Thu, 12 Sep 2024 10:21:07 +0200 Subject: [PATCH 09/14] Correct ip defrag pool new return type --- etherparse/src/defrag/ip_defrag_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etherparse/src/defrag/ip_defrag_pool.rs b/etherparse/src/defrag/ip_defrag_pool.rs index 28b886bf..59110987 100644 --- a/etherparse/src/defrag/ip_defrag_pool.rs +++ b/etherparse/src/defrag/ip_defrag_pool.rs @@ -35,7 +35,7 @@ where Timestamp: Sized + core::fmt::Debug + Clone, CustomChannelId: Sized + core::fmt::Debug + Clone + core::hash::Hash + Eq + PartialEq, { - pub fn new() -> IpDefragPool { + pub fn new() -> IpDefragPool { IpDefragPool { active: HashMap::new(), finished_data_bufs: Vec::new(), From 0cb510e0b03d092ac1337b87589deb41f3dd2a66 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Thu, 12 Sep 2024 14:25:16 +0200 Subject: [PATCH 10/14] Corrected fragment reconstruction --- etherparse/src/defrag/ip_defrag_buf.rs | 37 ++++++++++++++------------ etherparse/src/net/ip_frag_offset.rs | 6 +++++ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/etherparse/src/defrag/ip_defrag_buf.rs b/etherparse/src/defrag/ip_defrag_buf.rs index 9e5553c4..8d4b9e3c 100644 --- a/etherparse/src/defrag/ip_defrag_buf.rs +++ b/etherparse/src/defrag/ip_defrag_buf.rs @@ -80,7 +80,7 @@ impl IpDefragBuf { }); }; - let Some(end) = offset.value().checked_add(len_u16) else { + let Some(end) = offset.byte_offset().checked_add(len_u16) else { return Err(SegmentTooBig { offset, payload_len: payload.len(), @@ -88,8 +88,8 @@ impl IpDefragBuf { }); }; - // validate that the payload len is a multiple of 16 in case it is not the end - if more_fragments && 0 != payload.len() & 0b1111 { + // validate that the payload len is a multiple of 8 in case it is not the end + if more_fragments && 0 != payload.len() & 0b111 { return Err(UnalignedFragmentPayloadLen { offset, payload_len: payload.len(), @@ -124,12 +124,12 @@ impl IpDefragBuf { } // insert new data - let data_offset = usize::from(offset.value()); + let data_offset = usize::from(offset.byte_offset()); self.data[data_offset..data_offset + payload.len()].copy_from_slice(payload); // update sections let mut new_section = IpFragRange { - start: offset.value(), + start: offset.byte_offset(), end, }; @@ -234,8 +234,9 @@ mod test { (true, (48, false, &sequence(48,16))), ]; for a in actions { + assert!(0 == (a.1.0 % 8)); buffer.add( - IpFragOffset::try_new(a.1.0).unwrap(), + IpFragOffset::try_new(a.1.0 / 8).unwrap(), a.1.1, a.1.2 ).unwrap(); @@ -259,8 +260,9 @@ mod test { (true, (16, true, sequence(16,16))), ]; for a in actions { + assert!(0 == (a.1.0 % 8)); buffer.add( - IpFragOffset::try_new(a.1.0).unwrap(), + IpFragOffset::try_new(a.1.0 / 8).unwrap(), a.1.1, &a.1.2 ).unwrap(); @@ -280,8 +282,9 @@ mod test { (true, (0, true, &sequence(0,16))), ]; for a in actions { + assert!(0 == (a.1.0 % 8)); buffer.add( - IpFragOffset::try_new(a.1.0).unwrap(), + IpFragOffset::try_new(a.1.0 / 8).unwrap(), a.1.1, &a.1.2 ).unwrap(); @@ -310,9 +313,9 @@ mod test { let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); let payload_len = usize::from(u16::MAX) - 32 - 16 + 1; assert_eq!( - SegmentTooBig { offset: IpFragOffset::try_new(32 + 16).unwrap(), payload_len, max: u16::MAX }, + SegmentTooBig { offset: IpFragOffset::try_new((32 + 16)/8).unwrap(), payload_len, max: u16::MAX }, buffer.add( - IpFragOffset::try_new(32 + 16).unwrap(), + IpFragOffset::try_new((32 + 16)/8).unwrap(), true, &sequence(0,payload_len) ).unwrap_err() @@ -327,7 +330,7 @@ mod test { assert_eq!( Ok(()), buffer.add( - IpFragOffset::try_new(16).unwrap(), + IpFragOffset::try_new(16/8).unwrap(), false, &sequence(0, payload_len) ) @@ -335,15 +338,15 @@ mod test { } // packets conflicting with previously seen end - for bad_offset in 1..16 { + for bad_offset in 1..8 { let mut buffer = IpDefragBuf::new(IpNumber::UDP, Vec::new(), Vec::new()); assert_eq!( UnalignedFragmentPayloadLen { - offset: IpFragOffset::try_new(48).unwrap(), + offset: IpFragOffset::try_new(48/8).unwrap(), payload_len: bad_offset }, buffer.add( - IpFragOffset::try_new(48).unwrap(), + IpFragOffset::try_new(48/8).unwrap(), true, &sequence(0, bad_offset) ).unwrap_err() @@ -356,7 +359,7 @@ mod test { // setup an end (aka no more segements) buffer.add( - IpFragOffset::try_new(32).unwrap(), + IpFragOffset::try_new(32/8).unwrap(), false, &sequence(32,16) ).unwrap(); @@ -365,7 +368,7 @@ mod test { assert_eq!( ConflictingEnd { previous_end: 32 + 16, conflicting_end: 48 + 16 }, buffer.add( - IpFragOffset::try_new(48).unwrap(), + IpFragOffset::try_new(48/8).unwrap(), true, &sequence(48,16) ).unwrap_err() @@ -375,7 +378,7 @@ mod test { assert_eq!( ConflictingEnd { previous_end: 32 + 16, conflicting_end: 16 + 16 }, buffer.add( - IpFragOffset::try_new(16).unwrap(), + IpFragOffset::try_new(16/8).unwrap(), false, &sequence(16,16) ).unwrap_err() diff --git a/etherparse/src/net/ip_frag_offset.rs b/etherparse/src/net/ip_frag_offset.rs index 41192842..8370baf8 100644 --- a/etherparse/src/net/ip_frag_offset.rs +++ b/etherparse/src/net/ip_frag_offset.rs @@ -120,6 +120,12 @@ impl IpFragOffset { pub const fn value(self) -> u16 { self.0 } + + /// Returns the offset in bytes (offset raw value multiplied by 8). + #[inline] + pub const fn byte_offset(self) -> u16 { + self.0 << 3 + } } impl core::fmt::Display for IpFragOffset { From e3b93fe88e3134b07e3fee9a1c4166c27d4b44e4 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Tue, 17 Sep 2024 09:07:42 +0200 Subject: [PATCH 11/14] Extended tests for frag pool --- README.md | 6 +- etherparse/Cargo.toml | 2 +- etherparse/examples/ip_defrag.rs | 78 ++++ etherparse/src/defrag/ip_defrag_pool.rs | 451 +++++++++++++++++++- etherparse/src/lib.rs | 6 +- etherparse/src/sliced_packet.rs | 10 + etherparse/src/transport/transport_slice.rs | 6 +- 7 files changed, 545 insertions(+), 14 deletions(-) create mode 100644 etherparse/examples/ip_defrag.rs diff --git a/README.md b/README.md index 67bbc7fd..19a2505e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # etherparse -A mostly zero allocation library for parsing & writing a bunch of packet based protocols (EthernetII, IPv4, IPv6, UDP, TCP ...). +A zero allocation supporting library for parsing & writing a bunch of packet based protocols (EthernetII, IPv4, IPv6, UDP, TCP ...). Currently supported are: * Ethernet II @@ -17,13 +17,15 @@ Currently supported are: * TCP * ICMP & ICMPv6 (not all message types are supported) +Reconstruction of fragmented IP packets is also supported, but requires allocations. + ## Usage Add the following to your `Cargo.toml`: ```toml [dependencies] -etherparse = "0.15" +etherparse = "0.16" ``` ## What is etherparse? diff --git a/etherparse/Cargo.toml b/etherparse/Cargo.toml index 0385e40e..64a338d0 100644 --- a/etherparse/Cargo.toml +++ b/etherparse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "etherparse" -version = "0.15.0" +version = "0.16.0" authors = ["Julian Schmid "] edition = "2021" repository = "https://github.com/JulianSchmid/etherparse" diff --git a/etherparse/examples/ip_defrag.rs b/etherparse/examples/ip_defrag.rs new file mode 100644 index 00000000..111e2b62 --- /dev/null +++ b/etherparse/examples/ip_defrag.rs @@ -0,0 +1,78 @@ +use etherparse::*; + +fn main() { + // setup some network data to parse + let builder = PacketBuilder::ethernet2( + // source mac + [1, 2, 3, 4, 5, 6], + // destination mac + [7, 8, 9, 10, 11, 12], + ) + .ip(IpHeaders::Ipv4( + Ipv4Header { + total_len: 0, // will be overwritten by builder + identification: 1234, + dont_fragment: false, + more_fragments: true, + fragment_offset: IpFragOffset::try_new(1024 / 8).unwrap(), + time_to_live: 20, + protocol: IpNumber::UDP, + header_checksum: 0, // will be overwritten by builder + source: [1, 2, 3, 4], + destination: [2, 3, 4, 5], + ..Default::default() + }, + Default::default(), + )) + .udp( + 21, // source port + 1234, // desitnation port + ); + + // payload of the udp packet + let payload = [1, 2, 3, 4, 5, 6, 7, 8]; + + // get some memory to store the serialized data + let mut serialized = Vec::::with_capacity(builder.size(payload.len())); + builder.write(&mut serialized, &payload).unwrap(); + + // pool that manages the different fragmented packets & the different memory buffers for re-assembly + let mut ip_defrag_pool = defrag::IpDefragPool::<(), ()>::new(); + + // slice the packet into the different header components + let sliced_packet = match SlicedPacket::from_ethernet(&serialized) { + Err(err) => { + println!("Err {:?}", err); + return; + } + Ok(v) => v, + }; + + // constructed + if sliced_packet.is_ip_payload_fragmented() { + let defrag_result = ip_defrag_pool.process_sliced_packet(&sliced_packet, (), ()); + match defrag_result { + Ok(Some(finished)) => { + println!( + "Successfully reconstructed fragmented IP packet ({} bytes, protocol {:?})", + finished.payload.len(), + finished.ip_number, + ); + + // continue parsing the payload + // ... fill in your code here + + // IMPORTANT: After done return the finished packet buffer to avoid unneeded allocations + ip_defrag_pool.return_buf(finished); + } + Ok(None) => { + println!( + "Received a fragmented packet, but the reconstruction was not yet finished" + ); + } + Err(err) => { + println!("Error reconstructing fragmented IPv4 packet: {err}"); + } + } + } +} diff --git a/etherparse/src/defrag/ip_defrag_pool.rs b/etherparse/src/defrag/ip_defrag_pool.rs index 59110987..b5a5277f 100644 --- a/etherparse/src/defrag/ip_defrag_pool.rs +++ b/etherparse/src/defrag/ip_defrag_pool.rs @@ -5,6 +5,11 @@ use std::vec::Vec; /// Pool of buffers to reconstruct multiple fragmented IP packets in /// parallel (re-uses buffers to minimize allocations). /// +/// It differentiates the packets based on their inner & outer vlan as well as +/// source and destination ip address and allows the user to add their own +/// custom "channel id" type to further differentiate different streams. +/// The custom channel id can be used to +/// /// # This implementation is NOT safe against "Out of Memory" attacks /// /// If you use the [`DefragPool`] in an untrusted environment an attacker could @@ -87,12 +92,6 @@ where ) } Some(NetSlice::Ipv6(ipv6)) => { - // skip unfragmented packets - if false == ipv6.is_payload_fragmented() { - // nothing to defragment here, skip packet - return Ok(None); - } - // get fragmentation header let frag = { let mut f = None; @@ -104,7 +103,12 @@ where } } if let Some(f) = f { - f.to_header() + if f.is_fragmenting_payload() { + f.to_header() + } else { + // nothing to defragment here, skip packet + return Ok(None); + } } else { // nothing to defragment here, skip packet return Ok(None); @@ -218,7 +222,438 @@ where pub fn return_buf(&mut self, buf: IpDefragPayloadVec) { self.finished_data_bufs.push(buf.payload); } + + /// Retains only the elements specified by the predicate. + pub fn retain(&mut self, f: F) + where + F: Fn(&Timestamp) -> bool, + { + if self.active.iter().any(|(_, (_, t))| false == f(t)) { + self.active = self + .active + .drain() + .filter_map(|(k, v)| { + if f(&v.1) { + Some((k, v)) + } else { + let (data, sections) = v.0.take_bufs(); + self.finished_data_bufs.push(data); + self.finished_section_bufs.push(sections); + None + } + }) + .collect(); + } + } } #[cfg(test)] -mod test {} +mod test { + use std::cmp::max; + + use super::*; + + #[test] + fn new() { + { + let pool = IpDefragPool::<(), ()>::new(); + assert_eq!(pool.active.len(), 0); + assert_eq!(pool.finished_data_bufs.len(), 0); + assert_eq!(pool.finished_section_bufs.len(), 0); + } + { + let pool = IpDefragPool::::new(); + assert_eq!(pool.active.len(), 0); + assert_eq!(pool.finished_data_bufs.len(), 0); + assert_eq!(pool.finished_section_bufs.len(), 0); + } + } + + fn build_packet( + id: IpFragId, + offset: u16, + more: bool, + payload: &[u8], + ) -> Vec { + let mut buf = Vec::with_capacity( + Ethernet2Header::LEN + + SingleVlanHeader::LEN + + SingleVlanHeader::LEN + + max( + Ipv4Header::MIN_LEN, + Ipv6Header::LEN + Ipv6FragmentHeader::LEN, + ) + + payload.len(), + ); + + let ip_ether_type = match id.ip { + IpFragVersionSpecId::Ipv4 { + source: _, + destination: _, + identification: _, + } => EtherType::IPV4, + IpFragVersionSpecId::Ipv6 { + source: _, + destination: _, + identification: _, + } => EtherType::IPV6, + }; + + buf.extend_from_slice( + &Ethernet2Header { + source: [0; 6], + destination: [0; 6], + ether_type: if id.outer_vlan_id.is_some() || id.inner_vlan_id.is_some() { + EtherType::VLAN_TAGGED_FRAME + } else { + ip_ether_type + }, + } + .to_bytes(), + ); + + if let Some(vlan_id) = id.outer_vlan_id { + buf.extend_from_slice( + &SingleVlanHeader { + pcp: VlanPcp::try_new(0).unwrap(), + drop_eligible_indicator: false, + vlan_id, + ether_type: if id.inner_vlan_id.is_some() { + EtherType::VLAN_TAGGED_FRAME + } else { + ip_ether_type + }, + } + .to_bytes(), + ); + } + + if let Some(vlan_id) = id.inner_vlan_id { + buf.extend_from_slice( + &SingleVlanHeader { + pcp: VlanPcp::try_new(0).unwrap(), + drop_eligible_indicator: false, + vlan_id, + ether_type: ip_ether_type, + } + .to_bytes(), + ); + } + + match id.ip { + IpFragVersionSpecId::Ipv4 { + source, + destination, + identification, + } => { + let mut header = Ipv4Header { + identification, + more_fragments: more, + fragment_offset: IpFragOffset::try_new(offset).unwrap(), + protocol: id.payload_ip_number, + source, + destination, + total_len: (Ipv4Header::MIN_LEN + payload.len()) as u16, + time_to_live: 2, + ..Default::default() + }; + header.header_checksum = header.calc_header_checksum(); + buf.extend_from_slice(&header.to_bytes()); + } + IpFragVersionSpecId::Ipv6 { + source, + destination, + identification, + } => { + buf.extend_from_slice( + &Ipv6Header { + traffic_class: 0, + flow_label: Default::default(), + payload_length: (payload.len() + Ipv6FragmentHeader::LEN) as u16, + next_header: IpNumber::IPV6_FRAGMENTATION_HEADER, + hop_limit: 2, + source, + destination, + } + .to_bytes(), + ); + buf.extend_from_slice( + &Ipv6FragmentHeader { + next_header: id.payload_ip_number, + fragment_offset: IpFragOffset::try_new(offset).unwrap(), + more_fragments: more, + identification, + } + .to_bytes(), + ); + } + } + buf.extend_from_slice(payload); + buf + } + + #[test] + fn process_sliced_packet() { + // v4 non fragmented + { + let mut pool = IpDefragPool::<(), ()>::new(); + let pdata = build_packet( + IpFragId { + outer_vlan_id: None, + inner_vlan_id: None, + ip: IpFragVersionSpecId::Ipv4 { + source: [0; 4], + destination: [0; 4], + identification: 0, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }, + 0, + false, + &UdpHeader { + source_port: 0, + destination_port: 0, + length: 0, + checksum: 0, + } + .to_bytes(), + ); + let pslice = SlicedPacket::from_ethernet(&pdata).unwrap(); + let v = pool.process_sliced_packet(&pslice, (), ()); + assert_eq!(Ok(None), v); + + // check the effect had no effect + assert_eq!(pool.active.len(), 0); + assert_eq!(pool.finished_data_bufs.len(), 0); + assert_eq!(pool.finished_section_bufs.len(), 0); + } + + // v6 non fragmented + { + let mut pool = IpDefragPool::<(), ()>::new(); + let pdata = build_packet( + IpFragId { + outer_vlan_id: None, + inner_vlan_id: None, + ip: IpFragVersionSpecId::Ipv6 { + source: [0; 16], + destination: [0; 16], + identification: 0, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }, + 0, + false, + &UdpHeader { + source_port: 0, + destination_port: 0, + length: 0, + checksum: 0, + } + .to_bytes(), + ); + let pslice = SlicedPacket::from_ethernet(&pdata).unwrap(); + let v = pool.process_sliced_packet(&pslice, (), ()); + assert_eq!(Ok(None), v); + + // check the effect had no effect + assert_eq!(pool.active.len(), 0); + assert_eq!(pool.finished_data_bufs.len(), 0); + assert_eq!(pool.finished_section_bufs.len(), 0); + } + + // v4 & v6 basic test + { + let frag_ids = [ + // v4 (no vlan) + IpFragId { + outer_vlan_id: None, + inner_vlan_id: None, + ip: IpFragVersionSpecId::Ipv4 { + source: [1, 2, 3, 4], + destination: [5, 6, 7, 8], + identification: 9, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }, + // v4 (single vlan) + IpFragId { + outer_vlan_id: Some(VlanId::try_new(12).unwrap()), + inner_vlan_id: None, + ip: IpFragVersionSpecId::Ipv4 { + source: [1, 2, 3, 4], + destination: [5, 6, 7, 8], + identification: 9, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }, + // v4 (double vlan) + IpFragId { + outer_vlan_id: Some(VlanId::try_new(12).unwrap()), + inner_vlan_id: Some(VlanId::try_new(23).unwrap()), + ip: IpFragVersionSpecId::Ipv4 { + source: [1, 2, 3, 4], + destination: [5, 6, 7, 8], + identification: 9, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }, + // v6 (no vlan) + IpFragId { + outer_vlan_id: None, + inner_vlan_id: None, + ip: IpFragVersionSpecId::Ipv6 { + source: [0; 16], + destination: [0; 16], + identification: 0, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }, + // v6 (single vlan) + IpFragId { + outer_vlan_id: Some(VlanId::try_new(12).unwrap()), + inner_vlan_id: None, + ip: IpFragVersionSpecId::Ipv6 { + source: [0; 16], + destination: [0; 16], + identification: 0, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }, + // v6 (double vlan) + IpFragId { + outer_vlan_id: Some(VlanId::try_new(12).unwrap()), + inner_vlan_id: Some(VlanId::try_new(23).unwrap()), + ip: IpFragVersionSpecId::Ipv6 { + source: [0; 16], + destination: [0; 16], + identification: 0, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }, + ]; + + let mut pool = IpDefragPool::<(), ()>::new(); + + for frag_id in frag_ids { + { + let pdata = build_packet(frag_id.clone(), 0, true, &[1, 2, 3, 4, 5, 6, 7, 8]); + let pslice = SlicedPacket::from_ethernet(&pdata).unwrap(); + let v = pool.process_sliced_packet(&pslice, (), ()); + assert_eq!(Ok(None), v); + + // check the frag id was correctly calculated + assert_eq!(1, pool.active.len()); + assert_eq!(pool.active.iter().next().unwrap().0, &frag_id); + } + + { + let pdata = build_packet(frag_id.clone(), 1, false, &[9, 10]); + let pslice = SlicedPacket::from_ethernet(&pdata).unwrap(); + let v = pool + .process_sliced_packet(&pslice, (), ()) + .unwrap() + .unwrap(); + assert_eq!(v.ip_number, IpNumber::UDP); + assert_eq!( + v.len_source, + if matches!( + frag_id.ip, + IpFragVersionSpecId::Ipv4 { + source: _, + destination: _, + identification: _ + } + ) { + LenSource::Ipv4HeaderTotalLen + } else { + LenSource::Ipv6HeaderPayloadLen + } + ); + assert_eq!(v.payload, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + // there should be nothing left + assert_eq!(pool.active.len(), 0); + assert_eq!(pool.finished_data_bufs.len(), 0); + assert_eq!(pool.finished_section_bufs.len(), 1); + + // return buffer + pool.return_buf(v); + + assert_eq!(pool.active.len(), 0); + assert_eq!(pool.finished_data_bufs.len(), 1); + assert_eq!(pool.finished_section_bufs.len(), 1); + } + } + } + } + + #[test] + fn retain() { + let frag_id_0 = IpFragId { + outer_vlan_id: None, + inner_vlan_id: None, + ip: IpFragVersionSpecId::Ipv4 { + source: [1, 2, 3, 4], + destination: [5, 6, 7, 8], + identification: 0, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }; + let frag_id_1 = IpFragId { + outer_vlan_id: None, + inner_vlan_id: None, + ip: IpFragVersionSpecId::Ipv4 { + source: [1, 2, 3, 4], + destination: [5, 6, 7, 8], + identification: 1, + }, + payload_ip_number: IpNumber::UDP, + channel_id: (), + }; + + let mut pool = IpDefragPool::::new(); + + // packet timestamp 1 + { + let pdata = build_packet(frag_id_0.clone(), 0, true, &[1, 2, 3, 4, 5, 6, 7, 8]); + let pslice = SlicedPacket::from_ethernet(&pdata).unwrap(); + let v = pool.process_sliced_packet(&pslice, 1, ()); + assert_eq!(Ok(None), v); + } + // packet timestamp 2 + { + let pdata = build_packet(frag_id_1.clone(), 0, true, &[1, 2, 3, 4, 5, 6, 7, 8]); + let pslice = SlicedPacket::from_ethernet(&pdata).unwrap(); + let v = pool.process_sliced_packet(&pslice, 2, ()); + assert_eq!(Ok(None), v); + } + + // check buffers are active + assert_eq!(pool.active.len(), 2); + assert_eq!(pool.finished_data_bufs.len(), 0); + assert_eq!(pool.finished_section_bufs.len(), 0); + + // call retain without effect + pool.retain(|ts| *ts > 0); + assert_eq!(pool.active.len(), 2); + assert_eq!(pool.finished_data_bufs.len(), 0); + assert_eq!(pool.finished_section_bufs.len(), 0); + + // call retain and delete timestamp 1 + pool.retain(|ts| *ts > 1); + assert_eq!(pool.active.len(), 1); + assert_eq!(pool.finished_data_bufs.len(), 1); + assert_eq!(pool.finished_section_bufs.len(), 1); + assert_eq!(pool.active.iter().next().unwrap().0, &frag_id_1); + } +} diff --git a/etherparse/src/lib.rs b/etherparse/src/lib.rs index 8863133c..2da16e7e 100644 --- a/etherparse/src/lib.rs +++ b/etherparse/src/lib.rs @@ -1,4 +1,4 @@ -//! A zero allocation library for parsing & writing a bunch of packet based protocols (EthernetII, IPv4, IPv6, UDP, TCP ...). +//! A zero allocation supporting library for parsing & writing a bunch of packet based protocols (EthernetII, IPv4, IPv6, UDP, TCP ...). //! //! Currently supported are: //! * Ethernet II @@ -9,13 +9,15 @@ //! * TCP //! * ICMP & ICMPv6 (not all message types are supported) //! +//! Reconstruction of fragmented IP packets is also supported, but requires allocations. +//! //! # Usage //! //! Add the following to your `Cargo.toml`: //! //! ```toml //! [dependencies] -//! etherparse = "0.15" +//! etherparse = "0.16" //! ``` //! //! # What is etherparse? diff --git a/etherparse/src/sliced_packet.rs b/etherparse/src/sliced_packet.rs index b5feff8e..54a88465 100644 --- a/etherparse/src/sliced_packet.rs +++ b/etherparse/src/sliced_packet.rs @@ -337,6 +337,16 @@ impl<'a> SlicedPacket<'a> { None } } + + /// Returns true if `net` contains an fragmented IPv4 or IPv6 payload. + pub fn is_ip_payload_fragmented(&self) -> bool { + use NetSlice::*; + match &self.net { + Some(Ipv4(v)) => v.is_payload_fragmented(), + Some(Ipv6(v)) => v.is_payload_fragmented(), + None => false, + } + } } #[cfg(test)] diff --git a/etherparse/src/transport/transport_slice.rs b/etherparse/src/transport/transport_slice.rs index a7b46b51..8cfab0bb 100644 --- a/etherparse/src/transport/transport_slice.rs +++ b/etherparse/src/transport/transport_slice.rs @@ -1,14 +1,18 @@ use crate::*; +/// Slice containing UDP, TCP, ICMP or ICMPv4 header & payload. #[derive(Clone, Debug, Eq, PartialEq)] pub enum TransportSlice<'a> { /// A slice containing an Icmp4 header & payload. Icmpv4(Icmpv4Slice<'a>), + /// A slice containing an Icmp6 header & payload. Icmpv6(Icmpv6Slice<'a>), + /// A slice containing an UDP header & payload. Udp(UdpSlice<'a>), - /// A slice containing a TCP header. + + /// A slice containing a TCP header & payload. Tcp(TcpSlice<'a>), } From 3b6b364dee7ab2520ac041e3afa0080dc2715577 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Tue, 17 Sep 2024 09:24:59 +0200 Subject: [PATCH 12/14] Resolved clippy warning --- etherparse/src/defrag/ip_defrag_pool.rs | 26 ++++++++++++++++++++++ etherparse/src/err/from_slice_error.rs | 8 +++++-- etherparse/src/lax_packet_headers.rs | 2 +- etherparse/src/lax_sliced_packet.rs | 6 ++--- etherparse/src/lax_sliced_packet_cursor.rs | 2 +- etherparse/src/net/ip_headers.rs | 2 +- etherparse/src/net/ipv4_exts_slice.rs | 6 ++--- etherparse/src/net/ipv6_exts_slice.rs | 5 +++-- etherparse/src/net/ipv6_slice.rs | 2 +- etherparse/src/net/lax_ipv6_slice.rs | 2 +- etherparse/src/packet_headers.rs | 6 +++-- etherparse/src/sliced_packet.rs | 8 +++---- 12 files changed, 54 insertions(+), 21 deletions(-) diff --git a/etherparse/src/defrag/ip_defrag_pool.rs b/etherparse/src/defrag/ip_defrag_pool.rs index b5a5277f..c5bd995e 100644 --- a/etherparse/src/defrag/ip_defrag_pool.rs +++ b/etherparse/src/defrag/ip_defrag_pool.rs @@ -247,6 +247,16 @@ where } } +impl Default for IpDefragPool +where + Timestamp: Sized + core::fmt::Debug + Clone, + CustomChannelId: Sized + core::fmt::Debug + Clone + core::hash::Hash + Eq + PartialEq, +{ + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod test { use std::cmp::max; @@ -269,6 +279,22 @@ mod test { } } + #[test] + fn default() { + { + let pool: IpDefragPool<(), ()> = Default::default(); + assert_eq!(pool.active.len(), 0); + assert_eq!(pool.finished_data_bufs.len(), 0); + assert_eq!(pool.finished_section_bufs.len(), 0); + } + { + let pool: IpDefragPool = Default::default(); + assert_eq!(pool.active.len(), 0); + assert_eq!(pool.finished_data_bufs.len(), 0); + assert_eq!(pool.finished_section_bufs.len(), 0); + } + } + fn build_packet( id: IpFragId, offset: u16, diff --git a/etherparse/src/err/from_slice_error.rs b/etherparse/src/err/from_slice_error.rs index a1f8ba01..e879edf9 100644 --- a/etherparse/src/err/from_slice_error.rs +++ b/etherparse/src/err/from_slice_error.rs @@ -1,7 +1,11 @@ use super::*; -/// Type aggregating errors that can be caused by decoding from a slice. This type can be used -/// as a "catch all" type for errors caused by `from_slice` functions +/// "Catch all" error for all `from_slice` errors (supports automatic conversion from all +/// other slice errors). +/// +/// This type aggregates all errors that can be caused by decoding from a slice. +/// +/// This type can be used as a "catch all" type for errors caused by `from_slice` functions /// as all errors from these functions can be converted into this type. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum FromSliceError { diff --git a/etherparse/src/lax_packet_headers.rs b/etherparse/src/lax_packet_headers.rs index 3b0685af..42f4df61 100644 --- a/etherparse/src/lax_packet_headers.rs +++ b/etherparse/src/lax_packet_headers.rs @@ -432,7 +432,7 @@ impl<'a> LaxPacketHeaders<'a> { /// } /// /// ``` - pub fn from_ip(slice: &'a [u8]) -> Result { + pub fn from_ip(slice: &'a [u8]) -> Result, err::ip::LaxHeaderSliceError> { let mut result = Self { link: None, vlan: None, diff --git a/etherparse/src/lax_sliced_packet.rs b/etherparse/src/lax_sliced_packet.rs index 451f330a..3c6364ca 100644 --- a/etherparse/src/lax_sliced_packet.rs +++ b/etherparse/src/lax_sliced_packet.rs @@ -83,7 +83,7 @@ impl<'a> LaxSlicedPacket<'a> { /// } /// /// ``` - pub fn from_ethernet(slice: &'a [u8]) -> Result { + pub fn from_ethernet(slice: &'a [u8]) -> Result, err::LenError> { LaxSlicedPacketCursor::parse_from_ethernet2(slice) } @@ -143,7 +143,7 @@ impl<'a> LaxSlicedPacket<'a> { /// println!("transport: {:?}", packet.transport); /// /// ``` - pub fn from_ether_type(ether_type: EtherType, slice: &'a [u8]) -> LaxSlicedPacket { + pub fn from_ether_type(ether_type: EtherType, slice: &'a [u8]) -> LaxSlicedPacket<'a> { LaxSlicedPacketCursor::parse_from_ether_type(ether_type, slice) } @@ -217,7 +217,7 @@ impl<'a> LaxSlicedPacket<'a> { /// } /// } /// ``` - pub fn from_ip(slice: &'a [u8]) -> Result { + pub fn from_ip(slice: &'a [u8]) -> Result, err::ip::LaxHeaderSliceError> { LaxSlicedPacketCursor::parse_from_ip(slice) } diff --git a/etherparse/src/lax_sliced_packet_cursor.rs b/etherparse/src/lax_sliced_packet_cursor.rs index f870896b..5464c162 100644 --- a/etherparse/src/lax_sliced_packet_cursor.rs +++ b/etherparse/src/lax_sliced_packet_cursor.rs @@ -213,7 +213,7 @@ impl<'a> LaxSlicedPacketCursor<'a> { self.slice_transport(payload) } - fn slice_transport(mut self, slice: LaxIpPayloadSlice<'a>) -> LaxSlicedPacket { + fn slice_transport(mut self, slice: LaxIpPayloadSlice<'a>) -> LaxSlicedPacket<'a> { use err::packet::SliceError as O; if slice.fragmented || self.result.stop_err.is_some() { // if an error occured in an upper layer or the payload is fragmented diff --git a/etherparse/src/net/ip_headers.rs b/etherparse/src/net/ip_headers.rs index 6a1d0a31..7b901bc0 100644 --- a/etherparse/src/net/ip_headers.rs +++ b/etherparse/src/net/ip_headers.rs @@ -852,7 +852,7 @@ impl IpHeaders { /// parse packets returned via ICMP as these usually only contain the start. /// * Parsing packets where the `payload_length` (in the IPv6 header) has not /// yet been set. This can be useful when parsing packets which have been - /// recorded in a layer before the length field was set (e.g. before the operating + /// recorded in a layer before the length field was set (e.g. before the operating /// system set the length fields). /// /// # Differences to `from_slice`: diff --git a/etherparse/src/net/ipv4_exts_slice.rs b/etherparse/src/net/ipv4_exts_slice.rs index 9c665477..f29fa371 100644 --- a/etherparse/src/net/ipv4_exts_slice.rs +++ b/etherparse/src/net/ipv4_exts_slice.rs @@ -18,7 +18,7 @@ impl<'a> Ipv4ExtensionsSlice<'a> { pub fn from_slice( start_ip_number: IpNumber, start_slice: &'a [u8], - ) -> Result<(Ipv4ExtensionsSlice, IpNumber, &[u8]), err::ip_auth::HeaderSliceError> { + ) -> Result<(Ipv4ExtensionsSlice<'a>, IpNumber, &'a [u8]), err::ip_auth::HeaderSliceError> { use ip_number::*; if AUTH == start_ip_number { let header = IpAuthHeaderSlice::from_slice(start_slice)?; @@ -116,9 +116,9 @@ impl<'a> Ipv4ExtensionsSlice<'a> { start_ip_number: IpNumber, start_slice: &'a [u8], ) -> ( - Ipv4ExtensionsSlice, + Ipv4ExtensionsSlice<'a>, IpNumber, - &[u8], + &'a [u8], Option, ) { use ip_number::*; diff --git a/etherparse/src/net/ipv6_exts_slice.rs b/etherparse/src/net/ipv6_exts_slice.rs index ae37d9a1..012cc7b8 100644 --- a/etherparse/src/net/ipv6_exts_slice.rs +++ b/etherparse/src/net/ipv6_exts_slice.rs @@ -32,7 +32,8 @@ impl<'a> Ipv6ExtensionsSlice<'a> { pub fn from_slice( start_ip_number: IpNumber, start_slice: &'a [u8], - ) -> Result<(Ipv6ExtensionsSlice, IpNumber, &'a [u8]), err::ipv6_exts::HeaderSliceError> { + ) -> Result<(Ipv6ExtensionsSlice<'a>, IpNumber, &'a [u8]), err::ipv6_exts::HeaderSliceError> + { let mut rest = start_slice; let mut next_header = start_ip_number; let mut fragmented = false; @@ -134,7 +135,7 @@ impl<'a> Ipv6ExtensionsSlice<'a> { start_ip_number: IpNumber, start_slice: &'a [u8], ) -> ( - Ipv6ExtensionsSlice, + Ipv6ExtensionsSlice<'a>, IpNumber, &'a [u8], Option<(err::ipv6_exts::HeaderSliceError, err::Layer)>, diff --git a/etherparse/src/net/ipv6_slice.rs b/etherparse/src/net/ipv6_slice.rs index 68eaac33..13d431f8 100644 --- a/etherparse/src/net/ipv6_slice.rs +++ b/etherparse/src/net/ipv6_slice.rs @@ -114,7 +114,7 @@ impl<'a> Ipv6Slice<'a> { /// parse packets returned via ICMP as these usually only contain the start. /// * Parsing packets where the `payload_length` (in the IPv6 header) has not /// yet been set. This can be useful when parsing packets which have been - /// recorded in a layer before the length field was set (e.g. before the operating + /// recorded in a layer before the length field was set (e.g. before the operating /// system set the length fields). /// /// # Differences to `from_slice`: diff --git a/etherparse/src/net/lax_ipv6_slice.rs b/etherparse/src/net/lax_ipv6_slice.rs index a9403feb..e0b08f5f 100644 --- a/etherparse/src/net/lax_ipv6_slice.rs +++ b/etherparse/src/net/lax_ipv6_slice.rs @@ -38,7 +38,7 @@ impl<'a> LaxIpv6Slice<'a> { /// parse packets returned via ICMP as these usually only contain the start. /// * Parsing packets where the `payload_length` (in the IPv6 header) has not /// yet been set. This can be useful when parsing packets which have been - /// recorded in a layer before the length field was set (e.g. before the operating + /// recorded in a layer before the length field was set (e.g. before the operating /// system set the length fields). /// /// # Differences to `from_slice`: diff --git a/etherparse/src/packet_headers.rs b/etherparse/src/packet_headers.rs index 73e69e60..a14f805f 100644 --- a/etherparse/src/packet_headers.rs +++ b/etherparse/src/packet_headers.rs @@ -68,7 +68,9 @@ impl<'a> PacketHeaders<'a> { /// } /// } /// ``` - pub fn from_ethernet_slice(slice: &'a [u8]) -> Result { + pub fn from_ethernet_slice( + slice: &'a [u8], + ) -> Result, err::packet::SliceError> { use err::packet::SliceError::Len; let (ethernet, rest) = Ethernet2Header::from_slice(slice).map_err(Len)?; @@ -139,7 +141,7 @@ impl<'a> PacketHeaders<'a> { pub fn from_ether_type( mut ether_type: EtherType, slice: &'a [u8], - ) -> Result { + ) -> Result, err::packet::SliceError> { use err::packet::SliceError::*; let mut rest = slice; diff --git a/etherparse/src/sliced_packet.rs b/etherparse/src/sliced_packet.rs index 54a88465..63c73825 100644 --- a/etherparse/src/sliced_packet.rs +++ b/etherparse/src/sliced_packet.rs @@ -90,7 +90,7 @@ impl<'a> SlicedPacket<'a> { /// } /// } /// ``` - pub fn from_ethernet(data: &'a [u8]) -> Result { + pub fn from_ethernet(data: &'a [u8]) -> Result, err::packet::SliceError> { SlicedPacketCursor::new(data).slice_ethernet2() } @@ -132,7 +132,7 @@ impl<'a> SlicedPacket<'a> { /// } /// } /// ``` - pub fn from_linux_sll(data: &'a [u8]) -> Result { + pub fn from_linux_sll(data: &'a [u8]) -> Result, err::packet::SliceError> { SlicedPacketCursor::new(data).slice_linux_sll() } @@ -191,7 +191,7 @@ impl<'a> SlicedPacket<'a> { pub fn from_ether_type( ether_type: EtherType, data: &'a [u8], - ) -> Result { + ) -> Result, err::packet::SliceError> { use ether_type::*; let mut cursor = SlicedPacketCursor::new(data); cursor.result.link = Some(LinkSlice::EtherPayload(EtherPayloadSlice { @@ -242,7 +242,7 @@ impl<'a> SlicedPacket<'a> { /// } /// } /// ``` - pub fn from_ip(data: &'a [u8]) -> Result { + pub fn from_ip(data: &'a [u8]) -> Result, err::packet::SliceError> { SlicedPacketCursor::new(data).slice_ip() } From a923c57724cda634b34535adaadc07e472ab5f5c Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Tue, 17 Sep 2024 09:25:34 +0200 Subject: [PATCH 13/14] Increment proptest crate version --- etherparse_proptest_generators/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etherparse_proptest_generators/Cargo.toml b/etherparse_proptest_generators/Cargo.toml index 3609a8b7..5059ff1e 100644 --- a/etherparse_proptest_generators/Cargo.toml +++ b/etherparse_proptest_generators/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "etherparse_proptest_generators" -version = "0.15.0" +version = "0.16.0" authors = ["Julian Schmid "] edition = "2021" repository = "https://github.com/JulianSchmid/etherparse" From 3fd38dff966b3128bf8e8a998ae222eeff617197 Mon Sep 17 00:00:00 2001 From: Julian Schmid Date: Tue, 17 Sep 2024 09:42:39 +0200 Subject: [PATCH 14/14] Resolved clippy warnings --- etherparse/src/defrag/ip_defrag_pool.rs | 22 ++++++---------------- etherparse/src/err/read_error.rs | 11 ++++++++--- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/etherparse/src/defrag/ip_defrag_pool.rs b/etherparse/src/defrag/ip_defrag_pool.rs index c5bd995e..d7c2f192 100644 --- a/etherparse/src/defrag/ip_defrag_pool.rs +++ b/etherparse/src/defrag/ip_defrag_pool.rs @@ -189,22 +189,12 @@ where let mut defrag_buf = IpDefragBuf::new(payload.ip_number, data_buf, sections); match defrag_buf.add(offset, more_fragments, payload.payload) { Ok(()) => { - if defrag_buf.is_complete() { - let (defraged_payload, sections) = defrag_buf.take_bufs(); - self.finished_section_bufs.push(sections); - Ok(Some(IpDefragPayloadVec { - ip_number: payload.ip_number, - len_source: if is_ipv4 { - LenSource::Ipv4HeaderTotalLen - } else { - LenSource::Ipv6HeaderPayloadLen - }, - payload: defraged_payload, - })) - } else { - entry.insert((defrag_buf, timestamp)); - Ok(None) - } + // no need to check if the defrag is done as the + // packet can not be defragmented on initial add + // otherwise `is_fragmenting_payload` would have + // been false + entry.insert((defrag_buf, timestamp)); + Ok(None) } Err(err) => { // return the buffers diff --git a/etherparse/src/err/read_error.rs b/etherparse/src/err/read_error.rs index 77e3e2a4..8fab928b 100644 --- a/etherparse/src/err/read_error.rs +++ b/etherparse/src/err/read_error.rs @@ -1,8 +1,13 @@ use crate::err::*; -/// Type aggregating errors that can be caused by reading. This type can be used -/// as a "catch all" type for errors caused by `from_slice` or `read` functions -/// as all errors from these functions can be converted into this type. +/// "Catch all" error for all `from_slice` or `read` errors (supports automatic conversion from all +/// other slice errors). +/// +/// This type aggregates all errors that can be caused by decoding from a slice or reading +/// from an io stream. +/// +/// This type can be used as a "catch all" type for errors caused by `from_slice` or +/// `read` functions as all errors from these functions can be converted into this type. #[derive(Debug)] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub enum ReadError {