Skip to main content

manycastr/worker/inbound/
ping.rs

1use crate::custom_module::manycastr::reply::ReplyData;
2use crate::custom_module::manycastr::{
3    Address, DiscoveryReply, MeasurementReply, Reply, TraceReply,
4};
5use crate::net::ICMPPacket;
6use crate::worker::inbound::ReplyMeta;
7
8/// Parse ICMP ping packets into a Reply result.
9/// Filters out spoofed packets and only parses ICMP echo replies valid for the current measurement.
10///
11/// # Arguments
12/// * `packet_bytes` - the bytes of the packet to parse
13/// * `m_id` - the ID of the current measurement
14/// * `is_traceroute` - handle echo reply as traceroute target reply
15/// * `meta` - received packet metadata (source address, TTL, kernel receive time)
16///
17/// # Returns
18/// * `Option<Reply>` - the received ping reply, None if invalid
19///
20/// # Remarks
21/// The function returns None if the packet is not an ICMP echo reply or if the packet is too short to contain the necessary information.
22pub fn parse_icmp(
23    packet_bytes: &[u8],
24    m_id: u32,
25    is_traceroute: bool,
26    meta: ReplyMeta,
27) -> Option<Reply> {
28    if meta.src.is_v6() {
29        // ICMPv6: no IP header in received data
30        if packet_bytes.len() < 56 || packet_bytes[0] != 129 {
31            return None;
32        }
33        let icmp_packet = ICMPPacket::from(packet_bytes);
34        parse_icmp_inner(&icmp_packet, m_id, is_traceroute, meta)
35    } else {
36        // Raw IPv4: IP header included, ICMP starts at offset 20
37        if packet_bytes.len() < 52 || packet_bytes[20] != 0 {
38            return None;
39        }
40        let icmp_packet = ICMPPacket::from(&packet_bytes[20..]);
41        parse_icmp_inner(&icmp_packet, m_id, is_traceroute, meta)
42    }
43}
44
45/// Parse ICMP ping packets into a Reply result (excluding the IP header).
46///
47/// # Arguments
48/// * `icmp_packet` - Unparsed ICMP packet
49/// * `m_id` - the ID of the current measurement
50/// * `is_traceroute` - whether this is a traceroute target ping reply
51/// * `meta` - received packet metadata (source address, TTL, kernel receive time)
52///
53/// # Returns
54/// * `Option<Reply>` - the received ping reply, None if invalid
55fn parse_icmp_inner(
56    icmp_packet: &ICMPPacket,
57    m_id: u32,
58    is_traceroute: bool,
59    meta: ReplyMeta,
60) -> Option<Reply> {
61    let ReplyMeta { src, ttl, rx_time } = meta;
62    // Verify this packet belongs to the current measurement (based on 16-bit m_id)
63    let pkt_probe_id = u32::from_be_bytes(icmp_packet.payload[0..4].try_into().ok()?);
64    if crate::m_id_of(pkt_probe_id) != m_id {
65        return None;
66    }
67    let session_id = crate::session_id_of(pkt_probe_id);
68
69    let is_ipv6 = src.is_v6();
70
71    let tx_time = u64::from_be_bytes(icmp_packet.payload[4..12].try_into().unwrap());
72    let mut tx_id = u32::from_be_bytes(icmp_packet.payload[12..16].try_into().unwrap());
73    let probe_dst = if is_ipv6 {
74        Address::from(u128::from_be_bytes(
75            icmp_packet.payload[32..48].try_into().unwrap(),
76        ))
77    } else {
78        Address::from(u32::from_be_bytes(
79            icmp_packet.payload[20..24].try_into().unwrap(),
80        ))
81    };
82
83    if probe_dst != src {
84        return None; // spoofed reply
85    }
86
87    let is_discovery = if tx_id > u16::MAX as u32 {
88        tx_id -= u16::MAX as u32;
89        true
90    } else {
91        false
92    };
93
94    if is_discovery {
95        // Discovery reply
96        Some(Reply {
97            reply_data: Some(ReplyData::Discovery(DiscoveryReply {
98                src: Some(src),
99                session_id,
100            })),
101        })
102    } else if is_traceroute {
103        // Trace reply
104        let trace_ttl: u8 = if is_ipv6 {
105            icmp_packet.payload[48]
106        } else {
107            icmp_packet.payload[24]
108        };
109
110        Some(Reply {
111            reply_data: Some(ReplyData::Trace(TraceReply {
112                hop_addr: Some(src),
113                ttl,
114                rtt: super::rtt_ms(rx_time, tx_time, super::TxEncoding::Micros),
115                tx_id,
116                trace_dst: Some(src),
117                hop_count: trace_ttl as u32,
118            })),
119        })
120    } else {
121        // Ping reply
122        Some(Reply {
123            reply_data: Some(ReplyData::Measurement(MeasurementReply {
124                src: Some(src),
125                ttl,
126                rtt: super::rtt_ms(rx_time, tx_time, super::TxEncoding::Micros),
127                tx_id,
128                chaos: None,
129                session_id,
130            })),
131        })
132    }
133}