blob: 812f8f82f5b161d37d938eeef530ade40449861f [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 UDP packets.
#[cfg(test)]
use std::fmt::{self, Debug, Formatter};
use std::num::NonZeroU16;
use byteorder::{ByteOrder, NetworkEndian};
use packet::{
BufferView, BufferViewMut, PacketBuilder, ParsablePacket, ParseMetadata, SerializeBuffer,
};
use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified, Unaligned};
use crate::error::ParseError;
use crate::ip::{Ip, IpAddr, IpProto};
use crate::wire::util::{fits_in_u16, fits_in_u32, Checksum};
// Header has the same memory layout (thanks to repr(C, packed)) as a UDP
// header. Thus, we can simply reinterpret the bytes of the UDP 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.
#[repr(C, packed)]
struct Header {
src_port: [u8; 2],
dst_port: [u8; 2],
length: [u8; 2],
checksum: [u8; 2],
}
unsafe impl FromBytes for Header {}
unsafe impl AsBytes for Header {}
unsafe impl Unaligned for Header {}
impl Header {
fn src_port(&self) -> u16 {
NetworkEndian::read_u16(&self.src_port)
}
fn set_src_port(&mut self, src_port: u16) {
NetworkEndian::write_u16(&mut self.src_port, src_port);
}
fn dst_port(&self) -> u16 {
NetworkEndian::read_u16(&self.dst_port)
}
fn set_dst_port(&mut self, dst_port: u16) {
NetworkEndian::write_u16(&mut self.dst_port, dst_port);
}
fn length(&self) -> u16 {
NetworkEndian::read_u16(&self.length)
}
fn checksum(&self) -> u16 {
NetworkEndian::read_u16(&self.checksum)
}
}
/// A UDP packet.
///
/// A `UdpPacket` shares its underlying memory with the byte slice it was parsed
/// from or serialized to, meaning that no copying or extra allocation is
/// necessary.
///
/// A `UdpPacket` - whether parsed using `parse` or created using `serialize` -
/// maintains the invariant that the checksum is always valid.
pub struct UdpPacket<B> {
header: LayoutVerified<B, Header>,
body: B,
}
/// Arguments required to parse a UDP packet.
pub struct UdpParseArgs<A: IpAddr> {
src_ip: A,
dst_ip: A,
}
impl<A: IpAddr> UdpParseArgs<A> {
/// Construct a new `UdpParseArgs`.
pub fn new(src_ip: A, dst_ip: A) -> UdpParseArgs<A> {
UdpParseArgs { src_ip, dst_ip }
}
}
impl<B: ByteSlice, A: IpAddr> ParsablePacket<B, UdpParseArgs<A>> for UdpPacket<B> {
type Error = ParseError;
fn parse_metadata(&self) -> ParseMetadata {
ParseMetadata::from_packet(self.header.bytes().len(), self.body.len(), 0)
}
fn parse<BV: BufferView<B>>(mut buffer: BV, args: UdpParseArgs<A>) -> Result<Self, ParseError> {
// See for details: https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure
let buf_len = buffer.len();
let header = buffer.take_obj_front::<Header>().ok_or_else(debug_err_fn!(
ParseError::Format,
"too few bytes for header"
))?;
let packet = UdpPacket {
header,
body: buffer.into_rest(),
};
let len = if packet.header.length() == 0 && A::Version::VERSION.is_v6() {
// IPv6 supports jumbograms, so a UDP packet may be greater than
// 2^16 bytes in size. In this case, the size doesn't fit in the
// 16-bit length field in the header, and so the length field is set
// to zero to indicate this.
buf_len
} else {
packet.header.length() as usize
};
if len != buf_len {
return debug_err!(
Err(ParseError::Format),
"length in header does not match packet length"
);
}
if packet.header.dst_port() == 0 {
return debug_err!(Err(ParseError::Format), "zero destination port");
}
// A 0 checksum indicates that the checksum wasn't computed. In IPv4,
// this means that it shouldn't be validated. In IPv6, the checksum is
// mandatory, so this is an error.
if packet.header.checksum != [0, 0] {
// When computing the checksum, a checksum of 0 is sent as 0xFFFF.
let target = if packet.header.checksum == [0xFF, 0xFF] {
0
} else {
NetworkEndian::read_u16(&packet.header.checksum)
};
if packet
.compute_checksum(args.src_ip, args.dst_ip)
.ok_or_else(debug_err_fn!(ParseError::Format, "segment too large"))?
!= target
{
return debug_err!(Err(ParseError::Checksum), "invalid checksum");
}
} else if A::Version::VERSION.is_v6() {
return debug_err!(Err(ParseError::Format), "missing checksum");
}
Ok(packet)
}
}
impl<B: ByteSlice> UdpPacket<B> {
// Compute the UDP checksum, skipping the checksum field itself. Returns
// None if the packet size is too large.
fn compute_checksum<A: IpAddr>(&self, src_ip: A, dst_ip: A) -> Option<u16> {
// See for details: https://en.wikipedia.org/wiki/User_Datagram_Protocol#Checksum_computation
let mut c = Checksum::new();
c.add_bytes(src_ip.bytes());
c.add_bytes(dst_ip.bytes());
if A::Version::VERSION.is_v4() {
c.add_bytes(&[0, IpProto::Udp as u8]);
c.add_bytes(&self.header.length);
} else {
let len = self.total_packet_len();
// For IPv6, the "UDP length" field in the pseudo-header is 32 bits.
if !fits_in_u32(len) {
return None;
}
let mut len_bytes = [0; 4];
NetworkEndian::write_u32(&mut len_bytes, len as u32);
c.add_bytes(&len_bytes);
c.add_bytes(&[0, 0, 0, IpProto::Udp as u8]);
}
c.add_bytes(&self.header.src_port);
c.add_bytes(&self.header.dst_port);
c.add_bytes(&self.header.length);
c.add_bytes(&self.body);
Some(c.checksum())
}
/// The packet body.
pub fn body(&self) -> &[u8] {
self.body.deref()
}
/// The source UDP port, if any.
///
/// The source port is optional, and may have been omitted by the sender.
pub fn src_port(&self) -> Option<NonZeroU16> {
NonZeroU16::new(self.header.src_port())
}
/// The destination UDP port.
pub fn dst_port(&self) -> NonZeroU16 {
NonZeroU16::new(self.header.dst_port()).unwrap()
}
/// Did this packet have a checksum?
///
/// On IPv4, the sender may optionally omit the checksum. If this function
/// returns false, the sender ommitted the checksum, and `parse` will not
/// have validated it.
///
/// On IPv6, it is guaranteed that `checksummed` will return true because
/// IPv6 requires a checksum, and so any UDP packet missing one will fail
/// validation in `parse`.
pub fn checksummed(&self) -> bool {
self.header.checksum() != 0
}
// The length of the header.
fn header_len(&self) -> usize {
self.header.bytes().len()
}
// The length of the packet as calculated from the header and body. This is
// not the same as the length field in the header.
fn total_packet_len(&self) -> usize {
self.header_len() + self.body.len()
}
/// Construct a builder with the same contents as this packet.
pub fn builder<A: IpAddr>(&self, src_ip: A, dst_ip: A) -> UdpPacketBuilder<A> {
UdpPacketBuilder {
src_ip,
dst_ip,
src_port: self.src_port(),
dst_port: self.dst_port(),
}
}
}
// NOTE(joshlf): In order to ensure that the checksum is always valid, we don't
// expose any setters for the fields of the UDP packet; the only way to set them
// is via UdpPacketBuilder::serialize. This, combined with checksum validation
// performed in UdpPacket::parse, provides the invariant that a UdpPacket always
// has a valid checksum.
/// A builder for UDP packets.
pub struct UdpPacketBuilder<A: IpAddr> {
src_ip: A,
dst_ip: A,
src_port: Option<NonZeroU16>,
dst_port: NonZeroU16,
}
impl<A: IpAddr> UdpPacketBuilder<A> {
/// Construct a new `UdpPacketBuilder`.
pub fn new(
src_ip: A, dst_ip: A, src_port: Option<NonZeroU16>, dst_port: NonZeroU16,
) -> UdpPacketBuilder<A> {
UdpPacketBuilder {
src_ip,
dst_ip,
src_port,
dst_port,
}
}
}
const HEADER_BYTES: usize = 8;
impl<A: IpAddr> PacketBuilder for UdpPacketBuilder<A> {
fn header_len(&self) -> usize {
HEADER_BYTES
}
fn min_body_len(&self) -> usize {
0
}
fn footer_len(&self) -> usize {
0
}
fn serialize<'a>(self, mut buffer: SerializeBuffer<'a>) {
// See for details: https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure
let (mut header, body, _) = buffer.parts();
// implements BufferViewMut, giving us take_obj_xxx_zero methods
let mut header = &mut header;
// SECURITY: Use _zero constructor to ensure we zero memory to prevent
// leaking information from packets previously stored in this buffer.
let header = header
.take_obj_front_zero::<Header>()
.expect("too few bytes for UDP header");
let mut packet = UdpPacket { header, body };
packet
.header
.set_src_port(self.src_port.map(|port| port.get()).unwrap_or(0));
packet.header.set_dst_port(self.dst_port.get());
let total_len = packet.total_packet_len();
let len_field = if fits_in_u16(total_len) {
total_len as u16
} else if A::Version::VERSION.is_v6() {
// IPv6 supports jumbograms, so a UDP packet may be greater than
// 2^16 bytes in size. In this case, the size doesn't fit in the
// 16-bit length field in the header, and so the length field is set
// to zero to indicate this.
0u16
} else {
panic!(
"total UDP packet length of {} bytes overflows 16-bit length field of UDP header",
total_len
);
};
NetworkEndian::write_u16(&mut packet.header.length, len_field);
// This ignores the checksum field in the header, so it's fine that we
// haven't set it yet, and so it could be filled with arbitrary bytes.
let c = packet
.compute_checksum(self.src_ip, self.dst_ip)
.expect(&format!(
"total UDP packet length of {} bytes overflow 32-bit length field of pseudo-header",
total_len
));
NetworkEndian::write_u16(
&mut packet.header.checksum,
if c == 0 {
// When computing the checksum, a checksum of 0 is sent as 0xFFFF.
0xFFFF
} else {
c
},
);
}
}
// needed by Result::unwrap_err in the tests below
#[cfg(test)]
impl<B> Debug for UdpPacket<B> {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(fmt, "UdpPacket")
}
}
#[cfg(test)]
mod tests {
use packet::{Buf, BufferSerializer, ParseBuffer, Serializer};
use super::*;
use crate::device::ethernet::EtherType;
use crate::ip::{Ipv4Addr, Ipv6Addr};
use crate::wire::ethernet::EthernetFrame;
use crate::wire::ipv4::Ipv4Packet;
const TEST_SRC_IPV4: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]);
const TEST_DST_IPV4: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]);
const TEST_SRC_IPV6: Ipv6Addr =
Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const TEST_DST_IPV6: Ipv6Addr = Ipv6Addr::new([
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
]);
#[test]
fn test_parse_serialize_full() {
use crate::wire::testdata::dns_request::*;
let mut buf = &ETHERNET_FRAME_BYTES[..];
let frame = buf.parse::<EthernetFrame<_>>().unwrap();
assert_eq!(frame.src_mac(), ETHERNET_SRC_MAC);
assert_eq!(frame.dst_mac(), ETHERNET_DST_MAC);
assert_eq!(frame.ethertype(), Some(Ok(EtherType::Ipv4)));
let mut body = frame.body();
let ip_packet = body.parse::<Ipv4Packet<_>>().unwrap();
assert_eq!(ip_packet.proto(), Ok(IpProto::Udp));
assert_eq!(ip_packet.dscp(), IP_DSCP);
assert_eq!(ip_packet.ecn(), IP_ECN);
assert_eq!(ip_packet.df_flag(), IP_DONT_FRAGMENT);
assert_eq!(ip_packet.mf_flag(), IP_MORE_FRAGMENTS);
assert_eq!(ip_packet.fragment_offset(), IP_FRAGMENT_OFFSET);
assert_eq!(ip_packet.id(), IP_ID);
assert_eq!(ip_packet.ttl(), IP_TTL);
assert_eq!(ip_packet.src_ip(), IP_SRC_IP);
assert_eq!(ip_packet.dst_ip(), IP_DST_IP);
let mut body = ip_packet.body();
let udp_packet = body
.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(
ip_packet.src_ip(),
ip_packet.dst_ip(),
))
.unwrap();
assert_eq!(
udp_packet.src_port().map(|p| p.get()).unwrap_or(0),
UDP_SRC_PORT
);
assert_eq!(udp_packet.dst_port().get(), UDP_DST_PORT);
assert_eq!(udp_packet.body(), UDP_BODY);
let buffer = udp_packet
.body()
.encapsulate(udp_packet.builder(ip_packet.src_ip(), ip_packet.dst_ip()))
.encapsulate(ip_packet.builder())
.encapsulate(frame.builder())
.serialize_outer();
assert_eq!(buffer.as_ref(), ETHERNET_FRAME_BYTES);
}
#[test]
fn test_parse() {
// source port of 0 (meaning none) is allowed, as is a missing checksum
let mut buf = &[0, 0, 1, 2, 0, 8, 0, 0][..];
let packet = buf
.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(TEST_SRC_IPV4, TEST_DST_IPV4))
.unwrap();
assert!(packet.src_port().is_none());
assert_eq!(packet.dst_port().get(), NetworkEndian::read_u16(&[1, 2]));
assert!(!packet.checksummed());
assert!(packet.body().is_empty());
// length of 0 is allowed in IPv6
let mut buf = &[0, 0, 1, 2, 0, 0, 0xFD, 0xD3][..];
let packet = buf
.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(TEST_SRC_IPV6, TEST_DST_IPV6))
.unwrap();
assert!(packet.src_port().is_none());
assert_eq!(packet.dst_port().get(), NetworkEndian::read_u16(&[1, 2]));
assert!(packet.checksummed());
assert!(packet.body().is_empty());
}
#[test]
fn test_serialize() {
let mut buf = (&[])
.encapsulate(UdpPacketBuilder::new(
TEST_SRC_IPV4,
TEST_DST_IPV4,
NonZeroU16::new(1),
NonZeroU16::new(2).unwrap(),
))
.serialize_outer();
assert_eq!(buf.as_ref(), [0, 1, 0, 2, 0, 8, 239, 199]);
let packet = buf
.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(TEST_SRC_IPV4, TEST_DST_IPV4))
.unwrap();
// assert that when we parse those bytes, we get the values we set in
// the builder
assert_eq!(packet.src_port().unwrap().get(), 1);
assert_eq!(packet.dst_port().get(), 2);
assert!(packet.checksummed());
}
#[test]
fn test_serialize_zeroes() {
// Test that UdpPacket::serialize properly zeroes memory before serializing
// the header.
let mut buf_0 = [0; 8];
BufferSerializer::new_vec(Buf::new(&mut buf_0[..], 8..))
.encapsulate(UdpPacketBuilder::new(
TEST_SRC_IPV4,
TEST_DST_IPV4,
NonZeroU16::new(1),
NonZeroU16::new(2).unwrap(),
))
.serialize_outer();
let mut buf_1 = [0xFF; 8];
BufferSerializer::new_vec(Buf::new(&mut buf_1[..], 8..))
.encapsulate(UdpPacketBuilder::new(
TEST_SRC_IPV4,
TEST_DST_IPV4,
NonZeroU16::new(1),
NonZeroU16::new(2).unwrap(),
))
.serialize_outer();
assert_eq!(buf_0, buf_1);
}
#[test]
fn test_parse_fail() {
// Test thact while a given byte pattern optionally succeeds, zeroing out
// certain bytes causes failure. `zero` is a list of byte indices to
// zero out that should cause failure.
fn test_zero<I: IpAddr>(src: I, dst: I, succeeds: bool, zero: &[usize], err: ParseError) {
// Set checksum to zero so that, in IPV4, it will be ignored. In
// IPv6, this /is/ the test.
let mut buf = [1, 2, 3, 4, 0, 8, 0, 0];
if succeeds {
let mut buf = &buf[..];
assert!(buf
.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(src, dst))
.is_ok());
}
for idx in zero {
buf[*idx] = 0;
}
let mut buf = &buf[..];
assert_eq!(
buf.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(src, dst))
.unwrap_err(),
err
);
}
// destination port of 0 is disallowed
test_zero(
TEST_SRC_IPV4,
TEST_DST_IPV4,
true,
&[2, 3],
ParseError::Format,
);
// length of 0 is disallowed in IPv4
test_zero(
TEST_SRC_IPV4,
TEST_DST_IPV4,
true,
&[4, 5],
ParseError::Format,
);
// missing checksum is disallowed in IPv6; this won't succeed ahead of
// time because the checksum bytes are already zero
test_zero(TEST_SRC_IPV6, TEST_DST_IPV6, false, &[], ParseError::Format);
// 2^32 overflows on 32-bit platforms
#[cfg(target_pointer_width = "64")]
{
// total length of 2^32 or greater is disallowed in IPv6
let mut buf = vec![0u8; 1 << 32];
(&mut buf[..8]).copy_from_slice(&[0, 0, 1, 2, 0, 0, 0xFF, 0xE4]);
assert_eq!(
(&buf[..])
.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(TEST_SRC_IPV6, TEST_DST_IPV6))
.unwrap_err(),
ParseError::Format
);
}
}
#[test]
#[should_panic]
fn test_serialize_fail_header_too_short() {
UdpPacketBuilder::new(
TEST_SRC_IPV4,
TEST_DST_IPV4,
None,
NonZeroU16::new(1).unwrap(),
)
.serialize(SerializeBuffer::new(&mut [0; 7][..], ..));
}
#[test]
#[should_panic]
fn test_serialize_fail_packet_too_long_ipv4() {
(&[0; (1 << 16) - 8][..])
.encapsulate(UdpPacketBuilder::new(
TEST_SRC_IPV4,
TEST_DST_IPV4,
None,
NonZeroU16::new(1).unwrap(),
))
.serialize_outer();
}
// TODO(joshlf): Figure out why compiling this test (yes, just compiling!)
// hangs the compiler.
// // This test tries to allocate 4GB of memory. Run at your own risk.
// #[test]
// #[should_panic]
// #[ignore]
// #[cfg(target_pointer_width = "64")] // 2^32 overflows on 32-bit platforms
// fn test_serialize_fail_packet_too_long_ipv6() {
// // total length of 2^32 or greater is disallowed in IPv6
// let mut buf = vec![0u8; 1 << 32];
// (&[0u8; (1 << 32) - 8])
// .encapsulate(UdpPacketBuilder::new(
// TEST_SRC_IPV4,
// TEST_DST_IPV4,
// None,
// NonZeroU16::new(1).unwrap(),
// ))
// .serialize_outer();
// }
}