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}