use std::net::Ipv4Addr;
pub const ICMP_ECHO_REPLY: u8 = 0;
pub const ICMP_DEST_UNREACHABLE: u8 = 3;
pub const ICMP_ECHO_REQUEST: u8 = 8;
pub const ICMP_TIME_EXCEEDED: u8 = 11;
const ICMP_HEADER_SIZE: usize = 8;
pub fn build_echo_request(identifier: u16, sequence: u16, payload: &[u8]) -> Vec<u8> {
let total_len = ICMP_HEADER_SIZE + payload.len();
let mut buf = vec![0u8; total_len];
buf[0] = ICMP_ECHO_REQUEST; buf[1] = 0; buf[4..6].copy_from_slice(&identifier.to_be_bytes());
buf[6..8].copy_from_slice(&sequence.to_be_bytes());
buf[ICMP_HEADER_SIZE..].copy_from_slice(payload);
let cksum = internet_checksum(&buf);
buf[2..4].copy_from_slice(&cksum.to_be_bytes());
buf
}
pub const fn echo_request_min_size() -> usize {
ICMP_HEADER_SIZE
}
pub fn parse_ipv4_header(data: &[u8]) -> Option<(usize, Ipv4Addr)> {
if data.len() < 20 {
return None;
}
let ihl = (data[0] & 0x0F) as usize * 4;
if data.len() < ihl {
return None;
}
let src = Ipv4Addr::new(data[12], data[13], data[14], data[15]);
Some((ihl, src))
}
pub fn ipv4_payload(data: &[u8]) -> Option<&[u8]> {
let (hdr_len, _) = parse_ipv4_header(data)?;
if data.len() > hdr_len {
Some(&data[hdr_len..])
} else {
None
}
}
#[derive(Debug, Clone)]
pub struct IcmpHeader {
pub icmp_type: u8,
pub icmp_code: u8,
}
pub fn parse_icmp_header(data: &[u8]) -> Option<IcmpHeader> {
if data.len() < 4 {
return None;
}
Some(IcmpHeader {
icmp_type: data[0],
icmp_code: data[1],
})
}
pub fn parse_echo_reply(data: &[u8]) -> Option<(u16, u16)> {
if data.len() < ICMP_HEADER_SIZE {
return None;
}
let identifier = u16::from_be_bytes([data[4], data[5]]);
let sequence = u16::from_be_bytes([data[6], data[7]]);
Some((identifier, sequence))
}
pub fn internet_checksum(data: &[u8]) -> u16 {
let mut sum: u32 = 0;
let mut i = 0;
while i + 1 < data.len() {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
}
if i < data.len() {
sum += (data[i] as u32) << 8;
}
while sum >> 16 != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!sum as u16
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_echo_request() {
let pkt = build_echo_request(0x1234, 0x0001, &[0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(pkt[0], ICMP_ECHO_REQUEST);
assert_eq!(pkt[1], 0); assert_eq!(u16::from_be_bytes([pkt[4], pkt[5]]), 0x1234); assert_eq!(u16::from_be_bytes([pkt[6], pkt[7]]), 0x0001); assert_eq!(&pkt[8..], &[0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(internet_checksum(&pkt), 0);
}
#[test]
fn test_internet_checksum() {
let pkt = build_echo_request(0, 0, &[]);
assert_eq!(internet_checksum(&pkt), 0);
}
#[test]
fn test_internet_checksum_odd_length() {
let data = [0x00, 0x01, 0x00];
let cksum = internet_checksum(&data);
assert_ne!(cksum, 0); }
#[test]
fn test_parse_ipv4_header() {
let mut hdr = vec![0u8; 20];
hdr[0] = 0x45; hdr[12] = 192;
hdr[13] = 168;
hdr[14] = 1;
hdr[15] = 1;
let (hdr_len, src) = parse_ipv4_header(&hdr).unwrap();
assert_eq!(hdr_len, 20);
assert_eq!(src, Ipv4Addr::new(192, 168, 1, 1));
}
#[test]
fn test_parse_ipv4_header_with_options() {
let mut hdr = vec![0u8; 24];
hdr[0] = 0x46; hdr[12] = 10;
hdr[13] = 0;
hdr[14] = 0;
hdr[15] = 1;
let (hdr_len, src) = parse_ipv4_header(&hdr).unwrap();
assert_eq!(hdr_len, 24);
assert_eq!(src, Ipv4Addr::new(10, 0, 0, 1));
}
#[test]
fn test_parse_ipv4_header_too_short() {
assert!(parse_ipv4_header(&[0x45; 10]).is_none());
}
#[test]
fn test_ipv4_payload() {
let mut pkt = vec![0u8; 24];
pkt[0] = 0x45; pkt[20] = 0x08;
let payload = ipv4_payload(&pkt).unwrap();
assert_eq!(payload.len(), 4);
assert_eq!(payload[0], 0x08);
}
#[test]
fn test_parse_icmp_header() {
let data = [ICMP_TIME_EXCEEDED, 0x00, 0x00, 0x00];
let hdr = parse_icmp_header(&data).unwrap();
assert_eq!(hdr.icmp_type, ICMP_TIME_EXCEEDED);
assert_eq!(hdr.icmp_code, 0);
}
#[test]
fn test_parse_echo_reply() {
let mut data = vec![0u8; 8];
data[0] = ICMP_ECHO_REPLY;
data[4..6].copy_from_slice(&0x1234u16.to_be_bytes());
data[6..8].copy_from_slice(&0x0005u16.to_be_bytes());
let (id, seq) = parse_echo_reply(&data).unwrap();
assert_eq!(id, 0x1234);
assert_eq!(seq, 0x0005);
}
#[test]
fn test_parse_echo_reply_too_short() {
assert!(parse_echo_reply(&[0; 4]).is_none());
}
}