use core::fmt; use byteorder::{ByteOrder, NetworkEndian}; use crate::{Error, Result}; use crate::phy::ChecksumCapabilities; use crate::wire::{IpProtocol, IpAddress}; use crate::wire::ip::checksum; /// A read/write wrapper around an User Datagram Protocol packet buffer. #[derive(Debug, PartialEq, Clone)] pub struct Packet> { buffer: T } mod field { #![allow(non_snake_case)] use crate::wire::field::*; pub const SRC_PORT: Field = 0..2; pub const DST_PORT: Field = 2..4; pub const LENGTH: Field = 4..6; pub const CHECKSUM: Field = 6..8; pub fn PAYLOAD(length: u16) -> Field { CHECKSUM.end..(length as usize) } } impl> Packet { /// Imbue a raw octet buffer with UDP packet structure. pub fn new_unchecked(buffer: T) -> Packet { Packet { buffer } } /// Shorthand for a combination of [new_unchecked] and [check_len]. /// /// [new_unchecked]: #method.new_unchecked /// [check_len]: #method.check_len pub fn new_checked(buffer: T) -> Result> { let packet = Self::new_unchecked(buffer); packet.check_len()?; Ok(packet) } /// Ensure that no accessor method will panic if called. /// Returns `Err(Error::Truncated)` if the buffer is too short. /// Returns `Err(Error::Malformed)` if the length field has a value smaller /// than the header length. /// /// The result of this check is invalidated by calling [set_len]. /// /// [set_len]: #method.set_len pub fn check_len(&self) -> Result<()> { let buffer_len = self.buffer.as_ref().len(); if buffer_len < field::CHECKSUM.end { Err(Error::Truncated) } else { let field_len = self.len() as usize; if buffer_len < field_len { Err(Error::Truncated) } else if field_len < field::CHECKSUM.end { Err(Error::Malformed) } else { Ok(()) } } } /// Consume the packet, returning the underlying buffer. pub fn into_inner(self) -> T { self.buffer } /// Return the source port field. #[inline] pub fn src_port(&self) -> u16 { let data = self.buffer.as_ref(); NetworkEndian::read_u16(&data[field::SRC_PORT]) } /// Return the destination port field. #[inline] pub fn dst_port(&self) -> u16 { let data = self.buffer.as_ref(); NetworkEndian::read_u16(&data[field::DST_PORT]) } /// Return the length field. #[inline] pub fn len(&self) -> u16 { let data = self.buffer.as_ref(); NetworkEndian::read_u16(&data[field::LENGTH]) } /// Return the checksum field. #[inline] pub fn checksum(&self) -> u16 { let data = self.buffer.as_ref(); NetworkEndian::read_u16(&data[field::CHECKSUM]) } /// Validate the packet checksum. /// /// # Panics /// This function panics unless `src_addr` and `dst_addr` belong to the same family, /// and that family is IPv4 or IPv6. /// /// # Fuzzing /// This function always returns `true` when fuzzing. pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool { if cfg!(fuzzing) { return true } let data = self.buffer.as_ref(); checksum::combine(&[ checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32), checksum::data(&data[..self.len() as usize]) ]) == !0 } } impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { /// Return a pointer to the payload. #[inline] pub fn payload(&self) -> &'a [u8] { let length = self.len(); let data = self.buffer.as_ref(); &data[field::PAYLOAD(length)] } } impl + AsMut<[u8]>> Packet { /// Set the source port field. #[inline] pub fn set_src_port(&mut self, value: u16) { let data = self.buffer.as_mut(); NetworkEndian::write_u16(&mut data[field::SRC_PORT], value) } /// Set the destination port field. #[inline] pub fn set_dst_port(&mut self, value: u16) { let data = self.buffer.as_mut(); NetworkEndian::write_u16(&mut data[field::DST_PORT], value) } /// Set the length field. #[inline] pub fn set_len(&mut self, value: u16) { let data = self.buffer.as_mut(); NetworkEndian::write_u16(&mut data[field::LENGTH], value) } /// Set the checksum field. #[inline] pub fn set_checksum(&mut self, value: u16) { let data = self.buffer.as_mut(); NetworkEndian::write_u16(&mut data[field::CHECKSUM], value) } /// Compute and fill in the header checksum. /// /// # Panics /// This function panics unless `src_addr` and `dst_addr` belong to the same family, /// and that family is IPv4 or IPv6. pub fn fill_checksum(&mut self, src_addr: &IpAddress, dst_addr: &IpAddress) { self.set_checksum(0); let checksum = { let data = self.buffer.as_ref(); !checksum::combine(&[ checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32), checksum::data(&data[..self.len() as usize]) ]) }; // UDP checksum value of 0 means no checksum; if the checksum really is zero, // use all-ones, which indicates that the remote end must verify the checksum. // Arithmetically, RFC 1071 checksums of all-zeroes and all-ones behave identically, // so no action is necessary on the remote end. self.set_checksum(if checksum == 0 { 0xffff } else { checksum }) } /// Return a mutable pointer to the payload. #[inline] pub fn payload_mut(&mut self) -> &mut [u8] { let length = self.len(); let data = self.buffer.as_mut(); &mut data[field::PAYLOAD(length)] } } impl> AsRef<[u8]> for Packet { fn as_ref(&self) -> &[u8] { self.buffer.as_ref() } } /// A high-level representation of an User Datagram Protocol packet. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Repr<'a> { pub src_port: u16, pub dst_port: u16, pub payload: &'a [u8] } impl<'a> Repr<'a> { /// Parse an User Datagram Protocol packet and return a high-level representation. pub fn parse(packet: &Packet<&'a T>, src_addr: &IpAddress, dst_addr: &IpAddress, checksum_caps: &ChecksumCapabilities) -> Result> where T: AsRef<[u8]> + ?Sized { // Destination port cannot be omitted (but source port can be). if packet.dst_port() == 0 { return Err(Error::Malformed) } // Valid checksum is expected... if checksum_caps.udp.rx() && !packet.verify_checksum(src_addr, dst_addr) { match (src_addr, dst_addr) { // ... except on UDP-over-IPv4, where it can be omitted. #[cfg(feature = "proto-ipv4")] (&IpAddress::Ipv4(_), &IpAddress::Ipv4(_)) if packet.checksum() == 0 => (), _ => { return Err(Error::Checksum) } } } Ok(Repr { src_port: packet.src_port(), dst_port: packet.dst_port(), payload: packet.payload() }) } /// Return the length of a packet that will be emitted from this high-level representation. pub fn buffer_len(&self) -> usize { field::CHECKSUM.end + self.payload.len() } /// Emit a high-level representation into an User Datagram Protocol packet. pub fn emit(&self, packet: &mut Packet<&mut T>, src_addr: &IpAddress, dst_addr: &IpAddress, checksum_caps: &ChecksumCapabilities) where T: AsRef<[u8]> + AsMut<[u8]> { packet.set_src_port(self.src_port); packet.set_dst_port(self.dst_port); packet.set_len((field::CHECKSUM.end + self.payload.len()) as u16); packet.payload_mut().copy_from_slice(self.payload); if checksum_caps.udp.tx() { packet.fill_checksum(src_addr, dst_addr) } else { // make sure we get a consistently zeroed checksum, // since implementations might rely on it packet.set_checksum(0); } } } impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Cannot use Repr::parse because we don't have the IP addresses. write!(f, "UDP src={} dst={} len={}", self.src_port(), self.dst_port(), self.payload().len()) } } impl<'a> fmt::Display for Repr<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "UDP src={} dst={} len={}", self.src_port, self.dst_port, self.payload.len()) } } use crate::wire::pretty_print::{PrettyPrint, PrettyIndent}; impl> PrettyPrint for Packet { fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter, indent: &mut PrettyIndent) -> fmt::Result { match Packet::new_checked(buffer) { Err(err) => write!(f, "{}({})", indent, err), Ok(packet) => write!(f, "{}{}", indent, packet) } } } #[cfg(test)] mod test { #[cfg(feature = "proto-ipv4")] use crate::wire::Ipv4Address; use super::*; #[cfg(feature = "proto-ipv4")] const SRC_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 1]); #[cfg(feature = "proto-ipv4")] const DST_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 2]); #[cfg(feature = "proto-ipv4")] static PACKET_BYTES: [u8; 12] = [0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff]; #[cfg(feature = "proto-ipv4")] static NO_CHECKSUM_PACKET: [u8; 12] = [0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x00, 0x00, 0xaa, 0x00, 0x00, 0xff]; #[cfg(feature = "proto-ipv4")] static PAYLOAD_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; #[test] #[cfg(feature = "proto-ipv4")] fn test_deconstruct() { let packet = Packet::new_unchecked(&PACKET_BYTES[..]); assert_eq!(packet.src_port(), 48896); assert_eq!(packet.dst_port(), 53); assert_eq!(packet.len(), 12); assert_eq!(packet.checksum(), 0x124d); assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]); assert_eq!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into()), true); } #[test] #[cfg(feature = "proto-ipv4")] fn test_construct() { let mut bytes = vec![0xa5; 12]; let mut packet = Packet::new_unchecked(&mut bytes); packet.set_src_port(48896); packet.set_dst_port(53); packet.set_len(12); packet.set_checksum(0xffff); packet.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]); packet.fill_checksum(&SRC_ADDR.into(), &DST_ADDR.into()); assert_eq!(&packet.into_inner()[..], &PACKET_BYTES[..]); } #[test] fn test_impossible_len() { let mut bytes = vec![0; 12]; let mut packet = Packet::new_unchecked(&mut bytes); packet.set_len(4); assert_eq!(packet.check_len(), Err(Error::Malformed)); } #[test] #[cfg(feature = "proto-ipv4")] fn test_zero_checksum() { let mut bytes = vec![0; 8]; let mut packet = Packet::new_unchecked(&mut bytes); packet.set_src_port(1); packet.set_dst_port(31881); packet.set_len(8); packet.fill_checksum(&SRC_ADDR.into(), &DST_ADDR.into()); assert_eq!(packet.checksum(), 0xffff); } #[cfg(feature = "proto-ipv4")] fn packet_repr() -> Repr<'static> { Repr { src_port: 48896, dst_port: 53, payload: &PAYLOAD_BYTES } } #[test] #[cfg(feature = "proto-ipv4")] fn test_parse() { let packet = Packet::new_unchecked(&PACKET_BYTES[..]); let repr = Repr::parse(&packet, &SRC_ADDR.into(), &DST_ADDR.into(), &ChecksumCapabilities::default()).unwrap(); assert_eq!(repr, packet_repr()); } #[test] #[cfg(feature = "proto-ipv4")] fn test_emit() { let repr = packet_repr(); let mut bytes = vec![0xa5; repr.buffer_len()]; let mut packet = Packet::new_unchecked(&mut bytes); repr.emit(&mut packet, &SRC_ADDR.into(), &DST_ADDR.into(), &ChecksumCapabilities::default()); assert_eq!(&packet.into_inner()[..], &PACKET_BYTES[..]); } #[test] #[cfg(feature = "proto-ipv4")] fn test_checksum_omitted() { let packet = Packet::new_unchecked(&NO_CHECKSUM_PACKET[..]); let repr = Repr::parse(&packet, &SRC_ADDR.into(), &DST_ADDR.into(), &ChecksumCapabilities::default()).unwrap(); assert_eq!(repr, packet_repr()); } }