123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- use byteorder::{ByteOrder, NetworkEndian};
- use core::{cmp, fmt};
- use super::{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 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 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 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 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 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)]
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
- pub struct Packet<T: AsRef<[u8]>> {
- 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<T: AsRef<[u8]>> Packet<T> {
- /// Imbue a raw octet buffer with ICMPv4 packet structure.
- pub fn new_unchecked(buffer: T) -> Packet<T> {
- 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<Packet<T>> {
- let packet = Self::new_unchecked(buffer);
- packet.check_len()?;
- Ok(packet)
- }
- /// Ensure that no accessor method will panic if called.
- /// Returns `Err(Error)` 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)
- } 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<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
- /// 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<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
- 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)]
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
- #[non_exhaustive]
- 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],
- },
- }
- impl<'a> Repr<'a> {
- /// Parse an Internet Control Message Protocol version 4 packet and return
- /// a high-level representation.
- pub fn parse<T>(
- packet: &Packet<&'a T>,
- checksum_caps: &ChecksumCapabilities,
- ) -> Result<Repr<'a>>
- where
- T: AsRef<[u8]> + ?Sized,
- {
- // Valid checksum is expected.
- if checksum_caps.icmpv4.rx() && !packet.verify_checksum() {
- return Err(Error);
- }
- 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);
- }
- Ok(Repr::DstUnreachable {
- reason: DstUnreachable::from(code),
- header: Ipv4Repr {
- src_addr: ip_packet.src_addr(),
- dst_addr: ip_packet.dst_addr(),
- next_header: ip_packet.next_header(),
- payload_len: payload.len(),
- hop_limit: ip_packet.hop_limit(),
- },
- data: payload,
- })
- }
- _ => Err(Error),
- }
- }
- /// 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()
- }
- }
- }
- /// Emit a high-level representation into an Internet Control Message Protocol version 4
- /// packet.
- pub fn emit<T>(&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)
- }
- }
- 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)
- }
- }
- }
- }
- use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
- impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
- 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!(packet.verify_checksum());
- }
- #[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));
- assert_eq!(Packet::new_checked(&bytes[..4]), Err(Error));
- assert!(Packet::new_checked(&bytes[..]).is_ok());
- }
- }
|