Skip to main content

starla_common/
traceroute.rs

1//! evtraceroute's options, as the reference probe parses them.
2//!
3//! Shared by the controller, which parses a CRONLINE/ONEOFF into them, and
4//! the measurement, which runs them. Names, types and defaults follow
5//! traceroute_init (probe-busybox/eperd/traceroute.c:4203-4466, tag 5130);
6//! fields eperd keeps in an `unsigned char` are `u8` here, as its
7//! assignments from getopt32's ints truncate the same way.
8
9use serde::{Deserialize, Serialize};
10
11/// `-I` ICMP echo, `-T` TCP SYN, otherwise UDP (`do_udp= !(do_icmp ||
12/// do_tcp)`, traceroute.c:4281).
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum TracerouteProtocol {
16    ICMP,
17    #[default]
18    UDP,
19    TCP,
20}
21
22/// One evtraceroute measurement's options (everything but the target,
23/// `-4`/`-6` and the `-A`/`-O` plumbing).
24#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub struct TracerouteOptions {
26    pub protocol: TracerouteProtocol,
27    /// `-c`: probes per hop (trtcount)
28    pub count: u8,
29    /// `-f`: first hop limit
30    pub first_hop: u8,
31    /// `-g`: after this many hops without an answer, one last-ditch hop
32    /// (255) and the result
33    pub gap_limit: u8,
34    /// `-m`: last hop limit
35    pub max_hops: u8,
36    /// `-S`: data bytes after the ICMP/UDP/TCP header (maxpacksize); the
37    /// result's envelope "size"
38    pub size: u16,
39    /// `-p`: TCP destination port
40    pub dest_port: u16,
41    /// `-t`: IP TOS / IPv6 traffic class of the probes (0: left alone)
42    pub tos: u32,
43    /// `-w`: ms to wait for each probe's reply
44    pub timeout_ms: u32,
45    /// `-z`: ms to wait for duplicates after a reply before the next probe
46    pub dup_timeout_ms: u32,
47    /// `-a`: Paris traceroute: the id cycles through `paris_mod` values, one
48    /// per run; 0 is classic traceroute (and no "paris_id" in the result)
49    pub paris_mod: u8,
50    /// `-b`: lowest Paris id
51    pub paris_base: u8,
52    /// `-H`: IPv6 hop-by-hop options header of this many bytes (PadN)
53    pub hbh_opt_size: u16,
54    /// `-D`: IPv6 destination options header of this many bytes (PadN)
55    pub dest_opt_size: u16,
56    /// `-F`: set don't-fragment (path MTU discovery) on the probes
57    pub dont_fragment: bool,
58    /// `-i`: bind the probes to this interface
59    pub interface: Option<String>,
60    /// `-B`: bundle id, written into the result as "bundle"
61    pub bundle: Option<String>,
62}
63
64impl Default for TracerouteOptions {
65    /// traceroute_init's defaults (traceroute.c:4237-4251).
66    fn default() -> Self {
67        Self {
68            protocol: TracerouteProtocol::UDP,
69            count: 3,
70            first_hop: 1,
71            gap_limit: 5,
72            max_hops: 32,
73            size: 40,
74            dest_port: 80,
75            tos: 0,
76            timeout_ms: 1000,
77            dup_timeout_ms: 10,
78            paris_mod: 16,
79            paris_base: 0,
80            hbh_opt_size: 0,
81            dest_opt_size: 0,
82            dont_fragment: false,
83            interface: None,
84            bundle: None,
85        }
86    }
87}
88
89impl TracerouteOptions {
90    /// The largest `-S` traceroute_init accepts (MAX_DATA_SIZE,
91    /// traceroute.c:46 and 4282-4286: "max. packet size too big").
92    pub const MAX_SIZE: u16 = 4096;
93
94    /// The Paris id of a measurement's `run`-th run (1 for the first).
95    /// traceroute_init sets paris to parisbase (traceroute.c:4408) and each
96    /// run's traceroute_start2 steps it (traceroute.c:4492-4497):
97    /// `paris= (paris-parisbase+1+parismod) % parismod + parisbase`, i.e.
98    /// `run % parismod + parisbase`. None without Paris (`-a 0`), where
99    /// report() writes no "paris_id" (traceroute.c:428-431).
100    pub fn paris_id(&self, run: u64) -> Option<u16> {
101        if self.paris_mod == 0 {
102            return None;
103        }
104        Some((run % self.paris_mod as u64) as u16 + self.paris_base as u16)
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    /// The reference's results in the 2026-10-13 twin run: paris_id 1 with
113    /// the default modulus and with `-a 16`, 0 with `-a 1` (25603503); 6052's
114    /// second run 2, its UDP probes going to 33436 instead of 33435.
115    #[test]
116    fn paris_id_steps_once_per_run_from_the_base() {
117        let opts = TracerouteOptions::default();
118        assert_eq!(opts.paris_id(1), Some(1));
119        assert_eq!(opts.paris_id(2), Some(2));
120        assert_eq!(opts.paris_id(15), Some(15));
121        assert_eq!(opts.paris_id(16), Some(0));
122        assert_eq!(opts.paris_id(17), Some(1));
123
124        let one = TracerouteOptions {
125            paris_mod: 1,
126            ..Default::default()
127        };
128        assert_eq!(one.paris_id(1), Some(0));
129        assert_eq!(one.paris_id(2), Some(0));
130
131        let based = TracerouteOptions {
132            paris_mod: 4,
133            paris_base: 10,
134            ..Default::default()
135        };
136        assert_eq!(based.paris_id(1), Some(11));
137        assert_eq!(based.paris_id(4), Some(10));
138
139        let classic = TracerouteOptions {
140            paris_mod: 0,
141            ..Default::default()
142        };
143        assert_eq!(classic.paris_id(1), None);
144    }
145}