use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use bytes::Bytes;
use crate::extractor::L4Proto;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum IcmpFamily {
V4,
V6,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IcmpMessage {
pub family: IcmpFamily,
pub ty: IcmpType,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "family", content = "ty", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum IcmpType {
V4(Icmpv4Type),
V6(Icmpv6Type),
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
#[non_exhaustive]
pub enum Icmpv4Type {
EchoReply { id: u16, seq: u16 },
DestinationUnreachable {
code: Icmpv4DestUnreachCode,
inner: Option<IcmpInner>,
},
Redirect {
code: Icmpv4RedirectCode,
gateway: Ipv4Addr,
inner: Option<IcmpInner>,
},
EchoRequest { id: u16, seq: u16 },
TimeExceeded {
code: Icmpv4TimeExceededCode,
inner: Option<IcmpInner>,
},
ParameterProblem {
pointer: u8,
inner: Option<IcmpInner>,
},
Timestamp {
id: u16,
seq: u16,
originate: u32,
receive: u32,
transmit: u32,
},
TimestampReply {
id: u16,
seq: u16,
originate: u32,
receive: u32,
transmit: u32,
},
Other {
raw_type: u8,
raw_code: u8,
raw_body: Bytes,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum Icmpv4DestUnreachCode {
Net,
Host,
Protocol,
Port,
FragmentationNeeded { mtu: Option<u16> },
SourceRouteFailed,
DestNetworkUnknown,
DestHostUnknown,
SourceHostIsolated,
NetworkProhibited,
HostProhibited,
NetworkTos,
HostTos,
CommunicationProhibited,
HostPrecedenceViolation,
PrecedenceCutoffInEffect,
Other(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum Icmpv4RedirectCode {
Network,
Host,
Tos,
TosHost,
Other(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum Icmpv4TimeExceededCode {
HopLimitExceeded,
FragmentReassemblyTimeExceeded,
Other(u8),
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
#[non_exhaustive]
pub enum Icmpv6Type {
DestinationUnreachable {
code: Icmpv6DestUnreachCode,
inner: Option<IcmpInner>,
},
PacketTooBig { mtu: u32, inner: Option<IcmpInner> },
TimeExceeded {
code: Icmpv6TimeExceededCode,
inner: Option<IcmpInner>,
},
ParameterProblem {
code: Icmpv6ParamProblemCode,
pointer: u32,
inner: Option<IcmpInner>,
},
EchoRequest { id: u16, seq: u16 },
EchoReply { id: u16, seq: u16 },
NeighborSolicitation { target: Ipv6Addr },
NeighborAdvertisement {
target: Ipv6Addr,
router: bool,
solicited: bool,
override_: bool,
},
Other {
raw_type: u8,
raw_code: u8,
raw_body: Bytes,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum Icmpv6DestUnreachCode {
NoRoute,
AdminProhibited,
BeyondScopeOfSource,
AddressUnreachable,
PortUnreachable,
SourceAddressFailedIngressPolicy,
RejectRouteToDestination,
Other(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum Icmpv6TimeExceededCode {
HopLimitExceeded,
FragmentReassemblyTimeExceeded,
Other(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum Icmpv6ParamProblemCode {
ErroneousHeaderField,
UnrecognizedNextHeaderType,
UnrecognizedIpv6Option,
Other(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IcmpInner {
pub src: IpAddr,
pub dst: IpAddr,
pub proto: L4Proto,
pub src_port: Option<u16>,
pub dst_port: Option<u16>,
}
impl IcmpType {
pub fn is_error(&self) -> bool {
matches!(
self,
IcmpType::V4(
Icmpv4Type::DestinationUnreachable { .. }
| Icmpv4Type::Redirect { .. }
| Icmpv4Type::TimeExceeded { .. }
| Icmpv4Type::ParameterProblem { .. },
) | IcmpType::V6(
Icmpv6Type::DestinationUnreachable { .. }
| Icmpv6Type::PacketTooBig { .. }
| Icmpv6Type::TimeExceeded { .. }
| Icmpv6Type::ParameterProblem { .. },
)
)
}
pub fn error_inner(&self) -> Option<(&'static str, &IcmpInner)> {
let (label, inner) = match self {
IcmpType::V4(Icmpv4Type::DestinationUnreachable { inner, .. }) => {
("dest_unreachable", inner.as_ref())
}
IcmpType::V4(Icmpv4Type::Redirect { inner, .. }) => ("redirect", inner.as_ref()),
IcmpType::V4(Icmpv4Type::TimeExceeded { inner, .. }) => {
("time_exceeded", inner.as_ref())
}
IcmpType::V4(Icmpv4Type::ParameterProblem { inner, .. }) => {
("parameter_problem", inner.as_ref())
}
IcmpType::V6(Icmpv6Type::DestinationUnreachable { inner, .. }) => {
("dest_unreachable", inner.as_ref())
}
IcmpType::V6(Icmpv6Type::PacketTooBig { inner, .. }) => {
("packet_too_big", inner.as_ref())
}
IcmpType::V6(Icmpv6Type::TimeExceeded { inner, .. }) => {
("time_exceeded", inner.as_ref())
}
IcmpType::V6(Icmpv6Type::ParameterProblem { inner, .. }) => {
("parameter_problem", inner.as_ref())
}
_ => return None,
};
inner.map(|i| (label, i))
}
pub fn short_kind(&self) -> &'static str {
match self {
IcmpType::V4(t) => match t {
Icmpv4Type::EchoReply { .. } => "echo_reply",
Icmpv4Type::DestinationUnreachable { .. } => "dest_unreachable",
Icmpv4Type::Redirect { .. } => "redirect",
Icmpv4Type::EchoRequest { .. } => "echo_request",
Icmpv4Type::TimeExceeded { .. } => "time_exceeded",
Icmpv4Type::ParameterProblem { .. } => "parameter_problem",
Icmpv4Type::Timestamp { .. } => "timestamp",
Icmpv4Type::TimestampReply { .. } => "timestamp_reply",
Icmpv4Type::Other { .. } => "other",
},
IcmpType::V6(t) => match t {
Icmpv6Type::DestinationUnreachable { .. } => "dest_unreachable",
Icmpv6Type::PacketTooBig { .. } => "packet_too_big",
Icmpv6Type::TimeExceeded { .. } => "time_exceeded",
Icmpv6Type::ParameterProblem { .. } => "parameter_problem",
Icmpv6Type::EchoRequest { .. } => "echo_request",
Icmpv6Type::EchoReply { .. } => "echo_reply",
Icmpv6Type::NeighborSolicitation { .. } => "neighbor_solicitation",
Icmpv6Type::NeighborAdvertisement { .. } => "neighbor_advertisement",
Icmpv6Type::Other { .. } => "other",
},
}
}
pub fn dest_unreachable_kind(&self) -> Option<DestUnreachableKind> {
use DestUnreachableKind::*;
match self {
IcmpType::V4(Icmpv4Type::DestinationUnreachable { code, .. }) => Some(match code {
Icmpv4DestUnreachCode::Host
| Icmpv4DestUnreachCode::DestHostUnknown
| Icmpv4DestUnreachCode::SourceHostIsolated => Host,
Icmpv4DestUnreachCode::Port => Port,
Icmpv4DestUnreachCode::Net | Icmpv4DestUnreachCode::DestNetworkUnknown => Network,
Icmpv4DestUnreachCode::Protocol => Protocol,
Icmpv4DestUnreachCode::NetworkProhibited
| Icmpv4DestUnreachCode::HostProhibited
| Icmpv4DestUnreachCode::CommunicationProhibited
| Icmpv4DestUnreachCode::PrecedenceCutoffInEffect => AdministrativelyProhibited,
Icmpv4DestUnreachCode::FragmentationNeeded { .. } => FragmentationNeeded,
_ => Other,
}),
IcmpType::V6(Icmpv6Type::DestinationUnreachable { code, .. }) => Some(match code {
Icmpv6DestUnreachCode::AddressUnreachable => Host,
Icmpv6DestUnreachCode::PortUnreachable => Port,
Icmpv6DestUnreachCode::NoRoute => Network,
Icmpv6DestUnreachCode::AdminProhibited
| Icmpv6DestUnreachCode::RejectRouteToDestination
| Icmpv6DestUnreachCode::SourceAddressFailedIngressPolicy => {
AdministrativelyProhibited
}
_ => Other,
}),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[non_exhaustive]
pub enum DestUnreachableKind {
Host,
Port,
Network,
Protocol,
AdministrativelyProhibited,
FragmentationNeeded,
Other,
}
impl DestUnreachableKind {
pub fn as_str(&self) -> &'static str {
match self {
DestUnreachableKind::Host => "host_unreachable",
DestUnreachableKind::Port => "port_unreachable",
DestUnreachableKind::Network => "network_unreachable",
DestUnreachableKind::Protocol => "protocol_unreachable",
DestUnreachableKind::AdministrativelyProhibited => "administratively_prohibited",
DestUnreachableKind::FragmentationNeeded => "fragmentation_needed",
DestUnreachableKind::Other => "dest_unreachable_other",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum MtuSignalKind {
FragmentationNeeded { next_hop_mtu: Option<u16> },
PacketTooBig { next_hop_mtu: u32 },
}
impl MtuSignalKind {
pub fn as_str(&self) -> &'static str {
match self {
MtuSignalKind::FragmentationNeeded { .. } => "fragmentation_needed",
MtuSignalKind::PacketTooBig { .. } => "packet_too_big",
}
}
pub fn next_hop_mtu(&self) -> Option<u32> {
match self {
MtuSignalKind::FragmentationNeeded { next_hop_mtu } => next_hop_mtu.map(u32::from),
MtuSignalKind::PacketTooBig { next_hop_mtu } => Some(*next_hop_mtu),
}
}
}
impl IcmpType {
pub fn mtu_signal(&self) -> Option<MtuSignalKind> {
match self {
IcmpType::V4(Icmpv4Type::DestinationUnreachable {
code: Icmpv4DestUnreachCode::FragmentationNeeded { mtu },
..
}) => Some(MtuSignalKind::FragmentationNeeded { next_hop_mtu: *mtu }),
IcmpType::V6(Icmpv6Type::PacketTooBig { mtu, .. }) => {
Some(MtuSignalKind::PacketTooBig { next_hop_mtu: *mtu })
}
_ => None,
}
}
}
impl IcmpMessage {
pub fn is_error(&self) -> bool {
self.ty.is_error()
}
pub fn error_inner(&self) -> Option<(&'static str, &IcmpInner)> {
self.ty.error_inner()
}
pub fn short_kind(&self) -> &'static str {
self.ty.short_kind()
}
pub fn dest_unreachable_kind(&self) -> Option<DestUnreachableKind> {
self.ty.dest_unreachable_kind()
}
pub fn mtu_signal(&self) -> Option<MtuSignalKind> {
self.ty.mtu_signal()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn du_v4(code: Icmpv4DestUnreachCode) -> IcmpType {
IcmpType::V4(Icmpv4Type::DestinationUnreachable { code, inner: None })
}
fn du_v6(code: Icmpv6DestUnreachCode) -> IcmpType {
IcmpType::V6(Icmpv6Type::DestinationUnreachable { code, inner: None })
}
#[test]
fn dest_unreachable_kind_returns_none_for_echo_request() {
let t = IcmpType::V4(Icmpv4Type::EchoRequest { id: 1, seq: 1 });
assert_eq!(t.dest_unreachable_kind(), None);
}
#[test]
fn dest_unreachable_kind_returns_none_for_time_exceeded() {
let t = IcmpType::V4(Icmpv4Type::TimeExceeded {
code: Icmpv4TimeExceededCode::HopLimitExceeded,
inner: None,
});
assert_eq!(t.dest_unreachable_kind(), None);
}
#[test]
fn dest_unreachable_kind_returns_none_for_v6_packet_too_big() {
let t = IcmpType::V6(Icmpv6Type::PacketTooBig {
mtu: 1500,
inner: None,
});
assert_eq!(t.dest_unreachable_kind(), None);
}
#[test]
fn v4_host_codes_map_to_host_kind() {
for code in [
Icmpv4DestUnreachCode::Host,
Icmpv4DestUnreachCode::DestHostUnknown,
Icmpv4DestUnreachCode::SourceHostIsolated,
] {
assert_eq!(
du_v4(code).dest_unreachable_kind(),
Some(DestUnreachableKind::Host),
);
}
}
#[test]
fn v4_port_maps_to_port_kind() {
assert_eq!(
du_v4(Icmpv4DestUnreachCode::Port).dest_unreachable_kind(),
Some(DestUnreachableKind::Port),
);
}
#[test]
fn v4_network_codes_map_to_network_kind() {
for code in [
Icmpv4DestUnreachCode::Net,
Icmpv4DestUnreachCode::DestNetworkUnknown,
] {
assert_eq!(
du_v4(code).dest_unreachable_kind(),
Some(DestUnreachableKind::Network),
);
}
}
#[test]
fn v4_protocol_maps_to_protocol_kind() {
assert_eq!(
du_v4(Icmpv4DestUnreachCode::Protocol).dest_unreachable_kind(),
Some(DestUnreachableKind::Protocol),
);
}
#[test]
fn v4_prohibited_codes_map_to_administratively_prohibited() {
for code in [
Icmpv4DestUnreachCode::NetworkProhibited,
Icmpv4DestUnreachCode::HostProhibited,
Icmpv4DestUnreachCode::CommunicationProhibited,
Icmpv4DestUnreachCode::PrecedenceCutoffInEffect,
] {
assert_eq!(
du_v4(code).dest_unreachable_kind(),
Some(DestUnreachableKind::AdministrativelyProhibited),
);
}
}
#[test]
fn v4_fragmentation_needed_maps_to_fragmentation_needed_kind() {
let code = Icmpv4DestUnreachCode::FragmentationNeeded { mtu: Some(1400) };
assert_eq!(
du_v4(code).dest_unreachable_kind(),
Some(DestUnreachableKind::FragmentationNeeded),
);
}
#[test]
fn v4_other_codes_map_to_other_kind() {
for code in [
Icmpv4DestUnreachCode::SourceRouteFailed,
Icmpv4DestUnreachCode::NetworkTos,
Icmpv4DestUnreachCode::HostTos,
Icmpv4DestUnreachCode::HostPrecedenceViolation,
Icmpv4DestUnreachCode::Other(42),
] {
assert_eq!(
du_v4(code).dest_unreachable_kind(),
Some(DestUnreachableKind::Other),
);
}
}
#[test]
fn v6_address_unreachable_maps_to_host_kind() {
assert_eq!(
du_v6(Icmpv6DestUnreachCode::AddressUnreachable).dest_unreachable_kind(),
Some(DestUnreachableKind::Host),
);
}
#[test]
fn v6_port_unreachable_maps_to_port_kind() {
assert_eq!(
du_v6(Icmpv6DestUnreachCode::PortUnreachable).dest_unreachable_kind(),
Some(DestUnreachableKind::Port),
);
}
#[test]
fn v6_no_route_maps_to_network_kind() {
assert_eq!(
du_v6(Icmpv6DestUnreachCode::NoRoute).dest_unreachable_kind(),
Some(DestUnreachableKind::Network),
);
}
#[test]
fn v6_prohibited_codes_map_to_administratively_prohibited() {
for code in [
Icmpv6DestUnreachCode::AdminProhibited,
Icmpv6DestUnreachCode::RejectRouteToDestination,
Icmpv6DestUnreachCode::SourceAddressFailedIngressPolicy,
] {
assert_eq!(
du_v6(code).dest_unreachable_kind(),
Some(DestUnreachableKind::AdministrativelyProhibited),
);
}
}
#[test]
fn v6_beyond_scope_maps_to_other_kind() {
assert_eq!(
du_v6(Icmpv6DestUnreachCode::BeyondScopeOfSource).dest_unreachable_kind(),
Some(DestUnreachableKind::Other),
);
}
#[test]
fn as_str_returns_stable_slugs() {
assert_eq!(DestUnreachableKind::Host.as_str(), "host_unreachable");
assert_eq!(DestUnreachableKind::Port.as_str(), "port_unreachable");
assert_eq!(DestUnreachableKind::Network.as_str(), "network_unreachable");
assert_eq!(
DestUnreachableKind::Protocol.as_str(),
"protocol_unreachable"
);
assert_eq!(
DestUnreachableKind::AdministrativelyProhibited.as_str(),
"administratively_prohibited",
);
assert_eq!(
DestUnreachableKind::FragmentationNeeded.as_str(),
"fragmentation_needed",
);
assert_eq!(
DestUnreachableKind::Other.as_str(),
"dest_unreachable_other"
);
}
#[test]
fn types_module_is_publicly_reachable() {
let _: crate::icmp::types::Icmpv6DestUnreachCode =
crate::icmp::types::Icmpv6DestUnreachCode::NoRoute;
}
#[test]
fn mtu_signal_returns_none_for_echo() {
let t = IcmpType::V4(Icmpv4Type::EchoRequest { id: 1, seq: 1 });
assert_eq!(t.mtu_signal(), None);
}
#[test]
fn mtu_signal_returns_none_for_non_fragneeded_du() {
for code in [
Icmpv4DestUnreachCode::Host,
Icmpv4DestUnreachCode::Port,
Icmpv4DestUnreachCode::Net,
] {
assert_eq!(du_v4(code).mtu_signal(), None);
}
for code in [
Icmpv6DestUnreachCode::AddressUnreachable,
Icmpv6DestUnreachCode::PortUnreachable,
Icmpv6DestUnreachCode::NoRoute,
] {
assert_eq!(du_v6(code).mtu_signal(), None);
}
}
#[test]
fn mtu_signal_v4_fragneeded_with_mtu() {
let t = du_v4(Icmpv4DestUnreachCode::FragmentationNeeded { mtu: Some(1492) });
assert_eq!(
t.mtu_signal(),
Some(MtuSignalKind::FragmentationNeeded {
next_hop_mtu: Some(1492)
}),
);
assert_eq!(t.mtu_signal().unwrap().next_hop_mtu(), Some(1492));
}
#[test]
fn mtu_signal_v4_fragneeded_without_mtu() {
let t = du_v4(Icmpv4DestUnreachCode::FragmentationNeeded { mtu: None });
assert_eq!(
t.mtu_signal(),
Some(MtuSignalKind::FragmentationNeeded { next_hop_mtu: None }),
);
assert_eq!(t.mtu_signal().unwrap().next_hop_mtu(), None);
}
#[test]
fn mtu_signal_v6_packet_too_big() {
let t = IcmpType::V6(Icmpv6Type::PacketTooBig {
mtu: 1500,
inner: None,
});
assert_eq!(
t.mtu_signal(),
Some(MtuSignalKind::PacketTooBig { next_hop_mtu: 1500 }),
);
assert_eq!(t.mtu_signal().unwrap().next_hop_mtu(), Some(1500));
}
#[test]
fn mtu_signal_as_str_stable_slugs() {
assert_eq!(
MtuSignalKind::FragmentationNeeded {
next_hop_mtu: Some(1492)
}
.as_str(),
"fragmentation_needed",
);
assert_eq!(
MtuSignalKind::FragmentationNeeded { next_hop_mtu: None }.as_str(),
"fragmentation_needed",
);
assert_eq!(
MtuSignalKind::PacketTooBig { next_hop_mtu: 1280 }.as_str(),
"packet_too_big",
);
}
#[test]
fn mtu_signal_proxies_via_icmp_message() {
let msg = IcmpMessage {
family: IcmpFamily::V4,
ty: du_v4(Icmpv4DestUnreachCode::FragmentationNeeded { mtu: Some(576) }),
};
assert_eq!(
msg.mtu_signal(),
Some(MtuSignalKind::FragmentationNeeded {
next_hop_mtu: Some(576)
}),
);
}
}