trippy_core/
strategy.rs

1use self::state::TracerState;
2use crate::config::StrategyConfig;
3use crate::error::{Error, Result};
4use crate::net::Network;
5use crate::probe::{
6    IcmpProtocolResponse, ProbeStatus, ProtocolResponse, Response, ResponseData,
7    TcpProtocolResponse, UdpProtocolResponse,
8};
9use crate::types::{Checksum, Sequence, TimeToLive, TraceId};
10use crate::{
11    Extensions, IcmpPacketType, MultipathStrategy, PortDirection, Probe, Protocol, TypeOfService,
12};
13use std::net::IpAddr;
14use std::time::{Duration, SystemTime};
15use tracing::instrument;
16
17/// The output from a round of tracing.
18#[derive(Debug, Clone)]
19pub struct Round<'a> {
20    /// The state of all `ProbeStatus` that were sent in the round.
21    pub probes: &'a [ProbeStatus],
22    /// The largest time-to-live (ttl) for which we received a reply in the round.
23    pub largest_ttl: TimeToLive,
24    /// Indicates what triggered the completion of the tracing round.
25    pub reason: CompletionReason,
26}
27
28impl<'a> Round<'a> {
29    #[must_use]
30    pub const fn new(
31        probes: &'a [ProbeStatus],
32        largest_ttl: TimeToLive,
33        reason: CompletionReason,
34    ) -> Self {
35        Self {
36            probes,
37            largest_ttl,
38            reason,
39        }
40    }
41}
42
43/// Indicates what triggered the completion of the tracing round.
44#[derive(Debug, Copy, Clone, Eq, PartialEq)]
45pub enum CompletionReason {
46    /// The round ended because the target was found.
47    TargetFound,
48    /// The round ended because the time exceeded the configured maximum round time.
49    RoundTimeLimitExceeded,
50}
51
52/// Trace a path to a target.
53#[derive(Debug, Clone)]
54pub struct Strategy<F> {
55    config: StrategyConfig,
56    publish: F,
57}
58
59impl<F: Fn(&Round<'_>)> Strategy<F> {
60    #[instrument(skip_all, level = "trace")]
61    pub fn new(config: &StrategyConfig, publish: F) -> Self {
62        tracing::debug!(?config);
63        Self {
64            config: *config,
65            publish,
66        }
67    }
68
69    /// Run a continuous trace and publish results.
70    #[instrument(skip(self, network), level = "trace")]
71    pub fn run<N: Network>(self, mut network: N) -> Result<()> {
72        let mut state = TracerState::new(self.config);
73        while !state.finished(self.config.max_rounds) {
74            self.send_request(&mut network, &mut state)?;
75            self.recv_response(&mut network, &mut state)?;
76            self.update_round(&mut state);
77        }
78        Ok(())
79    }
80
81    /// Send the next probe if required.
82    ///
83    /// Send a `ProbeStatus` for the next time-to-live (ttl) if all the following are true:
84    ///
85    /// 1 - the target host has not been found
86    /// 2 - the next ttl is not greater than the maximum allowed ttl
87    /// 3 - if the target ttl of the target is known:
88    ///       - the next ttl is not greater than the ttl of the target host observed from the prior
89    ///         round
90    ///     otherwise:
91    ///       - the number of unknown-in-flight probes is lower than the maximum allowed
92    fn send_request<N: Network>(&self, network: &mut N, st: &mut TracerState) -> Result<()> {
93        let can_send_ttl = if let Some(target_ttl) = st.target_ttl() {
94            st.ttl() <= target_ttl
95        } else {
96            st.ttl() - st.max_received_ttl().unwrap_or_default()
97                < TimeToLive(self.config.max_inflight.0)
98        };
99        if !st.target_found() && st.ttl() <= self.config.max_ttl && can_send_ttl {
100            let sent = SystemTime::now();
101            match self.config.protocol {
102                Protocol::Icmp => {
103                    let probe = st.next_probe(sent);
104                    Self::do_send(network, st, probe)?;
105                }
106                Protocol::Udp => {
107                    let probe = st.next_probe(sent);
108                    Self::do_send(network, st, probe)?;
109                }
110                Protocol::Tcp => {
111                    let mut probe = if st.round_has_capacity() {
112                        st.next_probe(sent)
113                    } else {
114                        return Err(Error::InsufficientCapacity);
115                    };
116                    while let Err(err) = Self::do_send(network, st, probe) {
117                        match err {
118                            Error::AddressInUse(_) => {
119                                if st.round_has_capacity() {
120                                    probe = st.reissue_probe(SystemTime::now());
121                                } else {
122                                    return Err(Error::InsufficientCapacity);
123                                }
124                            }
125                            other => return Err(other),
126                        }
127                    }
128                }
129            }
130        }
131        Ok(())
132    }
133
134    /// Send the probe and handle errors.
135    ///
136    /// Some errors are transient and should not be considered fatal.  In these cases we mark the
137    /// probe as failed and continue.
138    #[instrument(skip(network, st), level = "trace")]
139    fn do_send<N: Network>(network: &mut N, st: &mut TracerState, probe: Probe) -> Result<()> {
140        match network.send_probe(probe) {
141            Ok(()) => Ok(()),
142            Err(Error::ProbeFailed(_)) => {
143                st.fail_probe();
144                Ok(())
145            }
146            Err(err) => Err(err),
147        }
148    }
149
150    /// Read and process the next incoming `ICMP` packet.
151    ///
152    /// We allow multiple probes to be in-flight at any time, and we cannot guarantee that responses
153    /// will be received in-order.  We therefore maintain a buffer which holds details of each
154    /// `ProbeStatus` which is indexed by the offset of the sequence number from the sequence number
155    /// at the beginning of the round.  The sequence number is set in the outgoing `ICMP`
156    /// `EchoRequest` (or `UDP` / `TCP`) packet and returned in both the `TimeExceeded` and
157    /// `EchoReply` responses.
158    ///
159    /// Each incoming `ICMP` packet contains the original `ICMP` `EchoRequest` packet from which we
160    /// can read the `identifier` that we set which we can now validate to ensure we only
161    /// process responses which correspond to packets sent from this process.  For The `UDP` and
162    /// `TCP` protocols, only packets destined for our src port will be delivered to us by the
163    /// OS and so no other `identifier` is needed, and so we allow the special case value of 0.
164    ///
165    /// When we process an `EchoReply` from the target host we extract the time-to-live from the
166    /// corresponding original `EchoRequest`.  Note that this may not be the greatest
167    /// time-to-live that was sent in the round as the algorithm will send `EchoRequest` with
168    /// larger time-to-live values before the `EchoReply` is received.
169    fn recv_response<N: Network>(&self, network: &mut N, st: &mut TracerState) -> Result<()> {
170        let next = network.recv_probe()?;
171        if let Some(resp) = next {
172            if self.validate(resp.data()) {
173                let resp = StrategyResponse::from((resp, &self.config));
174                if self.check_trace_id(resp.trace_id) && st.in_round(resp.sequence) {
175                    st.complete_probe(resp);
176                }
177            }
178        }
179        Ok(())
180    }
181
182    /// Check if the round is complete and publish the results.
183    ///
184    /// A round is considered to be complete when:
185    ///
186    /// 1 - the round has exceeded the minimum round duration AND
187    /// 2 - the duration since the last packet was received exceeds the grace period AND
188    /// 3 - either:
189    ///     A - the target has been found OR
190    ///     B - the target has not been found and the round has exceeded the maximum round duration
191    fn update_round(&self, st: &mut TracerState) {
192        let now = SystemTime::now();
193        let round_duration = now.duration_since(st.round_start()).unwrap_or_default();
194        let round_min = round_duration > self.config.min_round_duration;
195        let grace_exceeded = exceeds(st.received_time(), now, self.config.grace_duration);
196        let round_max = round_duration > self.config.max_round_duration;
197        let target_found = st.target_found();
198        if round_min && grace_exceeded && target_found || round_max {
199            self.publish_trace(st);
200            st.advance_round(self.config.first_ttl);
201        }
202    }
203
204    /// Publish details of all `ProbeStatus` in the completed round.
205    ///
206    /// If the round completed without receiving an `EchoReply` from the target host then we also
207    /// publish the next `ProbeStatus` which is assumed to represent the TTL of the target host.
208    #[instrument(skip(self, state), level = "trace")]
209    fn publish_trace(&self, state: &TracerState) {
210        let max_received_ttl = if let Some(target_ttl) = state.target_ttl() {
211            target_ttl
212        } else {
213            state
214                .max_received_ttl()
215                .map_or(TimeToLive(0), |max_received_ttl| {
216                    let max_sent_ttl = state.ttl() - TimeToLive(1);
217                    max_sent_ttl.min(max_received_ttl + TimeToLive(1))
218                })
219        };
220        let probes = state.probes();
221        let largest_ttl = max_received_ttl;
222        let reason = if state.target_found() {
223            CompletionReason::TargetFound
224        } else {
225            CompletionReason::RoundTimeLimitExceeded
226        };
227        (self.publish)(&Round::new(probes, largest_ttl, reason));
228    }
229
230    /// Check if the `TraceId` matches the expected value for this tracer.
231    ///
232    /// A special value of `0` is accepted for `udp` and `tcp` which do not have an identifier.
233    #[instrument(skip(self), level = "trace")]
234    fn check_trace_id(&self, trace_id: TraceId) -> bool {
235        self.config.trace_identifier == trace_id || trace_id == TraceId(0)
236    }
237
238    /// Validate the probe response data.
239    ///
240    /// Carries out specific check for UDP/TCP probe responses.  This is
241    /// required as the network layer may receive incoming ICMP
242    /// `DestinationUnreachable` (and other types) packets with a UDP/TCP
243    /// original datagram which does not correspond to a probe sent by the
244    /// tracer and must therefore be ignored.
245    ///
246    /// For UDP and TCP probe responses, check that the src/dest ports and
247    /// dest address match the expected values.
248    ///
249    /// For ICMP probe responses no additional checks are required.
250    #[instrument(skip(self), level = "trace")]
251    fn validate(&self, resp: &ResponseData) -> bool {
252        const fn validate_ports(
253            port_direction: PortDirection,
254            src_port: u16,
255            dest_port: u16,
256        ) -> bool {
257            match port_direction {
258                PortDirection::FixedSrc(src) if src.0 == src_port => true,
259                PortDirection::FixedDest(dest) if dest.0 == dest_port => true,
260                PortDirection::FixedBoth(src, dest) if src.0 == src_port && dest.0 == dest_port => {
261                    true
262                }
263                _ => false,
264            }
265        }
266        match resp.proto_resp {
267            ProtocolResponse::Icmp(_) => true,
268            ProtocolResponse::Udp(UdpProtocolResponse {
269                dest_addr,
270                src_port,
271                dest_port,
272                has_magic,
273                ..
274            }) => {
275                let check_ports = validate_ports(self.config.port_direction, src_port, dest_port);
276                let check_dest_addr = self.config.target_addr == dest_addr;
277                let check_magic = match (self.config.multipath_strategy, self.config.target_addr) {
278                    (MultipathStrategy::Dublin, IpAddr::V6(_)) => has_magic,
279                    _ => true,
280                };
281                check_dest_addr && check_ports && check_magic
282            }
283            ProtocolResponse::Tcp(TcpProtocolResponse {
284                dest_addr,
285                src_port,
286                dest_port,
287                ..
288            }) => {
289                let check_ports = validate_ports(self.config.port_direction, src_port, dest_port);
290                let check_dest_addr = self.config.target_addr == dest_addr;
291                check_dest_addr && check_ports
292            }
293        }
294    }
295}
296
297/// Derived response based on strategy config.
298#[derive(Debug)]
299struct StrategyResponse {
300    icmp_packet_type: IcmpPacketType,
301    trace_id: TraceId,
302    sequence: Sequence,
303    tos: Option<TypeOfService>,
304    expected_udp_checksum: Option<Checksum>,
305    actual_udp_checksum: Option<Checksum>,
306    received: SystemTime,
307    addr: IpAddr,
308    is_target: bool,
309    exts: Option<Extensions>,
310}
311
312impl From<(Response, &StrategyConfig)> for StrategyResponse {
313    fn from((resp, config): (Response, &StrategyConfig)) -> Self {
314        match resp {
315            Response::TimeExceeded(data, code, exts) => {
316                let proto_resp = ProtocolStrategyResponse::from((data.proto_resp, config));
317                let is_target = data.addr == config.target_addr;
318                Self {
319                    icmp_packet_type: IcmpPacketType::TimeExceeded(code),
320                    trace_id: proto_resp.trace_id,
321                    sequence: proto_resp.sequence,
322                    tos: proto_resp.tos,
323                    expected_udp_checksum: proto_resp.expected_udp_checksum,
324                    actual_udp_checksum: proto_resp.actual_udp_checksum,
325                    received: data.recv,
326                    addr: data.addr,
327                    is_target,
328                    exts,
329                }
330            }
331            Response::DestinationUnreachable(data, code, exts) => {
332                let proto_resp = ProtocolStrategyResponse::from((data.proto_resp, config));
333                let is_target = data.addr == config.target_addr;
334                Self {
335                    icmp_packet_type: IcmpPacketType::Unreachable(code),
336                    trace_id: proto_resp.trace_id,
337                    sequence: proto_resp.sequence,
338                    tos: proto_resp.tos,
339                    expected_udp_checksum: proto_resp.expected_udp_checksum,
340                    actual_udp_checksum: proto_resp.actual_udp_checksum,
341                    received: data.recv,
342                    addr: data.addr,
343                    is_target,
344                    exts,
345                }
346            }
347            Response::EchoReply(data, code) => {
348                let proto_resp = ProtocolStrategyResponse::from((data.proto_resp, config));
349                Self {
350                    icmp_packet_type: IcmpPacketType::EchoReply(code),
351                    trace_id: proto_resp.trace_id,
352                    sequence: proto_resp.sequence,
353                    tos: proto_resp.tos,
354                    expected_udp_checksum: proto_resp.expected_udp_checksum,
355                    actual_udp_checksum: proto_resp.actual_udp_checksum,
356                    received: data.recv,
357                    addr: data.addr,
358                    is_target: true,
359                    exts: None,
360                }
361            }
362            Response::TcpReply(data) | Response::TcpRefused(data) => {
363                let proto_resp = ProtocolStrategyResponse::from((data.proto_resp, config));
364                Self {
365                    icmp_packet_type: IcmpPacketType::NotApplicable,
366                    trace_id: proto_resp.trace_id,
367                    sequence: proto_resp.sequence,
368                    tos: proto_resp.tos,
369                    expected_udp_checksum: proto_resp.expected_udp_checksum,
370                    actual_udp_checksum: proto_resp.actual_udp_checksum,
371                    received: data.recv,
372                    addr: data.addr,
373                    is_target: true,
374                    exts: None,
375                }
376            }
377        }
378    }
379}
380
381/// Derived response sequence based on strategy config.
382#[derive(Debug)]
383struct ProtocolStrategyResponse {
384    trace_id: TraceId,
385    sequence: Sequence,
386    tos: Option<TypeOfService>,
387    expected_udp_checksum: Option<Checksum>,
388    actual_udp_checksum: Option<Checksum>,
389}
390
391impl From<(ProtocolResponse, &StrategyConfig)> for ProtocolStrategyResponse {
392    fn from((proto_resp, config): (ProtocolResponse, &StrategyConfig)) -> Self {
393        match proto_resp {
394            ProtocolResponse::Icmp(IcmpProtocolResponse {
395                identifier,
396                sequence,
397                tos,
398            }) => Self {
399                trace_id: TraceId(identifier),
400                sequence: Sequence(sequence),
401                tos,
402                expected_udp_checksum: None,
403                actual_udp_checksum: None,
404            },
405            ProtocolResponse::Udp(UdpProtocolResponse {
406                identifier,
407                src_port,
408                dest_port,
409                tos,
410                expected_udp_checksum,
411                actual_udp_checksum,
412                payload_len,
413                ..
414            }) => {
415                let sequence = match (
416                    config.multipath_strategy,
417                    config.port_direction,
418                    config.target_addr,
419                ) {
420                    (MultipathStrategy::Classic, PortDirection::FixedDest(_), _) => src_port,
421                    (MultipathStrategy::Classic, _, _) => dest_port,
422                    (MultipathStrategy::Paris, _, _) => actual_udp_checksum,
423                    (MultipathStrategy::Dublin, _, IpAddr::V4(_)) => identifier,
424                    (MultipathStrategy::Dublin, _, IpAddr::V6(_)) => {
425                        config.initial_sequence.0 + payload_len
426                    }
427                };
428
429                let (expected_udp_checksum, actual_udp_checksum) =
430                    match (config.multipath_strategy, config.target_addr) {
431                        (MultipathStrategy::Dublin, IpAddr::V4(_)) => (
432                            Some(Checksum(expected_udp_checksum)),
433                            Some(Checksum(actual_udp_checksum)),
434                        ),
435                        _ => (None, None),
436                    };
437
438                Self {
439                    trace_id: TraceId(0),
440                    sequence: Sequence(sequence),
441                    tos,
442                    expected_udp_checksum,
443                    actual_udp_checksum,
444                }
445            }
446            ProtocolResponse::Tcp(TcpProtocolResponse {
447                src_port,
448                dest_port,
449                tos,
450                ..
451            }) => {
452                let sequence = match config.port_direction {
453                    PortDirection::FixedSrc(_) => dest_port,
454                    _ => src_port,
455                };
456                Self {
457                    trace_id: TraceId(0),
458                    sequence: Sequence(sequence),
459                    tos,
460                    expected_udp_checksum: None,
461                    actual_udp_checksum: None,
462                }
463            }
464        }
465    }
466}
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471    use crate::net::MockNetwork;
472    use crate::probe::IcmpPacketCode;
473    use crate::{MaxRounds, Port};
474    use std::net::Ipv4Addr;
475    use std::num::NonZeroUsize;
476
477    #[test]
478    fn test_time_exceeded_target_response() {
479        let config = StrategyConfig {
480            target_addr: IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),
481            ..Default::default()
482        };
483        let now = SystemTime::now();
484        let resp_data = Response::TimeExceeded(response_data(now), IcmpPacketCode(1), None);
485        let resp = StrategyResponse::from((resp_data, &config));
486        assert_eq!(
487            resp.icmp_packet_type,
488            IcmpPacketType::TimeExceeded(IcmpPacketCode(1))
489        );
490        assert_eq!(resp.trace_id, TraceId(0));
491        assert_eq!(resp.sequence, Sequence(33434));
492        assert_eq!(resp.received, now);
493        assert_eq!(resp.addr, IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)));
494        assert_eq!(resp.is_target, true);
495        assert!(resp.exts.is_none());
496    }
497
498    #[test]
499    fn test_time_exceeded_not_target_response() {
500        let config = StrategyConfig {
501            target_addr: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
502            ..Default::default()
503        };
504        let now = SystemTime::now();
505        let resp_data = Response::TimeExceeded(response_data(now), IcmpPacketCode(1), None);
506        let resp = StrategyResponse::from((resp_data, &config));
507        assert_eq!(
508            resp.icmp_packet_type,
509            IcmpPacketType::TimeExceeded(IcmpPacketCode(1))
510        );
511        assert_eq!(resp.trace_id, TraceId(0));
512        assert_eq!(resp.sequence, Sequence(33434));
513        assert_eq!(resp.received, now);
514        assert_eq!(resp.addr, IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)));
515        assert_eq!(resp.is_target, false);
516        assert!(resp.exts.is_none());
517    }
518
519    #[test]
520    fn test_destination_unreachable_target_response() {
521        let config = StrategyConfig {
522            target_addr: IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),
523            ..Default::default()
524        };
525        let now = SystemTime::now();
526        let resp_data =
527            Response::DestinationUnreachable(response_data(now), IcmpPacketCode(10), None);
528        let resp = StrategyResponse::from((resp_data, &config));
529        assert_eq!(
530            resp.icmp_packet_type,
531            IcmpPacketType::Unreachable(IcmpPacketCode(10))
532        );
533        assert_eq!(resp.trace_id, TraceId(0));
534        assert_eq!(resp.sequence, Sequence(33434));
535        assert_eq!(resp.received, now);
536        assert_eq!(resp.addr, IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)));
537        assert_eq!(resp.is_target, true);
538        assert!(resp.exts.is_none());
539    }
540
541    #[test]
542    fn test_destination_unreachable_not_target_response() {
543        let config = StrategyConfig::default();
544        let now = SystemTime::now();
545        let resp_data =
546            Response::DestinationUnreachable(response_data(now), IcmpPacketCode(10), None);
547        let resp = StrategyResponse::from((resp_data, &config));
548        assert_eq!(
549            resp.icmp_packet_type,
550            IcmpPacketType::Unreachable(IcmpPacketCode(10))
551        );
552        assert_eq!(resp.trace_id, TraceId(0));
553        assert_eq!(resp.sequence, Sequence(33434));
554        assert_eq!(resp.received, now);
555        assert_eq!(resp.addr, IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)));
556        assert_eq!(resp.is_target, false);
557        assert!(resp.exts.is_none());
558    }
559
560    #[test]
561    fn test_echo_reply_response() {
562        let config = StrategyConfig::default();
563        let now = SystemTime::now();
564        let resp_data = Response::EchoReply(response_data(now), IcmpPacketCode(99));
565        let resp = StrategyResponse::from((resp_data, &config));
566        assert_eq!(
567            resp.icmp_packet_type,
568            IcmpPacketType::EchoReply(IcmpPacketCode(99))
569        );
570        assert_eq!(resp.trace_id, TraceId(0));
571        assert_eq!(resp.sequence, Sequence(33434));
572        assert_eq!(resp.received, now);
573        assert_eq!(resp.addr, IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)));
574        assert_eq!(resp.is_target, true);
575        assert!(resp.exts.is_none());
576    }
577
578    #[test]
579    fn test_tcp_reply_response() {
580        let config = StrategyConfig::default();
581        let now = SystemTime::now();
582        let resp_data = Response::TcpReply(response_data(now));
583        let resp = StrategyResponse::from((resp_data, &config));
584        assert_eq!(resp.icmp_packet_type, IcmpPacketType::NotApplicable);
585        assert_eq!(resp.trace_id, TraceId(0));
586        assert_eq!(resp.sequence, Sequence(33434));
587        assert_eq!(resp.received, now);
588        assert_eq!(resp.addr, IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)));
589        assert_eq!(resp.is_target, true);
590        assert!(resp.exts.is_none());
591    }
592
593    #[test]
594    fn test_tcp_refused_response() {
595        let config = StrategyConfig::default();
596        let now = SystemTime::now();
597        let resp_data = Response::TcpRefused(response_data(now));
598        let resp = StrategyResponse::from((resp_data, &config));
599        assert_eq!(resp.icmp_packet_type, IcmpPacketType::NotApplicable);
600        assert_eq!(resp.trace_id, TraceId(0));
601        assert_eq!(resp.sequence, Sequence(33434));
602        assert_eq!(resp.received, now);
603        assert_eq!(resp.addr, IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)));
604        assert_eq!(resp.is_target, true);
605        assert!(resp.exts.is_none());
606    }
607
608    #[test]
609    fn test_icmp_response() {
610        let config = StrategyConfig::default();
611        let proto_resp = ProtocolResponse::Icmp(IcmpProtocolResponse {
612            identifier: 1234,
613            sequence: 33434,
614            tos: Some(TypeOfService(0)),
615        });
616        let strategy_resp = ProtocolStrategyResponse::from((proto_resp, &config));
617        assert_eq!(strategy_resp.trace_id, TraceId(1234));
618        assert_eq!(strategy_resp.sequence, Sequence(33434));
619    }
620
621    #[test]
622    fn test_udp_classic_fixed_src_response() {
623        let config = StrategyConfig {
624            protocol: Protocol::Udp,
625            port_direction: PortDirection::FixedSrc(Port(5000)),
626            ..Default::default()
627        };
628        let proto_resp = ProtocolResponse::Udp(UdpProtocolResponse {
629            identifier: 0,
630            dest_addr: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
631            src_port: 5000,
632            dest_port: 33434,
633            tos: Some(TypeOfService(0)),
634            expected_udp_checksum: 0,
635            actual_udp_checksum: 0,
636            payload_len: 0,
637            has_magic: false,
638        });
639        let strategy_resp = ProtocolStrategyResponse::from((proto_resp, &config));
640        assert_eq!(strategy_resp.trace_id, TraceId(0));
641        assert_eq!(strategy_resp.sequence, Sequence(33434));
642    }
643
644    #[test]
645    fn test_udp_classic_fixed_dest_response() {
646        let config = StrategyConfig {
647            protocol: Protocol::Udp,
648            port_direction: PortDirection::FixedDest(Port(5000)),
649            ..Default::default()
650        };
651        let proto_resp = ProtocolResponse::Udp(UdpProtocolResponse {
652            identifier: 0,
653            dest_addr: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
654            src_port: 33434,
655            dest_port: 5000,
656            tos: Some(TypeOfService(0)),
657            expected_udp_checksum: 0,
658            actual_udp_checksum: 0,
659            payload_len: 0,
660            has_magic: false,
661        });
662        let strategy_resp = ProtocolStrategyResponse::from((proto_resp, &config));
663        assert_eq!(strategy_resp.trace_id, TraceId(0));
664        assert_eq!(strategy_resp.sequence, Sequence(33434));
665    }
666
667    #[test]
668    fn test_udp_paris_response() {
669        let config = StrategyConfig {
670            protocol: Protocol::Udp,
671            multipath_strategy: MultipathStrategy::Paris,
672            port_direction: PortDirection::FixedSrc(Port(5000)),
673            ..Default::default()
674        };
675        let proto_resp = ProtocolResponse::Udp(UdpProtocolResponse {
676            identifier: 33434,
677            dest_addr: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
678            src_port: 5000,
679            dest_port: 35000,
680            tos: Some(TypeOfService(0)),
681            expected_udp_checksum: 33434,
682            actual_udp_checksum: 33434,
683            payload_len: 0,
684            has_magic: false,
685        });
686        let strategy_resp = ProtocolStrategyResponse::from((proto_resp, &config));
687        assert_eq!(strategy_resp.trace_id, TraceId(0));
688        assert_eq!(strategy_resp.sequence, Sequence(33434));
689    }
690
691    #[test]
692    fn test_udp_dublin_ipv4_response() {
693        let config = StrategyConfig {
694            protocol: Protocol::Udp,
695            multipath_strategy: MultipathStrategy::Dublin,
696            port_direction: PortDirection::FixedSrc(Port(5000)),
697            ..Default::default()
698        };
699        let proto_resp = ProtocolResponse::Udp(UdpProtocolResponse {
700            identifier: 33434,
701            dest_addr: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
702            src_port: 5000,
703            dest_port: 35000,
704            tos: Some(TypeOfService(0)),
705            expected_udp_checksum: 0,
706            actual_udp_checksum: 0,
707            payload_len: 0,
708            has_magic: false,
709        });
710        let strategy_resp = ProtocolStrategyResponse::from((proto_resp, &config));
711        assert_eq!(strategy_resp.trace_id, TraceId(0));
712        assert_eq!(strategy_resp.sequence, Sequence(33434));
713    }
714
715    #[test]
716    fn test_udp_dublin_ipv6_response() {
717        let config = StrategyConfig {
718            protocol: Protocol::Udp,
719            target_addr: IpAddr::V6("::1".parse().unwrap()),
720            multipath_strategy: MultipathStrategy::Dublin,
721            port_direction: PortDirection::FixedSrc(Port(5000)),
722            ..Default::default()
723        };
724        let proto_resp = ProtocolResponse::Udp(UdpProtocolResponse {
725            identifier: 0,
726            dest_addr: IpAddr::V6("::1".parse().unwrap()),
727            src_port: 5000,
728            dest_port: 35000,
729            tos: Some(TypeOfService(0)),
730            expected_udp_checksum: 0,
731            actual_udp_checksum: 0,
732            payload_len: 55,
733            has_magic: true,
734        });
735        let strategy_resp = ProtocolStrategyResponse::from((proto_resp, &config));
736        assert_eq!(strategy_resp.trace_id, TraceId(0));
737        assert_eq!(strategy_resp.sequence, Sequence(33489));
738    }
739
740    #[test]
741    fn test_tcp_fixed_dest_response() {
742        let config = StrategyConfig {
743            protocol: Protocol::Tcp,
744            port_direction: PortDirection::FixedDest(Port(80)),
745            ..Default::default()
746        };
747        let proto_resp = ProtocolResponse::Udp(UdpProtocolResponse {
748            identifier: 0,
749            dest_addr: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
750            src_port: 33434,
751            dest_port: 80,
752            tos: Some(TypeOfService(0)),
753            expected_udp_checksum: 0,
754            actual_udp_checksum: 0,
755            payload_len: 0,
756            has_magic: false,
757        });
758        let strategy_resp = ProtocolStrategyResponse::from((proto_resp, &config));
759        assert_eq!(strategy_resp.trace_id, TraceId(0));
760        assert_eq!(strategy_resp.sequence, Sequence(33434));
761    }
762
763    #[test]
764    fn test_tcp_fixed_src_response() {
765        let config = StrategyConfig {
766            protocol: Protocol::Tcp,
767            port_direction: PortDirection::FixedSrc(Port(5000)),
768            ..Default::default()
769        };
770        let proto_resp = ProtocolResponse::Udp(UdpProtocolResponse {
771            identifier: 0,
772            dest_addr: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
773            src_port: 5000,
774            dest_port: 33434,
775            tos: Some(TypeOfService(0)),
776            expected_udp_checksum: 0,
777            actual_udp_checksum: 0,
778            payload_len: 0,
779            has_magic: false,
780        });
781        let strategy_resp = ProtocolStrategyResponse::from((proto_resp, &config));
782        assert_eq!(strategy_resp.trace_id, TraceId(0));
783        assert_eq!(strategy_resp.sequence, Sequence(33434));
784    }
785
786    // The network can return both `DestinationUnreachable` and `TcpRefused`
787    // for the same sequence number.  This can occur for the target hop for
788    // TCP protocol as the network layer check for ICMP responses such as
789    // `DestinationUnreachable` and also synthesizes a `TcpRefused` response.
790    //
791    // This test simulates sending 1 TCP probe (seq=33434) and receiving two
792    // responses for that probe, a `DestinationUnreachable` followed by a
793    // `TcpRefused`.
794    #[test]
795    fn test_tcp_dest_unreachable_and_refused() -> anyhow::Result<()> {
796        let sequence = 33434;
797        let target_addr = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
798
799        let mut network = MockNetwork::new();
800        let mut seq = mockall::Sequence::new();
801        network.expect_send_probe().times(1).returning(|_| Ok(()));
802        network
803            .expect_recv_probe()
804            .times(1)
805            .in_sequence(&mut seq)
806            .returning(move || {
807                Ok(Some(Response::DestinationUnreachable(
808                    ResponseData::new(
809                        SystemTime::now(),
810                        target_addr,
811                        ProtocolResponse::Tcp(TcpProtocolResponse::new(
812                            target_addr,
813                            sequence,
814                            80,
815                            None,
816                        )),
817                    ),
818                    IcmpPacketCode(1),
819                    None,
820                )))
821            });
822        network
823            .expect_recv_probe()
824            .times(1)
825            .in_sequence(&mut seq)
826            .returning(move || {
827                Ok(Some(Response::TcpRefused(ResponseData::new(
828                    SystemTime::now(),
829                    target_addr,
830                    ProtocolResponse::Tcp(TcpProtocolResponse::new(
831                        target_addr,
832                        sequence,
833                        80,
834                        None,
835                    )),
836                ))))
837            });
838
839        let config = StrategyConfig {
840            target_addr,
841            max_rounds: Some(MaxRounds(NonZeroUsize::MIN)),
842            initial_sequence: Sequence(sequence),
843            port_direction: PortDirection::FixedDest(Port(80)),
844            protocol: Protocol::Tcp,
845            ..Default::default()
846        };
847        let tracer = Strategy::new(&config, |_| {});
848        let mut state = TracerState::new(config);
849        tracer.send_request(&mut network, &mut state)?;
850        tracer.recv_response(&mut network, &mut state)?;
851        tracer.recv_response(&mut network, &mut state)?;
852        Ok(())
853    }
854
855    const fn response_data(now: SystemTime) -> ResponseData {
856        ResponseData::new(
857            now,
858            IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),
859            ProtocolResponse::Icmp(IcmpProtocolResponse {
860                identifier: 0,
861                sequence: 33434,
862                tos: Some(TypeOfService(0)),
863            }),
864        )
865    }
866}
867
868/// Mutable state needed for the tracing algorithm.
869///
870/// This is contained within a submodule to ensure that mutations are only performed via methods on
871/// the `TracerState` struct.
872mod state {
873    use crate::constants::MAX_SEQUENCE_PER_ROUND;
874    use crate::probe::{Probe, ProbeStatus};
875    use crate::strategy::{StrategyConfig, StrategyResponse};
876    use crate::types::{MaxRounds, Port, RoundId, Sequence, TimeToLive, TraceId};
877    use crate::{Flags, MultipathStrategy, PortDirection, Protocol};
878    use std::array::from_fn;
879    use std::net::IpAddr;
880    use std::time::SystemTime;
881    use tracing::instrument;
882
883    /// The maximum number of `ProbeStatus` entries in the buffer.
884    ///
885    /// This is larger than maximum number of time-to-live (TTL) we can support to allow for skipped
886    /// sequences.
887    const BUFFER_SIZE: u16 = MAX_SEQUENCE_PER_ROUND;
888
889    /// The maximum sequence number.
890    ///
891    /// The sequence number is only ever wrapped between rounds, and so we need to ensure that there
892    /// are enough sequence numbers for a complete round.
893    ///
894    /// A sequence number can be skipped if, for example, the port for that sequence number cannot
895    /// be bound as it is already in use.
896    ///
897    /// To ensure each `ProbeStatus` is in the correct place in the buffer (i.e. the index into the
898    /// buffer is always `Probe.sequence - round_sequence`), when we skip a sequence we leave
899    /// the skipped `ProbeStatus` in-place and use the next slot for the next sequence.
900    ///
901    /// We cap the number of sequences that can potentially be skipped in a round to ensure that
902    /// sequence number does not even need to wrap around during a round.
903    ///
904    /// We only ever send `ttl` in the range 1..255, and so we may use all buffer capacity, except
905    /// the minimum needed to send up to a max `ttl` of 255 (a `ttl` of 0 is never sent).
906    const MAX_SEQUENCE: Sequence = Sequence(u16::MAX - BUFFER_SIZE);
907
908    /// Mutable state needed for the tracing algorithm.
909    #[derive(Debug)]
910    pub struct TracerState {
911        /// Tracer configuration.
912        config: StrategyConfig,
913        /// The state of all `ProbeStatus` requests and responses.
914        buffer: [ProbeStatus; BUFFER_SIZE as usize],
915        /// An increasing sequence number for every `EchoRequest`.
916        sequence: Sequence,
917        /// The starting sequence number of the current round.
918        round_sequence: Sequence,
919        /// The time-to-live for the _next_ `EchoRequest` packet to be sent.
920        ttl: TimeToLive,
921        /// The current round.
922        round: RoundId,
923        /// The timestamp of when the current round started.
924        round_start: SystemTime,
925        /// Did we receive an `EchoReply` from the target host in this round?
926        target_found: bool,
927        /// The maximum time-to-live echo response packet we have received.
928        max_received_ttl: Option<TimeToLive>,
929        /// The observed time-to-live of the `EchoReply` from the target host.
930        ///
931        /// Note that this is _not_ reset each round and that it can also _change_ over time,
932        /// including going _down_ as responses can be received out-of-order.
933        target_ttl: Option<TimeToLive>,
934        /// The timestamp of the echo response packet.
935        received_time: Option<SystemTime>,
936    }
937
938    impl TracerState {
939        pub fn new(config: StrategyConfig) -> Self {
940            Self {
941                config,
942                buffer: from_fn(|_| ProbeStatus::default()),
943                sequence: config.initial_sequence,
944                round_sequence: config.initial_sequence,
945                ttl: config.first_ttl,
946                round: RoundId(0),
947                round_start: SystemTime::now(),
948                target_found: false,
949                max_received_ttl: None,
950                target_ttl: None,
951                received_time: None,
952            }
953        }
954
955        /// Get a slice of `ProbeStatus` for the current round.
956        pub fn probes(&self) -> &[ProbeStatus] {
957            let round_size = self.sequence - self.round_sequence;
958            &self.buffer[..round_size.0 as usize]
959        }
960
961        /// Get the `ProbeStatus` for `sequence`
962        pub fn probe_at(&self, sequence: Sequence) -> ProbeStatus {
963            self.buffer[usize::from(sequence - self.round_sequence)].clone()
964        }
965
966        pub const fn ttl(&self) -> TimeToLive {
967            self.ttl
968        }
969
970        pub const fn round_start(&self) -> SystemTime {
971            self.round_start
972        }
973
974        pub const fn target_found(&self) -> bool {
975            self.target_found
976        }
977
978        pub const fn max_received_ttl(&self) -> Option<TimeToLive> {
979            self.max_received_ttl
980        }
981
982        pub const fn target_ttl(&self) -> Option<TimeToLive> {
983            self.target_ttl
984        }
985
986        pub const fn received_time(&self) -> Option<SystemTime> {
987            self.received_time
988        }
989
990        /// Is `sequence` in the current round?
991        pub fn in_round(&self, sequence: Sequence) -> bool {
992            sequence >= self.round_sequence && sequence.0 - self.round_sequence.0 < BUFFER_SIZE
993        }
994
995        /// Do we have capacity in the current round for another sequence?
996        pub fn round_has_capacity(&self) -> bool {
997            let round_size = self.sequence - self.round_sequence;
998            round_size.0 < BUFFER_SIZE
999        }
1000
1001        /// Are all rounds complete?
1002        pub const fn finished(&self, max_rounds: Option<MaxRounds>) -> bool {
1003            match max_rounds {
1004                None => false,
1005                Some(max_rounds) => self.round.0 > max_rounds.0.get() - 1,
1006            }
1007        }
1008
1009        /// Create and return the next `Probe` at the current `sequence` and `ttl`.
1010        ///
1011        /// We post-increment `ttl` here and so in practice we only allow `ttl` values in the range
1012        /// `1..254` to allow us to use a `u8`.
1013        #[instrument(skip(self), level = "trace")]
1014        pub fn next_probe(&mut self, sent: SystemTime) -> Probe {
1015            let (src_port, dest_port, identifier, flags) = self.probe_data();
1016            let probe = Probe::new(
1017                self.sequence,
1018                identifier,
1019                src_port,
1020                dest_port,
1021                self.ttl,
1022                self.round,
1023                sent,
1024                flags,
1025            );
1026            let probe_index = usize::from(self.sequence - self.round_sequence);
1027            self.buffer[probe_index] = ProbeStatus::Awaited(probe.clone());
1028            debug_assert!(self.ttl < TimeToLive(u8::MAX));
1029            self.ttl += TimeToLive(1);
1030            debug_assert!(self.sequence < Sequence(u16::MAX));
1031            self.sequence += Sequence(1);
1032            probe
1033        }
1034
1035        /// Re-issue the `Probe` with the next sequence number.
1036        ///
1037        /// This will mark the `ProbeStatus` at the previous `sequence` as skipped and re-create it
1038        /// with the previous `ttl` and the current `sequence`.
1039        ///
1040        /// For example, if the sequence is `4` and the `ttl` is `5` prior to calling this method
1041        /// then afterward:
1042        /// - The `ProbeStatus` at sequence `3` will be set to `Skipped` state
1043        /// - A new `ProbeStatus` will be created at sequence `4` with a `ttl` of `5`
1044        #[instrument(skip(self), level = "trace")]
1045        pub fn reissue_probe(&mut self, sent: SystemTime) -> Probe {
1046            let probe_index = usize::from(self.sequence - self.round_sequence);
1047            self.buffer[probe_index - 1] = ProbeStatus::Skipped;
1048            let (src_port, dest_port, identifier, flags) = self.probe_data();
1049            let probe = Probe::new(
1050                self.sequence,
1051                identifier,
1052                src_port,
1053                dest_port,
1054                self.ttl - TimeToLive(1),
1055                self.round,
1056                sent,
1057                flags,
1058            );
1059            self.buffer[probe_index] = ProbeStatus::Awaited(probe.clone());
1060            debug_assert!(self.sequence < Sequence(u16::MAX));
1061            self.sequence += Sequence(1);
1062            probe
1063        }
1064
1065        /// Mark the `ProbeStatus` at the current `sequence` as failed.
1066        #[instrument(skip(self), level = "trace")]
1067        pub fn fail_probe(&mut self) {
1068            let probe_index = usize::from(self.sequence - self.round_sequence);
1069            let probe = self.buffer[probe_index - 1].clone();
1070            match probe {
1071                ProbeStatus::Awaited(awaited) => {
1072                    self.buffer[probe_index - 1] = ProbeStatus::Failed(awaited.failed());
1073                }
1074                _ => unreachable!("expected ProbeStatus::Awaited"),
1075            }
1076        }
1077
1078        /// Determine the `src_port`, `dest_port` and `identifier` for the current probe.
1079        ///
1080        /// This will differ depending on the `TracerProtocol`, `MultipathStrategy` &
1081        /// `PortDirection`.
1082        fn probe_data(&self) -> (Port, Port, TraceId, Flags) {
1083            match self.config.protocol {
1084                Protocol::Icmp => self.probe_icmp_data(),
1085                Protocol::Udp => self.probe_udp_data(),
1086                Protocol::Tcp => self.probe_tcp_data(),
1087            }
1088        }
1089
1090        /// Determine the `src_port`, `dest_port` and `identifier` for the current ICMP probe.
1091        const fn probe_icmp_data(&self) -> (Port, Port, TraceId, Flags) {
1092            (
1093                Port(0),
1094                Port(0),
1095                self.config.trace_identifier,
1096                Flags::empty(),
1097            )
1098        }
1099
1100        /// Determine the `src_port`, `dest_port` and `identifier` for the current UDP probe.
1101        fn probe_udp_data(&self) -> (Port, Port, TraceId, Flags) {
1102            match self.config.multipath_strategy {
1103                MultipathStrategy::Classic => match self.config.port_direction {
1104                    PortDirection::FixedSrc(src_port) => (
1105                        Port(src_port.0),
1106                        Port(self.sequence.0),
1107                        TraceId(0),
1108                        Flags::empty(),
1109                    ),
1110                    PortDirection::FixedDest(dest_port) => (
1111                        Port(self.sequence.0),
1112                        Port(dest_port.0),
1113                        TraceId(0),
1114                        Flags::empty(),
1115                    ),
1116                    PortDirection::FixedBoth(_, _) | PortDirection::None => {
1117                        unimplemented!()
1118                    }
1119                },
1120                MultipathStrategy::Paris => {
1121                    let round_port = ((self.config.initial_sequence.0 as usize + self.round.0)
1122                        % usize::from(u16::MAX)) as u16;
1123                    match self.config.port_direction {
1124                        PortDirection::FixedSrc(src_port) => (
1125                            Port(src_port.0),
1126                            Port(round_port),
1127                            TraceId(0),
1128                            Flags::PARIS_CHECKSUM,
1129                        ),
1130                        PortDirection::FixedDest(dest_port) => (
1131                            Port(round_port),
1132                            Port(dest_port.0),
1133                            TraceId(0),
1134                            Flags::PARIS_CHECKSUM,
1135                        ),
1136                        PortDirection::FixedBoth(src_port, dest_port) => (
1137                            Port(src_port.0),
1138                            Port(dest_port.0),
1139                            TraceId(0),
1140                            Flags::PARIS_CHECKSUM,
1141                        ),
1142                        PortDirection::None => unimplemented!(),
1143                    }
1144                }
1145                MultipathStrategy::Dublin => {
1146                    let round_port = ((self.config.initial_sequence.0 as usize + self.round.0)
1147                        % usize::from(u16::MAX)) as u16;
1148                    match self.config.port_direction {
1149                        PortDirection::FixedSrc(src_port) => (
1150                            Port(src_port.0),
1151                            Port(round_port),
1152                            TraceId(self.sequence.0),
1153                            Flags::DUBLIN_IPV6_PAYLOAD_LENGTH,
1154                        ),
1155                        PortDirection::FixedDest(dest_port) => (
1156                            Port(round_port),
1157                            Port(dest_port.0),
1158                            TraceId(self.sequence.0),
1159                            Flags::DUBLIN_IPV6_PAYLOAD_LENGTH,
1160                        ),
1161                        PortDirection::FixedBoth(src_port, dest_port) => (
1162                            Port(src_port.0),
1163                            Port(dest_port.0),
1164                            TraceId(self.sequence.0),
1165                            Flags::DUBLIN_IPV6_PAYLOAD_LENGTH,
1166                        ),
1167                        PortDirection::None => unimplemented!(),
1168                    }
1169                }
1170            }
1171        }
1172
1173        /// Determine the `src_port`, `dest_port` and `identifier` for the current TCP probe.
1174        fn probe_tcp_data(&self) -> (Port, Port, TraceId, Flags) {
1175            let (src_port, dest_port) = match self.config.port_direction {
1176                PortDirection::FixedSrc(src_port) => (src_port.0, self.sequence.0),
1177                PortDirection::FixedDest(dest_port) => (self.sequence.0, dest_port.0),
1178                PortDirection::FixedBoth(_, _) | PortDirection::None => unimplemented!(),
1179            };
1180            (Port(src_port), Port(dest_port), TraceId(0), Flags::empty())
1181        }
1182
1183        /// Update the state of a `ProbeStatus` and the trace.
1184        ///
1185        /// We want to update:
1186        ///
1187        /// - the `target_ttl` to be the time-to-live of the `ProbeStatus` request from the target
1188        /// - the `max_received_ttl` we have observed this round
1189        /// - the latest packet `received_time` in this round
1190        /// - whether the target has been found in this round
1191        ///
1192        /// The ICMP replies may arrive out-of-order, and so we must be careful here to avoid
1193        /// overwriting the state with stale values.  We may also receive multiple replies
1194        /// from the target host with differing time-to-live values and so must ensure we
1195        /// use the time-to-live with the lowest sequence number.
1196        #[instrument(skip(self), level = "trace")]
1197        pub fn complete_probe(&mut self, resp: StrategyResponse) {
1198            // Retrieve and update the `ProbeStatus` at `sequence`.
1199            let probe = self.probe_at(resp.sequence);
1200            let awaited = match probe {
1201                ProbeStatus::Awaited(awaited) => awaited,
1202                // there is a valid scenario for TCP where a probe is already
1203                // `Complete`, see `test_tcp_dest_unreachable_and_refused`.
1204                ProbeStatus::Complete(_) => {
1205                    return;
1206                }
1207                _ => {
1208                    debug_assert!(
1209                        false,
1210                        "completed probe was not in Awaited state (probe={probe:#?})"
1211                    );
1212                    return;
1213                }
1214            };
1215            let completed = awaited.complete(
1216                resp.addr,
1217                resp.received,
1218                resp.icmp_packet_type,
1219                resp.tos,
1220                resp.expected_udp_checksum,
1221                resp.actual_udp_checksum,
1222                resp.exts,
1223            );
1224            let ttl = completed.ttl;
1225            self.buffer[usize::from(resp.sequence - self.round_sequence)] =
1226                ProbeStatus::Complete(completed);
1227
1228            // If this `ProbeStatus` found the target then we set the `target_ttl` if not already
1229            // set, being careful to account for `Probes` being received out-of-order.
1230            //
1231            // If this `ProbeStatus` did not find the target but has a ttl that is greater or equal
1232            // to the target ttl (if known) then we reset the target ttl to None.  This
1233            // is to support Equal Cost Multi-path Routing (ECMP) cases where the number
1234            // of hops to the target will vary over the lifetime of the trace.
1235            self.target_ttl = if resp.is_target {
1236                match self.target_ttl {
1237                    None => Some(ttl),
1238                    Some(target_ttl) if ttl < target_ttl => Some(ttl),
1239                    Some(target_ttl) => Some(target_ttl),
1240                }
1241            } else {
1242                match self.target_ttl {
1243                    Some(target_ttl) if ttl >= target_ttl => None,
1244                    Some(target_ttl) => Some(target_ttl),
1245                    None => None,
1246                }
1247            };
1248
1249            self.max_received_ttl = match self.max_received_ttl {
1250                None => Some(ttl),
1251                Some(max_received_ttl) => Some(max_received_ttl.max(ttl)),
1252            };
1253
1254            self.received_time = Some(resp.received);
1255            self.target_found |= resp.is_target;
1256        }
1257
1258        /// Advance to the next round.
1259        ///
1260        /// If, during the round which just completed, we went above the max sequence number then we
1261        /// reset it here. We do this here to avoid having to deal with the sequence number
1262        /// wrapping during a round, which is more problematic.
1263        #[instrument(skip(self), level = "trace")]
1264        pub fn advance_round(&mut self, first_ttl: TimeToLive) {
1265            if self.sequence >= self.max_sequence() {
1266                self.sequence = self.config.initial_sequence;
1267            }
1268            self.target_found = false;
1269            self.round_sequence = self.sequence;
1270            self.received_time = None;
1271            self.round_start = SystemTime::now();
1272            self.max_received_ttl = None;
1273            self.round += RoundId(1);
1274            self.ttl = first_ttl;
1275        }
1276
1277        /// The maximum sequence number allowed.
1278        ///
1279        /// The Dublin multipath strategy for IPv6/udp encodes the sequence
1280        /// number as the payload length and consequently the maximum sequence
1281        /// number must be no larger than the maximum IPv6/udp payload size.
1282        ///
1283        /// It is also required that the range of possible sequence numbers is
1284        /// _at least_ `BUFFER_SIZE` to ensure delayed responses from a prior
1285        /// round are not incorrectly associated with later rounds (see
1286        /// `in_round` function).
1287        fn max_sequence(&self) -> Sequence {
1288            match (self.config.multipath_strategy, self.config.target_addr) {
1289                (MultipathStrategy::Dublin, IpAddr::V6(_)) => {
1290                    self.config.initial_sequence + Sequence(BUFFER_SIZE)
1291                }
1292                _ => MAX_SEQUENCE,
1293            }
1294        }
1295    }
1296
1297    #[cfg(test)]
1298    mod tests {
1299        use super::*;
1300        use crate::probe::{IcmpPacketCode, IcmpPacketType};
1301        use crate::types::MaxInflight;
1302        use crate::TypeOfService;
1303        use rand::Rng;
1304        use std::net::{IpAddr, Ipv4Addr};
1305        use std::time::Duration;
1306
1307        #[allow(
1308            clippy::cognitive_complexity,
1309            clippy::too_many_lines,
1310            clippy::bool_assert_comparison
1311        )]
1312        #[test]
1313        fn test_state() {
1314            let mut state = TracerState::new(cfg(Sequence(33434)));
1315
1316            // Validate the initial `TracerState`
1317            assert_eq!(state.round, RoundId(0));
1318            assert_eq!(state.sequence, Sequence(33434));
1319            assert_eq!(state.round_sequence, Sequence(33434));
1320            assert_eq!(state.ttl, TimeToLive(1));
1321            assert_eq!(state.max_received_ttl, None);
1322            assert_eq!(state.received_time, None);
1323            assert_eq!(state.target_ttl, None);
1324            assert_eq!(state.target_found, false);
1325
1326            // The initial state of the probe before sending
1327            let prob_init = state.probe_at(Sequence(33434));
1328            assert_eq!(ProbeStatus::NotSent, prob_init);
1329
1330            // Prepare probe 1 (round 0, sequence 33434, ttl 1) for sending
1331            let sent_1 = SystemTime::now();
1332            let probe_1 = state.next_probe(sent_1);
1333            assert_eq!(probe_1.sequence, Sequence(33434));
1334            assert_eq!(probe_1.ttl, TimeToLive(1));
1335            assert_eq!(probe_1.round, RoundId(0));
1336            assert_eq!(probe_1.sent, sent_1);
1337
1338            // Update the state of the probe 1 after receiving a `TimeExceeded`
1339            let received_1 = SystemTime::now();
1340            let host = IpAddr::V4(Ipv4Addr::LOCALHOST);
1341            state.complete_probe(StrategyResponse {
1342                icmp_packet_type: IcmpPacketType::TimeExceeded(IcmpPacketCode(1)),
1343                trace_id: TraceId(0),
1344                sequence: Sequence(33434),
1345                tos: Some(TypeOfService(0)),
1346                expected_udp_checksum: None,
1347                actual_udp_checksum: None,
1348                received: received_1,
1349                addr: host,
1350                is_target: false,
1351                exts: None,
1352            });
1353
1354            // Validate the state of the probe 1 after the update
1355            let probe_1_fetch = state.probe_at(Sequence(33434)).try_into_complete().unwrap();
1356            assert_eq!(probe_1_fetch.sequence, Sequence(33434));
1357            assert_eq!(probe_1_fetch.ttl, TimeToLive(1));
1358            assert_eq!(probe_1_fetch.round, RoundId(0));
1359            assert_eq!(probe_1_fetch.received, received_1);
1360            assert_eq!(probe_1_fetch.host, host);
1361            assert_eq!(probe_1_fetch.sent, sent_1);
1362            assert_eq!(
1363                probe_1_fetch.icmp_packet_type,
1364                IcmpPacketType::TimeExceeded(IcmpPacketCode(1))
1365            );
1366
1367            // Validate the `TracerState` after the update
1368            assert_eq!(state.round, RoundId(0));
1369            assert_eq!(state.sequence, Sequence(33435));
1370            assert_eq!(state.round_sequence, Sequence(33434));
1371            assert_eq!(state.ttl, TimeToLive(2));
1372            assert_eq!(state.max_received_ttl, Some(TimeToLive(1)));
1373            assert_eq!(state.received_time, Some(received_1));
1374            assert_eq!(state.target_ttl, None);
1375            assert_eq!(state.target_found, false);
1376
1377            // Validate the probes() iterator returns only a single probe
1378            {
1379                let mut probe_iter = state.probes().iter();
1380                let probe_next1 = probe_iter.next().unwrap();
1381                assert_eq!(ProbeStatus::Complete(probe_1_fetch), probe_next1.clone());
1382                assert_eq!(None, probe_iter.next());
1383            }
1384
1385            // Advance to the next round
1386            state.advance_round(TimeToLive(1));
1387
1388            // Validate the `TracerState` after the round update
1389            assert_eq!(state.round, RoundId(1));
1390            assert_eq!(state.sequence, Sequence(33435));
1391            assert_eq!(state.round_sequence, Sequence(33435));
1392            assert_eq!(state.ttl, TimeToLive(1));
1393            assert_eq!(state.max_received_ttl, None);
1394            assert_eq!(state.received_time, None);
1395            assert_eq!(state.target_ttl, None);
1396            assert_eq!(state.target_found, false);
1397
1398            // Prepare probe 2 (round 1, sequence 33001, ttl 1) for sending
1399            let sent_2 = SystemTime::now();
1400            let probe_2 = state.next_probe(sent_2);
1401            assert_eq!(probe_2.sequence, Sequence(33435));
1402            assert_eq!(probe_2.ttl, TimeToLive(1));
1403            assert_eq!(probe_2.round, RoundId(1));
1404            assert_eq!(probe_2.sent, sent_2);
1405
1406            // Prepare probe 3 (round 1, sequence 33002, ttl 2) for sending
1407            let sent_3 = SystemTime::now();
1408            let probe_3 = state.next_probe(sent_3);
1409            assert_eq!(probe_3.sequence, Sequence(33436));
1410            assert_eq!(probe_3.ttl, TimeToLive(2));
1411            assert_eq!(probe_3.round, RoundId(1));
1412            assert_eq!(probe_3.sent, sent_3);
1413
1414            // Update the state of probe 2 after receiving a `TimeExceeded`
1415            let received_2 = SystemTime::now();
1416            let host = IpAddr::V4(Ipv4Addr::LOCALHOST);
1417            state.complete_probe(StrategyResponse {
1418                icmp_packet_type: IcmpPacketType::TimeExceeded(IcmpPacketCode(1)),
1419                trace_id: TraceId(0),
1420                sequence: Sequence(33435),
1421                tos: Some(TypeOfService(0)),
1422                expected_udp_checksum: None,
1423                actual_udp_checksum: None,
1424                received: received_2,
1425                addr: host,
1426                is_target: false,
1427                exts: None,
1428            });
1429            let probe_2_recv = state.probe_at(Sequence(33435));
1430
1431            // Validate the `TracerState` after the update to probe 2
1432            assert_eq!(state.round, RoundId(1));
1433            assert_eq!(state.sequence, Sequence(33437));
1434            assert_eq!(state.round_sequence, Sequence(33435));
1435            assert_eq!(state.ttl, TimeToLive(3));
1436            assert_eq!(state.max_received_ttl, Some(TimeToLive(1)));
1437            assert_eq!(state.received_time, Some(received_2));
1438            assert_eq!(state.target_ttl, None);
1439            assert_eq!(state.target_found, false);
1440
1441            // Validate the probes() iterator returns the two probes in the states we expect
1442            {
1443                let mut probe_iter = state.probes().iter();
1444                let probe_next1 = probe_iter.next().unwrap();
1445                assert_eq!(&probe_2_recv, probe_next1);
1446                let probe_next2 = probe_iter.next().unwrap();
1447                assert_eq!(ProbeStatus::Awaited(probe_3), probe_next2.clone());
1448            }
1449
1450            // Update the state of probe 3 after receiving a `EchoReply`
1451            let received_3 = SystemTime::now();
1452            let host = IpAddr::V4(Ipv4Addr::LOCALHOST);
1453            state.complete_probe(StrategyResponse {
1454                icmp_packet_type: IcmpPacketType::EchoReply(IcmpPacketCode(0)),
1455                trace_id: TraceId(0),
1456                sequence: Sequence(33436),
1457                tos: Some(TypeOfService(0)),
1458                expected_udp_checksum: None,
1459                actual_udp_checksum: None,
1460                received: received_3,
1461                addr: host,
1462                is_target: true,
1463                exts: None,
1464            });
1465            let probe_3_recv = state.probe_at(Sequence(33436));
1466
1467            // Validate the `TracerState` after the update to probe 3
1468            assert_eq!(state.round, RoundId(1));
1469            assert_eq!(state.sequence, Sequence(33437));
1470            assert_eq!(state.round_sequence, Sequence(33435));
1471            assert_eq!(state.ttl, TimeToLive(3));
1472            assert_eq!(state.max_received_ttl, Some(TimeToLive(2)));
1473            assert_eq!(state.received_time, Some(received_3));
1474            assert_eq!(state.target_ttl, Some(TimeToLive(2)));
1475            assert_eq!(state.target_found, true);
1476
1477            // Validate the probes() iterator returns the two probes in the states we expect
1478            {
1479                let mut probe_iter = state.probes().iter();
1480                let probe_next1 = probe_iter.next().unwrap();
1481                assert_eq!(&probe_2_recv, probe_next1);
1482                let probe_next2 = probe_iter.next().unwrap();
1483                assert_eq!(&probe_3_recv, probe_next2);
1484            }
1485        }
1486
1487        #[test]
1488        fn test_sequence_wrap1() {
1489            // Start from `MAX_SEQUENCE` - 1 which is (65279 - 1) == 65278
1490            let initial_sequence = Sequence(65278);
1491            let mut state = TracerState::new(cfg(initial_sequence));
1492            assert_eq!(state.round, RoundId(0));
1493            assert_eq!(state.sequence, initial_sequence);
1494            assert_eq!(state.round_sequence, initial_sequence);
1495
1496            // Create a probe at seq 65278
1497            assert_eq!(
1498                state.next_probe(SystemTime::now()).sequence,
1499                Sequence(65278)
1500            );
1501            assert_eq!(state.sequence, Sequence(65279));
1502
1503            // Validate the probes()
1504            {
1505                let mut iter = state.probes().iter();
1506                assert_eq!(
1507                    iter.next()
1508                        .unwrap()
1509                        .clone()
1510                        .try_into_awaited()
1511                        .unwrap()
1512                        .sequence,
1513                    Sequence(65278)
1514                );
1515                iter.take(BUFFER_SIZE as usize - 1)
1516                    .for_each(|p| assert!(matches!(p, ProbeStatus::NotSent)));
1517            }
1518
1519            // Advance the round, which will wrap the sequence back to `initial_sequence`
1520            state.advance_round(TimeToLive(1));
1521            assert_eq!(state.round, RoundId(1));
1522            assert_eq!(state.sequence, initial_sequence);
1523            assert_eq!(state.round_sequence, initial_sequence);
1524
1525            // Create a probe at seq 65278
1526            assert_eq!(
1527                state.next_probe(SystemTime::now()).sequence,
1528                Sequence(65278)
1529            );
1530            assert_eq!(state.sequence, Sequence(65279));
1531
1532            // Validate the probes() again
1533            {
1534                let mut iter = state.probes().iter();
1535                assert_eq!(
1536                    iter.next()
1537                        .unwrap()
1538                        .clone()
1539                        .try_into_awaited()
1540                        .unwrap()
1541                        .sequence,
1542                    Sequence(65278)
1543                );
1544                iter.take(BUFFER_SIZE as usize - 1)
1545                    .for_each(|p| assert!(matches!(p, ProbeStatus::NotSent)));
1546            }
1547        }
1548
1549        #[test]
1550        fn test_sequence_wrap2() {
1551            let total_rounds = 2000;
1552            let max_probe_per_round = 254;
1553            let mut state = TracerState::new(cfg(Sequence(33434)));
1554            for _ in 0..total_rounds {
1555                for _ in 0..max_probe_per_round {
1556                    let _probe = state.next_probe(SystemTime::now());
1557                }
1558                state.advance_round(TimeToLive(1));
1559            }
1560            assert_eq!(state.round, RoundId(2000));
1561            assert_eq!(state.round_sequence, Sequence(33434));
1562            assert_eq!(state.sequence, Sequence(33434));
1563        }
1564
1565        #[test]
1566        fn test_sequence_wrap3() {
1567            let total_rounds = 2000;
1568            let max_probe_per_round = 20;
1569            let mut state = TracerState::new(cfg(Sequence(33434)));
1570            let mut rng = rand::rng();
1571            for _ in 0..total_rounds {
1572                for _ in 0..rng.random_range(0..max_probe_per_round) {
1573                    state.next_probe(SystemTime::now());
1574                }
1575                state.advance_round(TimeToLive(1));
1576            }
1577        }
1578
1579        #[test]
1580        fn test_sequence_wrap_with_skip() {
1581            let total_rounds = 2000;
1582            let max_probe_per_round = 254;
1583            let mut state = TracerState::new(cfg(Sequence(33434)));
1584            for _ in 0..total_rounds {
1585                for _ in 0..max_probe_per_round {
1586                    _ = state.next_probe(SystemTime::now());
1587                    _ = state.reissue_probe(SystemTime::now());
1588                }
1589                state.advance_round(TimeToLive(1));
1590            }
1591            assert_eq!(state.round, RoundId(2000));
1592            assert_eq!(state.round_sequence, Sequence(57310));
1593            assert_eq!(state.sequence, Sequence(57310));
1594        }
1595
1596        #[test]
1597        fn test_in_round() {
1598            let state = TracerState::new(cfg(Sequence(33434)));
1599            assert!(state.in_round(Sequence(33434)));
1600            assert!(state.in_round(Sequence(33945)));
1601            assert!(!state.in_round(Sequence(33946)));
1602        }
1603
1604        #[test]
1605        #[should_panic(expected = "assertion failed: !state.in_round(Sequence(64491))")]
1606        fn test_in_delayed_probe_not_in_round() {
1607            let mut state = TracerState::new(cfg(Sequence(64000)));
1608            for _ in 0..55 {
1609                _ = state.next_probe(SystemTime::now());
1610            }
1611            state.advance_round(TimeToLive(1));
1612            assert!(!state.in_round(Sequence(64491)));
1613        }
1614
1615        fn cfg(initial_sequence: Sequence) -> StrategyConfig {
1616            StrategyConfig {
1617                target_addr: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1618                protocol: Protocol::Icmp,
1619                trace_identifier: TraceId::default(),
1620                max_rounds: None,
1621                first_ttl: TimeToLive(1),
1622                max_ttl: TimeToLive(24),
1623                grace_duration: Duration::default(),
1624                max_inflight: MaxInflight::default(),
1625                initial_sequence,
1626                multipath_strategy: MultipathStrategy::Classic,
1627                port_direction: PortDirection::None,
1628                min_round_duration: Duration::default(),
1629                max_round_duration: Duration::default(),
1630            }
1631        }
1632    }
1633}
1634
1635/// Returns true if the duration between start and end is grater than a duration, false otherwise.
1636fn exceeds(start: Option<SystemTime>, end: SystemTime, dur: Duration) -> bool {
1637    start.is_some_and(|start| end.duration_since(start).unwrap_or_default() > dur)
1638}