use core::fmt;
use std::convert::TryFrom;
use std::io;
use std::net::IpAddr;
use crate::IcmpEchoStatus;
const ICMPV4_DEST_UNREACHABLE: u8 = 3;
const ICMPV4_TIME_EXCEEDED: u8 = 11;
const ICMPV6_DEST_UNREACHABLE: u8 = 1;
const ICMPV6_TIME_EXCEEDED: u8 = 3;
const ICMPV4_ECHO_REQUEST: u8 = 8;
const ICMPV6_ECHO_REQUEST: u8 = 128;
const IP_PROTO_ICMP: u8 = 1;
const IP_PROTO_ICMPV6: u8 = 58;
const IPV6_HEADER_LEN: usize = 40;
pub struct IcmpErrorInfo {
pub identifier: u16,
pub sequence: u16,
pub status: IcmpEchoStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IcmpType {
EchoRequestV4 = 8,
EchoReplyV4 = 0,
EchoRequestV6 = 128,
EchoReplyV6 = 129,
}
impl IcmpType {
pub fn echo_request_for(addr: IpAddr) -> Self {
match addr {
IpAddr::V4(_) => IcmpType::EchoRequestV4,
IpAddr::V6(_) => IcmpType::EchoRequestV6,
}
}
pub fn echo_reply_for(addr: IpAddr) -> Self {
match addr {
IpAddr::V4(_) => IcmpType::EchoReplyV4,
IpAddr::V6(_) => IcmpType::EchoReplyV6,
}
}
pub fn as_u8(self) -> u8 {
self as u8
}
}
impl TryFrom<u8> for IcmpType {
type Error = io::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(IcmpType::EchoReplyV4),
8 => Ok(IcmpType::EchoRequestV4),
128 => Ok(IcmpType::EchoRequestV6),
129 => Ok(IcmpType::EchoReplyV6),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid ICMP type value",
)),
}
}
}
impl fmt::Display for IcmpType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IcmpType::EchoRequestV4 => write!(f, "EchoRequestV4"),
IcmpType::EchoReplyV4 => write!(f, "EchoReplyV4"),
IcmpType::EchoRequestV6 => write!(f, "EchoRequestV6"),
IcmpType::EchoReplyV6 => write!(f, "EchoReplyV6"),
}
}
}
#[derive(Clone)]
pub struct IcmpPacket {
data: Vec<u8>, }
impl IcmpPacket {
pub fn new_echo_request(
target_addr: IpAddr,
identifier: u16,
sequence: u16,
payload: &[u8],
) -> Self {
let icmp_type = IcmpType::echo_request_for(target_addr);
Self::new(icmp_type, 0, identifier, sequence, payload)
}
pub fn new(
icmp_type: IcmpType,
code: u8,
identifier: u16,
sequence: u16,
payload: &[u8],
) -> Self {
let header_len = 8;
let total_len = header_len + payload.len();
let mut data = Vec::with_capacity(total_len);
data.push(icmp_type.as_u8()); data.push(code); data.push(0); data.push(0); data.extend_from_slice(&identifier.to_be_bytes()); data.extend_from_slice(&sequence.to_be_bytes());
data.extend_from_slice(payload);
let mut packet = IcmpPacket { data };
if matches!(icmp_type, IcmpType::EchoRequestV4 | IcmpType::EchoReplyV4) {
let checksum = Self::calculate_checksum(&packet.data);
packet.set_checksum(checksum);
}
packet
}
pub fn icmp_type(&self) -> u8 {
if self.data.is_empty() {
return 0;
}
self.data[0]
}
pub fn code(&self) -> u8 {
if self.data.len() < 2 {
return 0;
}
self.data[1]
}
#[cfg(test)]
pub fn checksum(&self) -> u16 {
if self.data.len() < 4 {
return 0;
}
u16::from_be_bytes([self.data[2], self.data[3]])
}
pub fn set_checksum(&mut self, checksum: u16) {
if self.data.len() >= 4 {
let bytes = checksum.to_be_bytes();
self.data[2] = bytes[0];
self.data[3] = bytes[1];
}
}
pub fn identifier(&self) -> u16 {
if self.data.len() < 6 {
return 0;
}
u16::from_be_bytes([self.data[4], self.data[5]])
}
pub fn sequence(&self) -> u16 {
if self.data.len() < 8 {
return 0;
}
u16::from_be_bytes([self.data[6], self.data[7]])
}
pub fn payload(&self) -> &[u8] {
if self.data.len() <= 8 {
return &[];
}
&self.data[8..]
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
#[cfg(test)]
fn verify_checksum(&self) -> bool {
let icmp_type = self.icmp_type();
if icmp_type != IcmpType::EchoRequestV4.as_u8()
&& icmp_type != IcmpType::EchoReplyV4.as_u8()
{
return true;
}
let mut sum = 0u32;
let mut i = 0;
while i < self.data.len() {
let word = if i + 1 < self.data.len() {
u16::from_be_bytes([self.data[i], self.data[i + 1]])
} else {
u16::from_be_bytes([self.data[i], 0])
};
sum += word as u32;
i += 2;
}
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
sum as u16 == 0xFFFF
}
pub fn parse(data: &[u8]) -> Option<IcmpPacket> {
if data.len() < 8 {
return None; }
Some(IcmpPacket {
data: data.to_vec(),
})
}
pub fn parse_reply(data: &[u8], target_addr: IpAddr) -> Option<IcmpPacket> {
let icmp_offset = Self::ipv4_header_offset(data, target_addr)?;
if data.len() < icmp_offset + 8 {
return None; }
let icmp_data = &data[icmp_offset..];
let expected_reply_type = IcmpType::echo_reply_for(target_addr);
if icmp_data[0] != expected_reply_type.as_u8() {
return None; }
Self::parse(icmp_data)
}
pub fn parse_error_reply(data: &[u8], target_addr: IpAddr) -> Option<IcmpErrorInfo> {
let outer_offset = Self::ipv4_header_offset(data, target_addr)?;
if data.len() < outer_offset + 8 {
return None;
}
let icmp_data = &data[outer_offset..];
let icmp_type = icmp_data[0];
let status = if target_addr.is_ipv4() {
match icmp_type {
ICMPV4_DEST_UNREACHABLE => IcmpEchoStatus::Unreachable,
ICMPV4_TIME_EXCEEDED => IcmpEchoStatus::TimedOut,
_ => return None,
}
} else {
match icmp_type {
ICMPV6_DEST_UNREACHABLE => IcmpEchoStatus::Unreachable,
ICMPV6_TIME_EXCEEDED => IcmpEchoStatus::TimedOut,
_ => return None,
}
};
let embedded = &icmp_data[8..];
if target_addr.is_ipv4() {
if embedded.is_empty() {
return None;
}
let version = (embedded[0] >> 4) & 0x0F;
if version != 4 {
return None;
}
let embedded_ihl = (embedded[0] & 0x0F) as usize * 4;
if embedded_ihl < 20 {
return None;
}
if embedded.len() < embedded_ihl + 8 {
return None;
}
if embedded[9] != IP_PROTO_ICMP {
return None;
}
let embedded_icmp = &embedded[embedded_ihl..];
if embedded_icmp[0] != ICMPV4_ECHO_REQUEST {
return None;
}
let identifier = u16::from_be_bytes([embedded_icmp[4], embedded_icmp[5]]);
let sequence = u16::from_be_bytes([embedded_icmp[6], embedded_icmp[7]]);
Some(IcmpErrorInfo {
identifier,
sequence,
status,
})
} else {
if embedded.len() < IPV6_HEADER_LEN + 8 {
return None;
}
let version = (embedded[0] >> 4) & 0x0F;
if version != 6 {
return None;
}
if embedded[6] != IP_PROTO_ICMPV6 {
return None;
}
let embedded_icmpv6 = &embedded[IPV6_HEADER_LEN..];
if embedded_icmpv6[0] != ICMPV6_ECHO_REQUEST {
return None;
}
let identifier = u16::from_be_bytes([embedded_icmpv6[4], embedded_icmpv6[5]]);
let sequence = u16::from_be_bytes([embedded_icmpv6[6], embedded_icmpv6[7]]);
Some(IcmpErrorInfo {
identifier,
sequence,
status,
})
}
}
fn ipv4_header_offset(data: &[u8], target_addr: IpAddr) -> Option<usize> {
if cfg!(target_os = "macos") && target_addr.is_ipv4() {
if data.is_empty() {
return None;
}
let ihl = (data[0] & 0x0F) as usize * 4;
if ihl < 20 {
return None;
}
Some(ihl)
} else {
Some(0)
}
}
fn calculate_checksum(data: &[u8]) -> u16 {
let mut sum = 0u32;
let mut i = 0;
while i < data.len() {
if i == 2 {
i += 2; continue;
}
let word = if i + 1 < data.len() {
u16::from_be_bytes([data[i], data[i + 1]])
} else {
u16::from_be_bytes([data[i], 0])
};
sum += word as u32;
i += 2;
}
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!sum as u16
}
}
impl fmt::Debug for IcmpPacket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match IcmpType::try_from(self.icmp_type()).ok() {
Some(IcmpType::EchoRequestV4) | Some(IcmpType::EchoReplyV4) => f
.debug_struct("IcmpPacket")
.field("icmp_type", &self.icmp_type())
.field("code", &self.code())
.field("identifier", &self.identifier())
.field("sequence", &self.sequence())
.field("payload", &self.payload())
.finish(),
Some(IcmpType::EchoRequestV6) | Some(IcmpType::EchoReplyV6) => f
.debug_struct("IcmpPacket")
.field("icmp_type", &self.icmp_type())
.field("code", &self.code())
.finish(),
None => f
.debug_struct("IcmpPacket")
.field("icmp_type", &self.icmp_type())
.finish(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
#[test]
fn test_icmp_type_conversions() {
assert_eq!(IcmpType::EchoRequestV4.as_u8(), 8);
assert_eq!(IcmpType::EchoReplyV4.as_u8(), 0);
assert_eq!(IcmpType::EchoRequestV6.as_u8(), 128);
assert_eq!(IcmpType::EchoReplyV6.as_u8(), 129);
assert_eq!(IcmpType::try_from(8).unwrap(), IcmpType::EchoRequestV4);
assert_eq!(IcmpType::try_from(0).unwrap(), IcmpType::EchoReplyV4);
assert_eq!(IcmpType::try_from(128).unwrap(), IcmpType::EchoRequestV6);
assert_eq!(IcmpType::try_from(129).unwrap(), IcmpType::EchoReplyV6);
assert!(IcmpType::try_from(1).is_err());
assert!(IcmpType::try_from(255).is_err());
assert!(IcmpType::try_from(100).is_err());
let error = IcmpType::try_from(42).unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
assert_eq!(error.to_string(), "Invalid ICMP type value");
}
#[test]
fn test_icmp_type_for_addresses() {
let ipv4_addr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let ipv6_addr = IpAddr::V6("::1".parse().unwrap());
assert_eq!(
IcmpType::echo_request_for(ipv4_addr),
IcmpType::EchoRequestV4
);
assert_eq!(
IcmpType::echo_request_for(ipv6_addr),
IcmpType::EchoRequestV6
);
assert_eq!(IcmpType::echo_reply_for(ipv4_addr), IcmpType::EchoReplyV4);
assert_eq!(IcmpType::echo_reply_for(ipv6_addr), IcmpType::EchoReplyV6);
}
#[test]
fn test_icmp_packet_creation() {
let target = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let payload = [1, 2, 3, 4, 5, 6, 7, 8];
let packet = IcmpPacket::new_echo_request(target, 0x1234, 0x5678, &payload);
assert_eq!(packet.icmp_type(), IcmpType::EchoRequestV4.as_u8());
assert_eq!(packet.code(), 0);
assert_eq!(packet.identifier(), 0x1234);
assert_eq!(packet.sequence(), 0x5678);
assert_eq!(packet.payload(), &payload);
assert_eq!(packet.data.len(), 8 + payload.len());
}
#[test]
fn test_icmp_packet_parsing() {
let original_payload = [0xAA, 0xBB, 0xCC, 0xDD];
let target = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
let packet = IcmpPacket::new_echo_request(target, 0x1111, 0x2222, &original_payload);
let parsed = IcmpPacket::parse(&packet.data).unwrap();
assert_eq!(parsed.icmp_type(), IcmpType::EchoRequestV4.as_u8());
assert_eq!(parsed.identifier(), 0x1111);
assert_eq!(parsed.sequence(), 0x2222);
assert_eq!(parsed.payload(), &original_payload);
}
#[test]
fn test_checksum_calculation() {
let target = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8));
let payload = [0; 8]; let packet = IcmpPacket::new_echo_request(target, 0x0001, 0x0001, &payload);
assert_ne!(packet.checksum(), 0);
assert!(packet.verify_checksum());
}
#[test]
fn test_ipv6_packet_no_checksum() {
let target = IpAddr::V6("::1".parse().unwrap());
let payload = [1, 2, 3, 4];
let packet = IcmpPacket::new_echo_request(target, 0x1234, 0x5678, &payload);
assert_eq!(packet.checksum(), 0);
assert!(packet.verify_checksum());
}
#[test]
#[cfg(target_os = "macos")]
fn test_parse_icmp_reply_macos() {
let mut ip_header = [0u8; 20];
ip_header[0] = 0x45; let icmp_data = [
0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD, ];
let mut full_packet = Vec::new();
full_packet.extend_from_slice(&ip_header);
full_packet.extend_from_slice(&icmp_data);
let target = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let parsed = IcmpPacket::parse_reply(&full_packet, target).unwrap();
assert_eq!(parsed.icmp_type(), IcmpType::EchoReplyV4.as_u8());
assert_eq!(parsed.identifier(), 0x1234);
assert_eq!(parsed.sequence(), 0x5678);
assert_eq!(parsed.payload(), &[0xAA, 0xBB, 0xCC, 0xDD]);
}
#[test]
#[cfg(target_os = "linux")]
fn test_parse_icmp_reply_linux() {
let icmp_data = [
0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD, ];
let target = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let parsed = IcmpPacket::parse_reply(&icmp_data, target).unwrap();
assert_eq!(parsed.icmp_type(), IcmpType::EchoReplyV4.as_u8());
assert_eq!(parsed.identifier(), 0x1234);
assert_eq!(parsed.sequence(), 0x5678);
assert_eq!(parsed.payload(), &[0xAA, 0xBB, 0xCC, 0xDD]);
}
#[test]
fn test_parse_icmp6_reply() {
let icmp6_data = [
129, 0, 0, 0, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD, ];
let target = IpAddr::V6("::1".parse().unwrap());
let parsed = IcmpPacket::parse_reply(&icmp6_data, target).unwrap();
assert_eq!(parsed.icmp_type(), IcmpType::EchoReplyV6.as_u8());
assert_eq!(parsed.code(), 0);
assert_eq!(parsed.identifier(), 0x1234);
assert_eq!(parsed.sequence(), 0x5678);
assert_eq!(parsed.payload(), &[0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(parsed.checksum(), 0); }
#[test]
fn test_invalid_packets() {
assert!(IcmpPacket::parse(&[1, 2, 3]).is_none());
let wrong_type_packet = [
8, 0, 0, 0, 0, 0, 0, 0, ];
let target = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
assert!(IcmpPacket::parse_reply(&wrong_type_packet, target).is_none());
}
fn make_ipv4_header(protocol: u8) -> Vec<u8> {
let mut hdr = vec![0u8; 20];
hdr[0] = 0x45; hdr[9] = protocol;
hdr
}
fn make_echo_request_v4(identifier: u16, sequence: u16) -> Vec<u8> {
let mut pkt = vec![0u8; 8];
pkt[0] = ICMPV4_ECHO_REQUEST; pkt[4..6].copy_from_slice(&identifier.to_be_bytes());
pkt[6..8].copy_from_slice(&sequence.to_be_bytes());
pkt
}
fn make_echo_request_v6(identifier: u16, sequence: u16) -> Vec<u8> {
let mut pkt = vec![0u8; 8];
pkt[0] = ICMPV6_ECHO_REQUEST; pkt[4..6].copy_from_slice(&identifier.to_be_bytes());
pkt[6..8].copy_from_slice(&sequence.to_be_bytes());
pkt
}
#[test]
#[cfg(target_os = "linux")]
fn test_parse_error_reply_v4_dest_unreachable_linux() {
let target = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&[ICMPV4_DEST_UNREACHABLE, 1, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
packet.extend_from_slice(&make_echo_request_v4(0xABCD, 0x0042));
let info = IcmpPacket::parse_error_reply(&packet, target).unwrap();
assert_eq!(info.identifier, 0xABCD);
assert_eq!(info.sequence, 0x0042);
assert_eq!(info.status, IcmpEchoStatus::Unreachable);
}
#[test]
#[cfg(target_os = "linux")]
fn test_parse_error_reply_v4_time_exceeded_linux() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&[ICMPV4_TIME_EXCEEDED, 0, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
packet.extend_from_slice(&make_echo_request_v4(0x1234, 0x0007));
let info = IcmpPacket::parse_error_reply(&packet, target).unwrap();
assert_eq!(info.identifier, 0x1234);
assert_eq!(info.sequence, 0x0007);
assert_eq!(info.status, IcmpEchoStatus::TimedOut);
}
#[test]
fn test_parse_error_reply_v6_dest_unreachable() {
let target: IpAddr = "2001:db8::1".parse().unwrap();
let mut packet = Vec::new();
packet.extend_from_slice(&[ICMPV6_DEST_UNREACHABLE, 0, 0, 0, 0, 0, 0, 0]);
let mut ipv6_hdr = vec![0u8; 40];
ipv6_hdr[0] = 0x60; ipv6_hdr[6] = IP_PROTO_ICMPV6; packet.extend_from_slice(&ipv6_hdr);
packet.extend_from_slice(&make_echo_request_v6(0x5678, 0x0003));
let info = IcmpPacket::parse_error_reply(&packet, target).unwrap();
assert_eq!(info.identifier, 0x5678);
assert_eq!(info.sequence, 0x0003);
assert_eq!(info.status, IcmpEchoStatus::Unreachable);
}
#[test]
fn test_parse_error_reply_v6_time_exceeded() {
let target: IpAddr = "fe80::1".parse().unwrap();
let mut packet = Vec::new();
packet.extend_from_slice(&[ICMPV6_TIME_EXCEEDED, 0, 0, 0, 0, 0, 0, 0]);
let mut ipv6_hdr = vec![0u8; 40];
ipv6_hdr[0] = 0x60;
ipv6_hdr[6] = IP_PROTO_ICMPV6;
packet.extend_from_slice(&ipv6_hdr);
packet.extend_from_slice(&make_echo_request_v6(0x9999, 0x000A));
let info = IcmpPacket::parse_error_reply(&packet, target).unwrap();
assert_eq!(info.identifier, 0x9999);
assert_eq!(info.sequence, 0x000A);
assert_eq!(info.status, IcmpEchoStatus::TimedOut);
}
#[test]
#[cfg(target_os = "linux")]
fn test_parse_error_reply_wrong_protocol_linux() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&[ICMPV4_DEST_UNREACHABLE, 0, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&make_ipv4_header(17));
packet.extend_from_slice(&make_echo_request_v4(0x1234, 0x0001));
assert!(IcmpPacket::parse_error_reply(&packet, target).is_none());
}
#[test]
#[cfg(target_os = "linux")]
fn test_parse_error_reply_wrong_embedded_type_linux() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&[ICMPV4_DEST_UNREACHABLE, 0, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
let mut echo = make_echo_request_v4(0x1234, 0x0001);
echo[0] = 0; packet.extend_from_slice(&echo);
assert!(IcmpPacket::parse_error_reply(&packet, target).is_none());
}
#[test]
#[cfg(target_os = "linux")]
fn test_parse_error_reply_truncated_v4_linux() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let packet = [ICMPV4_DEST_UNREACHABLE, 0, 0, 0, 0, 0, 0, 0];
assert!(IcmpPacket::parse_error_reply(&packet, target).is_none());
let mut packet2 = Vec::new();
packet2.extend_from_slice(&[ICMPV4_DEST_UNREACHABLE, 0, 0, 0, 0, 0, 0, 0]);
packet2.extend_from_slice(&[0x45, 0, 0, 0, 0, 0, 0, 0, 0, IP_PROTO_ICMP]);
assert!(IcmpPacket::parse_error_reply(&packet2, target).is_none());
}
#[test]
fn test_parse_error_reply_truncated_v6() {
let target_v6: IpAddr = "::1".parse().unwrap();
let mut packet = Vec::new();
packet.extend_from_slice(&[ICMPV6_DEST_UNREACHABLE, 0, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&[0x60; 20]); assert!(IcmpPacket::parse_error_reply(&packet, target_v6).is_none());
}
#[test]
#[cfg(target_os = "linux")]
fn test_parse_error_reply_not_an_error_linux() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]); packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
packet.extend_from_slice(&make_echo_request_v4(0x1234, 0x0001));
assert!(IcmpPacket::parse_error_reply(&packet, target).is_none());
}
#[test]
#[cfg(target_os = "macos")]
fn test_parse_error_reply_macos_with_outer_ip_header() {
let target = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
packet.extend_from_slice(&[ICMPV4_DEST_UNREACHABLE, 1, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
packet.extend_from_slice(&make_echo_request_v4(0xBEEF, 0x0010));
let info = IcmpPacket::parse_error_reply(&packet, target).unwrap();
assert_eq!(info.identifier, 0xBEEF);
assert_eq!(info.sequence, 0x0010);
assert_eq!(info.status, IcmpEchoStatus::Unreachable);
}
#[test]
#[cfg(target_os = "macos")]
fn test_parse_error_reply_v4_time_exceeded_macos() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP)); packet.extend_from_slice(&[ICMPV4_TIME_EXCEEDED, 0, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
packet.extend_from_slice(&make_echo_request_v4(0x1234, 0x0007));
let info = IcmpPacket::parse_error_reply(&packet, target).unwrap();
assert_eq!(info.identifier, 0x1234);
assert_eq!(info.sequence, 0x0007);
assert_eq!(info.status, IcmpEchoStatus::TimedOut);
}
#[test]
#[cfg(target_os = "macos")]
fn test_parse_error_reply_wrong_protocol_macos() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP)); packet.extend_from_slice(&[ICMPV4_DEST_UNREACHABLE, 0, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&make_ipv4_header(17)); packet.extend_from_slice(&make_echo_request_v4(0x1234, 0x0001));
assert!(IcmpPacket::parse_error_reply(&packet, target).is_none());
}
#[test]
#[cfg(target_os = "macos")]
fn test_parse_error_reply_wrong_embedded_type_macos() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP)); packet.extend_from_slice(&[ICMPV4_DEST_UNREACHABLE, 0, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
let mut echo = make_echo_request_v4(0x1234, 0x0001);
echo[0] = 0; packet.extend_from_slice(&echo);
assert!(IcmpPacket::parse_error_reply(&packet, target).is_none());
}
#[test]
#[cfg(target_os = "macos")]
fn test_parse_error_reply_truncated_v4_macos() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let packet = make_ipv4_header(IP_PROTO_ICMP);
assert!(IcmpPacket::parse_error_reply(&packet, target).is_none());
let mut packet2 = Vec::new();
packet2.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
packet2.extend_from_slice(&[ICMPV4_DEST_UNREACHABLE, 0, 0, 0, 0, 0, 0, 0]);
assert!(IcmpPacket::parse_error_reply(&packet2, target).is_none());
}
#[test]
#[cfg(target_os = "macos")]
fn test_parse_error_reply_not_an_error_macos() {
let target = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let mut packet = Vec::new();
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]);
packet.extend_from_slice(&make_ipv4_header(IP_PROTO_ICMP));
packet.extend_from_slice(&make_echo_request_v4(0x1234, 0x0001));
assert!(IcmpPacket::parse_error_reply(&packet, target).is_none());
}
}