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#[derive(Debug, Clone)]
19pub struct Round<'a> {
20 pub probes: &'a [ProbeStatus],
22 pub largest_ttl: TimeToLive,
24 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#[derive(Debug, Copy, Clone, Eq, PartialEq)]
45pub enum CompletionReason {
46 TargetFound,
48 RoundTimeLimitExceeded,
50}
51
52#[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 #[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 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 #[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 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 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 #[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 #[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 #[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#[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#[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 #[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
868mod 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 const BUFFER_SIZE: u16 = MAX_SEQUENCE_PER_ROUND;
888
889 const MAX_SEQUENCE: Sequence = Sequence(u16::MAX - BUFFER_SIZE);
907
908 #[derive(Debug)]
910 pub struct TracerState {
911 config: StrategyConfig,
913 buffer: [ProbeStatus; BUFFER_SIZE as usize],
915 sequence: Sequence,
917 round_sequence: Sequence,
919 ttl: TimeToLive,
921 round: RoundId,
923 round_start: SystemTime,
925 target_found: bool,
927 max_received_ttl: Option<TimeToLive>,
929 target_ttl: Option<TimeToLive>,
934 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 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 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 pub fn in_round(&self, sequence: Sequence) -> bool {
992 sequence >= self.round_sequence && sequence.0 - self.round_sequence.0 < BUFFER_SIZE
993 }
994
995 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 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 #[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 #[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 #[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 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 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 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 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 #[instrument(skip(self), level = "trace")]
1197 pub fn complete_probe(&mut self, resp: StrategyResponse) {
1198 let probe = self.probe_at(resp.sequence);
1200 let awaited = match probe {
1201 ProbeStatus::Awaited(awaited) => awaited,
1202 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 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 #[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 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 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 let prob_init = state.probe_at(Sequence(33434));
1328 assert_eq!(ProbeStatus::NotSent, prob_init);
1329
1330 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 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 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 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 {
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 state.advance_round(TimeToLive(1));
1387
1388 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 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 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 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 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 {
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 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 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 {
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 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 assert_eq!(
1498 state.next_probe(SystemTime::now()).sequence,
1499 Sequence(65278)
1500 );
1501 assert_eq!(state.sequence, Sequence(65279));
1502
1503 {
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 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 assert_eq!(
1527 state.next_probe(SystemTime::now()).sequence,
1528 Sequence(65278)
1529 );
1530 assert_eq!(state.sequence, Sequence(65279));
1531
1532 {
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
1635fn exceeds(start: Option<SystemTime>, end: SystemTime, dur: Duration) -> bool {
1637 start.is_some_and(|start| end.duration_since(start).unwrap_or_default() > dur)
1638}