| // 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 ARP packets. |
| |
| #![allow(private_in_public)] |
| |
| #[cfg(test)] |
| use std::fmt::{self, Debug, Formatter}; |
| use std::mem; |
| |
| use byteorder::{ByteOrder, NetworkEndian}; |
| use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified, Unaligned}; |
| |
| use device::arp::{ArpHardwareType, ArpOp}; |
| use device::ethernet::{EtherType, Mac}; |
| use error::ParseError; |
| use ip::Ipv4Addr; |
| |
| // Header has the same memory layout (thanks to repr(C, packed)) as an ARP |
| // header. Thus, we can simply reinterpret the bytes of the ARP header as a |
| // Header and then safely access its fields. |
| // Note the following caveats: |
| // - We cannot make any guarantees about the alignment of an instance of this |
| // struct in memory or of any of its fields. This is true both because |
| // repr(packed) removes the padding that would be used to ensure the alignment |
| // of individual fields, but also because we are given no guarantees about |
| // where within a given memory buffer a particular packet (and thus its |
| // header) will be located. |
| // - Individual fields are all either u8 or [u8; N] rather than u16, u32, etc. |
| // This is for two reasons: |
| // - u16 and larger have larger-than-1 alignments, which are forbidden as |
| // described above |
| // - We are not guaranteed that the local platform has the same endianness as |
| // network byte order (big endian), so simply treating a sequence of bytes |
| // as a u16 or other multi-byte number would not necessarily be correct. |
| // Instead, we use the NetworkEndian type and its reader and writer methods |
| // to correctly access these fields. |
| #[derive(Default)] |
| #[repr(C, packed)] |
| struct Header { |
| htype: [u8; 2], // Hardware (e.g. Ethernet) |
| ptype: [u8; 2], // Protocol (e.g. IPv4) |
| hlen: u8, // Length (in octets) of hardware address |
| plen: u8, // Length (in octets) of protocol address |
| oper: [u8; 2], // Operation: 1 for Req, 2 for Reply |
| } |
| |
| unsafe impl FromBytes for Header {} |
| unsafe impl AsBytes for Header {} |
| unsafe impl Unaligned for Header {} |
| |
| impl Header { |
| fn hardware_protocol(&self) -> u16 { |
| NetworkEndian::read_u16(&self.htype) |
| } |
| |
| fn set_hardware_protocol(&mut self, htype: ArpHardwareType, hlen: u8) -> &mut Self { |
| NetworkEndian::write_u16(&mut self.htype, htype as u16); |
| self.hlen = hlen; |
| self |
| } |
| |
| fn network_protocol(&self) -> u16 { |
| NetworkEndian::read_u16(&self.ptype) |
| } |
| |
| fn set_network_protocol(&mut self, ptype: EtherType, plen: u8) -> &mut Self { |
| NetworkEndian::write_u16(&mut self.ptype, ptype as u16); |
| self.plen = plen; |
| self |
| } |
| |
| fn op_code(&self) -> u16 { |
| NetworkEndian::read_u16(&self.oper) |
| } |
| |
| fn set_op_code(&mut self, op: ArpOp) -> &mut Self { |
| NetworkEndian::write_u16(&mut self.oper, op as u16); |
| self |
| } |
| |
| fn hardware_address_len(&self) -> u8 { |
| self.hlen |
| } |
| |
| fn protocol_address_len(&self) -> u8 { |
| self.plen |
| } |
| } |
| |
| /// Peek at an ARP header to see what hardware and protocol address types are |
| /// used. |
| /// |
| /// Since `ArpPacket` is statically typed with the hardware and protocol address |
| /// types expected in the header and body, these types must be known ahead of |
| /// time before calling `parse`. If multiple different types are valid in a |
| /// given parsing context, and so the caller cannot know ahead of time which |
| /// types to use, `peek_arp_types` can be used to peek at the header first to |
| /// figure out which static types should be used in a subsequent call to |
| /// `parse`. |
| /// |
| /// Note that `peek_arp_types` only inspects certain fields in the header, and |
| /// so `peek_arp_types` succeeding does not guarantee that a subsequent call to |
| /// `parse` will also succeed. |
| pub fn peek_arp_types<B: ByteSlice>(bytes: B) -> Result<(ArpHardwareType, EtherType), ParseError> { |
| let (header, _) = |
| LayoutVerified::<B, Header>::new_unaligned_from_prefix(bytes).ok_or(ParseError::Format)?; |
| let hw = ArpHardwareType::from_u16(header.hardware_protocol()).ok_or(ParseError::NotSupported)?; |
| let proto = EtherType::from_u16(header.network_protocol()).ok_or(ParseError::NotSupported)?; |
| let hlen = match hw { |
| ArpHardwareType::Ethernet => <Mac as HType>::hlen(), |
| }; |
| let plen = match proto { |
| EtherType::Ipv4 => <Ipv4Addr as PType>::plen(), |
| _ => return Err(ParseError::NotSupported), |
| }; |
| if header.hardware_address_len() != hlen || header.protocol_address_len() != plen { |
| return Err(ParseError::Format); |
| } |
| Ok((hw, proto)) |
| } |
| |
| // See comment on Header for an explanation of the memory safety requirements. |
| #[repr(C, packed)] |
| struct Body<HwAddr, ProtoAddr> { |
| sha: HwAddr, |
| spa: ProtoAddr, |
| tha: HwAddr, |
| tpa: ProtoAddr, |
| } |
| |
| unsafe impl<HwAddr: FromBytes, ProtoAddr: FromBytes> FromBytes for Body<HwAddr, ProtoAddr> {} |
| unsafe impl<HwAddr: AsBytes, ProtoAddr: AsBytes> AsBytes for Body<HwAddr, ProtoAddr> {} |
| unsafe impl<HwAddr: Unaligned, ProtoAddr: Unaligned> Unaligned for Body<HwAddr, ProtoAddr> {} |
| |
| impl<HwAddr: Copy, ProtoAddr: Copy> Body<HwAddr, ProtoAddr> { |
| fn set_sha(&mut self, sha: HwAddr) -> &mut Self { |
| self.sha = sha; |
| self |
| } |
| |
| fn set_spa(&mut self, spa: ProtoAddr) -> &mut Self { |
| self.spa = spa; |
| self |
| } |
| |
| fn set_tha(&mut self, tha: HwAddr) -> &mut Self { |
| self.tha = tha; |
| self |
| } |
| |
| fn set_tpa(&mut self, tpa: ProtoAddr) -> &mut Self { |
| self.tpa = tpa; |
| self |
| } |
| } |
| |
| trait HType { |
| fn htype() -> ArpHardwareType; |
| fn hlen() -> u8; |
| } |
| |
| trait PType { |
| fn ptype() -> EtherType; |
| fn plen() -> u8; |
| } |
| |
| impl HType for Mac { |
| fn htype() -> ArpHardwareType { |
| ArpHardwareType::Ethernet |
| } |
| fn hlen() -> u8 { |
| use std::convert::TryFrom; |
| u8::try_from(mem::size_of::<Mac>()).unwrap() |
| } |
| } |
| |
| impl PType for Ipv4Addr { |
| fn ptype() -> EtherType { |
| EtherType::Ipv4 |
| } |
| fn plen() -> u8 { |
| use std::convert::TryFrom; |
| u8::try_from(mem::size_of::<Ipv4Addr>()).unwrap() |
| } |
| } |
| |
| /// An ARP packet. |
| /// |
| /// A `ArpPacket` shares its underlying memory with the byte slice it was parsed |
| /// from or serialized to, meaning that no copying or extra allocation is |
| /// necessary. |
| pub struct ArpPacket<B, HwAddr, ProtoAddr> { |
| header: LayoutVerified<B, Header>, |
| body: LayoutVerified<B, Body<HwAddr, ProtoAddr>>, |
| } |
| |
| impl<B: ByteSlice, HwAddr, ProtoAddr> ArpPacket<B, HwAddr, ProtoAddr> |
| where |
| HwAddr: Copy + HType + FromBytes + Unaligned, |
| ProtoAddr: Copy + PType + FromBytes + Unaligned, |
| { |
| /// The length of an ARP packet in bytes. |
| /// |
| /// When calling `ArpPacket::serialize`, provide at least `PACKET_LEN` bytes |
| /// for the packet in order to guarantee that `serialize` will not panic. |
| /// |
| /// `PACKET_LEN` may have different values depending on the type parameters |
| /// `HwAddr` and `ProtoAddr`. |
| pub const PACKET_LEN: usize = |
| mem::size_of::<Header>() + mem::size_of::<Body<HwAddr, ProtoAddr>>(); |
| |
| /// Parse an ARP packet. |
| /// |
| /// `parse` parses `bytes` as an ARP packet and validates the header fields. |
| /// |
| /// If `bytes` are a valid ARP packet, but do not match the hardware address |
| /// and protocol address types `HwAddr` and `ProtoAddr`, `parse` will return |
| /// `Err(ParseError::NotExpected)`. If multiple hardware or protocol address |
| /// types are valid in a given context, `peek_arp_types` may be used to |
| /// peek at the header and determine what types are present so that the |
| /// correct types can then be used in a call to `parse`. |
| /// |
| /// The caller may provide more bytes than necessary. This allows the caller |
| /// to call `parse` on a payload which was itself padded to meet a minimum |
| /// length requirement (for example, for Ethernet frames). See the |
| /// `DETAILS.md` file in the repository root for more details. |
| pub fn parse(bytes: B) -> Result<ArpPacket<B, HwAddr, ProtoAddr>, ParseError> { |
| let (header, body) = LayoutVerified::<B, Header>::new_unaligned_from_prefix(bytes) |
| .ok_or(ParseError::Format)?; |
| let (body, _) = LayoutVerified::<B, Body<HwAddr, ProtoAddr>>::new_unaligned_from_prefix( |
| body, |
| ).ok_or(ParseError::Format)?; |
| |
| if header.hardware_protocol() != <HwAddr as HType>::htype() as u16 |
| || header.network_protocol() != <ProtoAddr as PType>::ptype() as u16 |
| { |
| return Err(ParseError::NotExpected); |
| } |
| if header.hardware_address_len() != <HwAddr as HType>::hlen() |
| || header.protocol_address_len() != <ProtoAddr as PType>::plen() |
| { |
| return Err(ParseError::Format); |
| } |
| |
| if ArpOp::from_u16(header.op_code()).is_none() { |
| return Err(ParseError::Format); |
| } |
| |
| Ok(ArpPacket { header, body }) |
| } |
| |
| /// The type of ARP packet |
| pub fn operation(&self) -> ArpOp { |
| // This is verified in `parse`, so should be safe to unwrap |
| ArpOp::from_u16(self.header.op_code()).unwrap() |
| } |
| |
| /// The hardware address of the ARP packet sender. |
| pub fn sender_hardware_address(&self) -> HwAddr { |
| self.body.sha |
| } |
| |
| /// The protocol address of the ARP packet sender. |
| pub fn sender_protocol_address(&self) -> ProtoAddr { |
| self.body.spa |
| } |
| |
| /// The hardware address of the ARP packet target. |
| pub fn target_hardware_address(&self) -> HwAddr { |
| self.body.tha |
| } |
| |
| /// The protocol address of the ARP packet target. |
| pub fn target_protocol_address(&self) -> ProtoAddr { |
| self.body.tpa |
| } |
| } |
| |
| impl<B, HwAddr, ProtoAddr> ArpPacket<B, HwAddr, ProtoAddr> |
| where |
| B: AsMut<[u8]>, |
| HwAddr: Copy + HType + FromBytes + AsBytes + Unaligned, |
| ProtoAddr: Copy + PType + FromBytes + AsBytes + Unaligned, |
| { |
| /// Serialize an ARP packet in an existing buffer. |
| /// |
| /// `serialize` serializes an `ArpPacket` which uses the provided `buffer` |
| /// for its storage, initializing all header and body fields. If the buffer |
| /// is larger than the packet size, it serializes from the beginning of the |
| /// buffer, and leaves any remaining bytes at the end of the buffer |
| /// untouched. Using a larger-than-necessary buffer can be useful if the |
| /// encapsulating protocol has a minimum payload size requirement, and an |
| /// ARP packet does not satisfy that minimum. |
| /// |
| /// # Examples |
| /// |
| /// ```rust |
| /// let mut buffer = [0u8; 1024]; |
| /// ArpPacket::serialize( |
| /// &mut buffer, |
| /// ArpOp::Request, |
| /// src_mac, |
| /// src_ip, |
| /// dst_mac, |
| /// dst_ip, |
| /// ); |
| /// ``` |
| /// |
| /// # Panics |
| /// |
| /// `serialize` panics if there is insufficient room in the buffer to store |
| /// the ARP packet. The caller can guarantee that there will be enough room |
| /// by providing a buffer of at least `ArpPacket::MAX_LEN` bytes. |
| pub fn serialize( |
| mut buffer: B, operation: ArpOp, sender_hardware_addr: HwAddr, |
| sender_protocol_addr: ProtoAddr, target_hardware_addr: HwAddr, |
| target_protocol_addr: ProtoAddr, |
| ) { |
| // SECURITY: Use _zeroed constructors to ensure we zero memory to |
| // prevent leaking information from packets previously stored in |
| // this buffer. |
| let (mut header, rest) = LayoutVerified::<_, Header>::new_unaligned_from_prefix_zeroed( |
| buffer.as_mut(), |
| ).expect("not enough bytes for an ARP packet"); |
| let (mut body, _) = |
| LayoutVerified::<_, Body<HwAddr, ProtoAddr>>::new_unaligned_from_prefix_zeroed(rest) |
| .expect("not enough bytes for an ARP packet"); |
| |
| header |
| .set_hardware_protocol(<HwAddr as HType>::htype(), <HwAddr as HType>::hlen()) |
| .set_network_protocol(<ProtoAddr as PType>::ptype(), <ProtoAddr as PType>::plen()) |
| .set_op_code(operation); |
| |
| body.set_sha(sender_hardware_addr) |
| .set_spa(sender_protocol_addr) |
| .set_tha(target_hardware_addr) |
| .set_tpa(target_protocol_addr); |
| } |
| } |
| |
| #[cfg(test)] |
| impl<B, HwAddr, ProtoAddr> Debug for ArpPacket<B, HwAddr, ProtoAddr> { |
| fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { |
| write!(fmt, "ArpPacket") |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use ip::Ipv4Addr; |
| use wire::ethernet::EthernetFrame; |
| |
| const TEST_SENDER_IPV4: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]); |
| const TEST_TARGET_IPV4: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]); |
| const TEST_SENDER_MAC: Mac = Mac::new([0, 1, 2, 3, 4, 5]); |
| const TEST_TARGET_MAC: Mac = Mac::new([6, 7, 8, 9, 10, 11]); |
| |
| #[test] |
| fn test_parse_full() { |
| use wire::testdata::*; |
| |
| let (frame, _) = EthernetFrame::parse(ARP_REQUEST).unwrap(); |
| assert_eq!(frame.ethertype(), Some(Ok(EtherType::Arp))); |
| |
| let (hw, proto) = peek_arp_types(frame.body()).unwrap(); |
| assert_eq!(hw, ArpHardwareType::Ethernet); |
| assert_eq!(proto, EtherType::Ipv4); |
| let arp = ArpPacket::<_, Mac, Ipv4Addr>::parse(frame.body()).unwrap(); |
| assert_eq!(arp.operation(), ArpOp::Request); |
| assert_eq!(frame.src_mac(), arp.sender_hardware_address()); // These will be the same |
| } |
| |
| fn header_to_bytes(header: Header) -> [u8; 8] { |
| let mut bytes = [0; 8]; |
| { |
| let mut lv = LayoutVerified::new_unaligned(&mut bytes[..]).unwrap(); |
| *lv = header; |
| } |
| bytes |
| } |
| |
| // Return a new Header for an Ethernet/IPv4 ARP request. |
| fn new_header() -> Header { |
| let mut header = Header::default(); |
| header.set_hardware_protocol(<Mac as HType>::htype(), <Mac as HType>::hlen()); |
| header.set_network_protocol(<Ipv4Addr as PType>::ptype(), <Ipv4Addr as PType>::plen()); |
| header.set_op_code(ArpOp::Request); |
| header |
| } |
| |
| #[test] |
| fn test_peek() { |
| let header = new_header(); |
| let (hw, proto) = peek_arp_types(&header_to_bytes(header)[..]).unwrap(); |
| assert_eq!(hw, ArpHardwareType::Ethernet); |
| assert_eq!(proto, EtherType::Ipv4); |
| |
| // Test that an invalid operation is not rejected; peek_arp_types does |
| // not inspect the operation. |
| let mut header = new_header(); |
| NetworkEndian::write_u16(&mut header.oper[..], 3); |
| let (hw, proto) = peek_arp_types(&header_to_bytes(header)[..]).unwrap(); |
| assert_eq!(hw, ArpHardwareType::Ethernet); |
| assert_eq!(proto, EtherType::Ipv4); |
| } |
| |
| #[test] |
| fn test_parse() { |
| let mut bytes = [ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 5, 6, 7, 8, |
| ]; |
| (&mut bytes[..8]).copy_from_slice(&header_to_bytes(new_header())); |
| let (hw, proto) = peek_arp_types(&bytes[..]).unwrap(); |
| assert_eq!(hw, ArpHardwareType::Ethernet); |
| assert_eq!(proto, EtherType::Ipv4); |
| let packet = ArpPacket::<_, Mac, Ipv4Addr>::parse(&bytes[..]).unwrap(); |
| assert_eq!(packet.sender_hardware_address(), TEST_SENDER_MAC); |
| assert_eq!(packet.sender_protocol_address(), TEST_SENDER_IPV4); |
| assert_eq!(packet.target_hardware_address(), TEST_TARGET_MAC); |
| assert_eq!(packet.target_protocol_address(), TEST_TARGET_IPV4); |
| assert_eq!(packet.operation(), ArpOp::Request); |
| } |
| |
| #[test] |
| fn test_serialize() { |
| let mut buf = [0; 28]; |
| { |
| ArpPacket::serialize( |
| &mut buf[..], |
| ArpOp::Request, |
| TEST_SENDER_MAC, |
| TEST_SENDER_IPV4, |
| TEST_TARGET_MAC, |
| TEST_TARGET_IPV4, |
| ); |
| } |
| assert_eq!( |
| buf, |
| [ |
| 0, 1, 8, 0, 6, 4, 0, 1, 0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 5, 6, 7, |
| 8, |
| ] |
| ); |
| let packet = ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..28]).unwrap(); |
| assert_eq!(packet.sender_hardware_address(), TEST_SENDER_MAC); |
| assert_eq!(packet.sender_protocol_address(), TEST_SENDER_IPV4); |
| assert_eq!(packet.target_hardware_address(), TEST_TARGET_MAC); |
| assert_eq!(packet.target_protocol_address(), TEST_TARGET_IPV4); |
| assert_eq!(packet.operation(), ArpOp::Request); |
| } |
| |
| #[test] |
| fn test_peek_error() { |
| // Test that a header which is too short is rejected. |
| let buf = [0; 7]; |
| assert_eq!(peek_arp_types(&buf[..]).unwrap_err(), ParseError::Format); |
| |
| // Test that an unexpected hardware protocol type is rejected. |
| let mut header = new_header(); |
| NetworkEndian::write_u16(&mut header.htype[..], 0); |
| assert_eq!( |
| peek_arp_types(&header_to_bytes(header)[..]).unwrap_err(), |
| ParseError::NotSupported |
| ); |
| |
| // Test that an unexpected network protocol type is rejected. |
| let mut header = new_header(); |
| NetworkEndian::write_u16(&mut header.ptype[..], 0); |
| assert_eq!( |
| peek_arp_types(&header_to_bytes(header)[..]).unwrap_err(), |
| ParseError::NotSupported |
| ); |
| |
| // Test that an incorrect hardware address len is rejected. |
| let mut header = new_header(); |
| header.hlen = 7; |
| assert_eq!( |
| peek_arp_types(&header_to_bytes(header)[..]).unwrap_err(), |
| ParseError::Format |
| ); |
| |
| // Test that an incorrect protocol address len is rejected. |
| let mut header = new_header(); |
| header.plen = 5; |
| assert_eq!( |
| peek_arp_types(&header_to_bytes(header)[..]).unwrap_err(), |
| ParseError::Format |
| ); |
| } |
| |
| #[test] |
| fn test_parse_error() { |
| // Test that a packet which is too short is rejected. |
| let buf = [0; 27]; |
| assert_eq!( |
| ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(), |
| ParseError::Format |
| ); |
| |
| let mut buf = [0; 28]; |
| |
| // Test that an unexpected hardware protocol type is rejected. |
| let mut header = new_header(); |
| NetworkEndian::write_u16(&mut header.htype[..], 0); |
| (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]); |
| assert_eq!( |
| ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(), |
| ParseError::NotExpected |
| ); |
| |
| // Test that an unexpected network protocol type is rejected. |
| let mut header = new_header(); |
| NetworkEndian::write_u16(&mut header.ptype[..], 0); |
| (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]); |
| assert_eq!( |
| ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(), |
| ParseError::NotExpected |
| ); |
| |
| // Test that an incorrect hardware address len is rejected. |
| let mut header = new_header(); |
| header.hlen = 7; |
| (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]); |
| assert_eq!( |
| ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(), |
| ParseError::Format |
| ); |
| |
| // Test that an incorrect protocol address len is rejected. |
| let mut header = new_header(); |
| header.plen = 5; |
| (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]); |
| assert_eq!( |
| ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(), |
| ParseError::Format |
| ); |
| |
| // Test that an invalid operation is rejected. |
| let mut header = new_header(); |
| NetworkEndian::write_u16(&mut header.oper[..], 3); |
| (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]); |
| assert_eq!( |
| ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(), |
| ParseError::Format |
| ); |
| } |
| |
| #[test] |
| fn test_serialize_zeroes() { |
| // Test that ArpPacket::serialize properly zeroes memory before |
| // serializing the packet. |
| let mut buf_0 = [0; 28]; |
| ArpPacket::serialize( |
| &mut buf_0[..], |
| ArpOp::Request, |
| TEST_SENDER_MAC, |
| TEST_SENDER_IPV4, |
| TEST_TARGET_MAC, |
| TEST_TARGET_IPV4, |
| ); |
| let mut buf_1 = [0xFF; 28]; |
| ArpPacket::serialize( |
| &mut buf_1[..], |
| ArpOp::Request, |
| TEST_SENDER_MAC, |
| TEST_SENDER_IPV4, |
| TEST_TARGET_MAC, |
| TEST_TARGET_IPV4, |
| ); |
| assert_eq!(buf_0, buf_1); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_serialize_panic_insufficient_packet_space() { |
| // Test that a buffer which doesn't leave enough room for the packet is |
| // rejected. |
| ArpPacket::serialize( |
| &mut [0; 27], |
| ArpOp::Request, |
| TEST_SENDER_MAC, |
| TEST_SENDER_IPV4, |
| TEST_TARGET_MAC, |
| TEST_TARGET_IPV4, |
| ); |
| } |
| } |