Skip to main content

trippy_core/
tracer.rs

1use crate::error::Result;
2use crate::{
3    Error, IcmpExtensionParseMode, MaxInflight, MaxRounds, MultipathStrategy, PacketSize,
4    PayloadPattern, PortDirection, PrivilegeMode, Protocol, Round, Sequence, State, TimeToLive,
5    TraceId, TypeOfService,
6};
7use std::fmt::Debug;
8use std::net::IpAddr;
9use std::sync::Arc;
10use std::thread;
11use std::thread::JoinHandle;
12use std::time::Duration;
13
14/// A traceroute implementation.
15///
16/// See the [`crate`] documentation for more information.
17///
18/// Note that this is type cheaply cloneable.
19#[derive(Debug, Clone)]
20pub struct Tracer {
21    inner: Arc<inner::TracerInner>,
22}
23
24impl Tracer {
25    /// Create a `Tracer`.
26    ///
27    /// Use the [`crate::Builder`] type to create a [`Tracer`].
28    #[allow(clippy::too_many_arguments)]
29    #[must_use]
30    pub(crate) fn new(
31        interface: Option<String>,
32        source_addr: Option<IpAddr>,
33        target_addr: IpAddr,
34        privilege_mode: PrivilegeMode,
35        protocol: Protocol,
36        packet_size: PacketSize,
37        payload_pattern: PayloadPattern,
38        tos: TypeOfService,
39        icmp_extension_parse_mode: IcmpExtensionParseMode,
40        read_timeout: Duration,
41        tcp_connect_timeout: Duration,
42        trace_identifier: TraceId,
43        max_rounds: Option<MaxRounds>,
44        first_ttl: TimeToLive,
45        max_ttl: TimeToLive,
46        grace_duration: Duration,
47        max_inflight: MaxInflight,
48        initial_sequence: Sequence,
49        multipath_strategy: MultipathStrategy,
50        port_direction: PortDirection,
51        min_round_duration: Duration,
52        max_round_duration: Duration,
53        max_samples: usize,
54        max_flows: usize,
55        drop_privileges: bool,
56    ) -> Self {
57        Self {
58            inner: Arc::new(inner::TracerInner::new(
59                interface,
60                source_addr,
61                target_addr,
62                privilege_mode,
63                protocol,
64                packet_size,
65                payload_pattern,
66                tos,
67                icmp_extension_parse_mode,
68                read_timeout,
69                tcp_connect_timeout,
70                trace_identifier,
71                max_rounds,
72                first_ttl,
73                max_ttl,
74                grace_duration,
75                max_inflight,
76                initial_sequence,
77                multipath_strategy,
78                port_direction,
79                min_round_duration,
80                max_round_duration,
81                max_samples,
82                max_flows,
83                drop_privileges,
84            )),
85        }
86    }
87
88    /// Run the [`Tracer`].
89    ///
90    /// This method will block until either the trace completes all rounds (if
91    /// [`crate::Builder::max_rounds`] has been called to set to a non-zero
92    /// value) or until the trace fails.
93    ///
94    /// At the completion of the trace, the state of the tracer can be
95    /// retrieved using the [`Tracer::snapshot`] method.
96    ///
97    /// If you want to run the tracer indefinitely (by not setting
98    /// [`crate::Builder::max_rounds`]), you can either clone and run the
99    /// tracer on a separate thread by using the [`Tracer::spawn`] method or
100    /// by use the [`Tracer::run_with`] method in the current thread to gather
101    /// pee round state manually.
102    ///
103    /// # Example
104    ///
105    /// The following will run the tracer for a fixed number (3) of rounds and
106    /// then retrieve the final state snapshot:
107    ///
108    /// ```no_run
109    /// # fn main() -> anyhow::Result<()> {
110    /// # use std::net::IpAddr;
111    /// # use std::str::FromStr;
112    /// use trippy_core::Builder;
113    ///
114    /// let addr = IpAddr::from_str("1.1.1.1")?;
115    /// let tracer = Builder::new(addr).max_rounds(Some(3)).build()?;
116    /// tracer.run()?;
117    /// let _state = tracer.snapshot();
118    /// # Ok(())
119    /// # }
120    /// ```
121    ///
122    /// # See Also
123    ///
124    /// - [`Tracer::run_with`] - Run the tracer with a custom round handler.
125    /// - [`Tracer::spawn`] - Spawn the tracer on a new thread without a custom round handler.
126    pub fn run(&self) -> Result<()> {
127        self.inner.run()
128    }
129
130    /// Run the [`Tracer`] with a custom round handler.
131    ///
132    /// This method will block until either the trace completes all rounds (if
133    /// [`crate::Builder::max_rounds`] has been called to set to a non-zero
134    /// value) or until the trace fails.
135    ///
136    /// At the completion of the trace, the state of the tracer can be
137    /// retrieved using the [`Tracer::snapshot`] method.
138    ///
139    /// This method will additionally call the provided function for each round
140    /// that is completed.  This can be useful if you want to gather round state
141    /// manually if the tracer is run indefinitely (by not setting
142    /// [`crate::Builder::max_rounds`])
143    ///
144    /// # Example
145    ///
146    /// The following will run the tracer indefinitely and print the data from
147    /// each round of tracing:
148    ///
149    /// ```no_run
150    /// # fn main() -> anyhow::Result<()> {
151    /// # use std::net::IpAddr;
152    /// # use std::str::FromStr;
153    /// use trippy_core::Builder;
154    ///
155    /// let addr = IpAddr::from_str("1.1.1.1")?;
156    /// let tracer = Builder::new(addr).build()?;
157    /// tracer.run_with(|round| println!("{:?}", round))?;
158    /// # Ok(())
159    /// # }
160    /// ```
161    ///
162    /// # See Also
163    ///
164    /// - [`Tracer::run`] - Run the tracer without a custom round handler.
165    pub fn run_with<F: Fn(&Round<'_>)>(&self, func: F) -> Result<()> {
166        self.inner.run_with(func)
167    }
168
169    /// Spawn the tracer on a new thread.
170    ///
171    /// This method will spawn a new thread to run the tracer and immediately
172    /// return the [`Tracer`] and a handle to the thread, so it may be joined
173    /// with [`JoinHandle::join`].
174    ///
175    /// If you want to run the tracer indefinitely (by not setting
176    /// [`crate::Builder::max_rounds`]) you can use this method to spawn the
177    /// tracer on a new thread and return the [`Tracer`] such that a
178    /// [`Tracer::snapshot`] of the state can be taken at any time.
179    ///
180    /// # Example
181    ///
182    /// The following will spawn a tracer on a new thread and take a snapshot
183    /// of the state every 5 seconds:
184    ///
185    /// ```no_run
186    /// # fn main() -> anyhow::Result<()> {
187    /// # use std::net::IpAddr;
188    /// # use std::str::FromStr;
189    /// # use std::thread;
190    /// # use std::time::Duration;
191    /// use trippy_core::Builder;
192    ///
193    /// let addr = IpAddr::from_str("1.1.1.1")?;
194    /// let (tracer, _) = Builder::new(addr).build()?.spawn()?;
195    /// loop {
196    ///     thread::sleep(Duration::from_secs(5));
197    ///     // get the latest state.
198    ///     let _state = tracer.snapshot();
199    /// }
200    /// # Ok(())
201    /// # }
202    /// ```
203    ///
204    /// # See Also
205    ///
206    /// - [`Tracer::run`] - Run the tracer on the current thread.
207    pub fn spawn(self) -> Result<(Self, JoinHandle<Result<()>>)> {
208        let tracer = self.clone();
209        let handle = thread::Builder::new()
210            .name(format!("tracer-{}", self.trace_identifier().0))
211            .spawn(move || tracer.run())
212            .map_err(|err| Error::Other(err.to_string()))?;
213        Ok((self, handle))
214    }
215
216    /// Spawn the tracer with a custom round handler on a new thread.
217    ///
218    /// This method will spawn a new thread to run the tracer with a custom
219    /// round handler and immediately return the [`Tracer`] and a handle to the
220    /// thread, so it may be joined with [`JoinHandle::join`].
221    ///
222    /// # Example
223    ///
224    /// The following will spawn a tracer on a new thread with a custom round
225    /// handler to print the data from each round of tracing and also take a
226    /// snapshot of the state every 5 seconds until the tracer completes all
227    /// rounds:
228    ///
229    /// ```no_run
230    /// # fn main() -> anyhow::Result<()> {
231    /// # use std::net::IpAddr;
232    /// # use std::str::FromStr;
233    /// # use std::thread;
234    /// # use std::time::Duration;
235    /// use trippy_core::Builder;
236    ///
237    /// let addr = IpAddr::from_str("1.1.1.1")?;
238    /// let (tracer, handle) = Builder::new(addr)
239    ///     .max_rounds(Some(3))
240    ///     .build()?
241    ///     .spawn_with(|round| println!("{:?}", round))?;
242    /// for i in 0..3 {
243    ///     thread::sleep(Duration::from_secs(5));
244    ///     // get the latest state.
245    ///     let _state = tracer.snapshot();
246    /// }
247    /// handle.join().unwrap()?;
248    /// # Ok(())
249    /// # }
250    /// ```
251    ///
252    /// # See Also
253    ///
254    /// - [`Tracer::spawn`] - Spawn the tracer on a new thread without a custom round handler.
255    pub fn spawn_with<F: Fn(&Round<'_>) + Send + 'static>(
256        self,
257        func: F,
258    ) -> Result<(Self, JoinHandle<Result<()>>)> {
259        let tracer = self.clone();
260        let handle = thread::Builder::new()
261            .name(format!("tracer-{}", self.trace_identifier().0))
262            .spawn(move || tracer.run_with(func))
263            .map_err(|err| Error::Other(err.to_string()))?;
264        Ok((self, handle))
265    }
266
267    /// Take a snapshot of the tracer state.
268    #[must_use]
269    pub fn snapshot(&self) -> State {
270        self.inner.snapshot()
271    }
272
273    /// Clear the tracer state.
274    pub fn clear(&self) {
275        self.inner.clear();
276    }
277
278    /// The maximum number of flows to record.
279    #[must_use]
280    pub fn max_flows(&self) -> usize {
281        self.inner.max_flows()
282    }
283
284    /// The maximum number of samples to record.
285    #[must_use]
286    pub fn max_samples(&self) -> usize {
287        self.inner.max_samples()
288    }
289
290    /// The privilege mode of the tracer.
291    #[must_use]
292    pub fn privilege_mode(&self) -> PrivilegeMode {
293        self.inner.privilege_mode()
294    }
295
296    /// The protocol of the tracer.
297    #[must_use]
298    pub fn protocol(&self) -> Protocol {
299        self.inner.protocol()
300    }
301
302    /// The interface to use for the tracer.
303    #[must_use]
304    pub fn interface(&self) -> Option<&str> {
305        self.inner.interface()
306    }
307
308    /// The source address of the tracer.
309    #[must_use]
310    pub fn source_addr(&self) -> Option<IpAddr> {
311        self.inner.source_addr()
312    }
313
314    /// The target address of the tracer.
315    #[must_use]
316    pub fn target_addr(&self) -> IpAddr {
317        self.inner.target_addr()
318    }
319
320    /// The packet size of the tracer.
321    #[must_use]
322    pub fn packet_size(&self) -> PacketSize {
323        self.inner.packet_size()
324    }
325
326    /// The payload pattern of the tracer.
327    #[must_use]
328    pub fn payload_pattern(&self) -> PayloadPattern {
329        self.inner.payload_pattern()
330    }
331
332    /// The initial sequence number of the tracer.
333    #[must_use]
334    pub fn initial_sequence(&self) -> Sequence {
335        self.inner.initial_sequence()
336    }
337
338    /// The type of service of the tracer.
339    #[must_use]
340    pub fn tos(&self) -> TypeOfService {
341        self.inner.tos()
342    }
343
344    /// The ICMP extension parse mode of the tracer.
345    #[must_use]
346    pub fn icmp_extension_parse_mode(&self) -> IcmpExtensionParseMode {
347        self.inner.icmp_extension_parse_mode()
348    }
349
350    /// The read timeout of the tracer.
351    #[must_use]
352    pub fn read_timeout(&self) -> Duration {
353        self.inner.read_timeout()
354    }
355
356    /// The TCP connect timeout of the tracer.
357    #[must_use]
358    pub fn tcp_connect_timeout(&self) -> Duration {
359        self.inner.tcp_connect_timeout()
360    }
361
362    /// The trace identifier of the tracer.
363    #[must_use]
364    pub fn trace_identifier(&self) -> TraceId {
365        self.inner.trace_identifier()
366    }
367
368    /// The maximum number of rounds of the tracer.
369    #[must_use]
370    pub fn max_rounds(&self) -> Option<MaxRounds> {
371        self.inner.max_rounds()
372    }
373
374    /// The first time-to-live value of the tracer.
375    #[must_use]
376    pub fn first_ttl(&self) -> TimeToLive {
377        self.inner.first_ttl()
378    }
379
380    /// The maximum time-to-live value of the tracer.
381    #[must_use]
382    pub fn max_ttl(&self) -> TimeToLive {
383        self.inner.max_ttl()
384    }
385
386    /// The grace duration of the tracer.
387    #[must_use]
388    pub fn grace_duration(&self) -> Duration {
389        self.inner.grace_duration()
390    }
391
392    /// The maximum number of in-flight probes of the tracer.
393    #[must_use]
394    pub fn max_inflight(&self) -> MaxInflight {
395        self.inner.max_inflight()
396    }
397
398    /// The multipath strategy of the tracer.
399    #[must_use]
400    pub fn multipath_strategy(&self) -> MultipathStrategy {
401        self.inner.multipath_strategy()
402    }
403
404    /// The port direction of the tracer.
405    #[must_use]
406    pub fn port_direction(&self) -> PortDirection {
407        self.inner.port_direction()
408    }
409
410    /// The minimum round duration of the tracer.
411    #[must_use]
412    pub fn min_round_duration(&self) -> Duration {
413        self.inner.min_round_duration()
414    }
415
416    /// The maximum round duration of the tracer.
417    #[must_use]
418    pub fn max_round_duration(&self) -> Duration {
419        self.inner.max_round_duration()
420    }
421}
422
423mod inner {
424    use crate::config::{ChannelConfig, StateConfig, StrategyConfig};
425    use crate::error::Result;
426    use crate::net::{PlatformImpl, SocketImpl};
427    use crate::{
428        Channel, Error, IcmpExtensionParseMode, MaxInflight, MaxRounds, MultipathStrategy,
429        PacketSize, PayloadPattern, PortDirection, PrivilegeMode, Protocol, Round, Sequence,
430        SourceAddr, State, Strategy, TimeToLive, TraceId, TypeOfService,
431    };
432    use parking_lot::RwLock;
433    use std::fmt::Debug;
434    use std::net::IpAddr;
435    use std::sync::OnceLock;
436    use std::time::Duration;
437    use tracing::instrument;
438    use trippy_privilege::Privilege;
439
440    #[derive(Debug)]
441    pub(super) struct TracerInner {
442        source_addr: Option<IpAddr>,
443        interface: Option<String>,
444        target_addr: IpAddr,
445        privilege_mode: PrivilegeMode,
446        protocol: Protocol,
447        packet_size: PacketSize,
448        payload_pattern: PayloadPattern,
449        tos: TypeOfService,
450        icmp_extension_parse_mode: IcmpExtensionParseMode,
451        read_timeout: Duration,
452        tcp_connect_timeout: Duration,
453        trace_identifier: TraceId,
454        max_rounds: Option<MaxRounds>,
455        first_ttl: TimeToLive,
456        max_ttl: TimeToLive,
457        grace_duration: Duration,
458        max_inflight: MaxInflight,
459        initial_sequence: Sequence,
460        multipath_strategy: MultipathStrategy,
461        port_direction: PortDirection,
462        min_round_duration: Duration,
463        max_round_duration: Duration,
464        max_samples: usize,
465        max_flows: usize,
466        drop_privileges: bool,
467        state: RwLock<State>,
468        src: OnceLock<IpAddr>,
469    }
470
471    impl TracerInner {
472        #[allow(clippy::too_many_arguments)]
473        pub(super) fn new(
474            interface: Option<String>,
475            source_addr: Option<IpAddr>,
476            target_addr: IpAddr,
477            privilege_mode: PrivilegeMode,
478            protocol: Protocol,
479            packet_size: PacketSize,
480            payload_pattern: PayloadPattern,
481            tos: TypeOfService,
482            icmp_extension_parse_mode: IcmpExtensionParseMode,
483            read_timeout: Duration,
484            tcp_connect_timeout: Duration,
485            trace_identifier: TraceId,
486            max_rounds: Option<MaxRounds>,
487            first_ttl: TimeToLive,
488            max_ttl: TimeToLive,
489            grace_duration: Duration,
490            max_inflight: MaxInflight,
491            initial_sequence: Sequence,
492            multipath_strategy: MultipathStrategy,
493            port_direction: PortDirection,
494            min_round_duration: Duration,
495            max_round_duration: Duration,
496            max_samples: usize,
497            max_flows: usize,
498            drop_privileges: bool,
499        ) -> Self {
500            Self {
501                source_addr,
502                interface,
503                target_addr,
504                privilege_mode,
505                protocol,
506                packet_size,
507                payload_pattern,
508                tos,
509                icmp_extension_parse_mode,
510                read_timeout,
511                tcp_connect_timeout,
512                trace_identifier,
513                max_rounds,
514                first_ttl,
515                max_ttl,
516                grace_duration,
517                max_inflight,
518                initial_sequence,
519                multipath_strategy,
520                port_direction,
521                min_round_duration,
522                max_round_duration,
523                max_samples,
524                max_flows,
525                drop_privileges,
526                state: RwLock::new(State::new(Self::make_state_config(max_flows, max_samples))),
527                src: OnceLock::new(),
528            }
529        }
530
531        #[instrument(skip_all, level = "trace")]
532        pub(super) fn run(&self) -> Result<()> {
533            self.run_internal(|_| ())
534                .map_err(|err| self.handle_error(err))
535        }
536
537        #[instrument(skip_all, level = "trace")]
538        pub(super) fn run_with<F: Fn(&Round<'_>)>(&self, func: F) -> Result<()> {
539            self.run_internal(func)
540                .map_err(|err| self.handle_error(err))
541        }
542
543        pub(super) fn snapshot(&self) -> State {
544            self.state.read().clone()
545        }
546
547        pub(super) fn clear(&self) {
548            *self.state.write() =
549                State::new(Self::make_state_config(self.max_flows, self.max_samples));
550        }
551
552        pub(super) const fn max_flows(&self) -> usize {
553            self.max_flows
554        }
555
556        pub(super) const fn max_samples(&self) -> usize {
557            self.max_samples
558        }
559
560        pub(super) const fn privilege_mode(&self) -> PrivilegeMode {
561            self.privilege_mode
562        }
563
564        pub(super) const fn protocol(&self) -> Protocol {
565            self.protocol
566        }
567
568        pub(super) fn interface(&self) -> Option<&str> {
569            self.interface.as_deref()
570        }
571
572        pub(super) fn source_addr(&self) -> Option<IpAddr> {
573            self.src.get().copied()
574        }
575
576        pub(super) const fn target_addr(&self) -> IpAddr {
577            self.target_addr
578        }
579
580        pub(super) const fn packet_size(&self) -> PacketSize {
581            self.packet_size
582        }
583
584        pub(super) const fn payload_pattern(&self) -> PayloadPattern {
585            self.payload_pattern
586        }
587
588        pub(super) const fn initial_sequence(&self) -> Sequence {
589            self.initial_sequence
590        }
591
592        pub(super) const fn tos(&self) -> TypeOfService {
593            self.tos
594        }
595
596        pub(super) const fn icmp_extension_parse_mode(&self) -> IcmpExtensionParseMode {
597            self.icmp_extension_parse_mode
598        }
599
600        pub(super) const fn read_timeout(&self) -> Duration {
601            self.read_timeout
602        }
603
604        pub(super) const fn tcp_connect_timeout(&self) -> Duration {
605            self.tcp_connect_timeout
606        }
607
608        pub(super) const fn trace_identifier(&self) -> TraceId {
609            self.trace_identifier
610        }
611
612        pub(super) const fn max_rounds(&self) -> Option<MaxRounds> {
613            self.max_rounds
614        }
615
616        pub(super) const fn first_ttl(&self) -> TimeToLive {
617            self.first_ttl
618        }
619
620        pub(super) const fn max_ttl(&self) -> TimeToLive {
621            self.max_ttl
622        }
623
624        pub(super) const fn grace_duration(&self) -> Duration {
625            self.grace_duration
626        }
627
628        pub(super) const fn max_inflight(&self) -> MaxInflight {
629            self.max_inflight
630        }
631
632        pub(super) const fn multipath_strategy(&self) -> MultipathStrategy {
633            self.multipath_strategy
634        }
635
636        pub(super) const fn port_direction(&self) -> PortDirection {
637            self.port_direction
638        }
639
640        pub(super) const fn min_round_duration(&self) -> Duration {
641            self.min_round_duration
642        }
643
644        pub(super) const fn max_round_duration(&self) -> Duration {
645            self.max_round_duration
646        }
647
648        #[instrument(skip_all, level = "trace")]
649        fn run_internal<F: Fn(&Round<'_>)>(&self, func: F) -> Result<()> {
650            // if we are given a source address, validate it otherwise
651            // discover it based on the target address and interface.
652            let source_addr = match self.source_addr {
653                None => SourceAddr::discover::<SocketImpl, PlatformImpl>(
654                    self.target_addr,
655                    self.port_direction,
656                    self.interface.as_deref(),
657                )?,
658                Some(addr) => SourceAddr::validate::<SocketImpl>(addr)?,
659            };
660            self.src
661                .set(source_addr)
662                .map_err(|_| Error::Other(String::from("failed to set source_addr")))?;
663            let channel_config = self.make_channel_config(source_addr);
664            let channel = Channel::<SocketImpl>::connect(&channel_config)?;
665            if self.drop_privileges {
666                Privilege::drop_privileges()?;
667            }
668            let strategy_config = self.make_strategy_config();
669            let strategy = Strategy::new(&strategy_config, |round| {
670                self.handler(round);
671                func(round);
672            });
673            strategy.run(channel)?;
674            Ok(())
675        }
676
677        fn handler(&self, round: &Round<'_>) {
678            self.state.write().update_from_round(round);
679        }
680
681        fn handle_error(&self, err: Error) -> Error {
682            self.state.write().set_error(Some(err.to_string()));
683            err
684        }
685
686        const fn make_state_config(max_flows: usize, max_samples: usize) -> StateConfig {
687            StateConfig {
688                max_samples,
689                max_flows,
690            }
691        }
692
693        const fn make_channel_config(&self, source_addr: IpAddr) -> ChannelConfig {
694            ChannelConfig {
695                privilege_mode: self.privilege_mode,
696                protocol: self.protocol,
697                source_addr,
698                target_addr: self.target_addr,
699                packet_size: self.packet_size,
700                payload_pattern: self.payload_pattern,
701                initial_sequence: self.initial_sequence,
702                tos: self.tos,
703                icmp_extension_parse_mode: self.icmp_extension_parse_mode,
704                read_timeout: self.read_timeout,
705                tcp_connect_timeout: self.tcp_connect_timeout,
706            }
707        }
708
709        const fn make_strategy_config(&self) -> StrategyConfig {
710            StrategyConfig {
711                target_addr: self.target_addr,
712                protocol: self.protocol,
713                trace_identifier: self.trace_identifier,
714                max_rounds: self.max_rounds,
715                first_ttl: self.first_ttl,
716                max_ttl: self.max_ttl,
717                grace_duration: self.grace_duration,
718                max_inflight: self.max_inflight,
719                initial_sequence: self.initial_sequence,
720                multipath_strategy: self.multipath_strategy,
721                port_direction: self.port_direction,
722                min_round_duration: self.min_round_duration,
723                max_round_duration: self.max_round_duration,
724            }
725        }
726    }
727}