| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Parsing and serialization of IPv4 packets. |
| |
| use alloc::vec::Vec; |
| use core::convert::TryFrom; |
| use core::fmt::{self, Debug, Formatter}; |
| use core::ops::Range; |
| |
| use internet_checksum::Checksum; |
| use net_types::ip::{Ipv4, Ipv4Addr}; |
| use packet::records::options::OptionsRaw; |
| use packet::{ |
| BufferView, BufferViewMut, FromRaw, MaybeParsed, PacketBuilder, PacketConstraints, |
| ParsablePacket, ParseMetadata, SerializeBuffer, |
| }; |
| use zerocopy::{AsBytes, ByteSlice, ByteSliceMut, FromBytes, LayoutVerified, Unaligned}; |
| |
| use crate::error::{IpParseError, IpParseResult, ParseError}; |
| use crate::ip::IpProto; |
| use crate::U16; |
| |
| pub(crate) use self::inner::IPV4_MIN_HDR_LEN; |
| use self::options::{Ipv4Option, Ipv4OptionsImpl}; |
| |
| const HDR_PREFIX_LEN: usize = 20; |
| |
| /// The maximum length of an IPv4 header. |
| const IPV4_MAX_HDR_LEN: usize = 60; |
| |
| /// The maximum length for options in an IPv4 header. |
| const MAX_OPTIONS_LEN: usize = IPV4_MAX_HDR_LEN - HDR_PREFIX_LEN; |
| |
| /// The range of bytes within an IPv4 header buffer that the fragment data fields uses. |
| const IPV4_FRAGMENT_DATA_BYTE_RANGE: Range<usize> = 4..8; |
| |
| #[allow(missing_docs)] |
| #[derive(FromBytes, AsBytes, Unaligned)] |
| #[repr(C)] |
| pub struct HeaderPrefix { |
| version_ihl: u8, |
| dscp_ecn: u8, |
| total_len: U16, |
| id: U16, |
| flags_frag_off: [u8; 2], |
| ttl: u8, |
| proto: u8, |
| hdr_checksum: [u8; 2], |
| src_ip: Ipv4Addr, |
| dst_ip: Ipv4Addr, |
| } |
| |
| const IP_VERSION: u8 = 4; |
| const VERSION_OFFSET: u8 = 4; |
| const IHL_MASK: u8 = 0xF; |
| const IHL_MAX: u8 = (1 << VERSION_OFFSET) - 1; |
| const DSCP_OFFSET: u8 = 2; |
| const DSCP_MAX: u8 = (1 << (8 - DSCP_OFFSET)) - 1; |
| const ECN_MAX: u8 = (1 << DSCP_OFFSET) - 1; |
| const FLAGS_OFFSET: u8 = 13; |
| const FLAGS_MAX: u8 = (1 << (16 - FLAGS_OFFSET)) - 1; |
| const FRAG_OFF_MAX: u16 = (1 << FLAGS_OFFSET) - 1; |
| |
| impl HeaderPrefix { |
| fn new( |
| ihl: u8, |
| dscp: u8, |
| ecn: u8, |
| total_len: u16, |
| id: u16, |
| flags: u8, |
| frag_off: u16, |
| ttl: u8, |
| proto: u8, |
| hdr_checksum: [u8; 2], |
| src_ip: Ipv4Addr, |
| dst_ip: Ipv4Addr, |
| ) -> HeaderPrefix { |
| debug_assert!(ihl <= IHL_MAX); |
| debug_assert!(dscp <= DSCP_MAX); |
| debug_assert!(ecn <= ECN_MAX); |
| debug_assert!(flags <= FLAGS_MAX); |
| debug_assert!(frag_off <= FRAG_OFF_MAX); |
| |
| HeaderPrefix { |
| version_ihl: (IP_VERSION << VERSION_OFFSET) | ihl, |
| dscp_ecn: (dscp << DSCP_OFFSET) | ecn, |
| total_len: U16::new(total_len), |
| id: U16::new(id), |
| flags_frag_off: ((u16::from(flags) << FLAGS_OFFSET) | frag_off).to_be_bytes(), |
| ttl, |
| proto, |
| src_ip, |
| dst_ip, |
| hdr_checksum, |
| } |
| } |
| |
| fn version(&self) -> u8 { |
| self.version_ihl >> VERSION_OFFSET |
| } |
| |
| /// Get the Internet Header Length (IHL). |
| pub(crate) fn ihl(&self) -> u8 { |
| self.version_ihl & IHL_MASK |
| } |
| |
| /// The More Fragments (MF) flag. |
| pub(crate) fn mf_flag(&self) -> bool { |
| // `FLAGS_OFFSET` refers to the offset within the 2-byte array |
| // containing both the flags and the fragment offset. Since we're |
| // accessing the first byte directly, we shift by an extra `FLAGS_OFFSET |
| // - 8` bits, not by an extra `FLAGS_OFFSET` bits. |
| self.flags_frag_off[0] & (1 << ((FLAGS_OFFSET - 8) + MF_FLAG_OFFSET)) > 0 |
| } |
| } |
| |
| /// Provides common access to IPv4 header fields. |
| /// |
| /// `Ipv4Header` provides access to IPv4 header fields as a common |
| /// implementation for both [`Ipv4Packet`] and [`Ipv4PacketRaw`] |
| pub trait Ipv4Header { |
| /// Gets a reference to the IPv4 [`HeaderPrefix`]. |
| fn get_header_prefix(&self) -> &HeaderPrefix; |
| |
| /// The Differentiated Services Code Point (DSCP). |
| fn dscp(&self) -> u8 { |
| self.get_header_prefix().dscp_ecn >> 2 |
| } |
| |
| /// The Explicit Congestion Notification (ECN). |
| fn ecn(&self) -> u8 { |
| self.get_header_prefix().dscp_ecn & 3 |
| } |
| |
| /// The identification. |
| fn id(&self) -> u16 { |
| self.get_header_prefix().id.get() |
| } |
| |
| /// The Don't Fragment (DF) flag. |
| fn df_flag(&self) -> bool { |
| // the flags are the top 3 bits, so we need to shift by an extra 5 bits |
| self.get_header_prefix().flags_frag_off[0] & (1 << (5 + DF_FLAG_OFFSET)) > 0 |
| } |
| |
| /// The More Fragments (MF) flag. |
| fn mf_flag(&self) -> bool { |
| self.get_header_prefix().mf_flag() |
| } |
| |
| /// The fragment offset. |
| fn fragment_offset(&self) -> u16 { |
| ((u16::from(self.get_header_prefix().flags_frag_off[0] & 0x1F)) << 8) |
| | u16::from(self.get_header_prefix().flags_frag_off[1]) |
| } |
| |
| /// The Time To Live (TTL). |
| fn ttl(&self) -> u8 { |
| self.get_header_prefix().ttl |
| } |
| |
| /// The IP Protocol. |
| /// |
| /// `proto` returns the `IpProto` from the protocol field. |
| fn proto(&self) -> IpProto { |
| IpProto::from(self.get_header_prefix().proto) |
| } |
| |
| /// The source IP address. |
| fn src_ip(&self) -> Ipv4Addr { |
| self.get_header_prefix().src_ip |
| } |
| |
| /// The destination IP address. |
| fn dst_ip(&self) -> Ipv4Addr { |
| self.get_header_prefix().dst_ip |
| } |
| } |
| |
| /// An IPv4 packet. |
| /// |
| /// An `Ipv4Packet` shares its underlying memory with the byte slice it was |
| /// parsed from or serialized to, meaning that no copying or extra allocation is |
| /// necessary. |
| /// |
| /// An `Ipv4Packet` - whether parsed using `parse` or created using |
| /// `Ipv4PacketBuilder` - maintains the invariant that the checksum is always |
| /// valid. |
| pub struct Ipv4Packet<B> { |
| hdr_prefix: LayoutVerified<B, HeaderPrefix>, |
| options: Options<B>, |
| body: B, |
| } |
| |
| impl<B: ByteSlice> Ipv4Header for Ipv4Packet<B> { |
| fn get_header_prefix(&self) -> &HeaderPrefix { |
| &self.hdr_prefix |
| } |
| } |
| |
| impl<B: ByteSlice> ParsablePacket<B, ()> for Ipv4Packet<B> { |
| type Error = IpParseError<Ipv4>; |
| |
| fn parse_metadata(&self) -> ParseMetadata { |
| let header_len = self.hdr_prefix.bytes().len() + self.options.bytes().len(); |
| ParseMetadata::from_packet(header_len, self.body.len(), 0) |
| } |
| |
| fn parse<BV: BufferView<B>>(buffer: BV, args: ()) -> IpParseResult<Ipv4, Self> { |
| Ipv4PacketRaw::<B>::parse(buffer, args).and_then(|u| Ipv4Packet::try_from_raw(u)) |
| } |
| } |
| |
| impl<B: ByteSlice> FromRaw<Ipv4PacketRaw<B>, ()> for Ipv4Packet<B> { |
| type Error = IpParseError<Ipv4>; |
| |
| fn try_from_raw_with(raw: Ipv4PacketRaw<B>, _args: ()) -> Result<Self, Self::Error> { |
| let hdr_prefix = raw.hdr_prefix; |
| let hdr_bytes = (hdr_prefix.ihl() * 4) as usize; |
| |
| if hdr_bytes < HDR_PREFIX_LEN { |
| return debug_err!(Err(ParseError::Format.into()), "invalid IHL: {}", hdr_prefix.ihl()); |
| } |
| |
| let options = match raw.options { |
| MaybeParsed::Incomplete(_) => { |
| return debug_err!(Err(ParseError::Format.into()), "Incomplete options"); |
| } |
| MaybeParsed::Complete(unchecked) => Options::try_from_raw(unchecked) |
| .map_err(|e| debug_err!(ParseError::Format, "malformed options: {:?}", e))?, |
| }; |
| |
| if hdr_prefix.version() != 4 { |
| return debug_err!( |
| Err(ParseError::Format.into()), |
| "unexpected IP version: {}", |
| hdr_prefix.version() |
| ); |
| } |
| |
| let body = match raw.body { |
| MaybeParsed::Incomplete(_) => { |
| if hdr_prefix.mf_flag() { |
| return debug_err!( |
| Err(ParseError::NotSupported.into()), |
| "fragmentation not supported" |
| ); |
| } else { |
| return debug_err!(Err(ParseError::Format.into()), "Incomplete body"); |
| } |
| } |
| MaybeParsed::Complete(bytes) => bytes, |
| }; |
| |
| let packet = Ipv4Packet { hdr_prefix, options, body }; |
| if packet.compute_header_checksum() != [0, 0] { |
| return debug_err!(Err(ParseError::Checksum.into()), "invalid checksum"); |
| } |
| Ok(packet) |
| } |
| } |
| |
| fn compute_header_checksum(hdr_prefix: &[u8], options: &[u8]) -> [u8; 2] { |
| let mut c = Checksum::new(); |
| c.add_bytes(hdr_prefix); |
| c.add_bytes(options); |
| c.checksum() |
| } |
| |
| impl<B: ByteSlice> Ipv4Packet<B> { |
| /// Iterate over the IPv4 header options. |
| pub fn iter_options<'a>(&'a self) -> impl Iterator<Item = Ipv4Option<'a>> { |
| self.options.iter() |
| } |
| |
| // Compute the header checksum, skipping the checksum field itself. |
| fn compute_header_checksum(&self) -> [u8; 2] { |
| compute_header_checksum(self.hdr_prefix.bytes(), self.options.bytes()) |
| } |
| |
| /// The packet body. |
| pub fn body(&self) -> &[u8] { |
| &self.body |
| } |
| |
| /// The size of the header prefix and options. |
| pub fn header_len(&self) -> usize { |
| self.hdr_prefix.bytes().len() + self.options.bytes().len() |
| } |
| |
| /// Return a buffer that is a copy of the header bytes in this |
| /// packet, but patched to be not fragmented. |
| /// |
| /// Return a buffer of this packet's header and options with |
| /// the fragment data zeroed out. |
| pub fn copy_header_bytes_for_fragment(&self) -> Vec<u8> { |
| let expected_bytes_len = self.header_len(); |
| let mut bytes = Vec::with_capacity(expected_bytes_len); |
| |
| bytes.extend_from_slice(self.hdr_prefix.bytes()); |
| bytes.extend_from_slice(self.options.bytes()); |
| |
| // `bytes`'s length should be exactly `expected_bytes_len`. |
| assert_eq!(bytes.len(), expected_bytes_len); |
| |
| // Zero out the fragment data. |
| bytes[IPV4_FRAGMENT_DATA_BYTE_RANGE].copy_from_slice(&[0; 4][..]); |
| |
| bytes |
| } |
| |
| /// Construct a builder with the same contents as this packet. |
| pub fn builder(&self) -> Ipv4PacketBuilder { |
| let mut s = Ipv4PacketBuilder { |
| dscp: self.dscp(), |
| ecn: self.ecn(), |
| id: self.id(), |
| flags: 0, |
| frag_off: self.fragment_offset(), |
| ttl: self.ttl(), |
| proto: self.hdr_prefix.proto, |
| src_ip: self.src_ip(), |
| dst_ip: self.dst_ip(), |
| }; |
| s.df_flag(self.df_flag()); |
| s.mf_flag(self.mf_flag()); |
| s |
| } |
| } |
| |
| impl<B> Ipv4Packet<B> |
| where |
| B: ByteSliceMut, |
| { |
| /// Set the Time To Live (TTL). |
| /// |
| /// Set the TTL and update the header checksum accordingly. |
| pub fn set_ttl(&mut self, ttl: u8) { |
| // See the internet_checksum::update documentation for why we need to |
| // provide two bytes which are at an even byte offset from the beginning |
| // of the header. |
| let old_bytes = [self.hdr_prefix.ttl, self.hdr_prefix.proto]; |
| let new_bytes = [ttl, self.hdr_prefix.proto]; |
| self.hdr_prefix.hdr_checksum = |
| internet_checksum::update(self.hdr_prefix.hdr_checksum, &old_bytes, &new_bytes); |
| self.hdr_prefix.ttl = ttl; |
| } |
| } |
| |
| impl<B> Debug for Ipv4Packet<B> |
| where |
| B: ByteSlice, |
| { |
| fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { |
| f.debug_struct("Ipv4Packet") |
| .field("src_ip", &self.src_ip()) |
| .field("dst_ip", &self.dst_ip()) |
| .field("id", &self.id()) |
| .field("ttl", &self.ttl()) |
| .field("proto", &self.proto()) |
| .field("frag_off", &self.fragment_offset()) |
| .field("dscp", &self.dscp()) |
| .field("ecn", &self.ecn()) |
| .field("mf_flag", &self.mf_flag()) |
| .field("df_flag", &self.df_flag()) |
| .field("body", &alloc::format!("<{} bytes>", self.body.len())) |
| .finish() |
| } |
| } |
| |
| /// A partially parsed and not yet validated IPv4 packet. |
| /// |
| /// `Ipv4PacketRaw` provides minimal parsing of an IPv4 packet, namely |
| /// it only requires that the fixed header part ([`HeaderPrefix`]) be retrieved, |
| /// all the other parts of the packet may be missing when attempting to create |
| /// it. |
| /// |
| /// [`Ipv4Packet`] provides a [`FromRaw`] implementation that can be used to |
| /// validate an `Ipv4PacketRaw`. |
| pub struct Ipv4PacketRaw<B> { |
| hdr_prefix: LayoutVerified<B, HeaderPrefix>, |
| options: MaybeParsed<OptionsRaw<B, Ipv4OptionsImpl>, B>, |
| body: MaybeParsed<B, B>, |
| } |
| |
| impl<B: ByteSlice> Ipv4Header for Ipv4PacketRaw<B> { |
| fn get_header_prefix(&self) -> &HeaderPrefix { |
| &self.hdr_prefix |
| } |
| } |
| |
| impl<B: ByteSlice> ParsablePacket<B, ()> for Ipv4PacketRaw<B> { |
| type Error = IpParseError<Ipv4>; |
| |
| fn parse_metadata(&self) -> ParseMetadata { |
| let header_len = self.hdr_prefix.bytes().len() + self.options.len(); |
| ParseMetadata::from_packet(header_len, self.body.len(), 0) |
| } |
| |
| fn parse<BV: BufferView<B>>(mut buffer: BV, _args: ()) -> IpParseResult<Ipv4, Self> { |
| let hdr_prefix = buffer |
| .take_obj_front::<HeaderPrefix>() |
| .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?; |
| let hdr_bytes = (hdr_prefix.ihl() * 4) as usize; |
| |
| let options = MaybeParsed::take_from_buffer_with( |
| &mut buffer, |
| // If the subtraction hdr_bytes - HDR_PREFIX_LEN would have been |
| // negative, that would imply that IHL has an invalid value. Even |
| // though this will end up being MaybeParsed::Complete, the IHL |
| // value is validated when transforming Ipv4PacketRaw to Ipv4Packet. |
| hdr_bytes.saturating_sub(HDR_PREFIX_LEN), |
| OptionsRaw::new, |
| ); |
| |
| let total_len: usize = hdr_prefix.total_len.get().into(); |
| let body_len = total_len.saturating_sub(hdr_bytes); |
| if buffer.len() > body_len { |
| // Discard the padding left by the previous layer. This unwrap is |
| // safe because of the check against total_len. |
| buffer.take_back(buffer.len() - body_len).unwrap(); |
| } |
| |
| let body = MaybeParsed::new_with_min_len(buffer.into_rest(), body_len); |
| |
| Ok(Self { hdr_prefix, options, body }) |
| } |
| } |
| |
| impl<B: ByteSlice> Ipv4PacketRaw<B> { |
| /// Return the body. |
| /// |
| /// `body` returns [`MaybeParsed::Complete`] if the entire body is present |
| /// (as determined by the header's "total length" and "internet header |
| /// length" fields), and [`MaybeParsed::Incomplete`] otherwise. |
| pub fn body(&self) -> MaybeParsed<&[u8], &[u8]> { |
| self.body.as_ref().map(|b| b.deref()).map_incomplete(|b| b.deref()) |
| } |
| } |
| |
| /// A records parser for IPv4 options. |
| /// |
| /// See [`Options`] for more details. |
| /// |
| /// [`Options`]: packet::records::options::Options |
| type Options<B> = packet::records::options::Options<B, Ipv4OptionsImpl>; |
| |
| /// A records serializer for IPv4 options. |
| /// |
| /// See [`OptionsSerializer`] for more details. |
| /// |
| /// [`OptionsSerializer`]: packet::records::options::OptionsSerializer |
| type OptionsSerializer<'a, I> = |
| packet::records::options::OptionsSerializer<'a, Ipv4OptionsImpl, Ipv4Option<'a>, I>; |
| |
| /// A PacketBuilder for Ipv4 Packets but with options. |
| #[derive(Debug)] |
| pub struct Ipv4PacketBuilderWithOptions<'a, I: Clone + Iterator<Item = &'a Ipv4Option<'a>>> { |
| prefix_builder: Ipv4PacketBuilder, |
| options: OptionsSerializer<'a, I>, |
| } |
| |
| impl<'a, I: Clone + Iterator<Item = &'a Ipv4Option<'a>>> Ipv4PacketBuilderWithOptions<'a, I> { |
| /// Creates a new IPv4 packet builder without options. |
| pub fn new<T: IntoIterator<Item = &'a Ipv4Option<'a>, IntoIter = I>>( |
| prefix_builder: Ipv4PacketBuilder, |
| options: T, |
| ) -> Option<Ipv4PacketBuilderWithOptions<'a, I>> { |
| let options = OptionsSerializer::new(options.into_iter()); |
| // The maximum header length for IPv4 packet is 60 bytes, minus the fixed 40 bytes, |
| // we only have 40 bytes for options. |
| if options.records_bytes_len() > MAX_OPTIONS_LEN { |
| return None; |
| } |
| Some(Ipv4PacketBuilderWithOptions { prefix_builder, options }) |
| } |
| |
| fn aligned_options_len(&self) -> usize { |
| let raw_len = self.options.records_bytes_len(); |
| // align to the next 4-byte boundary. |
| next_multiple_of_four(raw_len) |
| } |
| } |
| |
| fn next_multiple_of_four(x: usize) -> usize { |
| (x + 3) & !3 |
| } |
| |
| impl<'a, I: Clone + Iterator<Item = &'a Ipv4Option<'a>>> PacketBuilder |
| for Ipv4PacketBuilderWithOptions<'a, I> |
| { |
| fn constraints(&self) -> PacketConstraints { |
| let header_len = IPV4_MIN_HDR_LEN + self.aligned_options_len(); |
| assert_eq!(header_len % 4, 0); |
| PacketConstraints::new(header_len, 0, 0, (1 << 16) - 1 - header_len) |
| } |
| |
| fn serialize(&self, buffer: &mut SerializeBuffer<'_>) { |
| let (mut header, body, _) = buffer.parts(); |
| // implements BufferViewMut |
| let mut header = &mut header; |
| let opt_len = self.aligned_options_len(); |
| let options = header.take_back_zero(opt_len).expect("too few bytes for Ipv4 options"); |
| self.options.serialize_records(options); |
| self.prefix_builder.write_header_prefix(header, options, body.len()); |
| } |
| } |
| |
| /// A builder for IPv4 packets. |
| #[derive(Debug, Clone, Eq, PartialEq)] |
| pub struct Ipv4PacketBuilder { |
| dscp: u8, |
| ecn: u8, |
| id: u16, |
| flags: u8, |
| frag_off: u16, |
| ttl: u8, |
| proto: u8, |
| src_ip: Ipv4Addr, |
| dst_ip: Ipv4Addr, |
| } |
| |
| impl Ipv4PacketBuilder { |
| /// Construct a new `Ipv4PacketBuilder`. |
| pub fn new<S: Into<Ipv4Addr>, D: Into<Ipv4Addr>>( |
| src_ip: S, |
| dst_ip: D, |
| ttl: u8, |
| proto: IpProto, |
| ) -> Ipv4PacketBuilder { |
| Ipv4PacketBuilder { |
| dscp: 0, |
| ecn: 0, |
| id: 0, |
| flags: 0, |
| frag_off: 0, |
| ttl, |
| proto: proto.into(), |
| src_ip: src_ip.into(), |
| dst_ip: dst_ip.into(), |
| } |
| } |
| |
| /// Set the Differentiated Services Code Point (DSCP). |
| /// |
| /// # Panics |
| /// |
| /// `dscp` panics if `dscp` is greater than 2^6 - 1. |
| pub fn dscp(&mut self, dscp: u8) { |
| assert!(dscp <= 1 << 6, "invalid DCSP: {}", dscp); |
| self.dscp = dscp; |
| } |
| |
| /// Set the Explicit Congestion Notification (ECN). |
| /// |
| /// # Panics |
| /// |
| /// `ecn` panics if `ecn` is greater than 3. |
| pub fn ecn(&mut self, ecn: u8) { |
| assert!(ecn <= 3, "invalid ECN: {}", ecn); |
| self.ecn = ecn; |
| } |
| |
| /// Set the identification. |
| pub fn id(&mut self, id: u16) { |
| self.id = id; |
| } |
| |
| /// Set the Don't Fragment (DF) flag. |
| pub fn df_flag(&mut self, df: bool) { |
| if df { |
| self.flags |= 1 << DF_FLAG_OFFSET; |
| } else { |
| self.flags &= !(1 << DF_FLAG_OFFSET); |
| } |
| } |
| |
| /// Set the More Fragments (MF) flag. |
| pub fn mf_flag(&mut self, mf: bool) { |
| if mf { |
| self.flags |= 1 << MF_FLAG_OFFSET; |
| } else { |
| self.flags &= !(1 << MF_FLAG_OFFSET); |
| } |
| } |
| |
| /// Set the fragment offset. |
| /// |
| /// # Panics |
| /// |
| /// `fragment_offset` panics if `fragment_offset` is greater than 2^13 - 1. |
| pub fn fragment_offset(&mut self, fragment_offset: u16) { |
| assert!(fragment_offset < 1 << 13, "invalid fragment offset: {}", fragment_offset); |
| self.frag_off = fragment_offset; |
| } |
| } |
| |
| impl Ipv4PacketBuilder { |
| /// Writes a `HeaderPrefix` to the beginning of `buffer` according to the |
| /// given `options` and `body`. |
| fn write_header_prefix<B: ByteSliceMut, BV: BufferViewMut<B>>( |
| &self, |
| mut buffer: BV, |
| options: &[u8], |
| body_len: usize, |
| ) { |
| let header_len = core::mem::size_of::<HeaderPrefix>() + options.len(); |
| let total_len = header_len + body_len; |
| assert_eq!(header_len % 4, 0); |
| let ihl: u8 = u8::try_from(header_len / 4).expect("Header too large"); |
| |
| let mut hdr_prefix = HeaderPrefix::new( |
| ihl, |
| self.dscp, |
| self.ecn, |
| { |
| // The caller promises to supply a body whose length does not |
| // exceed max_body_len. Doing this as a debug_assert (rather |
| // than an assert) is fine because, with debug assertions |
| // disabled, we'll just write an incorrect header value, which |
| // is acceptable if the caller has violated their contract. |
| debug_assert!(total_len <= core::u16::MAX as usize); |
| total_len as u16 |
| }, |
| self.id, |
| self.flags, |
| self.frag_off, |
| self.ttl, |
| self.proto, |
| [0, 0], // header checksum |
| self.src_ip, |
| self.dst_ip, |
| ); |
| |
| let checksum = compute_header_checksum(hdr_prefix.as_bytes(), options); |
| hdr_prefix.hdr_checksum = checksum; |
| buffer.write_obj_front(&hdr_prefix).expect("too few bytes for IPv4 header prefix"); |
| } |
| } |
| |
| impl PacketBuilder for Ipv4PacketBuilder { |
| fn constraints(&self) -> PacketConstraints { |
| PacketConstraints::new(IPV4_MIN_HDR_LEN, 0, 0, (1 << 16) - 1 - IPV4_MIN_HDR_LEN) |
| } |
| |
| fn serialize(&self, buffer: &mut SerializeBuffer<'_>) { |
| let (mut header, body, _) = buffer.parts(); |
| self.write_header_prefix(&mut header, &[][..], body.len()); |
| } |
| } |
| |
| // bit positions into the flags bits |
| const DF_FLAG_OFFSET: u8 = 1; |
| const MF_FLAG_OFFSET: u8 = 0; |
| |
| /// Reassembles a fragmented IPv4 packet into a parsed IPv4 packet. |
| pub(crate) fn reassemble_fragmented_packet< |
| B: ByteSliceMut, |
| BV: BufferViewMut<B>, |
| I: Iterator<Item = Vec<u8>>, |
| >( |
| mut buffer: BV, |
| header: Vec<u8>, |
| body_fragments: I, |
| ) -> IpParseResult<Ipv4, Ipv4Packet<B>> { |
| let bytes = buffer.as_mut(); |
| |
| // First, copy over the header data. |
| bytes[0..header.len()].copy_from_slice(&header[..]); |
| let mut byte_count = header.len(); |
| |
| // Next, copy over the body fragments. |
| for p in body_fragments { |
| bytes[byte_count..byte_count + p.len()].copy_from_slice(&p[..]); |
| byte_count += p.len(); |
| } |
| |
| // Fix up the IPv4 header |
| |
| // Make sure that the packet length is not more than the maximum |
| // possible IPv4 packet length. |
| if byte_count > usize::from(core::u16::MAX) { |
| return debug_err!( |
| Err(ParseError::Format.into()), |
| "fragmented packet length of {} bytes is too large", |
| byte_count |
| ); |
| } |
| |
| // We know the call to `unwrap` will not fail because we just copied the header |
| // bytes into `bytes`. |
| let mut header = |
| LayoutVerified::<_, HeaderPrefix>::new_unaligned_from_prefix(&mut bytes[..]).unwrap().0; |
| |
| // Update the total length field. |
| header.total_len.set(u16::try_from(byte_count).unwrap()); |
| |
| // Zero out fragment related data since we will now have a |
| // reassembled packet that does not need reassembly. |
| header.flags_frag_off = [0; 2]; |
| |
| // Update header checksum. |
| header.hdr_checksum = [0; 2]; |
| header.hdr_checksum = compute_header_checksum(header.as_bytes(), &[]); |
| |
| Ipv4Packet::parse_mut(buffer, ()) |
| } |
| |
| /// Parsing and serialization of IPv4 options. |
| pub mod options { |
| use byteorder::{ByteOrder, NetworkEndian, WriteBytesExt}; |
| |
| use packet::records::options::{OptionsImpl, OptionsImplLayout, OptionsSerializerImpl}; |
| |
| const OPTION_KIND_EOL: u8 = 0; |
| const OPTION_KIND_NOP: u8 = 1; |
| const OPTION_KIND_RTRALRT: u8 = 148; |
| |
| const OPTION_RTRALRT_LEN: usize = 2; |
| |
| /// An IPv4 header option. |
| /// |
| /// An IPv4 header option comprises metadata about the option (which is stored |
| /// in the kind byte) and the option itself. |
| /// |
| /// See [Wikipedia] or [RFC 791] for more details. |
| /// |
| /// [Wikipedia]: https://en.wikipedia.org/wiki/IPv4#Options |
| /// [RFC 791]: https://tools.ietf.org/html/rfc791#page-15 |
| #[derive(PartialEq, Eq, Debug)] |
| pub struct Ipv4Option<'a> { |
| /// Whether this option needs to be copied into all fragments of a |
| /// fragmented packet. |
| pub copied: bool, |
| /// IPv4 option data. |
| // TODO(joshlf): include "Option Class"? The variable-length option data. |
| pub data: Ipv4OptionData<'a>, |
| } |
| |
| /// The data associated with an IPv4 header option. |
| /// |
| /// `Ipv4OptionData` represents the variable-length data field of an IPv4 header |
| /// option. |
| #[allow(missing_docs)] |
| #[derive(PartialEq, Eq, Debug)] |
| pub enum Ipv4OptionData<'a> { |
| /// Used to tell routers to inspect the packet. |
| /// |
| /// Used by IGMP host messages per [RFC 2236 section 2]. |
| /// |
| /// [RFC 2236 section 2]: https://tools.ietf.org/html/rfc2236#section-2 |
| RouterAlert { data: u16 }, |
| |
| /// An unrecognized IPv4 option. |
| // The maximum header length is 60 bytes, and the fixed-length header is 20 |
| // bytes, so there are 40 bytes for the options. That leaves a maximum |
| // options size of 1 kind byte + 1 length byte + 38 data bytes. Data for an |
| // unrecognized option kind. |
| // |
| // Any unrecognized option kind will have its data parsed using this |
| // variant. This allows code to copy unrecognized options into packets when |
| // forwarding. |
| // |
| // `data`'s length is in the range [0, 38]. |
| Unrecognized { kind: u8, len: u8, data: &'a [u8] }, |
| } |
| |
| /// An implementation of [`OptionsImpl`] for IPv4 options. |
| #[derive(Debug)] |
| pub(super) struct Ipv4OptionsImpl; |
| |
| impl OptionsImplLayout for Ipv4OptionsImpl { |
| type Error = (); |
| } |
| |
| impl<'a> OptionsImpl<'a> for Ipv4OptionsImpl { |
| type Option = Ipv4Option<'a>; |
| |
| fn parse(kind: u8, data: &'a [u8]) -> Result<Option<Ipv4Option<'a>>, ()> { |
| let copied = kind & (1 << 7) > 0; |
| match kind { |
| self::OPTION_KIND_EOL | self::OPTION_KIND_NOP => { |
| unreachable!("records::options::Options promises to handle EOL and NOP") |
| } |
| self::OPTION_KIND_RTRALRT => { |
| if data.len() == OPTION_RTRALRT_LEN { |
| Ok(Some(Ipv4Option { |
| copied, |
| data: Ipv4OptionData::RouterAlert { |
| data: NetworkEndian::read_u16(data), |
| }, |
| })) |
| } else { |
| Err(()) |
| } |
| } |
| kind => { |
| if data.len() > 38 { |
| Err(()) |
| } else { |
| Ok(Some(Ipv4Option { |
| copied, |
| data: Ipv4OptionData::Unrecognized { |
| kind, |
| len: data.len() as u8, |
| data, |
| }, |
| })) |
| } |
| } |
| } |
| } |
| } |
| |
| impl<'a> OptionsSerializerImpl<'a> for Ipv4OptionsImpl { |
| type Option = Ipv4Option<'a>; |
| |
| fn option_length(option: &Self::Option) -> usize { |
| match option.data { |
| Ipv4OptionData::RouterAlert { .. } => OPTION_RTRALRT_LEN, |
| Ipv4OptionData::Unrecognized { len, .. } => len as usize, |
| } |
| } |
| |
| fn option_kind(option: &Self::Option) -> u8 { |
| let number = match option.data { |
| Ipv4OptionData::RouterAlert { .. } => OPTION_KIND_RTRALRT, |
| Ipv4OptionData::Unrecognized { kind, .. } => kind, |
| }; |
| number | ((option.copied as u8) << 7) |
| } |
| |
| fn serialize(mut buffer: &mut [u8], option: &Self::Option) { |
| match option.data { |
| Ipv4OptionData::Unrecognized { data, .. } => buffer.copy_from_slice(data), |
| Ipv4OptionData::RouterAlert { data } => { |
| buffer.write_u16::<NetworkEndian>(data).unwrap() |
| } |
| }; |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use packet::records::options::Options; |
| use packet::records::RecordsSerializerImpl; |
| |
| #[test] |
| fn test_serialize_router_alert() { |
| let mut buffer = [0u8; 4]; |
| let option = Ipv4Option { copied: true, data: Ipv4OptionData::RouterAlert { data: 0 } }; |
| <Ipv4OptionsImpl as RecordsSerializerImpl>::serialize(&mut buffer, &option); |
| assert_eq!(buffer[0], 148); |
| assert_eq!(buffer[1], 4); |
| assert_eq!(buffer[2], 0); |
| assert_eq!(buffer[3], 0); |
| } |
| |
| #[test] |
| fn test_parse_router_alert() { |
| let mut buffer: Vec<u8> = vec![148, 4, 0, 0]; |
| let options = Options::<_, Ipv4OptionsImpl>::parse(buffer.as_mut()).unwrap(); |
| let rtralt = options.iter().nth(0).unwrap(); |
| assert!(rtralt.copied); |
| assert_eq!(rtralt.data, Ipv4OptionData::RouterAlert { data: 0 }); |
| } |
| } |
| } |
| |
| mod inner { |
| /// The minimum length of an IPv4 header. |
| pub const IPV4_MIN_HDR_LEN: usize = super::HDR_PREFIX_LEN; |
| } |
| |
| /// IPv4 packet parsing and serialization test utilities. |
| pub mod testutil { |
| pub use super::inner::IPV4_MIN_HDR_LEN; |
| |
| /// The offset to the TTL field within an IPv4 header, in bytes. |
| pub const IPV4_TTL_OFFSET: usize = 8; |
| |
| /// The offset to the checksum field within an IPv4 header, in bytes. |
| pub const IPV4_CHECKSUM_OFFSET: usize = 10; |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use net_types::ethernet::Mac; |
| use packet::{Buf, FragmentedBuffer, InnerPacketBuilder, ParseBuffer, Serializer}; |
| |
| use super::*; |
| use crate::ethernet::{ |
| EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck, |
| }; |
| use crate::testutil::*; |
| |
| const DEFAULT_SRC_MAC: Mac = Mac::new([1, 2, 3, 4, 5, 6]); |
| const DEFAULT_DST_MAC: Mac = Mac::new([7, 8, 9, 0, 1, 2]); |
| const DEFAULT_SRC_IP: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]); |
| const DEFAULT_DST_IP: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]); |
| |
| #[test] |
| fn test_parse_serialize_full_tcp() { |
| use crate::testdata::tls_client_hello_v4::*; |
| |
| let mut buf = ÐERNET_FRAME.bytes[..]; |
| let frame = buf.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check).unwrap(); |
| verify_ethernet_frame(&frame, ETHERNET_FRAME); |
| |
| let mut body = frame.body(); |
| let packet = body.parse::<Ipv4Packet<_>>().unwrap(); |
| verify_ipv4_packet(&packet, IPV4_PACKET); |
| |
| let buffer = packet |
| .body() |
| .into_serializer() |
| .encapsulate(packet.builder()) |
| .encapsulate(frame.builder()) |
| .serialize_vec_outer() |
| .unwrap(); |
| assert_eq!(buffer.as_ref(), ETHERNET_FRAME.bytes); |
| } |
| |
| #[test] |
| fn test_parse_serialize_full_udp() { |
| use crate::testdata::dns_request_v4::*; |
| |
| let mut buf = ETHERNET_FRAME.bytes; |
| let frame = buf.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check).unwrap(); |
| verify_ethernet_frame(&frame, ETHERNET_FRAME); |
| |
| let mut body = frame.body(); |
| let packet = body.parse::<Ipv4Packet<_>>().unwrap(); |
| verify_ipv4_packet(&packet, IPV4_PACKET); |
| |
| let buffer = packet |
| .body() |
| .into_serializer() |
| .encapsulate(packet.builder()) |
| .encapsulate(frame.builder()) |
| .serialize_vec_outer() |
| .unwrap(); |
| assert_eq!(buffer.as_ref(), ETHERNET_FRAME.bytes); |
| } |
| |
| fn hdr_prefix_to_bytes(hdr_prefix: HeaderPrefix) -> [u8; 20] { |
| let mut bytes = [0; 20]; |
| { |
| let mut lv = LayoutVerified::<_, HeaderPrefix>::new_unaligned(&mut bytes[..]).unwrap(); |
| *lv = hdr_prefix; |
| } |
| bytes |
| } |
| |
| // Return a new HeaderPrefix with reasonable defaults, including a valid |
| // header checksum. |
| fn new_hdr_prefix() -> HeaderPrefix { |
| HeaderPrefix::new( |
| 5, |
| 0, |
| 0, |
| 20, |
| 0x0102, |
| 0, |
| 0, |
| 0x03, |
| IpProto::Tcp.into(), |
| [0xa6, 0xcf], |
| DEFAULT_SRC_IP, |
| DEFAULT_DST_IP, |
| ) |
| } |
| |
| #[test] |
| fn test_parse() { |
| let mut bytes = &hdr_prefix_to_bytes(new_hdr_prefix())[..]; |
| let packet = bytes.parse::<Ipv4Packet<_>>().unwrap(); |
| assert_eq!(packet.id(), 0x0102); |
| assert_eq!(packet.ttl(), 0x03); |
| assert_eq!(packet.proto(), IpProto::Tcp); |
| assert_eq!(packet.src_ip(), DEFAULT_SRC_IP); |
| assert_eq!(packet.dst_ip(), DEFAULT_DST_IP); |
| assert_eq!(packet.body(), []); |
| } |
| |
| #[test] |
| fn test_parse_padding() { |
| // Test that we properly discard post-packet padding. |
| let mut buffer = Buf::new(Vec::new(), ..) |
| .encapsulate(Ipv4PacketBuilder::new(DEFAULT_DST_IP, DEFAULT_DST_IP, 0, IpProto::Tcp)) |
| .encapsulate(EthernetFrameBuilder::new( |
| DEFAULT_SRC_MAC, |
| DEFAULT_DST_MAC, |
| EtherType::Ipv4, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| buffer.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check).unwrap(); |
| // Test that the Ethernet body is the minimum length, which far exceeds |
| // the IPv4 packet header size of 20 bytes (without options). |
| assert_eq!(buffer.len(), 46); |
| let packet = buffer.parse::<Ipv4Packet<_>>().unwrap(); |
| // Test that we've properly discarded the post-packet padding, and have |
| // an empty body. |
| assert_eq!(packet.body().len(), 0); |
| // Test that we not only ignored the padding, but properly consumed it |
| // from the underlying buffer as we're required to do by the |
| // ParsablePacket contract. |
| assert_eq!(buffer.len(), 0); |
| } |
| |
| #[test] |
| fn test_parse_error() { |
| // Set the version to 5. The version must be 4. |
| let mut hdr_prefix = new_hdr_prefix(); |
| hdr_prefix.version_ihl = (5 << 4) | 5; |
| assert_eq!( |
| (&hdr_prefix_to_bytes(hdr_prefix)[..]).parse::<Ipv4Packet<_>>().unwrap_err(), |
| ParseError::Format.into() |
| ); |
| |
| // Set the IHL to 4, implying a header length of 16. This is smaller |
| // than the minimum of 20. |
| let mut hdr_prefix = new_hdr_prefix(); |
| hdr_prefix.version_ihl = (4 << 4) | 4; |
| assert_eq!( |
| (&hdr_prefix_to_bytes(hdr_prefix)[..]).parse::<Ipv4Packet<_>>().unwrap_err(), |
| ParseError::Format.into() |
| ); |
| |
| // Set the IHL to 6, implying a header length of 24. This is larger than |
| // the actual packet length of 20. |
| let mut hdr_prefix = new_hdr_prefix(); |
| hdr_prefix.version_ihl = (4 << 4) | 6; |
| assert_eq!( |
| (&hdr_prefix_to_bytes(hdr_prefix)[..]).parse::<Ipv4Packet<_>>().unwrap_err(), |
| ParseError::Format.into() |
| ); |
| } |
| |
| // Return a stock Ipv4PacketBuilder with reasonable default values. |
| fn new_builder() -> Ipv4PacketBuilder { |
| Ipv4PacketBuilder::new(DEFAULT_DST_IP, DEFAULT_DST_IP, 64, IpProto::Tcp) |
| } |
| |
| #[test] |
| fn test_serialize() { |
| let mut builder = new_builder(); |
| builder.dscp(0x12); |
| builder.ecn(3); |
| builder.id(0x0405); |
| builder.df_flag(true); |
| builder.mf_flag(true); |
| builder.fragment_offset(0x0607); |
| |
| let mut buf = (&[0, 1, 2, 3, 3, 4, 5, 7, 8, 9]) |
| .into_serializer() |
| .encapsulate(builder) |
| .serialize_vec_outer() |
| .unwrap(); |
| assert_eq!( |
| buf.as_ref(), |
| [ |
| 69, 75, 0, 30, 4, 5, 102, 7, 64, 6, 248, 103, 5, 6, 7, 8, 5, 6, 7, 8, 0, 1, 2, 3, |
| 3, 4, 5, 7, 8, 9 |
| ], |
| ); |
| let packet = buf.parse::<Ipv4Packet<_>>().unwrap(); |
| assert_eq!(packet.dscp(), 0x12); |
| assert_eq!(packet.ecn(), 3); |
| assert_eq!(packet.id(), 0x0405); |
| assert!(packet.df_flag()); |
| assert!(packet.mf_flag()); |
| assert_eq!(packet.fragment_offset(), 0x0607); |
| } |
| |
| #[test] |
| fn test_serialize_zeroes() { |
| // Test that Ipv4PacketBuilder::serialize properly zeroes memory before |
| // serializing the header. |
| let mut buf_0 = [0; IPV4_MIN_HDR_LEN]; |
| Buf::new(&mut buf_0[..], IPV4_MIN_HDR_LEN..) |
| .encapsulate(new_builder()) |
| .serialize_vec_outer() |
| .unwrap(); |
| let mut buf_1 = [0xFF; IPV4_MIN_HDR_LEN]; |
| Buf::new(&mut buf_1[..], IPV4_MIN_HDR_LEN..) |
| .encapsulate(new_builder()) |
| .serialize_vec_outer() |
| .unwrap(); |
| assert_eq!(buf_0, buf_1); |
| } |
| |
| #[test] |
| #[should_panic(expected = "(Mtu, Nested { inner: Buf { buf:")] |
| fn test_serialize_panic_packet_length() { |
| // Test that a packet which is longer than 2^16 - 1 bytes is rejected. |
| Buf::new(&mut [0; (1 << 16) - IPV4_MIN_HDR_LEN][..], ..) |
| .encapsulate(new_builder()) |
| .serialize_vec_outer() |
| .unwrap(); |
| } |
| |
| #[test] |
| fn test_copy_header_bytes_for_fragment() { |
| let hdr_prefix = new_hdr_prefix(); |
| let mut bytes = hdr_prefix_to_bytes(hdr_prefix); |
| let mut buf = &bytes[..]; |
| let packet = buf.parse::<Ipv4Packet<_>>().unwrap(); |
| let copied_bytes = packet.copy_header_bytes_for_fragment(); |
| bytes[IPV4_FRAGMENT_DATA_BYTE_RANGE].copy_from_slice(&[0; 4][..]); |
| assert_eq!(&copied_bytes[..], &bytes[..]); |
| } |
| |
| #[test] |
| fn test_partial_parsing() { |
| // Try something with only the header, but that would have a larger |
| // body: |
| let mut hdr_prefix = new_hdr_prefix(); |
| hdr_prefix.total_len = U16::new(256); |
| let mut bytes = hdr_prefix_to_bytes(hdr_prefix)[..].to_owned(); |
| bytes.extend(&[1, 2, 3, 4, 5]); |
| let mut buf = &bytes[..]; |
| let packet = buf.parse::<Ipv4PacketRaw<_>>().unwrap(); |
| assert_eq!(packet.hdr_prefix.bytes(), &bytes[0..20]); |
| assert_eq!(packet.options.as_ref().unwrap().len(), 0); |
| // We must've captured the incomplete bytes in body: |
| assert_eq!(packet.body.as_ref().unwrap_incomplete().len(), 5); |
| // validation should fail: |
| assert!(Ipv4Packet::try_from_raw(packet).is_err()); |
| |
| // Try something with the header plus incomplete options: |
| let mut hdr_prefix = new_hdr_prefix(); |
| hdr_prefix.version_ihl = (4 << 4) | 10; |
| let bytes = hdr_prefix_to_bytes(hdr_prefix); |
| let mut buf = &bytes[..]; |
| let packet = buf.parse::<Ipv4PacketRaw<_>>().unwrap(); |
| assert_eq!(packet.hdr_prefix.bytes(), bytes); |
| assert!(packet.options.is_incomplete()); |
| assert!(packet.body.is_complete()); |
| // validation should fail: |
| assert!(Ipv4Packet::try_from_raw(packet).is_err()); |
| |
| // Try an incomplete header: |
| let hdr_prefix = new_hdr_prefix(); |
| let bytes = &hdr_prefix_to_bytes(hdr_prefix); |
| let mut buf = &bytes[0..10]; |
| assert!(buf.parse::<Ipv4PacketRaw<_>>().is_err()); |
| } |
| |
| #[test] |
| fn test_next_multiple_of_four() { |
| for x in 0usize..=(std::u16::MAX - 3) as usize { |
| let y = next_multiple_of_four(x); |
| assert_eq!(y % 4, 0); |
| assert!(y >= x); |
| if x % 4 == 0 { |
| assert_eq!(x, y); |
| } else { |
| assert_eq!(x + (4 - x % 4), y); |
| } |
| } |
| } |
| } |