diff --git a/benches/bench.rs b/benches/bench.rs index a05ccff91..a3c46bf7b 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -2,7 +2,7 @@ mod wire { use smoltcp::phy::ChecksumCapabilities; - use smoltcp::wire::{IpAddress, IpProtocol}; + use smoltcp::wire::{IPV4_HEADER_LEN, IPV4_MAX_OPTIONS_SIZE, IpAddress, IpProtocol}; #[cfg(feature = "proto-ipv4")] use smoltcp::wire::{Ipv4Address, Ipv4Packet, Ipv4Repr}; #[cfg(feature = "proto-ipv6")] @@ -83,8 +83,16 @@ mod wire { src_addr: Ipv4Address::new(192, 168, 1, 1), dst_addr: Ipv4Address::new(192, 168, 1, 2), next_header: IpProtocol::Tcp, + header_len: IPV4_HEADER_LEN, payload_len: 100, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; IPV4_MAX_OPTIONS_SIZE], }; let mut bytes = vec![0xa5; repr.buffer_len()]; diff --git a/src/iface/fragmentation.rs b/src/iface/fragmentation.rs index 16c84f456..39ce1053a 100644 --- a/src/iface/fragmentation.rs +++ b/src/iface/fragmentation.rs @@ -9,8 +9,16 @@ use crate::storage::Assembler; use crate::time::{Duration, Instant}; use crate::wire::*; +use crate::iface::interface::DispatchError; +use crate::phy::ChecksumCapabilities; +#[cfg(feature = "proto-ipv4")] +use crate::wire::ipv4::{ALIGNMENT_32_BITS, HEADER_LEN, MAX_OPTIONS_SIZE, Packet, Repr}; use core::result::Result; +// Special option type octets. +const OPTION_TYPE_PADDING: u8 = 0x00; +const OPTION_TYPE_NO_OPERATION: u8 = 0x01; + #[cfg(feature = "alloc")] type Buffer = alloc::vec::Vec; #[cfg(not(feature = "alloc"))] @@ -261,6 +269,12 @@ pub(crate) struct FragmentsBuffer { #[cfg(feature = "_proto-fragmentation")] pub reassembly_timeout: Duration, + + #[cfg(feature = "proto-ipv4-fragmentation")] + pub options_buffer: [u8; MAX_OPTIONS_SIZE], + + #[cfg(feature = "proto-ipv4-fragmentation")] + pub options_len: usize, } #[cfg(not(feature = "_proto-fragmentation"))] @@ -332,8 +346,16 @@ impl Fragmenter { src_addr: Ipv4Address::new(0, 0, 0, 0), dst_addr: Ipv4Address::new(0, 0, 0, 0), next_header: IpProtocol::Unknown(0), + header_len: IPV4_HEADER_LEN, payload_len: 0, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 0, + options: [0u8; MAX_OPTIONS_SIZE], }, #[cfg(feature = "medium-ethernet")] dst_hardware_addr: EthernetAddress::default(), @@ -376,8 +398,16 @@ impl Fragmenter { src_addr: Ipv4Address::new(0, 0, 0, 0), dst_addr: Ipv4Address::new(0, 0, 0, 0), next_header: IpProtocol::Unknown(0), + header_len: IPV4_HEADER_LEN, payload_len: 0, hop_limit: 0, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; #[cfg(feature = "medium-ethernet")] { @@ -396,9 +426,118 @@ impl Fragmenter { } } +#[cfg(feature = "proto-ipv4-fragmentation")] +#[derive(PartialEq)] +enum OptionCopyBehavior { + // This option is copied for every fragment + Copy, + // This option is discarded after the first fragment + DontCopy, +} + +#[cfg(feature = "proto-ipv4-fragmentation")] +#[derive(PartialEq)] +enum OptionLengthType { + // This option has an octet specifying the length of the option + HasLength, + // This option has no length octet and is of single octet length + NoLength, +} + +#[cfg(feature = "proto-ipv4-fragmentation")] +impl Ipv4Fragmenter { + /// Determines two characteristics of the option from the type octet. + /// Returns (OptionCopyBehavior, OptionLengthType) + fn parse_option_type_octet(type_octet: u8) -> (OptionCopyBehavior, OptionLengthType) { + let copy_behavior = match (type_octet & 0x80) { + 0x80 => OptionCopyBehavior::Copy, + _ => OptionCopyBehavior::DontCopy, + }; + let length_type = match type_octet { + OPTION_TYPE_PADDING | OPTION_TYPE_NO_OPERATION => OptionLengthType::NoLength, + _ => OptionLengthType::HasLength, + }; + (copy_behavior, length_type) + } + + /// Filters the original option set and overwrites it in the repr for use with subsequent packet fragments. + /// Returns Ok(()) if no error occurs during filtering. + pub(crate) fn filter_options(&mut self) -> Result<(), DispatchError> { + let options_len = self.repr.header_len - HEADER_LEN; + // Exit nicely if no options are present, there is just nothing to filter. + if options_len == 0 { + return Ok(()); + } + // Check for a proper length. There must be enough bytes for at least one operable option. + if options_len < ALIGNMENT_32_BITS + || !options_len.is_multiple_of(ALIGNMENT_32_BITS) + || options_len > MAX_OPTIONS_SIZE + { + return Err(DispatchError::CannotFragment); + } + // Initialize read and write pointers. + let source: &[u8; MAX_OPTIONS_SIZE] = &self.repr.options; + let mut i_read: usize = 0; + let dest: &mut [u8; MAX_OPTIONS_SIZE] = &mut [0u8; MAX_OPTIONS_SIZE]; + let mut i_write: usize = 0; + // Iterate through the options. + while i_read < options_len { + // Parse the type octet to get our instructions for this option. + let type_octet = source[i_read]; + let (copy_behavior, length_type) = Self::parse_option_type_octet(type_octet); + match length_type { + OptionLengthType::HasLength => { + // Nothing prevents defining an option that has a length octet with a value that indicates zero length data, + // so we allow for the presence of a length octet prior to the last octet. + if i_read + 1 >= options_len { + // This is the last octet, and there is no more room for a length octet. + return Err(DispatchError::CannotFragment); + } + // Parse the length octet. + let length = source[i_read + 1] as usize; + // Safely copy the option based on its length. + if copy_behavior == OptionCopyBehavior::Copy { + // Prevent a length from overflowing the end. + if i_write + length > dest.len() || i_read + length > source.len() { + return Err(DispatchError::CannotFragment); + } + dest[i_write..i_write + length] + .copy_from_slice(&source[i_read..i_read + length]); + // Advance the write pointer. + i_write += length; + } + // Advance the read pointer. + i_read += length; + } + OptionLengthType::NoLength => { + // Advance past any single octets. They are not operable option bytes. + // Padding is inserted once the writing of all options is complete. + // Only option types 0x0 and 0x1 have the length bit unset. All other option types have the + // length bit set. Therefore, no operable option types have both the copy bit set + // and the length bit unset. See the IANA option number list at: + // https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml#ip-parameters-1 + i_read += 1; + } + } + } + // If necessary, safely pad the remainder of the alignment in the destination. + while i_write % ALIGNMENT_32_BITS != 0 && i_write < MAX_OPTIONS_SIZE { + dest[i_write] = OPTION_TYPE_PADDING; + i_write += 1; + } + // Apply the filtered options. + self.repr + .set_options(&dest[..i_write]) + .map_err(|_| DispatchError::CannotFragment) + } +} + #[cfg(test)] mod tests { use super::*; + use crate::phy::ChecksumCapabilities; + #[cfg(feature = "proto-ipv4")] + use crate::wire::ipv4::{Packet, Repr}; #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] struct Key { @@ -503,4 +642,182 @@ mod tests { assr.add(&[0x01], 1).unwrap(); assert_eq!(assr.assemble(), Some(&[0x00, 0x01][..])); } + + #[cfg(feature = "proto-ipv4-fragmentation")] + #[test] + fn filter_options_no_options_present() { + const PACKET_BYTES: [u8; 30] = [ + 0x45, 0x21, 0x00, 0x1e, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xd5, 0x4d, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, + ]; + let mut packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + let mut frag = Fragmenter::new(); + frag.ipv4.repr = repr; + assert!(frag.ipv4.filter_options().is_ok()); + assert_eq!(repr, frag.ipv4.repr); + } + + #[cfg(feature = "proto-ipv4-fragmentation")] + #[test] + fn filter_options_one_persisted_option_present() { + const PACKET_BYTES: [u8; 34] = [ + 0x46, 0x21, 0x00, 0x22, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xf1, 0xea, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0x88, 0x04, 0x5a, 0x5a, // Stream Identifier option + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload (10 bytes) + ]; + let mut packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + let mut frag = Fragmenter::new(); + frag.ipv4.repr = repr; + frag.ipv4.filter_options(); + // The stream id remains. Each fragment header is identical. + assert_eq!(repr, frag.ipv4.repr); + assert_eq!(frag.ipv4.repr.header_len, PACKET_BYTES.len() - 10); + assert_eq!(frag.ipv4.repr.payload_len, 10); + // Repeat as if on next fragment. + assert!(frag.ipv4.filter_options().is_ok()); + // The stream id remains. Each fragment header is identical. + assert_eq!(repr, frag.ipv4.repr); + assert_eq!(frag.ipv4.repr.header_len, PACKET_BYTES.len() - 10); + assert_eq!(frag.ipv4.repr.payload_len, 10); + } + + #[cfg(feature = "proto-ipv4-fragmentation")] + #[test] + fn filter_options_one_discarded_option_present_with_noop_padding() { + const PACKET_BYTES: [u8; 38] = [ + 0x47, 0x21, 0x00, 0x26, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xc2, 0x39, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0x07, 0x07, 0x04, 0x01, 0x02, 0x03, 0x04, // Route Record + 0x01, // Padding + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload (10 bytes) + ]; + let mut packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + let mut frag = Fragmenter::new(); + frag.ipv4.repr = repr; + assert!(frag.ipv4.filter_options().is_ok()); + assert_ne!(repr, frag.ipv4.repr); + // The route record is discarded in all further fragments. + assert_eq!(frag.ipv4.repr.header_len, HEADER_LEN); + assert_eq!(frag.ipv4.repr.payload_len, 10); + } + + #[cfg(feature = "proto-ipv4-fragmentation")] + #[test] + fn filter_options_one_discarded_and_one_persisted_with_middle_padding() { + const PACKET_BYTES: [u8; 42] = [ + 0x48, 0x21, 0x00, 0x2a, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xde, 0xd6, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0x07, 0x07, 0x04, 0x01, 0x02, 0x03, 0x04, // Route Record + 0x01, // Padding + 0x88, 0x04, 0x5a, 0x5a, // Stream Identifier option (4 bytes) + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload (10 bytes) + ]; + let mut packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + let mut frag = Fragmenter::new(); + frag.ipv4.repr = repr; + assert!(frag.ipv4.filter_options().is_ok()); + assert_ne!(repr, frag.ipv4.repr); + // The route record is discarded and only the stream id persists to all fragments. + assert_eq!(frag.ipv4.repr.header_len, HEADER_LEN + 4); // stream id only in options + assert_eq!(frag.ipv4.repr.options[0..4], [0x88, 0x04, 0x5a, 0x5a]); + assert_eq!(frag.ipv4.repr.payload_len, 10); + } + + #[cfg(feature = "proto-ipv4-fragmentation")] + #[test] + fn filter_options_max_options_present() { + const PACKET_BYTES: [u8; 70] = [ + 0x4F, 0x21, 0x00, 0x46, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0x14, 0xff, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0x07, 0x23, 0x20, // Route Record + 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, + 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, + 0x01, 0x02, 0x03, 0x04, 0x88, 0x04, 0x5a, + 0x5a, // Stream Identifier option (4 bytes) + 0x01, // Padding + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload (10 bytes) + ]; + let mut packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + let mut frag = Fragmenter::new(); + frag.ipv4.repr = repr; + assert!(frag.ipv4.filter_options().is_ok()); + assert_ne!(repr, frag.ipv4.repr); + // The route record is discarded and only the stream id persists to all fragments. + assert_eq!(frag.ipv4.repr.header_len, HEADER_LEN + 4); // stream id only in options + assert_eq!(frag.ipv4.repr.options[0..4], [0x88, 0x04, 0x5a, 0x5a]); + assert_eq!(frag.ipv4.repr.payload_len, 10); + } + + #[cfg(feature = "proto-ipv4-fragmentation")] + #[test] + fn filter_options_bad_option_at_end_does_not_cause_panic() { + const PACKET_BYTES: [u8; 70] = [ + 0x4F, 0x21, 0x00, 0x46, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0x14, 0x7f, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0x07, 0x23, 0x20, // Route Record + 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, + 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, + 0x01, 0x02, 0x03, 0x04, 0x88, 0x04, 0x5a, + 0x5a, // Stream Identifier option (4 bytes) + 0x81, // Bad octet that indicates a length octet is following, but we are at the end + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload (10 bytes) + ]; + let mut packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + let mut frag = Fragmenter::new(); + frag.ipv4.repr = repr; + assert!(frag.ipv4.filter_options().is_err()); + } + + #[cfg(feature = "proto-ipv4-fragmentation")] + #[test] + fn filter_options_one_discarded_and_one_persisted_with_padding_required_of_different_length() { + const PACKET_BYTES: [u8; 46] = [ + 0x49, 0x21, 0x00, 0x2e, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xac, 0x9d, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0x07, 0x07, 0x04, 0x01, 0x02, 0x03, 0x04, // Route Record + 0x83, 0x07, 0x04, 0x05, 0x06, 0x07, 0x08, // Loose Source + 0x00, 0x00, // Padding (two octets) + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload (10 bytes) + ]; + let mut packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + let mut frag = Fragmenter::new(); + frag.ipv4.repr = repr; + assert!(frag.ipv4.filter_options().is_ok()); + assert_ne!(repr, frag.ipv4.repr); + // The route record is discarded and only the loose source option persists to all fragments. + // Only one octet of padding is needed. + assert_eq!(frag.ipv4.repr.header_len, HEADER_LEN + 8); + assert_eq!(frag.ipv4.repr.payload_len, 10); + assert_eq!( + frag.ipv4.repr.options[0..8], + [0x83, 0x07, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00] + ); + } + + #[cfg(feature = "proto-ipv4-fragmentation")] + #[test] + fn filter_options_length_octet_overflow() { + const PACKET_BYTES: [u8; 70] = [ + 0x4F, 0x21, 0x00, 0x46, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xcb, 0x25, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xaa, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload (10 bytes) + ]; + let mut packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + let mut frag = Fragmenter::new(); + frag.ipv4.repr = repr; + assert!(frag.ipv4.filter_options().is_err()); + } } diff --git a/src/iface/interface/ipv4.rs b/src/iface/interface/ipv4.rs index 6049fd554..26914f6f5 100644 --- a/src/iface/interface/ipv4.rs +++ b/src/iface/interface/ipv4.rs @@ -1,4 +1,5 @@ use super::*; +use crate::wire::ipv4::MAX_OPTIONS_SIZE; impl Interface { /// Process fragments that still need to be sent for IPv4 packets. @@ -100,13 +101,21 @@ impl InterfaceInner { ipv4_packet: &Ipv4Packet<&'a [u8]>, frag: &'a mut FragmentsBuffer, ) -> Option> { - let ipv4_repr = check!(Ipv4Repr::parse(ipv4_packet, &self.caps.checksum)); + let mut ipv4_repr = check!(Ipv4Repr::parse(ipv4_packet, &self.caps.checksum)); if !self.is_unicast_v4(ipv4_repr.src_addr) && !ipv4_repr.src_addr.is_unspecified() { // Discard packets with non-unicast source addresses but allow unspecified net_debug!("non-unicast or unspecified source address"); return None; } + // If this is the first fragment, capture the options. + #[cfg(feature = "proto-ipv4-fragmentation")] + if ipv4_packet.frag_offset() == 0 && ipv4_packet.has_options() { + frag.options_buffer[..ipv4_repr.options_len()] + .copy_from_slice(&ipv4_repr.options[..ipv4_repr.options_len()]); + frag.options_len = ipv4_repr.options_len() + } + #[cfg(feature = "proto-ipv4-fragmentation")] let ip_payload = { if ipv4_packet.more_frags() || ipv4_packet.frag_offset() != 0 { @@ -133,10 +142,12 @@ impl InterfaceInner { return None; } - // NOTE: according to the standard, the total length needs to be - // recomputed, as well as the checksum. However, we don't really use - // the IPv4 header after the packet is reassembled. - f.assemble()? + // Returns early if assembly is incomplete. + let payload = f.assemble()?; + + // Update the payload length, so that the raw sockets get the correct value. + ipv4_repr.payload_len = payload.len(); + payload } else { ipv4_packet.payload() } @@ -145,6 +156,16 @@ impl InterfaceInner { #[cfg(not(feature = "proto-ipv4-fragmentation"))] let ip_payload = ipv4_packet.payload(); + #[cfg(feature = "proto-ipv4-fragmentation")] + // The first fragment will by definition have all options. The length of the options it + // contains will be greater than or equal to size of the options in the other fragments. + // Therefore, this is guaranteed to overwrite whatever was captured when the repr object + // parsed the current packet. + if let Err(e) = ipv4_repr.set_options(&frag.options_buffer[..frag.options_len]) { + net_debug!("fragmentation assembler options error: {:?}", e); + return None; + } + let ip_repr = IpRepr::Ipv4(ipv4_repr); #[cfg(feature = "socket-raw")] @@ -386,8 +407,16 @@ impl InterfaceInner { src_addr: ipv4_repr.dst_addr, dst_addr: ipv4_repr.src_addr, next_header: IpProtocol::Icmp, + header_len: IPV4_HEADER_LEN, payload_len: icmp_repr.buffer_len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; Some(Packet::new_ipv4( ipv4_reply_repr, @@ -402,8 +431,16 @@ impl InterfaceInner { src_addr, dst_addr: ipv4_repr.src_addr, next_header: IpProtocol::Icmp, + header_len: IPV4_HEADER_LEN, payload_len: icmp_repr.buffer_len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; Some(Packet::new_ipv4( ipv4_reply_repr, @@ -423,9 +460,9 @@ impl InterfaceInner { pub(super) fn dispatch_ipv4_frag(&mut self, tx_token: Tx, frag: &mut Fragmenter) { let caps = self.caps.clone(); - let mtu_max = self.ip_mtu(); - let ip_len = (frag.packet_len - frag.sent_bytes + frag.ipv4.repr.buffer_len()).min(mtu_max); - let payload_len = ip_len - frag.ipv4.repr.buffer_len(); + let max_fragment_size = caps.max_ipv4_fragment_size(frag.ipv4.repr.buffer_len()); + let payload_len = (frag.packet_len - frag.sent_bytes).min(max_fragment_size); + let ip_len = payload_len + frag.ipv4.repr.buffer_len(); let more_frags = (frag.packet_len - frag.sent_bytes) != payload_len; frag.ipv4.repr.payload_len = payload_len; diff --git a/src/iface/interface/mod.rs b/src/iface/interface/mod.rs index d10d053ff..c7a9a749f 100644 --- a/src/iface/interface/mod.rs +++ b/src/iface/interface/mod.rs @@ -63,6 +63,8 @@ macro_rules! check { } }; } +#[cfg(feature = "proto-ipv4")] +use crate::wire::ipv4::MAX_OPTIONS_SIZE; use check; /// Result returned by [`Interface::poll`]. @@ -239,6 +241,12 @@ impl Interface { assembler: PacketAssemblerSet::new(), #[cfg(feature = "_proto-fragmentation")] reassembly_timeout: Duration::from_secs(60), + + #[cfg(feature = "proto-ipv4-fragmentation")] + options_buffer: [0u8; MAX_OPTIONS_SIZE], + + #[cfg(feature = "proto-ipv4-fragmentation")] + options_len: 0, }, fragmenter: Fragmenter::new(), inner: InterfaceInner { @@ -1223,10 +1231,16 @@ impl InterfaceInner { net_debug!("start fragmentation"); // Calculate how much we will send now (including the Ethernet header). - let tx_len = self.caps.max_transmission_unit; let ip_header_len = repr.buffer_len(); - let first_frag_ip_len = self.caps.ip_mtu(); + let first_frag_data_len = + self.caps.max_ipv4_fragment_size(repr.buffer_len()); + let first_frag_ip_len = first_frag_data_len + ip_header_len; + let mut tx_len = first_frag_ip_len; + #[cfg(feature = "medium-ethernet")] + if matches!(caps.medium, Medium::Ethernet) { + tx_len += EthernetFrame::<&[u8]>::header_len(); + } if frag.buffer.len() < total_ip_len { net_debug!( @@ -1248,15 +1262,23 @@ impl InterfaceInner { // Save the IP header for other fragments. frag.ipv4.repr = *repr; - // Save how much bytes we will send now. - frag.sent_bytes = first_frag_ip_len; - // Modify the IP header - repr.payload_len = first_frag_ip_len - repr.buffer_len(); + repr.payload_len = first_frag_data_len; + + // Save the number of bytes we will send now. + frag.sent_bytes = first_frag_ip_len; // Emit the IP header to the buffer. emit_ip(&ip_repr, &mut frag.buffer); + // Verify that we can filter the options for the subsequent packets. + if frag.ipv4.filter_options().is_err() { + net_debug!( + "Could not fragment packet because options cannot be filtered. Dropping." + ); + return Ok(()); + }; + let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut frag.buffer[..]); frag.ipv4.ident = ipv4_id; ipv4_packet.set_ident(ipv4_id); @@ -1336,7 +1358,7 @@ impl InterfaceInner { #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -enum DispatchError { +pub enum DispatchError { /// No route to dispatch this packet. Retrying won't help unless /// configuration is changed. NoRoute, @@ -1344,4 +1366,6 @@ enum DispatchError { /// the neighbor for it yet. Discovery has been initiated, dispatch /// should be retried later. NeighborPending, + /// The packet must be fragmented but there was a parse error. + CannotFragment, } diff --git a/src/iface/interface/multicast.rs b/src/iface/interface/multicast.rs index 71dbaa110..17fc41184 100644 --- a/src/iface/interface/multicast.rs +++ b/src/iface/interface/multicast.rs @@ -6,6 +6,8 @@ use super::{Interface, InterfaceInner}; use super::{IpPayload, Packet, check}; use crate::config::{IFACE_MAX_ADDR_COUNT, IFACE_MAX_MULTICAST_GROUP_COUNT}; use crate::phy::{Device, PacketMeta}; +#[cfg(feature = "proto-ipv4")] +use crate::wire::ipv4::MAX_OPTIONS_SIZE; use crate::wire::*; /// Error type for `join_multicast_group`, `leave_multicast_group`. @@ -480,7 +482,15 @@ impl InterfaceInner { dst_addr: group_addr, next_header: IpProtocol::Igmp, payload_len: igmp_repr.buffer_len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 1, + options: [0u8; MAX_OPTIONS_SIZE], // [#183](https://github.com/m-labs/smoltcp/issues/183). }, IpPayload::Igmp(igmp_repr), @@ -498,7 +508,15 @@ impl InterfaceInner { dst_addr: IPV4_MULTICAST_ALL_ROUTERS, next_header: IpProtocol::Igmp, payload_len: igmp_repr.buffer_len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 1, + options: [0u8; MAX_OPTIONS_SIZE], }, IpPayload::Igmp(igmp_repr), ) diff --git a/src/iface/interface/tests/ipv4.rs b/src/iface/interface/tests/ipv4.rs index f22526e38..d9e0e7cf4 100644 --- a/src/iface/interface/tests/ipv4.rs +++ b/src/iface/interface/tests/ipv4.rs @@ -1,4 +1,7 @@ use super::*; +#[cfg(feature = "proto-ipv4-fragmentation")] +use crate::phy::IPV4_FRAGMENT_PAYLOAD_ALIGNMENT; +use crate::wire::ipv4::MAX_OPTIONS_SIZE; #[rstest] #[case(Medium::Ethernet)] @@ -80,7 +83,15 @@ fn test_no_icmp_no_unicast(#[case] medium: Medium) { dst_addr: Ipv4Address::BROADCAST, next_header: IpProtocol::Unknown(0x0c), payload_len: 0, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 0x40, + options: [0u8; MAX_OPTIONS_SIZE], }); let mut bytes = vec![0u8; 54]; @@ -118,7 +129,15 @@ fn test_icmp_error_no_payload(#[case] medium: Medium) { dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), next_header: IpProtocol::Unknown(0x0c), payload_len: 0, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 0x40, + options: [0u8; MAX_OPTIONS_SIZE], }); let mut bytes = vec![0u8; 34]; @@ -134,7 +153,15 @@ fn test_icmp_error_no_payload(#[case] medium: Medium) { dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), next_header: IpProtocol::Unknown(12), payload_len: 0, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }, data: &NO_BYTES, }; @@ -145,7 +172,15 @@ fn test_icmp_error_no_payload(#[case] medium: Medium) { dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), next_header: IpProtocol::Icmp, payload_len: icmp_repr.buffer_len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }, IpPayload::Icmpv4(icmp_repr), ); @@ -298,7 +333,15 @@ fn test_icmp_error_port_unreachable(#[case] medium: Medium) { dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), next_header: IpProtocol::Udp, payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }); // Emit the representations to a packet @@ -322,7 +365,15 @@ fn test_icmp_error_port_unreachable(#[case] medium: Medium) { dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), next_header: IpProtocol::Udp, payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }, data, }; @@ -332,7 +383,15 @@ fn test_icmp_error_port_unreachable(#[case] medium: Medium) { dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), next_header: IpProtocol::Icmp, payload_len: icmp_repr.buffer_len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }, IpPayload::Icmpv4(icmp_repr), ); @@ -351,7 +410,15 @@ fn test_icmp_error_port_unreachable(#[case] medium: Medium) { dst_addr: Ipv4Address::BROADCAST, next_header: IpProtocol::Udp, payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + header_len: IPV4_HEADER_LEN, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }); // Emit the representations to a packet @@ -405,8 +472,16 @@ fn test_handle_ipv4_broadcast(#[case] medium: Medium) { src_addr: src_ipv4_addr, dst_addr: Ipv4Address::BROADCAST, next_header: IpProtocol::Icmp, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, payload_len: icmpv4_repr.buffer_len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; // Emit to ip frame @@ -433,8 +508,16 @@ fn test_handle_ipv4_broadcast(#[case] medium: Medium) { src_addr: our_ipv4_addr, dst_addr: src_ipv4_addr, next_header: IpProtocol::Icmp, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, payload_len: expected_icmpv4_repr.buffer_len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; let expected_packet = Packet::new_ipv4(expected_ipv4_repr, IpPayload::Icmpv4(expected_icmpv4_repr)); @@ -665,7 +748,15 @@ fn test_icmpv4_socket(#[case] medium: Medium) { dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), next_header: IpProtocol::Icmp, payload_len: 24, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; // Open a socket and ensure the packet is handled due to the listening @@ -842,7 +933,15 @@ fn test_packet_len(#[case] medium: Medium) { dst_addr: Ipv4Address::new(127, 0, 0, 1), next_header: IpProtocol::Udp, payload_len: 0, + header_len: IPV4_HEADER_LEN, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; let udp_repr = UdpRepr { src_port: 12345, @@ -898,6 +997,49 @@ fn check_no_reply_raw_socket(medium: Medium, frame: &crate::wire::ipv4::Packet<& ); } +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "socket-raw", feature = "medium-ip"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "socket-raw", feature = "medium-ethernet"))] +/// Test raw socket will process options to receiving device +fn test_raw_socket_process_with_option(#[case] medium: Medium) { + const PACKET_BYTES: &[u8] = &[ + 0x46, 0x21, 0x00, 0x22, 0x01, 0x02, 0x40, 0x00, 0x1a, 0x01, 0x13, 0xee, 0x11, 0x12, 0x13, + 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0x88, 0x04, 0x5a, 0x5a, // Stream Identifier option + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload + ]; + + let packet = crate::wire::ipv4::Packet::new_unchecked(PACKET_BYTES); + + let (mut iface, mut sockets, _) = setup(medium); + + let packet_count = 1; + let rx_buffer = raw::PacketBuffer::new( + vec![raw::PacketMetadata::EMPTY; packet_count], + vec![0; PACKET_BYTES.len()], + ); + let tx_buffer = raw::PacketBuffer::new( + vec![raw::PacketMetadata::EMPTY; packet_count], + vec![0; PACKET_BYTES.len()], + ); + let raw_socket = raw::Socket::new(Some(IpVersion::Ipv4), None, rx_buffer, tx_buffer); + let handle = sockets.add(raw_socket); + + let result = iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &packet, + &mut iface.fragments, + ); + assert_eq!(result, None); + let socket = sockets.get_mut::(handle); + assert_eq!(socket.recv_queue(), PACKET_BYTES.len()); + assert_eq!(socket.recv().unwrap(), PACKET_BYTES); +} + #[rstest] #[case(Medium::Ip)] #[cfg(all(feature = "socket-raw", feature = "medium-ip"))] @@ -920,8 +1062,16 @@ fn test_raw_socket_no_reply_udp(#[case] medium: Medium) { src_addr, dst_addr, next_header: IpProtocol::Udp, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, payload_len: udp_repr.header_len() + PAYLOAD_LEN, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; // Emit to frame @@ -978,8 +1128,16 @@ fn test_raw_socket_no_reply_tcp(#[case] medium: Medium) { src_addr, dst_addr, next_header: IpProtocol::Tcp, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, payload_len: tcp_repr.header_len() + PAYLOAD_LEN, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; // Emit to frame @@ -1065,8 +1223,16 @@ fn test_raw_socket_with_udp_socket(#[case] medium: Medium) { src_addr, dst_addr, next_header: IpProtocol::Udp, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; // Emit to frame @@ -1113,6 +1279,121 @@ fn test_raw_socket_with_udp_socket(#[case] medium: Medium) { ); } +#[rstest] +#[cfg(all(feature = "socket-raw", feature = "medium-ip"))] +fn test_raw_socket_tx_with_option() { + let (mut iface, _, _) = setup(Medium::Ip); + + static PAYLOAD: &[u8] = &[0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff]; + static OPTION: &[u8] = &[0x88, 0x04, 0x5a, 0x5a]; + + let mut ip_repr = Ipv4Repr { + src_addr: Ipv4Address::new(192, 168, 1, 3), + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Icmp, + header_len: IPV4_HEADER_LEN + OPTION.len(), + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + payload_len: 10, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], + }; + ip_repr.set_options(OPTION).unwrap(); + let ip_payload = IpPayload::Raw(PAYLOAD); + let packet = Packet::new_ipv4(ip_repr, ip_payload); + + struct TestTxToken; + + impl TxToken for TestTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = [0; 64]; + let result = f(&mut buffer[..len]); + let option_end = IPV4_HEADER_LEN + OPTION.len(); + let payload_end = option_end + PAYLOAD.len(); + assert_eq!(buffer[IPV4_HEADER_LEN..option_end], *OPTION); + assert_eq!(buffer[option_end..payload_end], *PAYLOAD); + result + } + } + + let result = iface.inner.dispatch_ip( + TestTxToken {}, + PacketMeta::default(), + packet, + &mut iface.fragmenter, + ); + + assert!(result.is_ok()); +} + +#[rstest] +#[cfg(all(feature = "socket-raw", feature = "medium-ip"))] +fn test_raw_socket_tx_with_bad_option() { + // Form the socket. + + let (mut iface, _, device) = setup(Medium::Ip); + let mtu: usize = device.capabilities().max_transmission_unit; + + // Form the packet to be sent. + + let packet_size = mtu * 5 / 4; // Larger than MTU, requires fragment + let payload_len = packet_size - IPV4_HEADER_LEN; + let payload = vec![0xa5u8; payload_len]; + + let mut ip_repr = Ipv4Repr { + src_addr: Ipv4Address::new(192, 168, 1, 3), + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + payload_len, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], + }; + + const OPTIONS_BYTES: [u8; 4] = [ + 0x88, 0xFF, 0x5a, 0x5a, // Stream Identifier option with bad length + ]; + + ip_repr.set_options(&OPTIONS_BYTES).unwrap(); + let ip_payload = IpPayload::Raw(&payload); + let packet = Packet::new_ipv4(ip_repr, ip_payload); + + struct TestPanicTxToken {} + + impl TxToken for TestPanicTxToken { + fn consume(self, _: usize, _: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + panic!("Test should never reach here"); + } + } + + let result = iface.inner.dispatch_ip( + TestPanicTxToken {}, + PacketMeta::default(), + packet, + &mut iface.fragmenter, + ); + + // Filtering should fail and the packet dropped, indicating the consume method in the tx token + // was never executed, otherwise the test would have panicked. + assert!(result.is_ok()); +} + #[rstest] #[case(Medium::Ip)] #[cfg(all( @@ -1162,8 +1443,16 @@ fn test_raw_socket_tx_fragmentation(#[case] medium: Medium) { src_addr: Ipv4Address::new(192, 168, 1, 3), dst_addr: Ipv4Address::BROADCAST, next_header: IpProtocol::Unknown(92), + header_len: IPV4_HEADER_LEN, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, payload_len, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; let ip_payload = IpPayload::Raw(&payload); let packet = Packet::new_ipv4(ip_repr, ip_payload); @@ -1183,6 +1472,430 @@ fn test_raw_socket_tx_fragmentation(#[case] medium: Medium) { } } +#[rstest] +#[cfg(all( + feature = "socket-raw", + feature = "medium-ip", + feature = "proto-ipv4-fragmentation", +))] +fn test_raw_socket_tx_fragmentation_with_options() { + // Form the socket. + + let (mut iface, mut sockets, device) = setup(Medium::Ip); + let mtu: usize = device.capabilities().max_transmission_unit; + + let packets: usize = 5; + let rx_buffer = raw::PacketBuffer::new( + vec![raw::PacketMetadata::EMPTY; packets], + vec![0; mtu * packets], + ); + let tx_buffer = raw::PacketBuffer::new( + vec![raw::PacketMetadata::EMPTY; packets], + vec![0; mtu * packets], + ); + let socket = raw::Socket::new( + Some(IpVersion::Ipv4), + Some(IpProtocol::Udp), + rx_buffer, + tx_buffer, + ); + let _handle = sockets.add(socket); + + // Form the packet to be sent. + + let packet_size = mtu * 9 / 4; // Larger than MTU, requires two fragments + let payload_len = packet_size - IPV4_HEADER_LEN; + let payload = vec![0xa5u8; payload_len]; + + let mut ip_repr = Ipv4Repr { + src_addr: Ipv4Address::new(192, 168, 1, 3), + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Unknown(92), + header_len: IPV4_HEADER_LEN, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + payload_len, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], + }; + + const OPTIONS_BYTES: [u8; 12] = [ + 0x07, 0x07, 0x04, 0x01, 0x02, 0x03, 0x04, // Route Record + 0x01, // Padding + 0x88, 0x04, 0x5a, 0x5a, // Stream Identifier option + ]; + + ip_repr.set_options(&OPTIONS_BYTES).unwrap(); + let ip_payload = IpPayload::Raw(&payload); + let packet = Packet::new_ipv4(ip_repr, ip_payload); + + // Define test tokens for capturing the fragments. + + struct TestFirstFragmentTxToken {} + + // The first fragment should have all the options. + impl TxToken for TestFirstFragmentTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // Buffer is something arbitrarily large. + // We cannot capture the dynamic packet_size calculation here. + let mut buffer = [0; 2048]; + let result = f(&mut buffer[..len]); + let option_end = IPV4_HEADER_LEN + OPTIONS_BYTES.len(); + assert_eq!(buffer[IPV4_HEADER_LEN..option_end], OPTIONS_BYTES); + // Verify the payload size is aligned. + let payload_size = len - option_end; + assert!(payload_size.is_multiple_of(IPV4_FRAGMENT_PAYLOAD_ALIGNMENT)); + result + } + } + + struct TestSubsequentFragmentTxToken {} + + // Remaining fragments should only have the stream ID. + impl TxToken for TestSubsequentFragmentTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = [0; 2048]; + let result = f(&mut buffer[..len]); + let stream_id = [0x88, 0x04, 0x5a, 0x5a]; + let option_end = IPV4_HEADER_LEN + stream_id.len(); + assert_ne!(buffer[IPV4_HEADER_LEN..option_end], OPTIONS_BYTES); + assert_eq!(buffer[IPV4_HEADER_LEN..option_end], stream_id); + result + } + } + + // Send the packets. Test assertions are in the test token `consume()` implementations. + + let result = iface.inner.dispatch_ip( + TestFirstFragmentTxToken {}, + PacketMeta::default(), + packet, + &mut iface.fragmenter, + ); + assert!(result.is_ok()); + + // Verify that the fragment offset is correct. + let unaligned_length = mtu - IPV4_HEADER_LEN - OPTIONS_BYTES.len(); + // This check ensures a valid test in which we actually do adjust for alignment. + assert!(!unaligned_length.is_multiple_of(IPV4_FRAGMENT_PAYLOAD_ALIGNMENT)); + let remainder = unaligned_length % IPV4_FRAGMENT_PAYLOAD_ALIGNMENT; + let expected_fragment_offset = mtu - IPV4_HEADER_LEN - OPTIONS_BYTES.len() - remainder; + let frag_offset = iface.fragmenter.ipv4.frag_offset; + assert_eq!(frag_offset as usize, expected_fragment_offset); + + for _ in 0..2 { + iface + .inner + .dispatch_ipv4_frag(TestSubsequentFragmentTxToken {}, &mut iface.fragmenter); + } + + // The fragment offset should be the complete payload length once transmission is complete. + let frag_offset = iface.fragmenter.ipv4.frag_offset; + assert_eq!(frag_offset as usize, payload_len); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all( + feature = "socket-raw", + feature = "proto-ipv4-fragmentation", + feature = "medium-ip" +))] +#[case(Medium::Ethernet)] +#[cfg(all( + feature = "socket-raw", + feature = "proto-ipv4-fragmentation", + feature = "medium-ethernet" +))] +fn test_raw_socket_rx_fragmentation(#[case] medium: Medium) { + use crate::wire::{IpProtocol, IpVersion, Ipv4Address, Ipv4Packet, Ipv4Repr}; + + let (mut iface, mut sockets, _device) = setup(medium); + + // Raw socket bound to IPv4 and a custom protocol. + let packets = 1; + let rx_buffer = raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 64]); + let tx_buffer = raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 64]); + let raw_socket = raw::Socket::new( + Some(IpVersion::Ipv4), + Some(IpProtocol::Unknown(99)), + rx_buffer, + tx_buffer, + ); + let handle = sockets.add(raw_socket); + + // Build two IPv4 fragments that together form one packet. + let src_addr = Ipv4Address::new(127, 0, 0, 2); + let dst_addr = Ipv4Address::new(127, 0, 0, 1); + let proto = IpProtocol::Unknown(99); + let ident: u16 = 0x1234; + + const OPTIONS_BYTES: [u8; 12] = [ + 0x07, 0x07, 0x04, 0x01, 0x02, 0x03, 0x04, // Route Record + 0x01, // Padding + 0x88, 0x04, 0x5a, 0x5a, // Stream Identifier option + ]; + + let total_payload_len = 30usize; + let first_payload_len = 24usize; // must be a multiple of 8 + let last_payload_len = total_payload_len - first_payload_len; + + // Helper to build one fragment as on-the-wire bytes + let build_fragment = |payload_len: usize, + more_frags: bool, + frag_offset_octets: u16, + payload_byte: u8, + options: &[u8]| + -> Vec { + let mut repr = Ipv4Repr { + src_addr, + dst_addr, + next_header: proto, + hop_limit: 64, + payload_len, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + options: [0u8; MAX_OPTIONS_SIZE], + }; + repr.set_options(options).unwrap(); + + let header_len = repr.buffer_len(); + let mut bytes = vec![0u8; header_len + payload_len]; + { + let mut pkt = Ipv4Packet::new_unchecked(&mut bytes[..]); + repr.emit(&mut pkt, &ChecksumCapabilities::default()); + pkt.set_ident(ident); + pkt.set_dont_frag(false); + pkt.set_more_frags(more_frags); + pkt.set_frag_offset(frag_offset_octets); + + // Recompute checksum after changing fragmentation fields. + pkt.fill_checksum(); + } + // Fill payload with a simple pattern for validation + for b in &mut bytes[header_len..] { + *b = payload_byte; + } + bytes + }; + + let frag1_bytes = build_fragment(first_payload_len, true, 0, 0xAA, &OPTIONS_BYTES[..]); + let frag2_bytes = build_fragment( + last_payload_len, + false, + first_payload_len as u16, + 0xBB, + &OPTIONS_BYTES[8..], + ); + + let frag1 = Ipv4Packet::new_unchecked(&frag1_bytes[..]); + let frag2 = Ipv4Packet::new_unchecked(&frag2_bytes[..]); + + // First fragment alone should not be delivered to the raw socket. + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frag1, + &mut iface.fragments + ), + None + ); + { + let socket = sockets.get_mut::(handle); + assert!(!socket.can_recv()); + } + + // After the last fragment, the reassembled packet should be delivered. + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frag2, + &mut iface.fragments + ), + None + ); + + // Validate the raw socket received one defragmented packet with correct payload. + let socket = sockets.get_mut::(handle); + assert!(socket.can_recv()); + let data = socket.recv().expect("raw socket should have a packet"); + let packet = Ipv4Packet::new_unchecked(data); + let repr = Ipv4Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + assert_eq!(repr.src_addr, src_addr); + assert_eq!(repr.dst_addr, dst_addr); + assert_eq!(repr.next_header, proto); + assert_eq!(repr.payload_len, total_payload_len); + assert_eq!(repr.options_len(), OPTIONS_BYTES.len()); + assert_eq!( + repr.options[0..repr.options_len()], + OPTIONS_BYTES[0..repr.options_len()] + ); + + let payload = packet.payload(); + assert_eq!(payload.len(), total_payload_len); + assert!(payload[..first_payload_len].iter().all(|&b| b == 0xAA)); + assert!(payload[first_payload_len..].iter().all(|&b| b == 0xBB)); +} + +#[rstest] +#[cfg(all( + feature = "socket-raw", + feature = "proto-ipv4-fragmentation", + feature = "medium-ip" +))] +fn test_raw_socket_rx_fragmentation_with_options_out_of_order_recv() { + use crate::wire::{IpProtocol, IpVersion, Ipv4Address, Ipv4Packet, Ipv4Repr}; + + let (mut iface, mut sockets, _device) = setup(Medium::Ip); + + // Raw socket bound to IPv4 and a custom protocol. + let packets = 1; + let rx_buffer = raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 64]); + let tx_buffer = raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 64]); + let raw_socket = raw::Socket::new( + Some(IpVersion::Ipv4), + Some(IpProtocol::Unknown(99)), + rx_buffer, + tx_buffer, + ); + let handle = sockets.add(raw_socket); + + // Build two IPv4 fragments that together form one packet. + let src_addr = Ipv4Address::new(127, 0, 0, 2); + let dst_addr = Ipv4Address::new(127, 0, 0, 1); + let proto = IpProtocol::Unknown(99); + let ident: u16 = 0x1234; + + let total_payload_len = 30usize; + let first_payload_len = 24usize; // must be a multiple of 8 + let last_payload_len = total_payload_len - first_payload_len; + + // Helper to build one fragment as on-the-wire bytes + let build_fragment = |payload_len: usize, + more_frags: bool, + frag_offset_octets: u16, + payload_byte: u8, + options: &[u8]| + -> Vec { + let mut repr = Ipv4Repr { + src_addr, + dst_addr, + next_header: proto, + hop_limit: 64, + payload_len, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + options: [0u8; MAX_OPTIONS_SIZE], + }; + repr.set_options(options).unwrap(); + let header_len = repr.buffer_len(); + let mut bytes = vec![0u8; header_len + payload_len]; + { + let mut pkt = Ipv4Packet::new_unchecked(&mut bytes[..]); + repr.emit(&mut pkt, &ChecksumCapabilities::default()); + pkt.set_ident(ident); + pkt.set_dont_frag(false); + pkt.set_more_frags(more_frags); + pkt.set_frag_offset(frag_offset_octets); + // Recompute checksum after changing fragmentation fields. + pkt.fill_checksum(); + } + // Fill payload with a simple pattern for validation + for b in &mut bytes[header_len..] { + *b = payload_byte; + } + bytes + }; + + // Define a full option list and a filtered option list. + let full_options = [ + 0x07, 0x07, 0x04, 0x01, 0x02, 0x03, 0x04, // Route Record + 0x01, // Padding + 0x88, 0x04, 0x5a, 0x5a, // Stream Identifier option (4 bytes) + ]; + let filtered_options = [0x88, 0x04, 0x5a, 0x5a]; + + let frag1_bytes = build_fragment(first_payload_len, true, 0, 0xAA, full_options.as_slice()); + let frag2_bytes = build_fragment( + last_payload_len, + false, + first_payload_len as u16, + 0xBB, + filtered_options.as_slice(), + ); + + let frag1 = Ipv4Packet::new_unchecked(&frag1_bytes[..]); + let frag2 = Ipv4Packet::new_unchecked(&frag2_bytes[..]); + + // Send the last fragment first. + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frag2, + &mut iface.fragments + ), + None + ); + { + let socket = sockets.get_mut::(handle); + assert!(!socket.can_recv()); + } + + // Send the first fragment last. + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frag1, + &mut iface.fragments + ), + None + ); + + // Validate the raw socket received one defragmented packet with correct options and payload. + let socket = sockets.get_mut::(handle); + assert!(socket.can_recv()); + let data = socket.recv().expect("raw socket should have a packet"); + let packet = Ipv4Packet::new_unchecked(data); + let repr = Ipv4Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + assert_eq!(repr.payload_len, total_payload_len); + assert_eq!(repr.header_len, IPV4_HEADER_LEN + full_options.len()); + assert_eq!(repr.options_len(), full_options.len()); + assert_eq!(repr.options[..repr.options_len()], full_options); + + let payload = packet.payload(); + assert_eq!(payload.len(), total_payload_len); + assert!(payload[..first_payload_len].iter().all(|&b| b == 0xAA)); + assert!(payload[first_payload_len..].iter().all(|&b| b == 0xBB)); +} + #[rstest] #[case(Medium::Ip)] #[cfg(all(feature = "socket-udp", feature = "medium-ip"))] @@ -1218,8 +1931,16 @@ fn test_icmp_reply_size(#[case] medium: Medium) { src_addr, dst_addr, next_header: IpProtocol::Udp, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; let payload = packet.into_inner(); @@ -1233,8 +1954,16 @@ fn test_icmp_reply_size(#[case] medium: Medium) { src_addr: dst_addr, dst_addr: src_addr, next_header: IpProtocol::Icmp, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, payload_len: expected_icmp_repr.buffer_len(), + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + options: [0u8; MAX_OPTIONS_SIZE], }; assert_eq!( @@ -1336,3 +2065,13 @@ fn get_source_address_empty_interface(#[case] medium: Medium) { None ); } + +#[rstest] +#[cfg(all(feature = "medium-ip", feature = "proto-ipv4-fragmentation",))] +fn test_ipv4_fragment_size() { + let (_, _, device) = setup(Medium::Ip); + let caps = device.capabilities(); + assert_eq!(caps.ip_mtu(), 1500); // this is assumed + assert_eq!(caps.max_ipv4_fragment_size(20), 1480); + assert_eq!(caps.max_ipv4_fragment_size(32), 1464); +} diff --git a/src/iface/interface/tests/mod.rs b/src/iface/interface/tests/mod.rs index 907b22bbd..8ef878d90 100644 --- a/src/iface/interface/tests/mod.rs +++ b/src/iface/interface/tests/mod.rs @@ -109,6 +109,10 @@ fn test_handle_udp_broadcast(#[case] medium: Medium) { dst_addr: Ipv4Address::BROADCAST, next_header: IpProtocol::Udp, payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 0x40, }); let dst_addr = ip_repr.dst_addr(); diff --git a/src/phy/mod.rs b/src/phy/mod.rs index 79bacce9d..1d3de66e5 100644 --- a/src/phy/mod.rs +++ b/src/phy/mod.rs @@ -132,6 +132,10 @@ pub use self::tracer::{Tracer, TracerDirection, TracerPacket}; ))] pub use self::tuntap_interface::TunTapInterface; +/// The IPV4 payload fragment size must be an increment of this value. +#[cfg(feature = "proto-ipv4-fragmentation")] +pub const IPV4_FRAGMENT_PAYLOAD_ALIGNMENT: usize = 8; + /// Metadata associated to a packet. /// /// The packet metadata is a set of attributes associated to network packets @@ -287,6 +291,13 @@ impl DeviceCapabilities { Medium::Ieee802154 => self.max_transmission_unit, // TODO(thvdveld): what is the MTU for Medium::IEEE802 } } + + /// Special case method to determine the maximum payload size that is based on the MTU and also aligned per spec. + #[cfg(feature = "proto-ipv4-fragmentation")] + pub fn max_ipv4_fragment_size(&self, ip_header_len: usize) -> usize { + let payload_mtu = self.ip_mtu() - ip_header_len; + payload_mtu - (payload_mtu % IPV4_FRAGMENT_PAYLOAD_ALIGNMENT) + } } /// Type of medium of a device. diff --git a/src/phy/tracer.rs b/src/phy/tracer.rs index 4fa9417ae..4ebf5166b 100644 --- a/src/phy/tracer.rs +++ b/src/phy/tracer.rs @@ -217,6 +217,8 @@ mod tests { use super::*; use crate::phy::ChecksumCapabilities; + #[cfg(feature = "proto-ipv4")] + use crate::wire::{IPV4_HEADER_LEN, ipv4::MAX_OPTIONS_SIZE}; use crate::{ phy::{Device, Loopback, RxToken, TxToken}, time::Instant, @@ -331,8 +333,16 @@ mod tests { src_addr: Ipv4Address::new(10, 0, 0, 1), dst_addr: Ipv4Address::new(10, 0, 0, 2), next_header: IpProtocol::Unknown(255), + header_len: IPV4_HEADER_LEN, payload_len: 0, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; let mut buffer = vec![0_u8; repr.buffer_len()]; diff --git a/src/socket/dhcpv4.rs b/src/socket/dhcpv4.rs index 3dc4cf003..202dd8e94 100644 --- a/src/socket/dhcpv4.rs +++ b/src/socket/dhcpv4.rs @@ -1,20 +1,20 @@ #[cfg(feature = "async")] use core::task::Waker; +#[cfg(feature = "async")] +use super::WakerRegistration; use crate::iface::Context; use crate::time::{Duration, Instant}; use crate::wire::dhcpv4::field as dhcpv4_field; +use crate::wire::ipv4::MAX_OPTIONS_SIZE; use crate::wire::{ DHCP_CLIENT_PORT, DHCP_MAX_DNS_SERVER_COUNT, DHCP_SERVER_PORT, DhcpMessageType, DhcpPacket, - DhcpRepr, IpAddress, IpProtocol, Ipv4Address, Ipv4AddressExt, Ipv4Cidr, Ipv4Repr, - UDP_HEADER_LEN, UdpRepr, + DhcpRepr, IPV4_HEADER_LEN, IpAddress, IpProtocol, Ipv4Address, Ipv4AddressExt, Ipv4Cidr, + Ipv4Repr, UDP_HEADER_LEN, UdpRepr, }; use crate::wire::{DhcpOption, HardwareAddress}; use heapless::Vec; -#[cfg(feature = "async")] -use super::WakerRegistration; - use super::PollAt; const DEFAULT_LEASE_DURATION: Duration = Duration::from_secs(120); @@ -610,8 +610,16 @@ impl<'a> Socket<'a> { src_addr: Ipv4Address::UNSPECIFIED, dst_addr: Ipv4Address::BROADCAST, next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, payload_len: 0, // filled right before emit + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; match &mut self.state { @@ -796,7 +804,7 @@ mod test { use std::ops::{Deref, DerefMut}; use super::*; - use crate::wire::EthernetAddress; + use crate::wire::{EthernetAddress, IPV4_HEADER_LEN}; // =========================================================================================// // Helper functions @@ -909,40 +917,80 @@ mod test { src_addr: Ipv4Address::UNSPECIFIED, dst_addr: Ipv4Address::BROADCAST, next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, payload_len: 0, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; const IP_BROADCAST_ADDRESSED: Ipv4Repr = Ipv4Repr { src_addr: MY_IP, dst_addr: Ipv4Address::BROADCAST, next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, payload_len: 0, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; const IP_SERVER_BROADCAST: Ipv4Repr = Ipv4Repr { src_addr: SERVER_IP, dst_addr: Ipv4Address::BROADCAST, next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, payload_len: 0, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; const IP_RECV: Ipv4Repr = Ipv4Repr { src_addr: SERVER_IP, dst_addr: MY_IP, next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, payload_len: 0, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; const IP_SEND: Ipv4Repr = Ipv4Repr { src_addr: MY_IP, dst_addr: SERVER_IP, next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, payload_len: 0, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; const UDP_SEND: UdpRepr = UdpRepr { diff --git a/src/socket/icmp.rs b/src/socket/icmp.rs index dee3416a3..6b47f3a1c 100644 --- a/src/socket/icmp.rs +++ b/src/socket/icmp.rs @@ -10,7 +10,7 @@ use crate::socket::{Context, PollAt}; use crate::storage::Empty; use crate::wire::IcmpRepr; #[cfg(feature = "proto-ipv4")] -use crate::wire::{Icmpv4Packet, Icmpv4Repr, Ipv4Repr}; +use crate::wire::{IPV4_HEADER_LEN, Icmpv4Packet, Icmpv4Repr, Ipv4Repr, ipv4::MAX_OPTIONS_SIZE}; #[cfg(feature = "proto-ipv6")] use crate::wire::{Icmpv6Packet, Icmpv6Repr, Ipv6Repr}; use crate::wire::{IpAddress, IpListenEndpoint, IpProtocol, IpRepr}; @@ -597,8 +597,16 @@ impl<'a> Socket<'a> { src_addr, dst_addr, next_header: IpProtocol::Icmp, + header_len: IPV4_HEADER_LEN, payload_len: repr.buffer_len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit, + options: [0u8; MAX_OPTIONS_SIZE], }); emit(cx, (ip_repr, IcmpRepr::Ipv4(repr))) } @@ -686,6 +694,7 @@ mod test_ipv4 { use rstest::*; use super::tests_common::*; + use crate::wire::ipv4::MAX_OPTIONS_SIZE; use crate::wire::{Icmpv4DstUnreachable, IpEndpoint, Ipv4Address}; const REMOTE_IPV4: Ipv4Address = Ipv4Address::new(192, 168, 1, 2); @@ -705,16 +714,32 @@ mod test_ipv4 { src_addr: LOCAL_IPV4, dst_addr: REMOTE_IPV4, next_header: IpProtocol::Icmp, + header_len: IPV4_HEADER_LEN, payload_len: 24, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 0x40, + options: [0u8; MAX_OPTIONS_SIZE], }); static REMOTE_IPV4_REPR: Ipv4Repr = Ipv4Repr { src_addr: REMOTE_IPV4, dst_addr: LOCAL_IPV4, next_header: IpProtocol::Icmp, + header_len: IPV4_HEADER_LEN, payload_len: 24, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 0x40, + options: [0u8; MAX_OPTIONS_SIZE], }; #[test] @@ -811,8 +836,16 @@ mod test_ipv4 { src_addr: LOCAL_IPV4, dst_addr: REMOTE_IPV4, next_header: IpProtocol::Icmp, + header_len: IPV4_HEADER_LEN, payload_len: ECHOV4_REPR.buffer_len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 0x2a, + options: [0u8; MAX_OPTIONS_SIZE], }) ); Ok::<_, ()>(()) @@ -908,8 +941,16 @@ mod test_ipv4 { src_addr: LOCAL_IPV4, dst_addr: REMOTE_IPV4, next_header: IpProtocol::Icmp, + header_len: IPV4_HEADER_LEN, payload_len: 12, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 0x40, + options: [0u8; MAX_OPTIONS_SIZE], }, data, }; @@ -917,8 +958,16 @@ mod test_ipv4 { src_addr: REMOTE_IPV4, dst_addr: LOCAL_IPV4, next_header: IpProtocol::Icmp, + header_len: IPV4_HEADER_LEN, payload_len: icmp_repr.buffer_len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 0x40, + options: [0u8; MAX_OPTIONS_SIZE], }; assert!(!socket.can_recv()); diff --git a/src/socket/raw.rs b/src/socket/raw.rs index a366b1b28..08b3d3085 100644 --- a/src/socket/raw.rs +++ b/src/socket/raw.rs @@ -483,7 +483,8 @@ mod test { use rstest::*; use super::*; - use crate::wire::IpRepr; + use crate::wire::ipv4::MAX_OPTIONS_SIZE; + use crate::wire::{IPV4_HEADER_LEN, IpRepr}; #[cfg(feature = "proto-ipv4")] use crate::wire::{Ipv4Address, Ipv4Repr}; #[cfg(feature = "proto-ipv6")] @@ -496,6 +497,8 @@ mod test { #[cfg(feature = "proto-ipv4")] mod ipv4_locals { use super::*; + use crate::wire::IPV4_HEADER_LEN; + use crate::wire::ipv4::MAX_OPTIONS_SIZE; pub fn socket( rx_buffer: PacketBuffer<'static>, @@ -514,8 +517,16 @@ mod test { src_addr: Ipv4Address::new(10, 0, 0, 1), dst_addr: Ipv4Address::new(10, 0, 0, 2), next_header: IpProtocol::Unknown(IP_PROTO), + header_len: IPV4_HEADER_LEN, payload_len: 4, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }); pub const PACKET_BYTES: [u8; 24] = [ 0x45, 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x00, 0x40, 0x3f, 0x00, 0x00, 0x0a, 0x00, @@ -929,8 +940,16 @@ mod test { src_addr: Ipv4Address::new(10, 0, 0, 1), dst_addr: Ipv4Address::new(10, 0, 0, 2), next_header: proto, + header_len: IPV4_HEADER_LEN, payload_len: 4, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }); assert!(socket.accepts(&header_repr)); } diff --git a/src/socket/tcp.rs b/src/socket/tcp.rs index 2d2de388f..59af490b5 100644 --- a/src/socket/tcp.rs +++ b/src/socket/tcp.rs @@ -2729,9 +2729,10 @@ impl<'a> fmt::Write for Socket<'a> { mod test { use super::*; use crate::wire::IpRepr; + #[cfg(feature = "proto-ipv4")] + use crate::wire::{IPV4_HEADER_LEN, ipv4::MAX_OPTIONS_SIZE}; use std::ops::{Deref, DerefMut}; use std::vec::Vec; - // =========================================================================================// // Constants // =========================================================================================// @@ -2791,13 +2792,33 @@ mod test { } } - const SEND_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr { - src_addr: LOCAL_ADDR, - dst_addr: REMOTE_ADDR, - next_header: IpProtocol::Tcp, - payload_len: 20, - hop_limit: 64, - }); + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + const SEND_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Tcp, + header_len: IPV4_HEADER_LEN, + payload_len: 20, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } else { + const SEND_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Tcp, + payload_len: 20, + hop_limit: 64, + }); + } + } const SEND_TEMPL: TcpRepr<'static> = TcpRepr { src_port: REMOTE_PORT, dst_port: LOCAL_PORT, @@ -2812,13 +2833,33 @@ mod test { timestamp: None, payload: &[], }; - const _RECV_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr { - src_addr: LOCAL_ADDR, - dst_addr: REMOTE_ADDR, - next_header: IpProtocol::Tcp, - payload_len: 20, - hop_limit: 64, - }); + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + const _RECV_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Tcp, + header_len: IPV4_HEADER_LEN, + payload_len: 20, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } else { + const _RECV_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Tcp, + payload_len: 20, + hop_limit: 64, + }); + } + } const RECV_TEMPL: TcpRepr<'static> = TcpRepr { src_port: LOCAL_PORT, dst_port: REMOTE_PORT, @@ -2864,13 +2905,33 @@ mod test { ) -> Option> { socket.cx.set_now(timestamp); - let ip_repr = IpReprIpvX(IpvXRepr { - src_addr: REMOTE_ADDR, - dst_addr: LOCAL_ADDR, - next_header: IpProtocol::Tcp, - payload_len: repr.buffer_len(), - hop_limit: 64, - }); + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + let ip_repr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Tcp, + header_len: IPV4_HEADER_LEN, + payload_len: repr.buffer_len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } else { + let ip_repr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Tcp, + payload_len: repr.buffer_len(), + hop_limit: 64, + }); + } + } net_trace!("send: {}", repr); assert!(socket.socket.accepts(&mut socket.cx, &ip_repr, repr)); @@ -8511,31 +8572,91 @@ mod test { ..SEND_TEMPL }; - let ip_repr = IpReprIpvX(IpvXRepr { - src_addr: REMOTE_ADDR, - dst_addr: LOCAL_ADDR, - next_header: IpProtocol::Tcp, - payload_len: tcp_repr.buffer_len(), - hop_limit: 64, - }); + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + let ip_repr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Tcp, + header_len: IPV4_HEADER_LEN, + payload_len: tcp_repr.buffer_len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } else { + let ip_repr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Tcp, + payload_len: tcp_repr.buffer_len(), + hop_limit: 64, + }); + } + } assert!(s.socket.accepts(&mut s.cx, &ip_repr, &tcp_repr)); - let ip_repr_wrong_src = IpReprIpvX(IpvXRepr { - src_addr: OTHER_ADDR, - dst_addr: LOCAL_ADDR, - next_header: IpProtocol::Tcp, - payload_len: tcp_repr.buffer_len(), - hop_limit: 64, - }); + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + let ip_repr_wrong_src = IpReprIpvX(IpvXRepr { + src_addr: OTHER_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Tcp, + header_len: IPV4_HEADER_LEN, + payload_len: tcp_repr.buffer_len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } else { + let ip_repr_wrong_src = IpReprIpvX(IpvXRepr { + src_addr: OTHER_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Tcp, + payload_len: tcp_repr.buffer_len(), + hop_limit: 64, + }); + } + } assert!(!s.socket.accepts(&mut s.cx, &ip_repr_wrong_src, &tcp_repr)); - let ip_repr_wrong_dst = IpReprIpvX(IpvXRepr { - src_addr: REMOTE_ADDR, - dst_addr: OTHER_ADDR, - next_header: IpProtocol::Tcp, - payload_len: tcp_repr.buffer_len(), - hop_limit: 64, - }); + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + let ip_repr_wrong_dst = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: OTHER_ADDR, + next_header: IpProtocol::Tcp, + header_len: IPV4_HEADER_LEN, + payload_len: tcp_repr.buffer_len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } else { + let ip_repr_wrong_dst = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: OTHER_ADDR, + next_header: IpProtocol::Tcp, + payload_len: tcp_repr.buffer_len(), + hop_limit: 64, + }); + } + } assert!(!s.socket.accepts(&mut s.cx, &ip_repr_wrong_dst, &tcp_repr)); } diff --git a/src/socket/udp.rs b/src/socket/udp.rs index b1a843904..97a6fd270 100644 --- a/src/socket/udp.rs +++ b/src/socket/udp.rs @@ -610,6 +610,8 @@ mod test { use crate::phy::Medium; use crate::tests::setup; + #[cfg(feature = "proto-ipv4")] + use crate::wire::ipv4::MAX_OPTIONS_SIZE; use rstest::*; fn buffer(packets: usize) -> PacketBuffer<'static> { @@ -635,6 +637,7 @@ mod test { if #[cfg(feature = "proto-ipv4")] { use crate::wire::Ipv4Address as IpvXAddress; use crate::wire::Ipv4Repr as IpvXRepr; + use crate::wire::IPV4_HEADER_LEN; use IpRepr::Ipv4 as IpReprIpvX; const LOCAL_ADDR: IpvXAddress = IpvXAddress::new(192, 168, 1, 1); @@ -677,29 +680,89 @@ mod test { } } - pub const LOCAL_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { - src_addr: LOCAL_ADDR, - dst_addr: REMOTE_ADDR, - next_header: IpProtocol::Udp, - payload_len: 8 + 6, - hop_limit: 64, - }); - - pub const REMOTE_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { - src_addr: REMOTE_ADDR, - dst_addr: LOCAL_ADDR, - next_header: IpProtocol::Udp, - payload_len: 8 + 6, - hop_limit: 64, - }); - - pub const BAD_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { - src_addr: REMOTE_ADDR, - dst_addr: OTHER_ADDR, - next_header: IpProtocol::Udp, - payload_len: 8 + 6, - hop_limit: 64, - }); + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + pub const LOCAL_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, + payload_len: 8 + 6, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } else { + pub const LOCAL_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Udp, + payload_len: 8 + 6, + hop_limit: 64, + }); + } + } + + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + pub const REMOTE_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, + payload_len: 8 + 6, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } else { + pub const REMOTE_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Udp, + payload_len: 8 + 6, + hop_limit: 64, + }); + } + } + + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + pub const BAD_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: OTHER_ADDR, + next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, + payload_len: 8 + 6, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } else { + pub const BAD_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: OTHER_ADDR, + next_header: IpProtocol::Udp, + payload_len: 8 + 6, + hop_limit: 64, + }); + } + } const LOCAL_UDP_REPR: UdpRepr = UdpRepr { src_port: LOCAL_PORT, @@ -979,18 +1042,37 @@ mod test { s.set_hop_limit(Some(0x2a)); assert_eq!(s.send_slice(b"abcdef", REMOTE_END), Ok(())); + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + let expected = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Udp, + header_len: IPV4_HEADER_LEN, + payload_len: 8 + 6, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, + hop_limit: 0x2a, + options: [0u8; MAX_OPTIONS_SIZE], + }); + } + else { + let expected = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Udp, + payload_len: 8 + 6, + hop_limit: 0x2a, + }); + } + } assert_eq!( s.dispatch(cx, |_, _, (ip_repr, _, _)| { - assert_eq!( - ip_repr, - IpReprIpvX(IpvXRepr { - src_addr: LOCAL_ADDR, - dst_addr: REMOTE_ADDR, - next_header: IpProtocol::Udp, - payload_len: 8 + 6, - hop_limit: 0x2a, - }) - ); + assert_eq!(ip_repr, expected); Ok::<_, ()>(()) }), Ok(()) diff --git a/src/wire/icmpv4.rs b/src/wire/icmpv4.rs index 3b39d8018..e1a7a174c 100644 --- a/src/wire/icmpv4.rs +++ b/src/wire/icmpv4.rs @@ -4,6 +4,7 @@ use core::{cmp, fmt}; use super::{Error, Result}; use crate::phy::ChecksumCapabilities; use crate::wire::ip::checksum; +use crate::wire::ipv4::MAX_OPTIONS_SIZE; use crate::wire::{Ipv4Packet, Ipv4Repr}; enum_with_unknown! { @@ -435,8 +436,16 @@ impl<'a> Repr<'a> { src_addr: ip_packet.src_addr(), dst_addr: ip_packet.dst_addr(), next_header: ip_packet.next_header(), + header_len: ip_packet.header_len() as usize, payload_len: payload.len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: ip_packet.hop_limit(), + options: [0u8; MAX_OPTIONS_SIZE], }, data: payload, }) @@ -458,8 +467,16 @@ impl<'a> Repr<'a> { src_addr: ip_packet.src_addr(), dst_addr: ip_packet.dst_addr(), next_header: ip_packet.next_header(), + header_len: ip_packet.header_len() as usize, payload_len: payload.len(), + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit: ip_packet.hop_limit(), + options: [0u8; MAX_OPTIONS_SIZE], }, data: payload, }) diff --git a/src/wire/ip.rs b/src/wire/ip.rs index ef8ddf512..207249ebb 100644 --- a/src/wire/ip.rs +++ b/src/wire/ip.rs @@ -4,7 +4,10 @@ use core::fmt; use super::{Error, Result}; use crate::phy::ChecksumCapabilities; #[cfg(feature = "proto-ipv4")] -use crate::wire::{Ipv4Address, Ipv4AddressExt, Ipv4Cidr, Ipv4Packet, Ipv4Repr}; +use crate::wire::{ + IPV4_HEADER_LEN, Ipv4Address, Ipv4AddressExt, Ipv4Cidr, Ipv4Packet, Ipv4Repr, + ipv4::MAX_OPTIONS_SIZE, +}; #[cfg(feature = "proto-ipv6")] use crate::wire::{Ipv6Address, Ipv6AddressExt, Ipv6Cidr, Ipv6Packet, Ipv6Repr}; @@ -607,7 +610,15 @@ impl Repr { dst_addr, next_header, payload_len, + header_len: IPV4_HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: false, + more_frags: false, + frag_offset: 0, hop_limit, + options: [0u8; MAX_OPTIONS_SIZE], }), #[cfg(feature = "proto-ipv6")] (Address::Ipv6(src_addr), Address::Ipv6(dst_addr)) => Self::Ipv6(Ipv6Repr { @@ -1061,8 +1072,16 @@ pub(crate) mod test { src_addr: crate::wire::ipv4::Address::new(0x11, 0x12, 0x13, 0x14), dst_addr: crate::wire::ipv4::Address::new(0x21, 0x22, 0x23, 0x24), next_header: Protocol::Icmp, + header_len: IPV4_HEADER_LEN, payload_len: 0, + dscp: 0, + ecn: 0, + ident: 1, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], }; let packet = Packet::new_unchecked(&ipv4_packet_bytes[..]); diff --git a/src/wire/ipv4.rs b/src/wire/ipv4.rs index 5164d1580..7c214e767 100644 --- a/src/wire/ipv4.rs +++ b/src/wire/ipv4.rs @@ -32,6 +32,16 @@ pub const MULTICAST_ALL_SYSTEMS: Address = Address::new(224, 0, 0, 1); /// All multicast-capable routers pub const MULTICAST_ALL_ROUTERS: Address = Address::new(224, 0, 0, 2); +/// Maximum size of options in octets. The header length field is 4 bits, which limits the +/// possible header size, and is in units of 4-octets. Since the fixed size fields are always +/// present in the header, the remaining possible size is the maximum size of the options field. +/// 0xF * 4 - HEADER_LEN == 40 +pub const MAX_OPTIONS_SIZE: usize = 40; + +/// Size of 32 bits in octets for alignment within the header. The header length must be a multiple +/// of this value. +pub const ALIGNMENT_32_BITS: usize = 4; + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Key { @@ -219,6 +229,7 @@ mod field { pub const CHECKSUM: Field = 10..12; pub const SRC_ADDR: Field = 12..16; pub const DST_ADDR: Field = 16..20; + pub const OPTIONS_START: usize = 20; } pub const HEADER_LEN: usize = field::DST_ADDR.end; @@ -260,6 +271,8 @@ impl> Packet { Err(Error) } else if len < self.total_len() as usize { Err(Error) + } else if (self.header_len() as usize) < HEADER_LEN { + Err(Error) } else { Ok(()) } @@ -366,6 +379,29 @@ impl> Packet { Address::from_bytes(&data[field::DST_ADDR]) } + /// Return true if options exist according to the header length. + #[inline] + pub fn has_options(&self) -> bool { + self.header_len() as usize > HEADER_LEN + } + + /// Return a reference to the options if the field exists according to the header length. + #[inline] + pub fn options(&self) -> Option<&[u8]> { + if self.has_options() { + let data = self.buffer.as_ref(); + Some(&data[field::OPTIONS_START..self.header_len() as usize]) + } else { + None + } + } + + /// Return the length of the options field as calculated from the header length. + #[inline] + pub fn options_len(&self) -> usize { + self.header_len() as usize - HEADER_LEN + } + /// Validate the header checksum. /// /// # Fuzzing @@ -529,6 +565,25 @@ impl + AsMut<[u8]>> Packet { let data = self.buffer.as_mut(); &mut data[range] } + + /// Return a mutable pointer to the options if they are present according to the header length. + #[inline] + pub fn options_mut(&mut self) -> Option<&mut [u8]> { + if self.has_options() { + let range = field::OPTIONS_START..self.header_len() as usize; + let data = self.buffer.as_mut(); + Some(&mut data[range]) + } else { + None + } + } + + /// Set options without checking if the input is sized properly or if the header length is + /// appropriate. + pub fn set_options_unchecked(&mut self, options: &[u8]) { + let data = self.buffer.as_mut(); + data[HEADER_LEN..HEADER_LEN + options.len()].copy_from_slice(options); + } } impl> AsRef<[u8]> for Packet { @@ -544,8 +599,16 @@ pub struct Repr { pub src_addr: Address, pub dst_addr: Address, pub next_header: Protocol, + pub header_len: usize, pub payload_len: usize, + pub dscp: u8, + pub ecn: u8, + pub ident: u16, + pub dont_frag: bool, + pub more_frags: bool, + pub frag_offset: u16, pub hop_limit: u8, + pub options: [u8; MAX_OPTIONS_SIZE], } impl Repr { @@ -575,19 +638,32 @@ impl Repr { // All DSCP values are acceptable, since they are of no concern to receiving endpoint. // All ECN values are acceptable, since ECN requires opt-in from both endpoints. // All TTL values are acceptable, since we do not perform routing. + + let mut options = [0u8; MAX_OPTIONS_SIZE]; + if let Some(bytes) = packet.options() { + options[..packet.options_len()].copy_from_slice(bytes); + } + Ok(Repr { src_addr: packet.src_addr(), dst_addr: packet.dst_addr(), next_header: packet.next_header(), + header_len: packet.header_len() as usize, payload_len, + dscp: packet.dscp(), + ecn: packet.ecn(), + ident: packet.ident(), + dont_frag: packet.dont_frag(), + more_frags: packet.more_frags(), + frag_offset: packet.frag_offset(), hop_limit: packet.hop_limit(), + options, }) } /// Return the length of a header that will be emitted from this high-level representation. pub const fn buffer_len(&self) -> usize { - // We never emit any options. - field::DST_ADDR.end + self.header_len } /// Emit a high-level representation into an Internet Protocol version 4 packet. @@ -597,21 +673,23 @@ impl Repr { checksum_caps: &ChecksumCapabilities, ) { packet.set_version(4); - packet.set_header_len(field::DST_ADDR.end as u8); - packet.set_dscp(0); - packet.set_ecn(0); + packet.set_header_len(self.header_len as u8); + packet.set_dscp(self.dscp); + packet.set_ecn(self.ecn); let total_len = packet.header_len() as u16 + self.payload_len as u16; packet.set_total_len(total_len); - packet.set_ident(0); + packet.set_ident(self.ident); packet.clear_flags(); - packet.set_more_frags(false); - packet.set_dont_frag(true); - packet.set_frag_offset(0); + packet.set_more_frags(self.more_frags); + packet.set_dont_frag(self.dont_frag); + packet.set_frag_offset(self.frag_offset); packet.set_hop_limit(self.hop_limit); packet.set_next_header(self.next_header); packet.set_src_addr(self.src_addr); packet.set_dst_addr(self.dst_addr); + packet.set_options_unchecked(&self.options[..packet.options_len()]); + if checksum_caps.ipv4.tx() { packet.fill_checksum(); } else { @@ -620,6 +698,23 @@ impl Repr { packet.set_checksum(0); } } + + /// Write options. Result is ok if input is sized properly. + pub fn set_options(&mut self, options: &[u8]) -> Result<()> { + if options.len() <= MAX_OPTIONS_SIZE && options.len() % 4 == 0 { + self.options[..options.len()].copy_from_slice(options); + self.header_len = HEADER_LEN + options.len(); + Ok(()) + } else { + Err(Error) + } + } + + /// Return the length of the options included in the header that will be emitted from this + /// high-level representation. + pub const fn options_len(&self) -> usize { + self.header_len - HEADER_LEN + } } impl + ?Sized> fmt::Display for Packet<&T> { @@ -639,7 +734,9 @@ impl + ?Sized> fmt::Display for Packet<&T> { if self.version() != 4 { write!(f, " ver={}", self.version())?; } - if self.header_len() != 20 { + if (self.header_len() as usize) < HEADER_LEN + || self.header_len() as usize > HEADER_LEN + MAX_OPTIONS_SIZE + { write!(f, " hlen={}", self.header_len())?; } if self.dscp() != 0 { @@ -732,7 +829,7 @@ pub(crate) mod test { pub(crate) const MOCK_UNSPECIFIED: Address = Address::UNSPECIFIED; static PACKET_BYTES: [u8; 30] = [ - 0x45, 0x00, 0x00, 0x1e, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xd5, 0x6e, 0x11, 0x12, 0x13, + 0x45, 0x21, 0x00, 0x1e, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xd5, 0x4d, 0x11, 0x12, 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, ]; @@ -743,8 +840,8 @@ pub(crate) mod test { let packet = Packet::new_unchecked(&PACKET_BYTES[..]); assert_eq!(packet.version(), 4); assert_eq!(packet.header_len(), 20); - assert_eq!(packet.dscp(), 0); - assert_eq!(packet.ecn(), 0); + assert_eq!(packet.dscp(), 8); + assert_eq!(packet.ecn(), 1); assert_eq!(packet.total_len(), 30); assert_eq!(packet.ident(), 0x102); assert!(packet.more_frags()); @@ -752,7 +849,7 @@ pub(crate) mod test { assert_eq!(packet.frag_offset(), 0x203 * 8); assert_eq!(packet.hop_limit(), 0x1a); assert_eq!(packet.next_header(), Protocol::Icmp); - assert_eq!(packet.checksum(), 0xd56e); + assert_eq!(packet.checksum(), 0xd54d); assert_eq!(packet.src_addr(), Address::new(0x11, 0x12, 0x13, 0x14)); assert_eq!(packet.dst_addr(), Address::new(0x21, 0x22, 0x23, 0x24)); assert!(packet.verify_checksum()); @@ -766,8 +863,8 @@ pub(crate) mod test { packet.set_version(4); packet.set_header_len(20); packet.clear_flags(); - packet.set_dscp(0); - packet.set_ecn(0); + packet.set_dscp(8); + packet.set_ecn(1); packet.set_total_len(30); packet.set_ident(0x102); packet.set_more_frags(true); @@ -779,8 +876,68 @@ pub(crate) mod test { packet.set_dst_addr(Address::new(0x21, 0x22, 0x23, 0x24)); packet.fill_checksum(); packet.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]); + assert_eq!(packet.options_mut(), None); assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]); } + const OPTION_PACKET_BYTES: [u8; 34] = [ + 0x46, 0x21, 0x00, 0x22, 0x01, 0x02, 0x40, 0x00, 0x1a, 0x01, 0x13, 0xee, 0x11, 0x12, 0x13, + 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0x88, 0x04, 0x5a, 0x5a, // Stream Identifier option + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload + ]; + + const OPTION_BYTES: [u8; 4] = [0x88, 0x04, 0x5a, 0x5a]; + + const OPTION_PAYLOAD_BYTES: [u8; 10] = + [0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff]; + + #[test] + fn test_deconstruct_with_option() { + let packet = Packet::new_unchecked(&OPTION_PACKET_BYTES[..]); + assert_eq!(packet.version(), 4); + assert_eq!(packet.header_len(), 24); + assert_eq!(packet.dscp(), 8); + assert_eq!(packet.ecn(), 1); + assert_eq!(packet.total_len(), 34); + assert_eq!(packet.ident(), 0x102); + assert!(!packet.more_frags()); + assert!(packet.dont_frag()); + assert_eq!(packet.frag_offset(), 0); + assert_eq!(packet.hop_limit(), 0x1a); + assert_eq!(packet.next_header(), Protocol::Icmp); + assert_eq!(packet.checksum(), 0x13ee); + assert_eq!(packet.src_addr(), Address::new(0x11, 0x12, 0x13, 0x14)); + assert_eq!(packet.dst_addr(), Address::new(0x21, 0x22, 0x23, 0x24)); + assert!(packet.verify_checksum()); + assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]); + } + + #[test] + fn test_construct_with_option() { + let mut bytes = vec![0xa5; 34]; + let mut packet = crate::wire::ipv4::Packet::new_unchecked(&mut bytes); + packet.set_version(4); + packet.set_header_len(24); + packet.clear_flags(); + packet.set_dscp(8); + packet.set_ecn(1); + packet.set_total_len(34); + packet.set_ident(0x102); + packet.set_more_frags(false); + packet.set_dont_frag(true); + packet.set_frag_offset(0); + packet.set_hop_limit(0x1a); + packet.set_next_header(Protocol::Icmp); + packet.set_src_addr(Address::new(0x11, 0x12, 0x13, 0x14)); + packet.set_dst_addr(Address::new(0x21, 0x22, 0x23, 0x24)); + packet.set_options_unchecked(&OPTION_BYTES[..]); + packet.fill_checksum(); + packet + .payload_mut() + .copy_from_slice(&OPTION_PAYLOAD_BYTES[..]); + assert_eq!(packet.options_mut().unwrap(), OPTION_BYTES); + assert_eq!(&*packet.into_inner(), &OPTION_PACKET_BYTES[..]); + } #[test] fn test_overlong() { @@ -820,7 +977,15 @@ pub(crate) mod test { dst_addr: Address::new(0x21, 0x22, 0x23, 0x24), next_header: Protocol::Icmp, payload_len: 4, + header_len: HEADER_LEN, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; MAX_OPTIONS_SIZE], } } @@ -831,6 +996,59 @@ pub(crate) mod test { assert_eq!(repr, packet_repr()); } + #[test] + fn test_parse_with_option() { + let packet = Packet::new_unchecked(&OPTION_PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + assert_eq!( + repr, + Repr { + src_addr: Address::new(0x11, 0x12, 0x13, 0x14), + dst_addr: Address::new(0x21, 0x22, 0x23, 0x24), + next_header: Protocol::Icmp, + payload_len: 10, + header_len: HEADER_LEN + 4, + dscp: 8, + ecn: 1, + ident: 0x102, + dont_frag: true, + more_frags: false, + frag_offset: 0, + hop_limit: 0x1a, + options: [ + 0x88, 0x04, 0x5a, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ] + } + ); + } + + #[test] + fn test_manually_adding_options() { + let packet = Packet::new_unchecked(&REPR_PACKET_BYTES[..]); + let mut repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + assert!(repr.set_options(&[]).is_ok()); + assert!(repr.set_options(&[0x0]).is_err()); + assert!(repr.set_options(&[0x0, 0x0, 0x0, 0x0]).is_ok()); + assert!(repr.set_options(&[0u8; 42]).is_err()); + } + + #[test] + fn test_parse_bad_option_no_padding() { + // Checksum is correct, length indicates packet should be 38 octets. + const PACKET_BYTES: [u8; 37] = [ + 0x47, 0x21, 0x00, 0x22, 0x01, 0x02, 0x40, 0x00, 0x1a, 0x01, 0xe4, 0x41, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, // Fixed header + 0x07, 0x07, 0x04, 0x01, 0x02, 0x03, 0x04, // Route Record, missing pad byte at end + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // Payload + ]; + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()); + assert!(repr.is_err()); + } + #[test] fn test_parse_bad_version() { let mut bytes = vec![0; 24]; diff --git a/src/wire/mod.rs b/src/wire/mod.rs index 477df66b9..5ba86dc36 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -51,8 +51,16 @@ let repr = Ipv4Repr { src_addr: Ipv4Address::new(10, 0, 0, 1), dst_addr: Ipv4Address::new(10, 0, 0, 2), next_header: IpProtocol::Tcp, + header_len: IPV4_HEADER_LEN, payload_len: 10, + dscp: 0, + ecn: 0, + ident: 0, + dont_frag: true, + more_frags: false, + frag_offset: 0, hop_limit: 64, + options: [0u8; IPV4_MAX_OPTIONS_SIZE], }; let mut buffer = vec![0; repr.buffer_len() + repr.payload_len]; { // emission @@ -187,7 +195,8 @@ pub use self::ip::{ #[cfg(feature = "proto-ipv4")] pub use self::ipv4::{ Address as Ipv4Address, Cidr as Ipv4Cidr, HEADER_LEN as IPV4_HEADER_LEN, Key as Ipv4FragKey, - MIN_MTU as IPV4_MIN_MTU, MULTICAST_ALL_ROUTERS as IPV4_MULTICAST_ALL_ROUTERS, + MAX_OPTIONS_SIZE as IPV4_MAX_OPTIONS_SIZE, MIN_MTU as IPV4_MIN_MTU, + MULTICAST_ALL_ROUTERS as IPV4_MULTICAST_ALL_ROUTERS, MULTICAST_ALL_SYSTEMS as IPV4_MULTICAST_ALL_SYSTEMS, Packet as Ipv4Packet, Repr as Ipv4Repr, };