trippy_core/
probe.rs

1use crate::types::{Checksum, Flags, Port, RoundId, Sequence, TimeToLive, TraceId};
2use crate::TypeOfService;
3use std::net::IpAddr;
4use std::time::SystemTime;
5
6/// A network tracing probe.
7///
8/// A `Probe` is a packet sent across the network to trace the path to a target host.
9/// It contains information such as sequence number, trace identifier, ports, and TTL.
10///
11/// A probe is always in one of the following states:
12///
13/// - `NotSent` - The probe has not been sent.
14/// - `Skipped` - The probe was skipped.
15/// - `Awaited` - The probe has been sent and is awaiting a response.
16/// - `Complete` - The probe has been sent and a response has been received.
17#[derive(Debug, Clone, PartialEq, Eq, Default)]
18pub enum ProbeStatus {
19    /// The probe has not been sent.
20    #[default]
21    NotSent,
22    /// The probe was skipped.
23    ///
24    /// A probe may be skipped if, for TCP, it could not be bound to a local
25    /// port.  When a probe is skipped, it will be marked as `Skipped` and a
26    /// new probe will be sent with the same TTL next available sequence number.
27    Skipped,
28    /// The probe has failed.
29    ///
30    /// A probe is considered failed when an error occurs while sending or
31    /// receiving.
32    Failed(ProbeFailed),
33    /// The probe has been sent and is awaiting a response.
34    ///
35    /// If no response is received within the timeout, the probe will remain
36    /// in this state indefinitely.
37    Awaited(Probe),
38    /// The probe has been sent and a response has been received.
39    Complete(ProbeComplete),
40}
41
42/// An incomplete network tracing probe.
43///
44/// A `Probe` is a packet sent across the network to trace the path to a target host.
45/// It contains information such as sequence number, trace identifier, ports, and TTL.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Probe {
48    /// The sequence of the probe.
49    pub sequence: Sequence,
50    /// The trace identifier.
51    pub identifier: TraceId,
52    /// The source port (UDP/TCP only).
53    pub src_port: Port,
54    /// The destination port (UDP/TCP only).
55    pub dest_port: Port,
56    /// The TTL of the probe.
57    pub ttl: TimeToLive,
58    /// Which round the probe belongs to.
59    pub round: RoundId,
60    /// Timestamp when the probe was sent.
61    pub sent: SystemTime,
62    /// Probe flags.
63    pub flags: Flags,
64}
65
66impl Probe {
67    /// Create a new probe.
68    #[must_use]
69    #[allow(clippy::too_many_arguments)]
70    pub(crate) const fn new(
71        sequence: Sequence,
72        identifier: TraceId,
73        src_port: Port,
74        dest_port: Port,
75        ttl: TimeToLive,
76        round: RoundId,
77        sent: SystemTime,
78        flags: Flags,
79    ) -> Self {
80        Self {
81            sequence,
82            identifier,
83            src_port,
84            dest_port,
85            ttl,
86            round,
87            sent,
88            flags,
89        }
90    }
91
92    /// A response has been received and the probe is now complete.
93    #[allow(clippy::too_many_arguments)]
94    #[must_use]
95    pub(crate) const fn complete(
96        self,
97        host: IpAddr,
98        received: SystemTime,
99        icmp_packet_type: IcmpPacketType,
100        tos: Option<TypeOfService>,
101        expected_udp_checksum: Option<Checksum>,
102        actual_udp_checksum: Option<Checksum>,
103        extensions: Option<Extensions>,
104    ) -> ProbeComplete {
105        ProbeComplete {
106            sequence: self.sequence,
107            identifier: self.identifier,
108            src_port: self.src_port,
109            dest_port: self.dest_port,
110            ttl: self.ttl,
111            round: self.round,
112            sent: self.sent,
113            host,
114            received,
115            icmp_packet_type,
116            tos,
117            expected_udp_checksum,
118            actual_udp_checksum,
119            extensions,
120        }
121    }
122
123    /// The probe has failed to send.
124    #[must_use]
125    pub(crate) const fn failed(self) -> ProbeFailed {
126        ProbeFailed {
127            sequence: self.sequence,
128            identifier: self.identifier,
129            src_port: self.src_port,
130            dest_port: self.dest_port,
131            ttl: self.ttl,
132            round: self.round,
133            sent: self.sent,
134        }
135    }
136}
137
138/// A complete network tracing probe.
139///
140/// A probe is considered complete when one of the following responses has been
141/// received:
142///
143/// - `TimeExceeded` - an ICMP packet indicating the TTL has expired.
144/// - `EchoReply` - an ICMP packet indicating the probe has reached the target.
145/// - `DestinationUnreachable` - an ICMP packet indicating the probe could not reach the target.
146/// - `NotApplicable` - a non-ICMP response (i.e. for some `UDP` & `TCP` probes).
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct ProbeComplete {
149    /// The sequence of the probe.
150    pub sequence: Sequence,
151    /// The trace identifier.
152    pub identifier: TraceId,
153    /// The source port (UDP/TCP only)
154    pub src_port: Port,
155    /// The destination port (UDP/TCP only)
156    pub dest_port: Port,
157    /// The TTL of the probe.
158    pub ttl: TimeToLive,
159    /// Which round the probe belongs to.
160    pub round: RoundId,
161    /// Timestamp when the probe was sent.
162    pub sent: SystemTime,
163    /// The host which responded to the probe.
164    pub host: IpAddr,
165    /// Timestamp when the response to the probe was received.
166    pub received: SystemTime,
167    /// The type of ICMP response packet received for the probe.
168    pub icmp_packet_type: IcmpPacketType,
169    /// The type of service (DSCP/ECN) of the original datagram.
170    pub tos: Option<TypeOfService>,
171    /// The expected UDP checksum of the original datagram.
172    pub expected_udp_checksum: Option<Checksum>,
173    /// The actual UDP checksum of the original datagram.
174    pub actual_udp_checksum: Option<Checksum>,
175    /// The ICMP response extensions.
176    pub extensions: Option<Extensions>,
177}
178
179/// A failed network tracing probe.
180///
181/// A probe is considered failed when an error occurs while sending or
182/// receiving.
183#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct ProbeFailed {
185    /// The sequence of the probe.
186    pub sequence: Sequence,
187    /// The trace identifier.
188    pub identifier: TraceId,
189    /// The source port (UDP/TCP only)
190    pub src_port: Port,
191    /// The destination port (UDP/TCP only)
192    pub dest_port: Port,
193    /// The TTL of the probe.
194    pub ttl: TimeToLive,
195    /// Which round the probe belongs to.
196    pub round: RoundId,
197    /// Timestamp when the probe was sent.
198    pub sent: SystemTime,
199}
200
201/// The type of ICMP packet received.
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum IcmpPacketType {
204    /// `TimeExceeded` packet.
205    TimeExceeded(IcmpPacketCode),
206    /// `EchoReply` packet.
207    EchoReply(IcmpPacketCode),
208    /// Unreachable packet.
209    Unreachable(IcmpPacketCode),
210    /// Non-ICMP response (i.e. for some `UDP` & `TCP` probes).
211    NotApplicable,
212}
213
214/// The code of `TimeExceeded`, `EchoReply` and `Unreachable` ICMP packets.
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub struct IcmpPacketCode(pub u8);
217
218/// The response to a probe.
219#[derive(Debug, Clone)]
220pub enum Response {
221    TimeExceeded(ResponseData, IcmpPacketCode, Option<Extensions>),
222    DestinationUnreachable(ResponseData, IcmpPacketCode, Option<Extensions>),
223    EchoReply(ResponseData, IcmpPacketCode),
224    TcpReply(ResponseData),
225    TcpRefused(ResponseData),
226}
227
228impl Response {
229    /// The data in the probe response.
230    pub const fn data(&self) -> &ResponseData {
231        match self {
232            Self::TimeExceeded(data, _, _)
233            | Self::DestinationUnreachable(data, _, _)
234            | Self::EchoReply(data, _)
235            | Self::TcpReply(data)
236            | Self::TcpRefused(data) => data,
237        }
238    }
239}
240
241/// The ICMP extensions for a probe response.
242#[derive(Debug, Clone, PartialEq, Eq, Default)]
243pub struct Extensions {
244    pub extensions: Vec<Extension>,
245}
246
247/// A probe response extension.
248#[derive(Debug, Clone, PartialEq, Eq)]
249pub enum Extension {
250    Unknown(UnknownExtension),
251    Mpls(MplsLabelStack),
252}
253
254impl Default for Extension {
255    fn default() -> Self {
256        Self::Unknown(UnknownExtension::default())
257    }
258}
259
260/// The members of a MPLS probe response extension.
261#[derive(Debug, Clone, PartialEq, Eq, Default)]
262pub struct MplsLabelStack {
263    pub members: Vec<MplsLabelStackMember>,
264}
265
266/// A member of a MPLS probe response extension.
267#[derive(Debug, Clone, PartialEq, Eq, Default)]
268pub struct MplsLabelStackMember {
269    pub label: u32,
270    pub exp: u8,
271    pub bos: u8,
272    pub ttl: u8,
273}
274
275/// An unknown ICMP extension.
276#[derive(Debug, Clone, PartialEq, Eq, Default)]
277pub struct UnknownExtension {
278    pub class_num: u8,
279    pub class_subtype: u8,
280    pub bytes: Vec<u8>,
281}
282
283/// The data in the probe response.
284#[derive(Debug, Clone)]
285pub struct ResponseData {
286    /// Timestamp of the probe response.
287    pub recv: SystemTime,
288    /// The `IpAddr` that responded to the probe.
289    pub addr: IpAddr,
290    /// Protocol specific response information.
291    pub proto_resp: ProtocolResponse,
292}
293
294impl ResponseData {
295    pub const fn new(recv: SystemTime, addr: IpAddr, proto_resp: ProtocolResponse) -> Self {
296        Self {
297            recv,
298            addr,
299            proto_resp,
300        }
301    }
302}
303
304/// Protocol specific response information.
305///
306/// This includes protocol specific information that is used to:
307///
308/// - determine the sequence number for matching the incoming probe response
309///   against the outgoing probe.
310/// - validate the probe response against the expected values and discard
311///   invalid responses.
312/// - record information from the probe Original Datagram such as the type of
313///   service (DSCP/ECN) and the expected UDP checksum.
314#[derive(Debug, Clone)]
315pub enum ProtocolResponse {
316    Icmp(IcmpProtocolResponse),
317    Udp(UdpProtocolResponse),
318    Tcp(TcpProtocolResponse),
319}
320
321/// The data in the response to an ICMP probe.
322#[derive(Debug, Clone)]
323pub struct IcmpProtocolResponse {
324    /// The ICMP identifier.
325    pub identifier: u16,
326    /// The ICMP sequence number.
327    pub sequence: u16,
328    /// The type of service (DSCP/ECN) of the original datagram.
329    pub tos: Option<TypeOfService>,
330}
331
332impl IcmpProtocolResponse {
333    pub const fn new(identifier: u16, sequence: u16, tos: Option<TypeOfService>) -> Self {
334        Self {
335            identifier,
336            sequence,
337            tos,
338        }
339    }
340}
341
342/// The data in the response to a UDP probe.
343#[derive(Debug, Clone)]
344pub struct UdpProtocolResponse {
345    /// The IPv4 identifier.
346    ///
347    /// This will be the sequence number for IPv4/Dublin.
348    pub identifier: u16,
349    /// The destination IP address.
350    ///
351    /// This is used to validate the probe response matches the expected values.
352    pub dest_addr: IpAddr,
353    /// The source port.
354    ///
355    /// This is used to validate the probe response matches the expected values.
356    pub src_port: u16,
357    /// The destination port.
358    ///
359    /// This is used to validate the probe response matches the expected values.
360    pub dest_port: u16,
361    /// The type of service (DSCP/ECN) of the original datagram.
362    pub tos: Option<TypeOfService>,
363    /// The expected UDP checksum.
364    ///
365    /// This is calculated based on the data from the probe response and should
366    /// match the checksum that in the probe that was sent.
367    pub expected_udp_checksum: u16,
368    /// The actual UDP checksum.
369    ///
370    /// This will contain the sequence number for IPv4 and IPv6 Paris.
371    pub actual_udp_checksum: u16,
372    /// The length of the UDP payload.
373    ///
374    /// This payload length will be the sequence number (offset from the
375    /// initial sequence number) for IPv6 Dublin.  Note that this length
376    /// does not include the length of the MAGIC payload prefix.
377    pub payload_len: u16,
378    /// Whether the response had the MAGIC payload prefix.
379    ///
380    /// This will be true for IPv6 Dublin for probe responses which
381    /// originated from the tracer and is used to validate the probe response.
382    pub has_magic: bool,
383}
384
385impl UdpProtocolResponse {
386    #[allow(clippy::too_many_arguments)]
387    pub const fn new(
388        identifier: u16,
389        dest_addr: IpAddr,
390        src_port: u16,
391        dest_port: u16,
392        tos: Option<TypeOfService>,
393        expected_udp_checksum: u16,
394        actual_udp_checksum: u16,
395        payload_len: u16,
396        has_magic: bool,
397    ) -> Self {
398        Self {
399            identifier,
400            dest_addr,
401            src_port,
402            dest_port,
403            tos,
404            expected_udp_checksum,
405            actual_udp_checksum,
406            payload_len,
407            has_magic,
408        }
409    }
410}
411
412/// The data in the response to an TCP probe.
413#[derive(Debug, Clone)]
414pub struct TcpProtocolResponse {
415    /// The destination IP address.
416    ///
417    /// This is used to validate the probe response matches the expected values.
418    pub dest_addr: IpAddr,
419    /// The source port.
420    ///
421    /// This is used to validate the probe response matches the expected values.
422    pub src_port: u16,
423    /// The destination port.
424    ///
425    /// This is used to validate the probe response matches the expected values.
426    pub dest_port: u16,
427    /// The type of service (DSCP/ECN) of the original datagram.
428    pub tos: Option<TypeOfService>,
429}
430
431impl TcpProtocolResponse {
432    pub const fn new(
433        dest_addr: IpAddr,
434        src_port: u16,
435        dest_port: u16,
436        tos: Option<TypeOfService>,
437    ) -> Self {
438        Self {
439            dest_addr,
440            src_port,
441            dest_port,
442            tos,
443        }
444    }
445}
446
447#[cfg(test)]
448impl ProbeStatus {
449    #[must_use]
450    pub fn try_into_awaited(self) -> Option<Probe> {
451        if let Self::Awaited(awaited) = self {
452            Some(awaited)
453        } else {
454            None
455        }
456    }
457
458    #[must_use]
459    pub fn try_into_complete(self) -> Option<ProbeComplete> {
460        if let Self::Complete(complete) = self {
461            Some(complete)
462        } else {
463            None
464        }
465    }
466}