trippy_tui/config/
cmd.rs

1use crate::config::binding::TuiCommandItem;
2use crate::config::theme::TuiThemeItem;
3use crate::config::{
4    AddressFamilyConfig, AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, IcmpExtensionMode,
5    LogFormat, LogSpanEvents, Mode, MultipathStrategyConfig, ProtocolConfig, TuiColor,
6    TuiKeyBinding,
7};
8use anyhow::anyhow;
9use clap::builder::Styles;
10use clap::Parser;
11use clap_complete::Shell;
12use std::net::IpAddr;
13use std::str::FromStr;
14use std::time::Duration;
15
16/// Trace a route to a host and record statistics
17#[allow(clippy::doc_markdown)]
18#[derive(Parser, Debug)]
19#[command(name = "trip", author, version, about, long_about = None, arg_required_else_help(true), styles=Styles::styled())]
20pub struct Args {
21    /// A space delimited list of hostnames and IPs to trace
22    #[arg(required_unless_present_any(["print_tui_theme_items", "print_tui_binding_commands", "print_config_template", "generate", "generate_man", "print_locales"]))]
23    pub targets: Vec<String>,
24
25    /// Config file
26    #[arg(value_enum, short = 'c', long, value_hint = clap::ValueHint::FilePath)]
27    pub config_file: Option<String>,
28
29    /// Output mode [default: tui]
30    #[arg(value_enum, short = 'm', long)]
31    pub mode: Option<Mode>,
32
33    /// Trace without requiring elevated privileges on supported platforms [default: false]
34    #[arg(short = 'u', long)]
35    pub unprivileged: bool,
36
37    /// Tracing protocol [default: icmp]
38    #[arg(value_enum, short = 'p', long)]
39    pub protocol: Option<ProtocolConfig>,
40
41    /// Trace using the UDP protocol
42    #[arg(
43        long,
44        conflicts_with = "protocol",
45        conflicts_with = "tcp",
46        conflicts_with = "icmp"
47    )]
48    pub udp: bool,
49
50    /// Trace using the TCP protocol
51    #[arg(
52        long,
53        conflicts_with = "protocol",
54        conflicts_with = "udp",
55        conflicts_with = "icmp"
56    )]
57    pub tcp: bool,
58
59    /// Trace using the ICMP protocol
60    #[arg(
61        long,
62        conflicts_with = "protocol",
63        conflicts_with = "udp",
64        conflicts_with = "tcp"
65    )]
66    pub icmp: bool,
67
68    /// The address family [default: ipv4-then-ipv6]
69    #[arg(value_enum, short = 'F', long)]
70    pub addr_family: Option<AddressFamilyConfig>,
71
72    /// Use IPv4 only
73    #[arg(
74        short = '4',
75        long,
76        conflicts_with = "ipv6",
77        conflicts_with = "addr_family"
78    )]
79    pub ipv4: bool,
80
81    /// Use IPv6 only
82    #[arg(
83        short = '6',
84        long,
85        conflicts_with = "ipv4",
86        conflicts_with = "addr_family"
87    )]
88    pub ipv6: bool,
89
90    /// The target port (TCP & UDP only) [default: 80]
91    #[arg(long, short = 'P')]
92    pub target_port: Option<u16>,
93
94    /// The source port (TCP & UDP only) [default: auto]
95    #[arg(long, short = 'S')]
96    pub source_port: Option<u16>,
97
98    /// The source IP address [default: auto]
99    #[arg(short = 'A', long, value_parser = parse_addr, conflicts_with = "interface")]
100    pub source_address: Option<IpAddr>,
101
102    /// The network interface [default: auto]
103    #[arg(short = 'I', long)]
104    pub interface: Option<String>,
105
106    /// The minimum duration of every round [default: 1s]
107    #[arg(short = 'i', long, value_parser = parse_duration)]
108    pub min_round_duration: Option<Duration>,
109
110    /// The maximum duration of every round [default: 1s]
111    #[arg(short = 'T', long, value_parser = parse_duration)]
112    pub max_round_duration: Option<Duration>,
113
114    /// The period of time to wait for additional ICMP responses after the target has responded
115    /// [default: 100ms]
116    #[arg(short = 'g', long, value_parser = parse_duration)]
117    pub grace_duration: Option<Duration>,
118
119    /// The initial sequence number [default: 33434]
120    #[arg(long)]
121    pub initial_sequence: Option<u16>,
122
123    /// The Equal-cost Multi-Path routing strategy (UDP only) [default: classic]
124    #[arg(value_enum, short = 'R', long)]
125    pub multipath_strategy: Option<MultipathStrategyConfig>,
126
127    /// The maximum number of in-flight ICMP echo requests [default: 24]
128    #[arg(short = 'U', long)]
129    pub max_inflight: Option<u8>,
130
131    /// The TTL to start from [default: 1]
132    #[arg(short = 'f', long)]
133    pub first_ttl: Option<u8>,
134
135    /// The maximum number of TTL hops [default: 64]
136    #[arg(short = 't', long)]
137    pub max_ttl: Option<u8>,
138
139    /// The size of IP packet to send (IP header + ICMP header + payload) [default: 84]
140    #[arg(long)]
141    pub packet_size: Option<u16>,
142
143    /// The repeating pattern in the payload of the ICMP packet [default: 0]
144    #[arg(long)]
145    pub payload_pattern: Option<u8>,
146
147    /// The TOS (i.e. DSCP+ECN) IP header value (IPv4 only) [default: 0]
148    #[arg(short = 'Q', long)]
149    pub tos: Option<u8>,
150
151    /// Parse ICMP extensions
152    #[arg(short = 'e', long)]
153    pub icmp_extensions: bool,
154
155    /// The socket read timeout [default: 10ms]
156    #[arg(long, value_parser = parse_duration)]
157    pub read_timeout: Option<Duration>,
158
159    /// How to perform DNS queries [default: system]
160    #[arg(value_enum, short = 'r', long)]
161    pub dns_resolve_method: Option<DnsResolveMethodConfig>,
162
163    /// Trace to all IPs resolved from DNS lookup [default: false]
164    #[arg(short = 'y', long)]
165    pub dns_resolve_all: bool,
166
167    /// The maximum time to wait to perform DNS queries [default: 5s]
168    #[arg(long, value_parser = parse_duration)]
169    pub dns_timeout: Option<Duration>,
170
171    /// The time-to-live (TTL) of DNS entries [default: 300s]
172    #[arg(long, value_parser = parse_duration)]
173    pub dns_ttl: Option<Duration>,
174
175    /// Lookup autonomous system (AS) information during DNS queries [default: false]
176    #[arg(long, short = 'z')]
177    pub dns_lookup_as_info: bool,
178
179    /// The maximum number of samples to record per hop [default: 256]
180    #[arg(long, short = 's')]
181    pub max_samples: Option<usize>,
182
183    /// The maximum number of flows to record [default: 64]
184    #[arg(long)]
185    pub max_flows: Option<usize>,
186
187    /// How to render addresses [default: host]
188    #[arg(value_enum, short = 'a', long)]
189    pub tui_address_mode: Option<AddressMode>,
190
191    /// How to render autonomous system (AS) information [default: asn]
192    #[arg(value_enum, long)]
193    pub tui_as_mode: Option<AsMode>,
194
195    /// Custom columns to be displayed in the TUI hops table [default: holsravbwdt]
196    #[arg(long)]
197    pub tui_custom_columns: Option<String>,
198
199    /// How to render ICMP extensions [default: off]
200    #[arg(value_enum, long)]
201    pub tui_icmp_extension_mode: Option<IcmpExtensionMode>,
202
203    /// How to render GeoIp information [default: short]
204    #[arg(value_enum, long)]
205    pub tui_geoip_mode: Option<GeoIpMode>,
206
207    /// The maximum number of addresses to show per hop [default: auto]
208    #[arg(short = 'M', long)]
209    pub tui_max_addrs: Option<u8>,
210
211    /// Preserve the screen on exit [default: false]
212    #[arg(long)]
213    pub tui_preserve_screen: bool,
214
215    /// The TUI refresh rate [default: 100ms]
216    #[arg(long, value_parser = parse_duration)]
217    pub tui_refresh_rate: Option<Duration>,
218
219    /// The maximum ttl of hops which will be masked for privacy [default: none]
220    ///
221    /// If set, the source IP address and hostname will also be hidden.
222    #[arg(long)]
223    pub tui_privacy_max_ttl: Option<u8>,
224
225    /// The locale to use for the TUI [default: auto]
226    #[arg(long)]
227    pub tui_locale: Option<String>,
228
229    /// The timezone to use for the TUI [default: auto]
230    ///
231    /// The timezone must be a valid IANA timezone identifier.
232    #[arg(long)]
233    pub tui_timezone: Option<String>,
234
235    /// The TUI theme colors [item=color,item=color,..]
236    #[arg(long, value_delimiter(','), value_parser = parse_tui_theme_color_value)]
237    pub tui_theme_colors: Vec<(TuiThemeItem, TuiColor)>,
238
239    /// Print all TUI theme items and exit
240    #[arg(long)]
241    pub print_tui_theme_items: bool,
242
243    /// The TUI key bindings [command=key,command=key,..]
244    #[arg(long, value_delimiter(','), value_parser = parse_tui_binding_value)]
245    pub tui_key_bindings: Vec<(TuiCommandItem, TuiKeyBinding)>,
246
247    /// Print all TUI commands that can be bound and exit
248    #[arg(long)]
249    pub print_tui_binding_commands: bool,
250
251    /// The number of report cycles to run [default: 10]
252    #[arg(short = 'C', long)]
253    pub report_cycles: Option<usize>,
254
255    /// The supported MaxMind or IPinfo GeoIp mmdb file
256    #[arg(short = 'G', long, value_hint = clap::ValueHint::FilePath)]
257    pub geoip_mmdb_file: Option<String>,
258
259    /// Generate shell completion
260    #[arg(long)]
261    pub generate: Option<Shell>,
262
263    /// Generate ROFF man page
264    #[arg(long)]
265    pub generate_man: bool,
266
267    /// Print a template toml config file and exit
268    #[arg(long)]
269    pub print_config_template: bool,
270
271    /// Print all available TUI locales and exit
272    #[arg(long)]
273    pub print_locales: bool,
274
275    /// The debug log format [default: pretty]
276    #[arg(long)]
277    pub log_format: Option<LogFormat>,
278
279    /// The debug log filter [default: trippy=debug]
280    #[arg(long)]
281    pub log_filter: Option<String>,
282
283    /// The debug log format [default: off]
284    #[arg(long)]
285    pub log_span_events: Option<LogSpanEvents>,
286
287    /// Enable verbose debug logging
288    #[arg(short = 'v', long, default_value_t = false)]
289    pub verbose: bool,
290}
291
292fn parse_tui_theme_color_value(value: &str) -> anyhow::Result<(TuiThemeItem, TuiColor)> {
293    let pos = value
294        .find('=')
295        .ok_or_else(|| anyhow!("invalid theme value: expected format `item=value`"))?;
296    let item = TuiThemeItem::try_from(&value[..pos])?;
297    let color = TuiColor::try_from(&value[pos + 1..])?;
298    Ok((item, color))
299}
300
301fn parse_tui_binding_value(value: &str) -> anyhow::Result<(TuiCommandItem, TuiKeyBinding)> {
302    let pos = value
303        .find('=')
304        .ok_or_else(|| anyhow!("invalid binding value: expected format `item=value`"))?;
305    let item = TuiCommandItem::try_from(&value[..pos])?;
306    let binding = TuiKeyBinding::try_from(&value[pos + 1..])?;
307    if item == TuiCommandItem::DeprecatedTogglePrivacy {
308        return Err(anyhow!(
309            "toggle-privacy is deprecated, use expand-privacy and contract-privacy instead"
310        ));
311    }
312    Ok((item, binding))
313}
314
315fn parse_duration(value: &str) -> anyhow::Result<Duration> {
316    Ok(humantime::parse_duration(value)?)
317}
318
319fn parse_addr(value: &str) -> anyhow::Result<IpAddr> {
320    Ok(IpAddr::from_str(value)?)
321}