use core::{fmt, ops};
use byteorder::{ByteOrder, NetworkEndian};
use crate::wire::{ip::v4, Checksum, Error, Result, Payload, PayloadMut};
use crate::wire::pretty_print::{PrettyPrint, PrettyIndent};
use super::ip::checksum;
enum_with_unknown! {
pub doc enum Message(u8) {
EchoReply = 0,
DstUnreachable = 3,
Redirect = 5,
EchoRequest = 8,
RouterAdvert = 9,
RouterSolicit = 10,
TimeExceeded = 11,
ParamProblem = 12,
Timestamp = 13,
TimestampReply = 14,
ExtendedEcho = 42,
ExtendedReply = 43,
}
}
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::ExtendedEcho => write!(f, "extended echo request"),
Message::ExtendedReply => write!(f, "extended echo reply"),
Message::Unknown(id) => write!(f, "{}", id)
}
}
}
enum_with_unknown! {
pub doc enum DstUnreachable(u8) {
NetUnreachable = 0,
HostUnreachable = 1,
ProtoUnreachable = 2,
PortUnreachable = 3,
FragRequired = 4,
SrcRouteFailed = 5,
DstNetUnknown = 6,
DstHostUnknown = 7,
SrcHostIsolated = 8,
NetProhibited = 9,
HostProhibited = 10,
NetUnreachToS = 11,
HostUnreachToS = 12,
CommProhibited = 13,
HostPrecedViol = 14,
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! {
pub doc enum Redirect(u8) {
Net = 0,
Host = 1,
NetToS = 2,
HostToS = 3
}
}
enum_with_unknown! {
pub doc enum TimeExceeded(u8) {
TtlExpired = 0,
FragExpired = 1
}
}
enum_with_unknown! {
pub doc enum ParamProblem(u8) {
AtPointer = 0,
MissingOption = 1,
BadLength = 2
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Packet<T> {
buffer: T,
repr: Repr,
}
byte_wrapper! {
#[derive(Debug, PartialEq, Eq)]
pub struct icmpv4([u8]);
}
mod field {
use crate::wire::field::Field;
pub(crate) const TYPE: usize = 0;
pub(crate) const CODE: usize = 1;
pub(crate) const CHECKSUM: Field = 2..4;
pub(crate) const UNUSED: Field = 4..8;
pub(crate) const ECHO_IDENT: Field = 4..6;
pub(crate) const ECHO_SEQNO: Field = 6..8;
pub(crate) const HEADER_END: usize = 8;
}
impl icmpv4 {
pub fn new_unchecked(buffer: &[u8]) -> &icmpv4 {
Self::__from_macro_new_unchecked(buffer)
}
pub fn new_unchecked_mut(buffer: &mut [u8]) -> &mut icmpv4 {
Self::__from_macro_new_unchecked_mut(buffer)
}
pub fn new_checked(data: &[u8]) -> Result<&icmpv4> {
let packet = Self::new_unchecked(data);
packet.check_len()?;
Ok(packet)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
&mut self.0
}
pub fn check_len(&self) -> Result<()> {
if self.0.len() < field::HEADER_END {
Err(Error::Truncated)
} else {
Ok(())
}
}
#[inline]
pub fn msg_type(&self) -> Message {
Message::from(self.0[field::TYPE])
}
#[inline]
pub fn msg_code(&self) -> u8 {
self.0[field::CODE]
}
#[inline]
pub fn checksum(&self) -> u16 {
NetworkEndian::read_u16(&self.0[field::CHECKSUM])
}
#[inline]
pub fn echo_ident(&self) -> u16 {
NetworkEndian::read_u16(&self.0[field::ECHO_IDENT])
}
#[inline]
pub fn echo_seq_no(&self) -> u16 {
NetworkEndian::read_u16(&self.0[field::ECHO_SEQNO])
}
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 }
}
pub fn verify_checksum(&self) -> bool {
if cfg!(fuzzing) { return true }
checksum::data(self.as_bytes()) == !0
}
#[inline]
pub fn set_msg_type(&mut self, value: Message) {
self.0[field::TYPE] = value.into();
}
#[inline]
pub fn set_msg_code(&mut self, value: u8) {
self.0[field::CODE] = value;
}
#[inline]
pub fn set_checksum(&mut self, value: u16) {
NetworkEndian::write_u16(&mut self.0[field::CHECKSUM], value);
}
#[inline]
pub fn set_echo_ident(&mut self, value: u16) {
NetworkEndian::write_u16(&mut self.0[field::ECHO_IDENT], value);
}
#[inline]
pub fn set_echo_seq_no(&mut self, value: u16) {
NetworkEndian::write_u16(&mut self.0[field::ECHO_SEQNO], value);
}
pub fn fill_checksum(&mut self) {
self.set_checksum(0);
let checksum = !checksum::data(&self.0);
self.set_checksum(checksum);
}
pub fn payload_slice(&self) -> &[u8] {
&self.0[field::HEADER_END..]
}
pub fn payload_mut_slice(&mut self) -> &mut [u8] {
&mut self.0[field::HEADER_END..]
}
}
impl AsRef<[u8]> for icmpv4 {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl AsMut<[u8]> for icmpv4 {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
impl<T: Payload> Packet<T> {
pub fn new_checked(buffer: T, checksum: Checksum) -> Result<Packet<T>> {
let repr = {
let packet = icmpv4::new_checked(buffer.payload())?;
Repr::parse(packet, checksum)?
};
Ok(Packet {
buffer,
repr,
})
}
pub fn repr(&self) -> Repr {
self.repr
}
pub fn new_unchecked(buffer: T, repr: Repr) -> Self {
Packet {
buffer,
repr,
}
}
}
impl<T: PayloadMut> Packet<T> {
pub fn payload_mut_slice(&mut self) -> &mut [u8] {
icmpv4::new_unchecked_mut(self.buffer.payload_mut())
.payload_mut_slice()
}
pub fn fill_checksum(&mut self, checksum: Checksum) {
if checksum.manual() {
icmpv4::new_unchecked_mut(self.buffer.payload_mut())
.fill_checksum()
}
}
}
impl<T> Packet<T> {
pub fn into_inner(self) -> T {
self.buffer
}
}
impl<T: Payload> ops::Deref for Packet<T> {
type Target = icmpv4;
fn deref(&self) -> &icmpv4 {
icmpv4::new_unchecked(self.buffer.payload())
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Repr {
EchoRequest {
ident: u16,
seq_no: u16,
payload: usize,
},
EchoReply {
ident: u16,
seq_no: u16,
payload: usize,
},
DstUnreachable {
reason: DstUnreachable,
header: v4::Repr,
},
#[doc(hidden)]
__Nonexhaustive
}
impl Repr {
pub fn echo_reply(self) -> Option<Repr> {
match self {
Repr::EchoRequest { ident, seq_no, payload, } =>
Some(Repr::EchoReply { ident, seq_no, payload, }),
_ => None,
}
}
pub fn parse(packet: &icmpv4, checksum: Checksum)
-> Result<Repr>
{
if checksum.manual() && !packet.verify_checksum() { return Err(Error::WrongChecksum) }
match (packet.msg_type(), packet.msg_code()) {
(Message::EchoRequest, 0) => {
Ok(Repr::EchoRequest {
ident: packet.echo_ident(),
seq_no: packet.echo_seq_no(),
payload: packet.payload_slice().len(),
})
},
(Message::EchoReply, 0) => {
Ok(Repr::EchoReply {
ident: packet.echo_ident(),
seq_no: packet.echo_seq_no(),
payload: packet.payload_slice().len(),
})
},
(Message::DstUnreachable, code) => {
let ip_packet = v4::Packet::new_checked(packet.payload_slice(), checksum)?;
let payload = ip_packet.payload_slice();
if payload.len() < 8 { return Err(Error::Truncated) }
Ok(Repr::DstUnreachable {
reason: DstUnreachable::from(code),
header: v4::Repr {
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(),
},
})
}
(Message::Unknown(_), _) => Err(Error::Unrecognized),
_ => Err(Error::Unsupported),
}
}
pub fn buffer_len(&self) -> usize {
match self {
Repr::EchoRequest { payload, .. } |
Repr::EchoReply { payload, .. } => {
field::HEADER_END + payload
},
Repr::DstUnreachable { header, .. } => {
field::HEADER_END + header.buffer_len() + 8
}
Repr::__Nonexhaustive => unreachable!()
}
}
pub fn emit(&self, packet: &mut icmpv4, checksum: Checksum) {
packet.set_msg_code(0);
match self {
&Repr::EchoRequest { ident, seq_no, payload: _ } => {
packet.set_msg_type(Message::EchoRequest);
packet.set_msg_code(0);
packet.set_echo_ident(ident);
packet.set_echo_seq_no(seq_no);
},
&Repr::EchoReply { ident, seq_no, payload: _ } => {
packet.set_msg_type(Message::EchoReply);
packet.set_msg_code(0);
packet.set_echo_ident(ident);
packet.set_echo_seq_no(seq_no);
},
&Repr::DstUnreachable { reason, header, } => {
packet.set_msg_type(Message::DstUnreachable);
packet.set_msg_code(reason.into());
let ip_packet = v4::packet::new_unchecked_mut(packet.payload_mut_slice());
header.emit(ip_packet, checksum);
},
&Repr::__Nonexhaustive => unreachable!()
}
if checksum.manual() {
packet.fill_checksum()
} else {
packet.set_checksum(0);
}
}
}
impl<T: Payload> fmt::Display for Packet<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match Repr::parse(&self, Checksum::Manual) {
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 fmt::Display for Repr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Repr::EchoRequest { ident, seq_no, payload } =>
write!(f, "ICMPv4 echo request id={} seq={} len={}",
ident, seq_no, payload),
&Repr::EchoReply { ident, seq_no, payload } =>
write!(f, "ICMPv4 echo reply id={} seq={} len={}",
ident, seq_no, payload),
&Repr::DstUnreachable { reason, .. } =>
write!(f, "ICMPv4 destination unreachable ({})",
reason),
&Repr::__Nonexhaustive => unreachable!()
}
}
}
impl PrettyPrint for icmpv4 {
fn pretty_print(buffer: &[u8], f: &mut fmt::Formatter,
indent: &mut PrettyIndent) -> fmt::Result {
let packet = match icmpv4::new_checked(buffer) {
Err(err) => return write!(f, "{}({})", indent, err),
Ok(packet) => packet
};
let repr = match Repr::parse(packet, Checksum::Ignored) {
Err(err) => return write!(f, "{}({})", indent, err),
Ok(ip_repr) => ip_repr,
};
write!(f, "{}{}", indent, repr)?;
match packet.msg_type() {
Message::DstUnreachable => {
indent.increase(f)?;
v4::packet::pretty_print(packet.payload_slice(), 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 = icmpv4::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.payload_slice(), &ECHO_DATA_BYTES[..]);
assert_eq!(packet.verify_checksum(), true);
}
#[test]
fn test_echo_construct() {
let mut bytes = vec![0xa5; 12];
let packet = icmpv4::new_unchecked_mut(&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.payload_mut_slice().copy_from_slice(&ECHO_DATA_BYTES[..]);
packet.fill_checksum();
assert_eq!(packet.as_bytes(), &ECHO_PACKET_BYTES[..]);
}
fn echo_packet_repr() -> Repr {
Repr::EchoRequest {
ident: 0x1234,
seq_no: 0xabcd,
payload: ECHO_DATA_BYTES.len(),
}
}
#[test]
fn test_echo_parse() {
let packet = icmpv4::new_unchecked(&ECHO_PACKET_BYTES[..]);
let repr = Repr::parse(&packet, Checksum::Manual).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 = icmpv4::new_unchecked_mut(&mut bytes);
repr.emit(&mut packet, Checksum::Manual);
packet.payload_mut_slice().copy_from_slice(&ECHO_DATA_BYTES[..]);
packet.fill_checksum();
assert_eq!(packet.as_bytes(), &ECHO_PACKET_BYTES[..]);
}
#[test]
fn test_check_len() {
let bytes = [0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00];
assert_eq!(Packet::new_checked(&bytes[..0], Checksum::Ignored), Err(Error::Truncated));
assert_eq!(Packet::new_checked(&bytes[..4], Checksum::Ignored), Err(Error::Truncated));
Packet::new_checked(&bytes[..], Checksum::Ignored).unwrap();
}
}