use core::{cmp, fmt}; use byteorder::{ByteOrder, NetworkEndian}; use crate::{Error, Result}; use crate::phy::ChecksumCapabilities; use crate::wire::ip::checksum; use crate::wire::{Ipv4Packet, Ipv4Repr}; enum_with_unknown! { /// Internet protocol control message type. pub doc enum Message(u8) { /// Echo reply EchoReply = 0, /// Destination unreachable DstUnreachable = 3, /// Message redirect Redirect = 5, /// Echo request EchoRequest = 8, /// Router advertisement RouterAdvert = 9, /// Router solicitation RouterSolicit = 10, /// Time exceeded TimeExceeded = 11, /// Parameter problem ParamProblem = 12, /// Timestamp Timestamp = 13, /// Timestamp reply TimestampReply = 14 } } impl fmt::Display for Message { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Message::EchoReply => write!(f, "echo reply"), Message::DstUnreachable => write!(f, "destination unreachable"), Message::Redirect => write!(f, "message redirect"), Message::EchoRequest => write!(f, "echo request"), Message::RouterAdvert => write!(f, "router advertisement"), Message::RouterSolicit => write!(f, "router solicitation"), Message::TimeExceeded => write!(f, "time exceeded"), Message::ParamProblem => write!(f, "parameter problem"), Message::Timestamp => write!(f, "timestamp"), Message::TimestampReply => write!(f, "timestamp reply"), Message::Unknown(id) => write!(f, "{}", id) } } } enum_with_unknown! { /// Internet protocol control message subtype for type "Destination Unreachable". pub doc enum DstUnreachable(u8) { /// Destination network unreachable NetUnreachable = 0, /// Destination host unreachable HostUnreachable = 1, /// Destination protocol unreachable ProtoUnreachable = 2, /// Destination port unreachable PortUnreachable = 3, /// Fragmentation required, and DF flag set FragRequired = 4, /// Source route failed SrcRouteFailed = 5, /// Destination network unknown DstNetUnknown = 6, /// Destination host unknown DstHostUnknown = 7, /// Source host isolated SrcHostIsolated = 8, /// Network administratively prohibited NetProhibited = 9, /// Host administratively prohibited HostProhibited = 10, /// Network unreachable for ToS NetUnreachToS = 11, /// Host unreachable for ToS HostUnreachToS = 12, /// Communication administratively prohibited CommProhibited = 13, /// Host precedence violation HostPrecedViol = 14, /// Precedence cutoff in effect PrecedCutoff = 15 } } impl fmt::Display for DstUnreachable { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { DstUnreachable::NetUnreachable => write!(f, "destination network unreachable"), DstUnreachable::HostUnreachable => write!(f, "destination host unreachable"), DstUnreachable::ProtoUnreachable => write!(f, "destination protocol unreachable"), DstUnreachable::PortUnreachable => write!(f, "destination port unreachable"), DstUnreachable::FragRequired => write!(f, "fragmentation required, and DF flag set"), DstUnreachable::SrcRouteFailed => write!(f, "source route failed"), DstUnreachable::DstNetUnknown => write!(f, "destination network unknown"), DstUnreachable::DstHostUnknown => write!(f, "destination host unknown"), DstUnreachable::SrcHostIsolated => write!(f, "source host isolated"), DstUnreachable::NetProhibited => write!(f, "network administratively prohibited"), DstUnreachable::HostProhibited => write!(f, "host administratively prohibited"), DstUnreachable::NetUnreachToS => write!(f, "network unreachable for ToS"), DstUnreachable::HostUnreachToS => write!(f, "host unreachable for ToS"), DstUnreachable::CommProhibited => write!(f, "communication administratively prohibited"), DstUnreachable::HostPrecedViol => write!(f, "host precedence violation"), DstUnreachable::PrecedCutoff => write!(f, "precedence cutoff in effect"), DstUnreachable::Unknown(id) => write!(f, "{}", id) } } } enum_with_unknown! { /// Internet protocol control message subtype for type "Redirect Message". pub doc enum Redirect(u8) { /// Redirect Datagram for the Network Net = 0, /// Redirect Datagram for the Host Host = 1, /// Redirect Datagram for the ToS & network NetToS = 2, /// Redirect Datagram for the ToS & host HostToS = 3 } } enum_with_unknown! { /// Internet protocol control message subtype for type "Time Exceeded". pub doc enum TimeExceeded(u8) { /// TTL expired in transit TtlExpired = 0, /// Fragment reassembly time exceeded FragExpired = 1 } } enum_with_unknown! { /// Internet protocol control message subtype for type "Parameter Problem". pub doc enum ParamProblem(u8) { /// Pointer indicates the error AtPointer = 0, /// Missing a required option MissingOption = 1, /// Bad length BadLength = 2 } } /// A read/write wrapper around an Internet Control Message Protocol version 4 packet buffer. #[derive(Debug, PartialEq, Clone)] pub struct Packet> { buffer: T } mod field { use crate::wire::field::*; pub const TYPE: usize = 0; pub const CODE: usize = 1; pub const CHECKSUM: Field = 2..4; pub const UNUSED: Field = 4..8; pub const ECHO_IDENT: Field = 4..6; pub const ECHO_SEQNO: Field = 6..8; pub const HEADER_END: usize = 8; } impl> Packet { /// Imbue a raw octet buffer with ICMPv4 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. /// /// The result of this check is invalidated by calling [set_header_len]. /// /// [set_header_len]: #method.set_header_len pub fn check_len(&self) -> Result<()> { let len = self.buffer.as_ref().len(); if len < field::HEADER_END { Err(Error::Truncated) } else { Ok(()) } } /// Consume the packet, returning the underlying buffer. pub fn into_inner(self) -> T { self.buffer } /// Return the message type field. #[inline] pub fn msg_type(&self) -> Message { let data = self.buffer.as_ref(); Message::from(data[field::TYPE]) } /// Return the message code field. #[inline] pub fn msg_code(&self) -> u8 { let data = self.buffer.as_ref(); data[field::CODE] } /// Return the checksum field. #[inline] pub fn checksum(&self) -> u16 { let data = self.buffer.as_ref(); NetworkEndian::read_u16(&data[field::CHECKSUM]) } /// Return the identifier field (for echo request and reply packets). /// /// # Panics /// This function may panic if this packet is not an echo request or reply packet. #[inline] pub fn echo_ident(&self) -> u16 { let data = self.buffer.as_ref(); NetworkEndian::read_u16(&data[field::ECHO_IDENT]) } /// Return the sequence number field (for echo request and reply packets). /// /// # Panics /// This function may panic if this packet is not an echo request or reply packet. #[inline] pub fn echo_seq_no(&self) -> u16 { let data = self.buffer.as_ref(); NetworkEndian::read_u16(&data[field::ECHO_SEQNO]) } /// Return the header length. /// The result depends on the value of the message type field. pub fn header_len(&self) -> usize { match self.msg_type() { Message::EchoRequest => field::ECHO_SEQNO.end, Message::EchoReply => field::ECHO_SEQNO.end, Message::DstUnreachable => field::UNUSED.end, _ => field::UNUSED.end // make a conservative assumption } } /// Validate the header checksum. /// /// # Fuzzing /// This function always returns `true` when fuzzing. pub fn verify_checksum(&self) -> bool { if cfg!(fuzzing) { return true } let data = self.buffer.as_ref(); checksum::data(data) == !0 } } impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { /// Return a pointer to the type-specific data. #[inline] pub fn data(&self) -> &'a [u8] { let data = self.buffer.as_ref(); &data[self.header_len()..] } } impl + AsMut<[u8]>> Packet { /// Set the message type field. #[inline] pub fn set_msg_type(&mut self, value: Message) { let data = self.buffer.as_mut(); data[field::TYPE] = value.into() } /// Set the message code field. #[inline] pub fn set_msg_code(&mut self, value: u8) { let data = self.buffer.as_mut(); data[field::CODE] = 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) } /// Set the identifier field (for echo request and reply packets). /// /// # Panics /// This function may panic if this packet is not an echo request or reply packet. #[inline] pub fn set_echo_ident(&mut self, value: u16) { let data = self.buffer.as_mut(); NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value) } /// Set the sequence number field (for echo request and reply packets). /// /// # Panics /// This function may panic if this packet is not an echo request or reply packet. #[inline] pub fn set_echo_seq_no(&mut self, value: u16) { let data = self.buffer.as_mut(); NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value) } /// Compute and fill in the header checksum. pub fn fill_checksum(&mut self) { self.set_checksum(0); let checksum = { let data = self.buffer.as_ref(); !checksum::data(data) }; self.set_checksum(checksum) } } impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'a mut T> { /// Return a mutable pointer to the type-specific data. #[inline] pub fn data_mut(&mut self) -> &mut [u8] { let range = self.header_len()..; let data = self.buffer.as_mut(); &mut data[range] } } impl> AsRef<[u8]> for Packet { fn as_ref(&self) -> &[u8] { self.buffer.as_ref() } } /// A high-level representation of an Internet Control Message Protocol version 4 packet header. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Repr<'a> { EchoRequest { ident: u16, seq_no: u16, data: &'a [u8] }, EchoReply { ident: u16, seq_no: u16, data: &'a [u8] }, DstUnreachable { reason: DstUnreachable, header: Ipv4Repr, data: &'a [u8] }, #[doc(hidden)] __Nonexhaustive } impl<'a> Repr<'a> { /// Parse an Internet Control Message Protocol version 4 packet and return /// a high-level representation. pub fn parse(packet: &Packet<&'a T>, checksum_caps: &ChecksumCapabilities) -> Result> where T: AsRef<[u8]> + ?Sized { // Valid checksum is expected. if checksum_caps.icmpv4.rx() && !packet.verify_checksum() { return Err(Error::Checksum) } match (packet.msg_type(), packet.msg_code()) { (Message::EchoRequest, 0) => { Ok(Repr::EchoRequest { ident: packet.echo_ident(), seq_no: packet.echo_seq_no(), data: packet.data() }) }, (Message::EchoReply, 0) => { Ok(Repr::EchoReply { ident: packet.echo_ident(), seq_no: packet.echo_seq_no(), data: packet.data() }) }, (Message::DstUnreachable, code) => { let ip_packet = Ipv4Packet::new_checked(packet.data())?; let payload = &packet.data()[ip_packet.header_len() as usize..]; // RFC 792 requires exactly eight bytes to be returned. // We allow more, since there isn't a reason not to, but require at least eight. if payload.len() < 8 { return Err(Error::Truncated) } Ok(Repr::DstUnreachable { reason: DstUnreachable::from(code), header: Ipv4Repr { src_addr: ip_packet.src_addr(), dst_addr: ip_packet.dst_addr(), protocol: ip_packet.protocol(), payload_len: payload.len(), hop_limit: ip_packet.hop_limit() }, data: payload }) } _ => Err(Error::Unrecognized) } } /// Return the length of a packet that will be emitted from this high-level representation. pub fn buffer_len(&self) -> usize { match self { &Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => { field::ECHO_SEQNO.end + data.len() }, &Repr::DstUnreachable { header, data, .. } => { field::UNUSED.end + header.buffer_len() + data.len() } &Repr::__Nonexhaustive => unreachable!() } } /// Emit a high-level representation into an Internet Control Message Protocol version 4 /// packet. pub fn emit(&self, packet: &mut Packet<&mut T>, checksum_caps: &ChecksumCapabilities) where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized { packet.set_msg_code(0); match *self { Repr::EchoRequest { ident, seq_no, data } => { packet.set_msg_type(Message::EchoRequest); packet.set_msg_code(0); packet.set_echo_ident(ident); packet.set_echo_seq_no(seq_no); let data_len = cmp::min(packet.data_mut().len(), data.len()); packet.data_mut()[..data_len].copy_from_slice(&data[..data_len]) }, Repr::EchoReply { ident, seq_no, data } => { packet.set_msg_type(Message::EchoReply); packet.set_msg_code(0); packet.set_echo_ident(ident); packet.set_echo_seq_no(seq_no); let data_len = cmp::min(packet.data_mut().len(), data.len()); packet.data_mut()[..data_len].copy_from_slice(&data[..data_len]) }, Repr::DstUnreachable { reason, header, data } => { packet.set_msg_type(Message::DstUnreachable); packet.set_msg_code(reason.into()); let mut ip_packet = Ipv4Packet::new_unchecked(packet.data_mut()); header.emit(&mut ip_packet, checksum_caps); let payload = &mut ip_packet.into_inner()[header.buffer_len()..]; payload.copy_from_slice(&data[..]) } Repr::__Nonexhaustive => unreachable!() } if checksum_caps.icmpv4.tx() { packet.fill_checksum() } 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 { match Repr::parse(self, &ChecksumCapabilities::default()) { Ok(repr) => write!(f, "{}", repr), Err(err) => { write!(f, "ICMPv4 ({})", err)?; write!(f, " type={:?}", self.msg_type())?; match self.msg_type() { Message::DstUnreachable => write!(f, " code={:?}", DstUnreachable::from(self.msg_code())), _ => write!(f, " code={}", self.msg_code()) } } } } } impl<'a> fmt::Display for Repr<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Repr::EchoRequest { ident, seq_no, data } => write!(f, "ICMPv4 echo request id={} seq={} len={}", ident, seq_no, data.len()), Repr::EchoReply { ident, seq_no, data } => write!(f, "ICMPv4 echo reply id={} seq={} len={}", ident, seq_no, data.len()), Repr::DstUnreachable { reason, .. } => write!(f, "ICMPv4 destination unreachable ({})", reason), Repr::__Nonexhaustive => unreachable!() } } } 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 { let packet = match Packet::new_checked(buffer) { Err(err) => return write!(f, "{}({})", indent, err), Ok(packet) => packet }; write!(f, "{}{}", indent, packet)?; match packet.msg_type() { Message::DstUnreachable => { indent.increase(f)?; super::Ipv4Packet::<&[u8]>::pretty_print(&packet.data(), f, indent) } _ => Ok(()) } } } #[cfg(test)] mod test { use super::*; static ECHO_PACKET_BYTES: [u8; 12] = [0x08, 0x00, 0x8e, 0xfe, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff]; static ECHO_DATA_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; #[test] fn test_echo_deconstruct() { let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]); assert_eq!(packet.msg_type(), Message::EchoRequest); assert_eq!(packet.msg_code(), 0); assert_eq!(packet.checksum(), 0x8efe); assert_eq!(packet.echo_ident(), 0x1234); assert_eq!(packet.echo_seq_no(), 0xabcd); assert_eq!(packet.data(), &ECHO_DATA_BYTES[..]); assert_eq!(packet.verify_checksum(), true); } #[test] fn test_echo_construct() { let mut bytes = vec![0xa5; 12]; let mut packet = Packet::new_unchecked(&mut bytes); packet.set_msg_type(Message::EchoRequest); packet.set_msg_code(0); packet.set_echo_ident(0x1234); packet.set_echo_seq_no(0xabcd); packet.data_mut().copy_from_slice(&ECHO_DATA_BYTES[..]); packet.fill_checksum(); assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]); } fn echo_packet_repr() -> Repr<'static> { Repr::EchoRequest { ident: 0x1234, seq_no: 0xabcd, data: &ECHO_DATA_BYTES } } #[test] fn test_echo_parse() { let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]); let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); assert_eq!(repr, echo_packet_repr()); } #[test] fn test_echo_emit() { let repr = echo_packet_repr(); let mut bytes = vec![0xa5; repr.buffer_len()]; let mut packet = Packet::new_unchecked(&mut bytes); repr.emit(&mut packet, &ChecksumCapabilities::default()); assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]); } #[test] fn test_check_len() { let bytes = [0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; assert_eq!(Packet::new_checked(&[]), Err(Error::Truncated)); assert_eq!(Packet::new_checked(&bytes[..4]), Err(Error::Truncated)); assert!(Packet::new_checked(&bytes[..]).is_ok()); } }