mod echo_reply;
mod echo_request;
mod redirect;
mod time_exceeded;
pub use self::echo_reply::*;
pub use self::echo_request::*;
pub use self::redirect::*;
pub use self::time_exceeded::*;
pub use capsule_macros::Icmpv4Packet;
use crate::packets::ip::v4::Ipv4;
use crate::packets::ip::ProtocolNumbers;
use crate::packets::types::u16be;
use crate::packets::{checksum, Internal, Packet};
use crate::{ensure, SizeOf};
use anyhow::{anyhow, Result};
use std::fmt;
use std::ptr::NonNull;
pub struct Icmpv4 {
envelope: Ipv4,
header: NonNull<Icmpv4Header>,
offset: usize,
}
impl Icmpv4 {
#[inline]
fn header(&self) -> &Icmpv4Header {
unsafe { self.header.as_ref() }
}
#[inline]
fn header_mut(&mut self) -> &mut Icmpv4Header {
unsafe { self.header.as_mut() }
}
#[inline]
pub fn msg_type(&self) -> Icmpv4Type {
Icmpv4Type::new(self.header().msg_type)
}
#[inline]
pub fn code(&self) -> u8 {
self.header().code
}
#[inline]
pub fn set_code(&mut self, code: u8) {
self.header_mut().code = code
}
#[inline]
pub fn checksum(&self) -> u16 {
self.header().checksum.into()
}
#[inline]
pub fn compute_checksum(&mut self) {
self.header_mut().checksum = u16be::default();
if let Ok(data) = self.mbuf().read_data_slice(self.offset(), self.len()) {
let data = unsafe { data.as_ref() };
let checksum = checksum::compute(0, data);
self.header_mut().checksum = checksum.into();
} else {
unreachable!()
}
}
#[inline]
pub fn downcast<T: Icmpv4Message>(self) -> Result<T> {
ensure!(
self.msg_type() == T::msg_type(),
anyhow!("the ICMPv4 packet is not {}.", T::msg_type())
);
T::try_parse(self, Internal(()))
}
}
impl fmt::Debug for Icmpv4 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("icmpv4")
.field("type", &format!("{}", self.msg_type()))
.field("code", &self.code())
.field("checksum", &format!("0x{:04x}", self.checksum()))
.field("$offset", &self.offset())
.field("$len", &self.len())
.field("$header_len", &self.header_len())
.finish()
}
}
impl Packet for Icmpv4 {
type Envelope = Ipv4;
#[inline]
fn envelope(&self) -> &Self::Envelope {
&self.envelope
}
#[inline]
fn envelope_mut(&mut self) -> &mut Self::Envelope {
&mut self.envelope
}
#[inline]
fn offset(&self) -> usize {
self.offset
}
#[inline]
fn header_len(&self) -> usize {
Icmpv4Header::size_of()
}
#[inline]
unsafe fn clone(&self, internal: Internal) -> Self {
Icmpv4 {
envelope: self.envelope.clone(internal),
header: self.header,
offset: self.offset,
}
}
#[inline]
fn try_parse(envelope: Self::Envelope, _internal: Internal) -> Result<Self> {
ensure!(
envelope.protocol() == ProtocolNumbers::Icmpv4,
anyhow!("not an ICMPv4 packet.")
);
let mbuf = envelope.mbuf();
let offset = envelope.payload_offset();
let header = mbuf.read_data(offset)?;
Ok(Icmpv4 {
envelope,
header,
offset,
})
}
#[inline]
fn try_push(_envelope: Self::Envelope, _internal: Internal) -> Result<Self> {
Err(anyhow!(
"cannot push a generic ICMPv4 header without a message body."
))
}
#[inline]
fn deparse(self) -> Self::Envelope {
self.envelope
}
#[inline]
fn reconcile(&mut self) {
self.compute_checksum();
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[repr(C, packed)]
pub struct Icmpv4Type(pub u8);
impl Icmpv4Type {
pub fn new(value: u8) -> Self {
Icmpv4Type(value)
}
}
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
pub mod Icmpv4Types {
use super::Icmpv4Type;
pub const EchoRequest: Icmpv4Type = Icmpv4Type(8);
pub const EchoReply: Icmpv4Type = Icmpv4Type(0);
pub const TimeExceeded: Icmpv4Type = Icmpv4Type(11);
pub const Redirect: Icmpv4Type = Icmpv4Type(5);
}
impl fmt::Display for Icmpv4Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
Icmpv4Types::EchoRequest => "Echo Request".to_string(),
Icmpv4Types::EchoReply => "Echo Reply".to_string(),
Icmpv4Types::TimeExceeded => "Time Exceeded".to_string(),
Icmpv4Types::Redirect => "Redirect".to_string(),
_ => format!("{}", self.0),
}
)
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Default, SizeOf)]
#[repr(C, packed)]
pub struct Icmpv4Header {
msg_type: u8,
code: u8,
checksum: u16be,
}
pub trait Icmpv4Message {
fn msg_type() -> Icmpv4Type;
fn icmp(&self) -> &Icmpv4;
fn icmp_mut(&mut self) -> &mut Icmpv4;
fn into_icmp(self) -> Icmpv4;
unsafe fn clone(&self, internal: Internal) -> Self;
fn try_parse(icmp: Icmpv4, internal: Internal) -> Result<Self>
where
Self: Sized;
fn try_push(icmp: Icmpv4, internal: Internal) -> Result<Self>
where
Self: Sized;
#[inline]
fn reconcile(&mut self) {
self.icmp_mut().compute_checksum()
}
}
pub trait Icmpv4Packet {
fn msg_type(&self) -> Icmpv4Type;
fn code(&self) -> u8;
fn set_code(&mut self, code: u8);
fn checksum(&self) -> u16;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packets::ip::v4::Ipv4;
use crate::packets::Ethernet;
use crate::testils::byte_arrays::{ICMPV4_PACKET, IPV4_UDP_PACKET};
use crate::Mbuf;
#[test]
fn size_of_icmpv4_header() {
assert_eq!(4, Icmpv4Header::size_of());
}
#[capsule::test]
fn parse_icmpv4_packet() {
let packet = Mbuf::from_bytes(&ICMPV4_PACKET).unwrap();
let ethernet = packet.parse::<Ethernet>().unwrap();
let ipv4 = ethernet.parse::<Ipv4>().unwrap();
let icmpv4 = ipv4.parse::<Icmpv4>().unwrap();
assert_eq!(Icmpv4Types::EchoRequest, icmpv4.msg_type());
assert_eq!(0, icmpv4.code());
assert_eq!(0x2a5c, icmpv4.checksum());
let echo = icmpv4.downcast::<EchoRequest>().unwrap();
assert_eq!(Icmpv4Types::EchoRequest, echo.msg_type());
assert_eq!(0, echo.code());
assert_eq!(0x2a5c, echo.checksum());
assert_eq!(0x0200, echo.identifier());
assert_eq!(0x2100, echo.seq_no());
let ipv4 = echo.deparse();
assert!(ipv4.parse::<EchoRequest>().is_ok());
}
#[capsule::test]
fn parse_wrong_icmpv4_type() {
let packet = Mbuf::from_bytes(&ICMPV4_PACKET).unwrap();
let ethernet = packet.parse::<Ethernet>().unwrap();
let ipv4 = ethernet.parse::<Ipv4>().unwrap();
let icmpv4 = ipv4.parse::<Icmpv4>().unwrap();
assert!(icmpv4.downcast::<EchoReply>().is_err());
}
#[capsule::test]
fn parse_non_icmpv4_packet() {
let packet = Mbuf::from_bytes(&IPV4_UDP_PACKET).unwrap();
let ethernet = packet.parse::<Ethernet>().unwrap();
let ipv4 = ethernet.parse::<Ipv4>().unwrap();
assert!(ipv4.parse::<Icmpv4>().is_err());
}
#[capsule::test]
fn compute_checksum() {
let packet = Mbuf::from_bytes(&ICMPV4_PACKET).unwrap();
let ethernet = packet.parse::<Ethernet>().unwrap();
let ipv4 = ethernet.parse::<Ipv4>().unwrap();
let mut icmpv4 = ipv4.parse::<Icmpv4>().unwrap();
let expected = icmpv4.checksum();
icmpv4.reconcile_all();
assert_eq!(expected, icmpv4.checksum());
}
#[capsule::test]
fn push_icmpv4_header_without_body() {
let packet = Mbuf::new().unwrap();
let ethernet = packet.push::<Ethernet>().unwrap();
let ipv4 = ethernet.push::<Ipv4>().unwrap();
assert!(ipv4.push::<Icmpv4>().is_err());
}
}