blob: f03eed8353108f2e6af7597aa56ec428a7cf8043 [file] [log] [blame]
// 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,
);
}
}