Skip to main content

manycastr/net/
icmp.rs

1use crate::custom_module::manycastr::{Address, address};
2use crate::net::{IPv4Packet, PacketPayload};
3use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
4use std::io::{Cursor, Write};
5
6/// An ICMP Packet (ping packet) <https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#header_rest>
7#[derive(Debug)]
8pub struct ICMPPacket {
9    pub icmp_type: u8,
10    pub code: u8,
11    pub checksum: u16,
12    pub icmp_identifier: u16,
13    pub sequence_number: u16,
14    pub payload: Vec<u8>,
15}
16
17/// Parsing from bytes to ICMPPacket
18impl From<&[u8]> for ICMPPacket {
19    fn from(data: &[u8]) -> Self {
20        let mut data = Cursor::new(data);
21        ICMPPacket {
22            icmp_type: data.read_u8().unwrap(),
23            code: data.read_u8().unwrap(),
24            checksum: data.read_u16::<NetworkEndian>().unwrap(),
25            icmp_identifier: data.read_u16::<NetworkEndian>().unwrap(),
26            sequence_number: data.read_u16::<NetworkEndian>().unwrap(),
27            payload: data.into_inner()[8..].to_vec(),
28        }
29    }
30}
31
32/// Convert ICMP Packet into a vector of bytes
33impl From<&ICMPPacket> for Vec<u8> {
34    fn from(packet: &ICMPPacket) -> Self {
35        let mut wtr = vec![];
36        wtr.write_u8(packet.icmp_type)
37            .expect("Unable to write to byte buffer for ICMP packet");
38        wtr.write_u8(packet.code)
39            .expect("Unable to write to byte buffer for ICMP packet");
40        wtr.write_u16::<NetworkEndian>(packet.checksum)
41            .expect("Unable to write to byte buffer for ICMP packet");
42        wtr.write_u16::<NetworkEndian>(packet.icmp_identifier)
43            .expect("Unable to write to byte buffer for ICMP packet");
44        wtr.write_u16::<NetworkEndian>(packet.sequence_number)
45            .expect("Unable to write to byte buffer for ICMP packet");
46        wtr.write_all(&packet.payload)
47            .expect("Unable to write to byte buffer for ICMP packet");
48        wtr
49    }
50}
51
52impl ICMPPacket {
53    /// Create a basic ICMP ECHO_REQUEST (8.0) packet with checksum.
54    ///
55    /// # Arguments
56    /// * `icmp_identifier` - the identifier for the ICMP header
57    /// * `sequence_number` - the sequence number for the ICMP header
58    /// * `body` - the ICMP payload
59    /// * `src` - the source address of the packet
60    /// * `dst` - the destination address of the packet
61    /// * `ttl` - the time to live of the packet
62    pub fn echo_request(
63        icmp_identifier: u16,
64        sequence_number: u16,
65        body: Vec<u8>,
66        src: &Address,
67        dst: &Address,
68        ttl: u8,
69    ) -> Vec<u8> {
70        let body_len = body.len() as u16;
71
72        match (src.value, dst.value) {
73            (Some(address::Value::V4(src)), Some(address::Value::V4(dst))) => {
74                let mut packet = ICMPPacket {
75                    icmp_type: 8, // ICMPv4 Echo Request
76                    code: 0,
77                    checksum: 0,
78                    icmp_identifier,
79                    sequence_number,
80                    payload: body,
81                };
82
83                let icmp_bytes: Vec<u8> = (&packet).into();
84                packet.checksum = ICMPPacket::calc_checksum(&icmp_bytes);
85
86                let v4_packet = IPv4Packet {
87                    length: 20 + 8 + body_len,
88                    identifier: 15037,
89                    ttl,
90                    src,
91                    dst,
92                    payload: PacketPayload::Icmp { value: packet },
93                };
94                (&v4_packet).into()
95            }
96
97            (Some(address::Value::V6(src)), Some(address::Value::V6(dst))) => {
98                let src_u128 = (src.high as u128) << 64 | (src.low as u128);
99                let dst_u128 = (dst.high as u128) << 64 | (dst.low as u128);
100                let mut packet = ICMPPacket {
101                    icmp_type: 128,
102                    code: 0,
103                    checksum: 0,
104                    icmp_identifier,
105                    sequence_number,
106                    payload: body,
107                };
108                let icmp_bytes: Vec<u8> = (&packet).into();
109
110                // Pseudo-header calculation
111                let mut pseudo = Vec::new();
112                pseudo.write_u128::<NetworkEndian>(src_u128).unwrap();
113                pseudo.write_u128::<NetworkEndian>(dst_u128).unwrap();
114                pseudo
115                    .write_u32::<NetworkEndian>((8 + packet.payload.len()) as u32)
116                    .unwrap();
117                pseudo.extend_from_slice(&[0, 0, 0, 58]);
118                pseudo.extend(icmp_bytes);
119
120                packet.checksum = ICMPPacket::calc_checksum(&pseudo);
121
122                (&packet).into()
123            }
124
125            _ => panic!("Source and Destination IP versions must match"),
126        }
127    }
128
129    /// Calculate the ICMP Checksum.
130    ///
131    /// This calculation covers the entire ICMP  message (16-bit one's complement).
132    /// Works for both ICMPv4 and ICMPv6
133    pub(crate) fn calc_checksum(buffer: &[u8]) -> u16 {
134        let mut sum: u32 = 0;
135        let len = buffer.len();
136
137        // Get all 16 bit words
138        let mut i = 0;
139        while i < len - 1 {
140            let word = ((buffer[i] as u32) << 8) | (buffer[i + 1] as u32);
141            sum += word;
142            i += 2;
143        }
144
145        // Handle trailing 8 bit word if it exists
146        if !len.is_multiple_of(2) {
147            sum += (buffer[len - 1] as u32) << 8;
148        }
149
150        // Cast sum into 16 bits
151        while sum >> 16 > 0 {
152            sum = (sum & 0xffff) + (sum >> 16);
153        }
154
155        // Return as one's complement
156        !sum as u16
157    }
158}