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}