use crate::{ err::{ValueTooBigError, ValueType}, *, }; use arrayvec::ArrayVec; /// IPv4 header with options. /// /// # Example Usage: /// /// ``` /// use etherparse::{Ipv4Header, IpNumber}; /// /// let mut header = Ipv4Header { /// source: [1,2,3,4], /// destination: [1,2,3,4], /// time_to_live: 4, /// total_len: Ipv4Header::MIN_LEN as u16 + 100, /// protocol: IpNumber::UDP, /// ..Default::default() /// }; /// /// // depending on your usecase you might want to set the correct checksum /// header.header_checksum = header.calc_header_checksum(); /// /// // header can be serialized into the "on the wire" format /// // using the "write" or "to_bytes" methods /// let bytes = header.to_bytes(); /// /// // IPv4 headers can be decoded via "read" or "from_slice" /// let (decoded, slice_rest) = Ipv4Header::from_slice(&bytes).unwrap(); /// assert_eq!(header, decoded); /// assert_eq!(slice_rest, &[]); /// ``` #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct Ipv4Header { /// Differentiated Services Code Point pub dscp: Ipv4Dscp, /// Explicit Congestion Notification pub ecn: Ipv4Ecn, /// Total length of the IPv4 header (including extension headers) and the payload after it. pub total_len: u16, /// Number used to identify packets that contain an originally fragmented packet. pub identification: u16, /// If set the packet is not allowed to fragmented. pub dont_fragment: bool, /// Indicates that the packet contains part of an fragmented message and that /// additional data is needed to reconstruct the original packet. pub more_fragments: bool, /// In case this message contains parts of a fragmented packet the fragment /// offset is the offset of payload the current message relative to the /// original payload of the message. pub fragment_offset: IpFragOffset, /// Number of hops the packet is allowed to take before it should be discarded. pub time_to_live: u8, /// IP protocol number specifying the next header or transport layer protocol. /// /// See [IpNumber] or [ip_number] for a definitions of ids. pub protocol: IpNumber, pub header_checksum: u16, /// IPv4 source address pub source: [u8; 4], /// IPv4 destination address pub destination: [u8; 4], /// Options in the header (in raw). pub options: Ipv4Options, } impl Ipv4Header { /// Minimum length of an IPv4 header in bytes/octets. pub const MIN_LEN: usize = 20; /// Minimum length of an IPv4 header in bytes/octets as an `u16`. pub const MIN_LEN_U16: u16 = 20; /// Maximum length of an IPv4 header in bytes/octets. /// /// This number is calculated by taking the maximum value /// that the "internet header length" field supports (0xf, /// as the field is only 4 bits long) and multiplying it /// with 4 as the "internet header length" specifies how /// many 4 bytes words are present in the header. pub const MAX_LEN: usize = 0b1111 * 4; /// Deprecated use [`Ipv4Header::MIN_LEN`] instead. #[deprecated(since = "0.14.0", note = "Use `Ipv4Header::MIN_LEN` instead")] pub const SERIALIZED_SIZE: usize = Ipv4Header::MIN_LEN; /// Constructs an Ipv4Header with standard values for non specified values. /// /// This method is equivalent to partially initializing a struct with /// default values: /// /// ``` /// use etherparse::{Ipv4Header, IpNumber}; /// /// let mut header = Ipv4Header::new(100, 4, IpNumber::UDP, [1,2,3,4], [5,6,7,8]).unwrap(); /// /// assert_eq!( /// header, /// Ipv4Header { /// total_len: (100 + Ipv4Header::MIN_LEN) as u16, /// time_to_live: 4, /// protocol: IpNumber::UDP, /// source: [1,2,3,4], /// destination: [5,6,7,8], /// ..Default::default() /// } /// ); /// /// // for the rest of the fields the following default values will be used: /// assert_eq!(0, header.dscp.value()); /// assert_eq!(0, header.ecn.value()); /// assert_eq!(0, header.identification); /// assert_eq!(true, header.dont_fragment); /// assert_eq!(false, header.more_fragments); /// assert_eq!(0, header.fragment_offset.value()); /// assert_eq!(0, header.header_checksum); /// /// // in case you also want to have a correct checksum you will have to /// // additionally update it: /// header.header_checksum = header.calc_header_checksum(); /// ``` pub fn new( payload_len: u16, time_to_live: u8, protocol: IpNumber, source: [u8; 4], destination: [u8; 4], ) -> Result> { const MAX_PAYLOAD: u16 = u16::MAX - (Ipv4Header::MIN_LEN as u16); if payload_len > MAX_PAYLOAD { Err(ValueTooBigError { actual: payload_len, max_allowed: MAX_PAYLOAD, value_type: ValueType::Ipv4PayloadLength, }) } else { Ok(Ipv4Header { dscp: Default::default(), ecn: Default::default(), total_len: payload_len + (Ipv4Header::MIN_LEN as u16), identification: 0, dont_fragment: true, more_fragments: false, fragment_offset: Default::default(), time_to_live, protocol, header_checksum: 0, source, destination, options: Default::default(), }) } } /// Length of the header in multiples of 4 bytes (often also called /// IHL - Internet Header length). This field is part of the serialized /// header and determines / is determined by the byte length of the options. /// /// The minimum allowed length of a header is 5 (= 20 bytes) and the /// maximum length is 15 (= 60 bytes). /// /// ``` /// use etherparse::Ipv4Header; /// { /// let header = Ipv4Header { /// options: [].into(), /// ..Default::default() /// }; /// // minimum IHL is 5 /// assert_eq!(5, header.ihl()); /// } /// { /// let header = Ipv4Header { /// options: [1,2,3,4].into(), /// ..Default::default() /// }; /// // IHL is increased by 1 for every 4 bytes of options /// assert_eq!(6, header.ihl()); /// } /// { /// let header = Ipv4Header { /// options: [0;40].into(), /// ..Default::default() /// }; /// // maximum ihl /// assert_eq!(15, header.ihl()); /// } /// ``` #[inline] pub fn ihl(&self) -> u8 { (self.options.len_u8() / 4) + 5 } /// Length of the header (includes options) in bytes. /// /// The minimum allowed length of a header is 5 (= 20 bytes) and the /// maximum length is 15 (= 60 bytes). /// /// ``` /// use etherparse::Ipv4Header; /// { /// let header = Ipv4Header { /// options: [].into(), /// ..Default::default() /// }; /// // minimum IHL is 5 /// assert_eq!(5, header.ihl()); /// } /// { /// let header = Ipv4Header { /// options: [1,2,3,4].into(), /// ..Default::default() /// }; /// // IHL is increased by 1 for every 4 bytes of options /// assert_eq!(6, header.ihl()); /// } /// { /// let header = Ipv4Header { /// options: [0;40].into(), /// ..Default::default() /// }; /// // maximum ihl /// assert_eq!(15, header.ihl()); /// } /// ``` #[inline] pub fn header_len(&self) -> usize { Ipv4Header::MIN_LEN + self.options.len() } /// Determine the payload length based on the ihl & total_length /// field of the header. /// /// # Example Usage /// /// ``` /// use etherparse::{Ipv4Header, Ipv4HeaderSlice}; /// /// let header = Ipv4Header{ /// // the payload len will be calculated by subtracting the /// // header length from the total length /// total_len: Ipv4Header::MIN_LEN as u16 + 100, /// ..Default::default() /// }; /// /// assert_eq!(Ok(100), header.payload_len()); /// /// // error case /// let bad_header = Ipv4Header { /// // total len should also include the header, in case it does /// // not it is not possible to calculate the payload length /// total_len: Ipv4Header::MIN_LEN as u16 - 1, /// ..Default::default() /// }; /// /// // in case the total_len is smaller then the header itself an /// // error is returned /// use etherparse::{LenSource, err::{LenError, Layer}}; /// assert_eq!( /// bad_header.payload_len(), /// Err(LenError { /// required_len: Ipv4Header::MIN_LEN, /// len: Ipv4Header::MIN_LEN - 1, /// len_source: LenSource::Ipv4HeaderTotalLen, /// layer: Layer::Ipv4Packet, /// layer_start_offset: 0, /// }) /// ); /// ``` #[inline] pub fn payload_len(&self) -> Result { // SAFETY: header_len() can be at most be 60 so a cast to u16 is safe. let header_len = self.header_len() as u16; if header_len <= self.total_len { Ok(self.total_len - header_len) } else { use err::{Layer, LenError}; Err(LenError { required_len: header_len.into(), len: self.total_len.into(), len_source: LenSource::Ipv4HeaderTotalLen, layer: Layer::Ipv4Packet, layer_start_offset: 0, }) } } /// Tries setting the [`Ipv4Header::total_len`] field given the length of /// the payload after the header & the current options length of the header. /// /// If the value is not too big. Otherwise an error is returned. /// /// Note that the set payload length is no longer valid if you change /// [`Ipv4Header::options`] length after calling [`Ipv4Header::set_payload_len`] /// as it uses the length of options to calculate the `total_len` value. /// /// # Example Usage: /// /// ``` /// use etherparse::Ipv4Header; /// /// let mut header = Ipv4Header{ /// total_len: 100, // will be reset by set_payload /// options: [1,2,3,4].into(), /// ..Default::default() /// }; /// /// // set_payload_len set the total_len field based on the header_len /// // and given payload length /// header.set_payload_len(100).unwrap(); /// assert_eq!(100 + header.header_len() as u16, header.total_len); /// /// // in case the payload is len is bigger then can represented in the /// // total_len field an error is returned /// use etherparse::err::{ValueTooBigError, ValueType}; /// let err = header.set_payload_len(usize::from(u16::MAX) - header.header_len() + 1); /// assert_eq!( /// err, /// Err(ValueTooBigError { /// actual: usize::from(u16::MAX) - header.header_len() + 1, /// max_allowed: usize::from(u16::MAX) - header.header_len(), /// value_type: ValueType::Ipv4PayloadLength /// }) /// ); /// ``` pub fn set_payload_len(&mut self, value: usize) -> Result<(), ValueTooBigError> { let max_allowed = usize::from(self.max_payload_len()); if value > max_allowed { Err(ValueTooBigError { actual: value, max_allowed, value_type: ValueType::Ipv4PayloadLength, }) } else { self.total_len = (self.header_len() + value) as u16; Ok(()) } } /// Returns the maximum payload size based on the current options size. #[inline] pub fn max_payload_len(&self) -> u16 { u16::MAX - u16::from(self.options.len_u8()) - (Ipv4Header::MIN_LEN as u16) } /// Returns a slice to the options part of the header (empty if no options are present). #[deprecated( since = "0.14.0", note = "Directly use `&(header.options[..])` instead." )] pub fn options(&self) -> &[u8] { &self.options[..] } /// Sets the options & header_length based on the provided length. /// The length of the given slice must be a multiple of 4 and maximum 40 bytes. /// If the length is not fulfilling these constraints, no data is set and /// an error is returned. #[deprecated( since = "0.14.0", note = "Directly set it via the header.options field instead." )] pub fn set_options(&mut self, data: &[u8]) -> Result<(), err::ipv4::BadOptionsLen> { self.options = data.try_into()?; Ok(()) } /// Renamed to `Ipv4Header::from_slice` #[deprecated(since = "0.10.1", note = "Renamed to `Ipv4Header::from_slice`")] #[inline] pub fn read_from_slice( slice: &[u8], ) -> Result<(Ipv4Header, &[u8]), err::ipv4::HeaderSliceError> { Ipv4Header::from_slice(slice) } /// Read an Ipv4Header from a slice and return the header & unused parts /// of the slice. /// /// Note that this function DOES NOT seperate the payload based on the /// `total_length` field present in the IPv4 header. It just returns the /// left over slice after the header. /// /// If you want to have correctly seperated payload including the IP extension /// headers use /// /// * [`IpHeaders::from_ipv4_slice`] (decodes all the fields of the IP headers) /// * [`Ipv4Slice::from_slice`] (just identifies the ranges in the slice where /// the headers and payload are present) /// /// or /// /// * [`IpHeaders::from_ipv4_slice_lax`] /// * [`LaxIpv4Slice::from_slice`] /// /// for a laxer version which falls back to slice length when the `total_length` /// contains an inconsistent value. pub fn from_slice(slice: &[u8]) -> Result<(Ipv4Header, &[u8]), err::ipv4::HeaderSliceError> { let header = Ipv4HeaderSlice::from_slice(slice)?.to_header(); let rest = &slice[header.header_len()..]; Ok((header, rest)) } /// Reads an IPv4 header from the current position (requires /// crate feature `std`). #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn read( reader: &mut T, ) -> Result { use err::ipv4::HeaderReadError::*; let mut first_byte: [u8; 1] = [0; 1]; reader.read_exact(&mut first_byte).map_err(Io)?; let version_number = first_byte[0] >> 4; if 4 != version_number { use err::ipv4::HeaderError::UnexpectedVersion; return Err(Content(UnexpectedVersion { version_number })); } Ipv4Header::read_without_version(reader, first_byte[0]) } /// Reads an IPv4 header assuming the version & ihl field have already /// been read (requires crate feature `std`). #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn read_without_version( reader: &mut T, first_byte: u8, ) -> Result { use err::ipv4::HeaderError::*; use err::ipv4::HeaderReadError::*; // read the basic ipv4 header (the header options can be // read only after the internet header length was read) let mut header_raw = [0u8; 20]; header_raw[0] = first_byte; reader.read_exact(&mut header_raw[1..]).map_err(Io)?; let ihl = header_raw[0] & 0xf; // validate that the internet header length is big enough to // contain a basic IPv4 header without options. if ihl < 5 { return Err(Content(HeaderLengthSmallerThanHeader { ihl })); } let (dscp, ecn) = { let value = header_raw[1]; (value >> 2, value & 0b0000_0011) }; let total_len = u16::from_be_bytes([header_raw[2], header_raw[3]]); let identification = u16::from_be_bytes([header_raw[4], header_raw[5]]); let (dont_fragment, more_fragments, fragments_offset) = ( 0 != (header_raw[6] & 0b0100_0000), 0 != (header_raw[6] & 0b0010_0000), u16::from_be_bytes([header_raw[6] & 0b0001_1111, header_raw[7]]), ); Ok(Ipv4Header { dscp: unsafe { // Safe as only 6 bits were used to decode the // dscp value Ipv4Dscp::new_unchecked(dscp) }, ecn: unsafe { // Safe as only 2 bits were used to decode the // ecn value Ipv4Ecn::new_unchecked(ecn) }, total_len, identification, dont_fragment, more_fragments, fragment_offset: unsafe { // Safe as only 13 bits were used to decode the // fragment offset IpFragOffset::new_unchecked(fragments_offset) }, time_to_live: header_raw[8], protocol: IpNumber(header_raw[9]), header_checksum: u16::from_be_bytes([header_raw[10], header_raw[11]]), source: [ header_raw[12], header_raw[13], header_raw[14], header_raw[15], ], destination: [ header_raw[16], header_raw[17], header_raw[18], header_raw[19], ], options: { let mut options = Ipv4Options::new(); options.len = (ihl - 5) * 4; if false == options.is_empty() { reader.read_exact(options.as_mut()).map_err(Io)?; } options }, }) } /// Writes a given IPv4 header to the current position (this method automatically calculates /// the header length and checksum). #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn write(&self, writer: &mut T) -> Result<(), std::io::Error> { // write with recalculations self.write_ipv4_header_internal(writer, self.calc_header_checksum()) } /// Writes a given IPv4 header to the current position (this method just writes the specified /// checksum and does not compute it). #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn write_raw( &self, writer: &mut T, ) -> Result<(), std::io::Error> { self.write_ipv4_header_internal(writer, self.header_checksum) } /// Returns the serialized header (note that this method does NOT /// update & calculate the checksum). pub fn to_bytes(&self) -> ArrayVec { // prep the values for copy let total_len_be = self.total_len.to_be_bytes(); let id_be = self.identification.to_be_bytes(); let frag_and_flags = { let frag_be: [u8; 2] = self.fragment_offset.value().to_be_bytes(); let flags = { let mut result = 0; if self.dont_fragment { result |= 64; } if self.more_fragments { result |= 32; } result }; [flags | (frag_be[0] & 0x1f), frag_be[1]] }; let header_checksum_be = self.header_checksum.to_be_bytes(); #[rustfmt::skip] let mut header_raw: ArrayVec = [ (4 << 4) | self.ihl(), (self.dscp.value() << 2) | self.ecn.value(), total_len_be[0], total_len_be[1], id_be[0], id_be[1], frag_and_flags[0], frag_and_flags[1], self.time_to_live, self.protocol.0, header_checksum_be[0], header_checksum_be[1], self.source[0], self.source[1], self.source[2], self.source[3], self.destination[0], self.destination[1], self.destination[2], self.destination[3], self.options.buf[0], self.options.buf[1], self.options.buf[2], self.options.buf[3], self.options.buf[4], self.options.buf[5], self.options.buf[6], self.options.buf[7], self.options.buf[8], self.options.buf[9], self.options.buf[10], self.options.buf[11], self.options.buf[12], self.options.buf[13], self.options.buf[14], self.options.buf[15], self.options.buf[16], self.options.buf[17], self.options.buf[18], self.options.buf[19], self.options.buf[20], self.options.buf[21], self.options.buf[22], self.options.buf[23], self.options.buf[24], self.options.buf[25], self.options.buf[26], self.options.buf[27], self.options.buf[28], self.options.buf[29], self.options.buf[30], self.options.buf[31], self.options.buf[32], self.options.buf[33], self.options.buf[34], self.options.buf[35], self.options.buf[36], self.options.buf[37], self.options.buf[38], self.options.buf[39], ].into(); // SAFETY: Safe as header_len() can never exceed the maximum length of an // IPv4 header which is the upper limit of the array vec. unsafe { header_raw.set_len(self.header_len()); } header_raw } /// Write the given header with the checksum and header length specified in the seperate arguments #[cfg(feature = "std")] fn write_ipv4_header_internal( &self, write: &mut T, header_checksum: u16, ) -> Result<(), std::io::Error> { let total_len_be = self.total_len.to_be_bytes(); let id_be = self.identification.to_be_bytes(); let frag_and_flags = { let frag_be: [u8; 2] = self.fragment_offset.value().to_be_bytes(); let flags = { let mut result = 0; if self.dont_fragment { result |= 64; } if self.more_fragments { result |= 32; } result }; [flags | (frag_be[0] & 0x1f), frag_be[1]] }; let header_checksum_be = header_checksum.to_be_bytes(); let header_raw = [ (4 << 4) | self.ihl(), (self.dscp.value() << 2) | self.ecn.value(), total_len_be[0], total_len_be[1], id_be[0], id_be[1], frag_and_flags[0], frag_and_flags[1], self.time_to_live, self.protocol.0, header_checksum_be[0], header_checksum_be[1], self.source[0], self.source[1], self.source[2], self.source[3], self.destination[0], self.destination[1], self.destination[2], self.destination[3], ]; write.write_all(&header_raw)?; //options write.write_all(&self.options)?; //done Ok(()) } /// Calculate header checksum of the current ipv4 header. pub fn calc_header_checksum(&self) -> u16 { checksum::Sum16BitWords::new() .add_2bytes([ (4 << 4) | self.ihl(), (self.dscp.value() << 2) | self.ecn.value(), ]) .add_2bytes(self.total_len.to_be_bytes()) .add_2bytes(self.identification.to_be_bytes()) .add_2bytes({ let frag_off_be = self.fragment_offset.value().to_be_bytes(); let flags = { let mut result = 0; if self.dont_fragment { result |= 64; } if self.more_fragments { result |= 32; } result }; [flags | (frag_off_be[0] & 0x1f), frag_off_be[1]] }) .add_2bytes([self.time_to_live, self.protocol.0]) .add_4bytes(self.source) .add_4bytes(self.destination) .add_slice(&self.options) .ones_complement() .to_be() } /// Returns true if the payload is fragmented. /// /// Either data is missing (more_fragments set) or there is /// an fragment offset. #[inline] pub fn is_fragmenting_payload(&self) -> bool { self.more_fragments || (0 != self.fragment_offset.value()) } } impl Default for Ipv4Header { fn default() -> Ipv4Header { Ipv4Header { dscp: Default::default(), ecn: Default::default(), total_len: 0, identification: 0, dont_fragment: true, more_fragments: false, fragment_offset: Default::default(), time_to_live: 0, protocol: IpNumber(255), header_checksum: 0, source: [0; 4], destination: [0; 4], options: Ipv4Options::new(), } } } #[cfg(test)] mod test { use crate::{ err::{Layer, LenError, ValueTooBigError, ValueType}, test_gens::*, *, }; use alloc::{format, vec::Vec}; use arrayvec::ArrayVec; use proptest::prelude::*; use std::io::Cursor; #[test] fn default() { let default: Ipv4Header = Default::default(); assert_eq!(5, default.ihl()); assert_eq!(0, default.dscp.value()); assert_eq!(0, default.ecn.value()); assert_eq!(0, default.total_len); assert_eq!(0, default.identification); assert_eq!(true, default.dont_fragment); assert_eq!(false, default.more_fragments); assert_eq!(0, default.fragment_offset.value()); assert_eq!(0, default.time_to_live); assert_eq!(IpNumber(255), default.protocol); assert_eq!(0, default.header_checksum); assert_eq!([0; 4], default.source); assert_eq!([0; 4], default.destination); assert_eq!(&default.options[..], &[]); } proptest! { #[test] fn debug(input in ipv4_any()) { assert_eq!(&format!("Ipv4Header {{ dscp: {:?}, ecn: {:?}, total_len: {}, identification: {}, dont_fragment: {}, more_fragments: {}, fragment_offset: {:?}, time_to_live: {}, protocol: {:?}, header_checksum: {}, source: {:?}, destination: {:?}, options: {:?} }}", input.dscp, input.ecn, input.total_len, input.identification, input.dont_fragment, input.more_fragments, input.fragment_offset, input.time_to_live, input.protocol, input.header_checksum, input.source, input.destination, input.options ), &format!("{:?}", input) ); } } proptest! { #[test] fn eq(a in ipv4_any(), b in ipv4_any()) { //check identity equality assert!(a == a); assert!(b == b); //check every field //differentiated_services_code_point assert_eq!( a.dscp == b.dscp, a == { let mut other = a.clone(); other.dscp = b.dscp; other } ); //explicit_congestion_notification assert_eq!( a.ecn == b.ecn, a == { let mut other = a.clone(); other.ecn = b.ecn; other } ); //total_len assert_eq!( a.total_len == b.total_len, a == { let mut other = a.clone(); other.total_len = b.total_len; other } ); //identification assert_eq!( a.identification == b.identification, a == { let mut other = a.clone(); other.identification = b.identification; other } ); //dont_fragment assert_eq!( a.dont_fragment == b.dont_fragment, a == { let mut other = a.clone(); other.dont_fragment = b.dont_fragment; other } ); //more_fragments assert_eq!( a.more_fragments == b.more_fragments, a == { let mut other = a.clone(); other.more_fragments = b.more_fragments; other } ); //fragments_offset assert_eq!( a.fragment_offset == b.fragment_offset, a == { let mut other = a.clone(); other.fragment_offset = b.fragment_offset; other } ); //time_to_live assert_eq!( a.time_to_live == b.time_to_live, a == { let mut other = a.clone(); other.time_to_live = b.time_to_live; other } ); //protocol assert_eq!( a.protocol == b.protocol, a == { let mut other = a.clone(); other.protocol = b.protocol; other } ); //header_checksum assert_eq!( a.header_checksum == b.header_checksum, a == { let mut other = a.clone(); other.header_checksum = b.header_checksum; other } ); //source assert_eq!( a.source == b.source, a == { let mut other = a.clone(); other.source = b.source; other } ); //destination assert_eq!( a.destination == b.destination, a == { let mut other = a.clone(); other.destination = b.destination; other } ); //options assert_eq!( a.options == b.options, a == { let mut other = a.clone(); other.options = b.options; other } ); } } proptest! { #[test] fn hash(header in ipv4_any()) { use std::collections::hash_map::DefaultHasher; use core::hash::{Hash, Hasher}; let a = { let mut hasher = DefaultHasher::new(); header.hash(&mut hasher); hasher.finish() }; let b = { let mut hasher = DefaultHasher::new(); header.hash(&mut hasher); hasher.finish() }; assert_eq!(a, b); } } proptest! { #[test] fn new( source_ip in prop::array::uniform4(any::()), dest_ip in prop::array::uniform4(any::()), ttl in any::(), ok_payload_len in 0u16..=(u16::MAX - Ipv4Header::MIN_LEN as u16), err_payload_len in (u16::MAX - Ipv4Header::MIN_LEN as u16 + 1)..=u16::MAX ) { // ok case { let result = Ipv4Header::new( ok_payload_len, ttl, ip_number::UDP, source_ip, dest_ip ).unwrap(); assert_eq!(result.dscp.value(), 0); assert_eq!(result.ecn.value(), 0); assert_eq!(result.total_len, ok_payload_len + Ipv4Header::MIN_LEN as u16); assert_eq!(result.identification, 0); assert_eq!(result.dont_fragment, true); assert_eq!(result.more_fragments, false); assert_eq!(result.fragment_offset.value(), 0); assert_eq!(result.time_to_live, ttl); assert_eq!(result.protocol, ip_number::UDP); assert_eq!(result.header_checksum, 0); assert_eq!(result.source, source_ip); assert_eq!(result.destination, dest_ip); assert_eq!(result.options.as_slice(), &[]); } // err { assert_eq!( Ipv4Header::new( err_payload_len, ttl, ip_number::UDP, source_ip, dest_ip ), Err(ValueTooBigError::{ actual: err_payload_len, max_allowed: u16::MAX - Ipv4Header::MIN_LEN as u16, value_type: ValueType::Ipv4PayloadLength, }) ); } } } proptest! { #[test] fn ihl(header in ipv4_any()) { assert_eq!(header.ihl(), (header.header_len() / 4) as u8); } } proptest! { #[test] fn header_len(header in ipv4_any()) { assert_eq!(header.header_len(), 20 + usize::from(header.options.len())); } } proptest! { #[test] fn payload_len( header in ipv4_any() ) { // ok case assert_eq!( header.payload_len().unwrap(), header.total_len - 20 - (header.options.len() as u16) ); // err case for bad_len in 0u16..(header.header_len() as u16) { let mut header = header.clone(); header.total_len = bad_len; assert_eq!( header.payload_len().unwrap_err(), LenError{ required_len: header.header_len(), len: bad_len.into(), len_source: LenSource::Ipv4HeaderTotalLen, layer: Layer::Ipv4Packet, layer_start_offset: 0 } ); } } } #[test] fn set_payload_len() { let mut header = Ipv4Header::new(0, 0, ip_number::UDP, [0; 4], [0; 4]).unwrap(); //add options (to make sure they are included in the calculation) header.options = [1, 2, 3, 4].into(); //zero check assert!(header.set_payload_len(0).is_ok()); assert_eq!(header.total_len, 24); //max check const MAX: usize = (core::u16::MAX as usize) - Ipv4Header::MIN_LEN - 4; assert!(header.set_payload_len(MAX).is_ok()); assert_eq!(header.total_len, core::u16::MAX); const OVER_MAX: usize = MAX + 1; assert_eq!( header.set_payload_len(OVER_MAX), Err(ValueTooBigError { actual: OVER_MAX, max_allowed: usize::from(u16::MAX) - header.header_len(), value_type: ValueType::Ipv4PayloadLength }) ); } proptest! { #[test] fn max_payload_len(header in ipv4_any()) { assert_eq!(header.max_payload_len(), core::u16::MAX - 20 - u16::from(header.options.len_u8())); } } #[test] #[allow(deprecated)] fn set_options() { //length of 1 { let mut header: Ipv4Header = Default::default(); let options = [1, 2, 3, 4]; assert_eq!(header.set_options(&options), Ok(())); assert_eq!(&options, header.options()); assert_eq!(24, header.header_len()); assert_eq!(0, header.total_len); assert_eq!(6, header.ihl()); //length 0 assert_eq!(header.set_options(&[]), Ok(())); assert_eq!(&options[..0], header.options()); assert_eq!(20, header.header_len()); assert_eq!(0, header.total_len); assert_eq!(5, header.ihl()); } //maximum length (40) { let mut header: Ipv4Header = Default::default(); let options = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, ]; assert_eq!(header.set_options(&options), Ok(())); assert_eq!(&options[..], header.options()); assert_eq!(60, header.header_len()); assert_eq!(0, header.total_len); assert_eq!(15, header.ihl()); } //errors { let buffer: [u8; 50] = [0; 50]; for len in &[ 1usize, 2, 3, //unaligned 5, 6, 7, 41, 44, //over max ] { let mut header: Ipv4Header = Default::default(); //expect an error use self::err::ipv4::BadOptionsLen; assert_eq!( Err(BadOptionsLen { bad_len: *len }), header.set_options(&buffer[..*len]) ); //check value was not taken assert_eq!(&buffer[..0], header.options()); assert_eq!(20, header.header_len()); assert_eq!(0, header.total_len); assert_eq!(5, header.ihl()); } } } proptest! { #[test] #[allow(deprecated)] fn read_from_slice(ref input in ipv4_any()) { //serialize let mut buffer: Vec = Vec::with_capacity(input.header_len()); input.write_raw(&mut buffer).unwrap(); assert_eq!(input.header_len(), buffer.len()); //deserialize (read_from_slice) let result = Ipv4Header::read_from_slice(&buffer).unwrap(); assert_eq!(input, &result.0); assert_eq!(&buffer[usize::from(input.header_len())..], result.1); } } proptest! { #[test] fn from_slice(header in ipv4_any()) { use err::ipv4::HeaderError::*; use err::ipv4::HeaderSliceError::*; // ok { let mut buffer = ArrayVec::::new(); buffer.try_extend_from_slice(&header.to_bytes()).unwrap(); buffer.try_extend_from_slice(&[1]).unwrap(); let (actual_header, actual_rest) = Ipv4Header::from_slice(&buffer).unwrap(); assert_eq!(actual_header, header); assert_eq!(actual_rest, &[1]); } // unexpected end of slice { let buffer = header.to_bytes(); for len in 0..header.header_len() { assert_eq!( Ipv4Header::from_slice(&buffer[..len]), Err(Len(err::LenError{ required_len: if len < Ipv4Header::MIN_LEN { Ipv4Header::MIN_LEN } else { header.header_len() }, len: len, len_source: LenSource::Slice, layer: err::Layer::Ipv4Header, layer_start_offset: 0, })) ); } } // version error for version_number in 0u8..0b1111u8 { if 4 != version_number { let mut buffer = header.to_bytes(); // inject the bad ihl buffer[0] = (version_number << 4) | (buffer[0] & 0b1111); // expect an error assert_eq!( Ipv4Header::from_slice(&buffer).unwrap_err(), Content(UnexpectedVersion{ version_number, }) ); } } // ihl too small error for ihl in 0u8..5u8 { let mut buffer = header.to_bytes(); // inject the bad ihl buffer[0] = (4 << 4) | ihl; // expect an error assert_eq!( Ipv4Header::from_slice(&buffer).unwrap_err(), Content(HeaderLengthSmallerThanHeader{ ihl, }) ); } } } proptest! { #[test] fn read_and_read_without_version(header in ipv4_any()) { use err::ipv4::HeaderError::*; use std::io::Cursor; // ok { let buffer = header.to_bytes(); // read { let mut cursor = Cursor::new(&buffer); let actual_header = Ipv4Header::read(&mut cursor).unwrap(); assert_eq!(actual_header, header); assert_eq!(cursor.position(), header.header_len() as u64); } // read_without_version { let mut cursor = Cursor::new(&buffer[1..]); let actual_header = Ipv4Header::read_without_version(&mut cursor, buffer[0]).unwrap(); assert_eq!(actual_header, header); assert_eq!(cursor.position(), (header.header_len() - 1) as u64); } } // io error { let buffer = header.to_bytes(); for len in 0..header.header_len() { // read { let mut cursor = Cursor::new(&buffer[..len]); let err = Ipv4Header::read(&mut cursor).unwrap_err(); assert!(err.io_error().is_some()); } // read_without_version if len > 0 { let mut cursor = Cursor::new(&buffer[1..len]); let err = Ipv4Header::read_without_version(&mut cursor, buffer[0]).unwrap_err(); assert!(err.io_error().is_some()); } } } // version error for version_number in 0u8..0b1111u8 { if 4 != version_number { let mut buffer = header.to_bytes(); // inject the bad version number buffer[0] = (version_number << 4) | (buffer[0] & 0b1111); // expect an error // read { let mut cursor = Cursor::new(&buffer[..]); let err = Ipv4Header::read(&mut cursor) .unwrap_err() .content_error() .unwrap(); assert_eq!(err, UnexpectedVersion{ version_number }); } // read_without_version skipped as version is not checked } } // ihl too small error for ihl in 0u8..5u8 { let mut buffer = header.to_bytes(); // inject the bad ihl buffer[0] = (4 << 4) | ihl; // expect an error // read { let mut cursor = Cursor::new(&buffer[..]); let err = Ipv4Header::read(&mut cursor) .unwrap_err() .content_error() .unwrap(); assert_eq!(err, HeaderLengthSmallerThanHeader{ ihl }); } // read_without_version { let mut cursor = Cursor::new(&buffer[1..]); let err = Ipv4Header::read_without_version(&mut cursor, buffer[0]) .unwrap_err() .content_error() .unwrap(); assert_eq!(err, HeaderLengthSmallerThanHeader{ ihl }); } } } } proptest! { #[test] fn write(ref base_header in ipv4_any()) { use std::io::Cursor; let header = { let mut header = base_header.clone(); // set the header checksum to something else to // ensure it is calculated during the write call header.header_checksum = 0; header }; // normal write { //serialize let buffer = { let mut buffer: Vec = Vec::with_capacity(header.header_len()); header.write(&mut buffer).unwrap(); buffer }; assert_eq!(header.header_len(), buffer.len()); //deserialize let mut cursor = Cursor::new(&buffer); let result = Ipv4Header::read(&mut cursor).unwrap(); assert_eq!(header.header_len(), cursor.position() as usize); //check equivalence (with calculated checksum) let header_with_checksum = { let mut h = header.clone(); h.header_checksum = h.calc_header_checksum(); h }; assert_eq!(header_with_checksum, result); } // io error for len in 0..header.header_len() { let mut buffer = [0u8; Ipv4Header::MAX_LEN]; let mut cursor = Cursor::new(&mut buffer[..len]); assert!( header.write(&mut cursor).is_err() ); } } } proptest! { #[test] fn write_raw(base_header in ipv4_any()) { // normal write { //serialize let buffer = { let mut buffer: Vec = Vec::with_capacity(base_header.header_len()); base_header.write_raw(&mut buffer).unwrap(); buffer }; assert_eq!(base_header.header_len(), buffer.len()); // decode and check for equality assert_eq!( Ipv4Header::from_slice(&buffer).unwrap().0, base_header ); } // io error for len in 0..base_header.header_len() { let mut buffer = [0u8; Ipv4Header::MAX_LEN]; let mut cursor = Cursor::new(&mut buffer[..len]); assert!( base_header.write_raw(&mut cursor).is_err() ); } } } proptest! { #[test] fn to_bytes(base_header in ipv4_any()) { let bytes = base_header.to_bytes(); assert_eq!( base_header, Ipv4HeaderSlice::from_slice(&bytes).unwrap().to_header() ); } } #[test] fn calc_header_checksum() { let base: Ipv4Header = Ipv4Header::new( 40, 4, // ttl ip_number::UDP, [192, 168, 1, 1], // source [212, 10, 11, 123], // destination ) .unwrap(); //without options { //dont_fragment && !more_fragments let header = base.clone(); assert_eq!(0xd582, header.calc_header_checksum()); // !dont_fragment && more_fragments let header = { let mut header = base.clone(); header.dont_fragment = false; header.more_fragments = true; header }; assert_eq!(0xf582, header.calc_header_checksum()); } //with options { let header = { let mut header = base.clone(); header.options = [1, 2, 3, 4, 5, 6, 7, 8].into(); header.total_len = (header.header_len() + 32) as u16; header }; assert_eq!(0xc36e, header.calc_header_checksum()); } } #[test] fn is_fragmenting_payload() { // not fragmenting { let mut header: Ipv4Header = Default::default(); header.fragment_offset = 0.try_into().unwrap(); header.more_fragments = false; assert_eq!(false, header.is_fragmenting_payload()); } // fragmenting based on offset { let mut header: Ipv4Header = Default::default(); header.fragment_offset = 1.try_into().unwrap(); header.more_fragments = false; assert!(header.is_fragmenting_payload()); } // fragmenting based on more_fragments { let mut header: Ipv4Header = Default::default(); header.fragment_offset = 0.try_into().unwrap(); header.more_fragments = true; assert!(header.is_fragmenting_payload()); } } }