use crate::ipv4::{address, parse_ipv4_header, IPv4Header};
use nom::{bytes::streaming::take, number, IResult};
use std::net::Ipv4Addr;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Unreachable {
DestinationNetworkUnreachable,
DestinationHostUnreachable,
DestinationProtocolUnreachable,
DestinationPortUnreachable,
FragmentationRequired,
SourceRouteFailed,
DestinationNetworkUnknown,
DestinationHostUnknown,
SourceHostIsolated,
NetworkAdministrativelyProhibited,
HostAdministrativelyProhibited,
NetworkUnreachableForTos,
HostUnreachableForTos,
CommunicationAdministrativelyProhibited,
HostPrecedenceViolation,
PrecedentCutoffInEffect,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Redirect {
Network,
Host,
TosAndNetwork,
TosAndHost,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TimeExceeded {
TTL,
FragmentReassembly,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ParameterProblem {
Pointer,
MissingRequiredOption,
BadLength,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ExtendedEchoReply {
NoError,
MalformedQuery,
NoSuchInterface,
NoSuchTableEntry,
MupltipleInterfacesStatisfyQuery,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum IcmpCode {
EchoReply,
Reserved,
DestinationUnreachable(Unreachable),
SourceQuench,
Redirect(Redirect),
EchoRequest,
RouterAdvertisment,
RouterSolicication,
TimeExceeded(TimeExceeded),
ParameterProblem(ParameterProblem),
Timestamp,
TimestampReply,
ExtendedEchoRequest,
ExtendedEchoReply(ExtendedEchoReply),
Other(u16),
}
impl From<u16> for IcmpCode {
fn from(raw: u16) -> Self {
let [t, c] = raw.to_be_bytes();
match t {
0x00 => Self::EchoReply,
0x01 => Self::Reserved,
0x02 => Self::Reserved,
0x03 => match c {
0x00 => Self::DestinationUnreachable(Unreachable::DestinationNetworkUnreachable),
0x01 => Self::DestinationUnreachable(Unreachable::DestinationHostUnreachable),
0x02 => Self::DestinationUnreachable(Unreachable::DestinationProtocolUnreachable),
0x03 => Self::DestinationUnreachable(Unreachable::DestinationPortUnreachable),
0x04 => Self::DestinationUnreachable(Unreachable::FragmentationRequired),
0x05 => Self::DestinationUnreachable(Unreachable::SourceRouteFailed),
0x06 => Self::DestinationUnreachable(Unreachable::DestinationNetworkUnknown),
0x07 => Self::DestinationUnreachable(Unreachable::DestinationHostUnknown),
0x08 => Self::DestinationUnreachable(Unreachable::SourceHostIsolated),
0x09 => {
Self::DestinationUnreachable(Unreachable::NetworkAdministrativelyProhibited)
}
0x0A => Self::DestinationUnreachable(Unreachable::HostAdministrativelyProhibited),
0x0B => Self::DestinationUnreachable(Unreachable::NetworkUnreachableForTos),
0x0C => Self::DestinationUnreachable(Unreachable::HostUnreachableForTos),
0x0D => Self::DestinationUnreachable(
Unreachable::CommunicationAdministrativelyProhibited,
),
0x0E => Self::DestinationUnreachable(Unreachable::HostPrecedenceViolation),
0x0F => Self::DestinationUnreachable(Unreachable::PrecedentCutoffInEffect),
_ => Self::Other(raw),
},
0x04 => match c {
0x00 => Self::SourceQuench,
_ => Self::Other(raw),
},
0x05 => match c {
0x00 => Self::Redirect(Redirect::Network),
0x01 => Self::Redirect(Redirect::Host),
0x02 => Self::Redirect(Redirect::TosAndNetwork),
0x03 => Self::Redirect(Redirect::TosAndHost),
_ => Self::Other(raw),
},
0x07 => Self::Reserved,
0x08 => Self::EchoRequest,
0x09 => Self::RouterAdvertisment,
0x0A => Self::RouterSolicication,
0x0B => match c {
0x00 => Self::TimeExceeded(TimeExceeded::TTL),
0x01 => Self::TimeExceeded(TimeExceeded::FragmentReassembly),
_ => Self::Other(raw),
},
0x0C => match c {
0x00 => Self::ParameterProblem(ParameterProblem::Pointer),
0x01 => Self::ParameterProblem(ParameterProblem::MissingRequiredOption),
0x02 => Self::ParameterProblem(ParameterProblem::BadLength),
_ => Self::Other(raw),
},
0x0D => Self::Timestamp,
0x0E => Self::TimestampReply,
0x2A => Self::ExtendedEchoRequest,
0x2B => match c {
0x00 => Self::ExtendedEchoReply(ExtendedEchoReply::NoError),
0x01 => Self::ExtendedEchoReply(ExtendedEchoReply::MalformedQuery),
0x02 => Self::ExtendedEchoReply(ExtendedEchoReply::NoSuchInterface),
0x03 => Self::ExtendedEchoReply(ExtendedEchoReply::NoSuchTableEntry),
0x04 => {
Self::ExtendedEchoReply(ExtendedEchoReply::MupltipleInterfacesStatisfyQuery)
}
_ => Self::Other(raw),
},
_ => Self::Other(raw),
}
}
}
fn parse_icmp_code(input: &[u8]) -> IResult<&[u8], IcmpCode> {
let (input, code) = number::streaming::be_u16(input)?;
Ok((input, code.into()))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct IcmpPayloadPacket([u8; 8]);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum IcmpData {
Unreachable {
nexthop_mtu: u16,
header: IPv4Header,
packet: IcmpPayloadPacket,
},
Redirect {
gateway: Ipv4Addr,
header: IPv4Header,
packet: IcmpPayloadPacket,
},
TimeExceeded {
header: IPv4Header,
packet: IcmpPayloadPacket,
},
None,
}
fn parse_ipv4_header_and_packet(input: &[u8]) -> IResult<&[u8], (IPv4Header, IcmpPayloadPacket)> {
let (input, header) = parse_ipv4_header(input)?;
let mut packet: [u8; 8] = Default::default();
let (input, data) = take(8usize)(input)?;
packet.copy_from_slice(data);
Ok((input, (header, IcmpPayloadPacket(packet))))
}
fn parse_icmp_unreachable_data(input: &[u8]) -> IResult<&[u8], IcmpData> {
let (input, _) = number::streaming::be_u16(input)?;
let (input, nexthop_mtu) = number::streaming::be_u16(input)?;
let (input, (header, packet)) = parse_ipv4_header_and_packet(input)?;
Ok((
input,
IcmpData::Unreachable {
nexthop_mtu,
header,
packet,
},
))
}
fn parse_icmp_redirect_data(input: &[u8]) -> IResult<&[u8], IcmpData> {
let (input, gateway) = address(input)?;
let (input, (header, packet)) = parse_ipv4_header_and_packet(input)?;
Ok((
input,
IcmpData::Redirect {
gateway,
header,
packet,
},
))
}
fn parse_icmp_timeexceeded_data(input: &[u8]) -> IResult<&[u8], IcmpData> {
let (input, _) = number::streaming::be_u32(input)?;
let (input, (header, packet)) = parse_ipv4_header_and_packet(input)?;
Ok((input, IcmpData::TimeExceeded { header, packet }))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IcmpHeader {
pub code: IcmpCode,
pub checksum: u16,
pub data: IcmpData,
}
pub fn parse_icmp_header(input: &[u8]) -> IResult<&[u8], IcmpHeader> {
let (input, code) = parse_icmp_code(input)?;
let (input, checksum) = number::streaming::be_u16(input)?;
let (input, data) = match code {
IcmpCode::DestinationUnreachable(_) => parse_icmp_unreachable_data(input)?,
IcmpCode::Redirect(_) => parse_icmp_redirect_data(input)?,
IcmpCode::TimeExceeded(_) => parse_icmp_timeexceeded_data(input)?,
_ => (input, IcmpData::None),
};
Ok((
input,
IcmpHeader {
code,
checksum,
data,
},
))
}
#[cfg(test)]
mod tests {
use super::{
parse_icmp_header, IcmpCode, IcmpData, IcmpHeader, IcmpPayloadPacket, Redirect, Unreachable,
};
use crate::ip::IPProtocol;
use crate::ipv4::IPv4Header;
use nom::{Err, Needed};
use std::net::Ipv4Addr;
const EMPTY_SLICE: &'static [u8] = &[];
fn get_icmp_ipv4_header_and_packet() -> (IPv4Header, IcmpPayloadPacket) {
(
IPv4Header {
version: 4,
ihl: 5,
tos: 0,
length: 1500,
id: 0x1ae6,
flags: 0x01,
fragment_offset: 0,
ttl: 64,
protocol: IPProtocol::ICMP,
chksum: 0x22ed,
source_addr: Ipv4Addr::new(10, 10, 1, 135),
dest_addr: Ipv4Addr::new(10, 10, 1, 180),
},
IcmpPayloadPacket([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]),
)
}
fn get_icmp_redirect_data() -> (Vec<u8>, IcmpHeader) {
let bytes = [
5, 1, 0xaa, 0xbb, 0x0a, 0x0a, 0x01, 0x86, 0x45,
0x00,
0x05, 0xdc,
0x1a, 0xe6,
0x20, 0x00,
0x40,
0x01,
0x22, 0xed,
0x0a, 0x0a, 0x01, 0x87,
0x0a, 0x0a, 0x01, 0xb4,
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
];
let (header, packet) = get_icmp_ipv4_header_and_packet();
let expected = IcmpHeader {
code: IcmpCode::Redirect(Redirect::Host),
checksum: 0xaabb,
data: IcmpData::Redirect {
gateway: Ipv4Addr::new(10, 10, 1, 134),
header: header,
packet: packet,
},
};
(bytes.to_vec(), expected)
}
fn get_icmp_unreachable_data() -> (Vec<u8>, IcmpHeader) {
let bytes = [
3, 1, 0xaa, 0xbb, 0x00, 0x00, 0x00, 0x7, 0x45,
0x00,
0x05, 0xdc,
0x1a, 0xe6,
0x20, 0x00,
0x40,
0x01,
0x22, 0xed,
0x0a, 0x0a, 0x01, 0x87,
0x0a, 0x0a, 0x01, 0xb4,
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
];
let (header, packet) = get_icmp_ipv4_header_and_packet();
let expected = IcmpHeader {
code: IcmpCode::DestinationUnreachable(Unreachable::DestinationHostUnreachable),
checksum: 0xaabb,
data: IcmpData::Unreachable {
nexthop_mtu: 7,
header: header,
packet: packet,
},
};
(bytes.to_vec(), expected)
}
#[test]
fn icmp_unreachable() {
let (bytes, expected) = get_icmp_unreachable_data();
assert_eq!(parse_icmp_header(&bytes), Ok((EMPTY_SLICE, expected)))
}
#[test]
fn icmp_unreachable_incomplete() {
let (mut bytes, _) = get_icmp_unreachable_data();
bytes.pop();
assert_eq!(
parse_icmp_header(&bytes),
Err(Err::Incomplete(Needed::new(1)))
)
}
#[test]
fn icmp_redirect() {
let (bytes, expected) = get_icmp_redirect_data();
assert_eq!(parse_icmp_header(&bytes), Ok((EMPTY_SLICE, expected)))
}
#[test]
fn icmp_redirect_incomplete() {
let (mut bytes, _) = get_icmp_redirect_data();
bytes.pop();
assert_eq!(
parse_icmp_header(&bytes),
Err(Err::Incomplete(Needed::new(1)))
)
}
}