diff --git a/README.md b/README.md index 8e621795..19a2505e 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 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? @@ -32,7 +34,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. diff --git a/etherparse/Cargo.toml b/etherparse/Cargo.toml index 275fa583..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" @@ -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/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/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(), diff --git a/etherparse/src/defrag/ip_defrag_buf.rs b/etherparse/src/defrag/ip_defrag_buf.rs new file mode 100644 index 00000000..8d4b9e3c --- /dev/null +++ b/etherparse/src/defrag/ip_defrag_buf.rs @@ -0,0 +1,388 @@ +use crate::{defrag::*, *}; +use std::vec::Vec; + +/// 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.byte_offset().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 8 in case it is not the end + if more_fragments && 0 != payload.len() & 0b111 { + 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.byte_offset()); + self.data[data_offset..data_offset + payload.len()].copy_from_slice(payload); + + // update sections + let mut new_section = IpFragRange { + start: offset.byte_offset(), + 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 super::*; + use std::{format, vec}; + + #[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 { + assert!(0 == (a.1.0 % 8)); + buffer.add( + IpFragOffset::try_new(a.1.0 / 8).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 { + assert!(0 == (a.1.0 % 8)); + buffer.add( + IpFragOffset::try_new(a.1.0 / 8).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 { + assert!(0 == (a.1.0 % 8)); + buffer.add( + IpFragOffset::try_new(a.1.0 / 8).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)/8).unwrap(), payload_len, max: u16::MAX }, + buffer.add( + IpFragOffset::try_new((32 + 16)/8).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/8).unwrap(), + false, + &sequence(0, payload_len) + ) + ); + } + + // packets conflicting with previously seen end + 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/8).unwrap(), + payload_len: bad_offset + }, + buffer.add( + IpFragOffset::try_new(48/8).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/8).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/8).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/8).unwrap(), + false, + &sequence(16,16) + ).unwrap_err() + ); + } + } +} diff --git a/etherparse/src/defrag/ip_defrag_error.rs b/etherparse/src/defrag/ip_defrag_error.rs new file mode 100644 index 00000000..d5bc2b77 --- /dev/null +++ b/etherparse/src/defrag/ip_defrag_error.rs @@ -0,0 +1,111 @@ +use crate::*; + +#[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 super::IpDefragError::*; + use super::*; + use std::format; + + #[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/src/defrag/ip_defrag_payload_vec.rs b/etherparse/src/defrag/ip_defrag_payload_vec.rs new file mode 100644 index 00000000..c94984d1 --- /dev/null +++ b/etherparse/src/defrag/ip_defrag_payload_vec.rs @@ -0,0 +1,67 @@ +use crate::*; +use std::vec::Vec; + +/// 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::*; + use std::{format, vec}; + + #[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/src/defrag/ip_defrag_pool.rs b/etherparse/src/defrag/ip_defrag_pool.rs new file mode 100644 index 00000000..d7c2f192 --- /dev/null +++ b/etherparse/src/defrag/ip_defrag_pool.rs @@ -0,0 +1,675 @@ +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). +/// +/// 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 +/// 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 IP packets. + active: HashMap, (IpDefragBuf, Timestamp)>, + + /// 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 +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, + 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)) => { + let header = ipv4.header(); + if false == header.is_fragmenting_payload() { + // nothing to defragment here, skip packet + return Ok(None); + } + + 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(), + }, + payload_ip_number: ipv4.payload().ip_number, + channel_id, + }, + header.fragments_offset(), + header.more_fragments(), + ipv4.payload(), + true, + ) + } + Some(NetSlice::Ipv6(ipv6)) => { + // 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 { + 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); + } + }; + + 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, + }, + payload_ip_number: ipv6.payload().ip_number, + channel_id, + }, + frag.fragment_offset, + frag.more_fragments, + ipv6.payload(), + false, + ) + } + None => { + // nothing to defragment here, skip packet + return Ok(None); + } + }; + + // 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(); + 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(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(()) => { + // 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 + 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); + } + + /// 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(); + } + } +} + +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; + + 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); + } + } + + #[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, + 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/defrag/ip_frag_id.rs b/etherparse/src/defrag/ip_frag_id.rs new file mode 100644 index 00000000..865d3f66 --- /dev/null +++ b/etherparse/src/defrag/ip_frag_id.rs @@ -0,0 +1,24 @@ +use crate::{defrag::*, *}; + +/// 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 & 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, +} diff --git a/etherparse/src/defrag/ip_frag_range.rs b/etherparse/src/defrag/ip_frag_range.rs new file mode 100644 index 00000000..eb9e56fc --- /dev/null +++ b/etherparse/src/defrag/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::*; + use alloc::format; + + #[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/src/defrag/ip_frag_version_spec_id.rs b/etherparse/src/defrag/ip_frag_version_spec_id.rs new file mode 100644 index 00000000..03729045 --- /dev/null +++ b/etherparse/src/defrag/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/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/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/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 { 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/lib.rs b/etherparse/src/lib.rs index 8176e2a9..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? @@ -302,6 +304,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::*; 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 { 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 b5feff8e..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() } @@ -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>), } 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"