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#[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 #[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 #[arg(value_enum, short = 'c', long, value_hint = clap::ValueHint::FilePath)]
27 pub config_file: Option<String>,
28
29 #[arg(value_enum, short = 'm', long)]
31 pub mode: Option<Mode>,
32
33 #[arg(short = 'u', long)]
35 pub unprivileged: bool,
36
37 #[arg(value_enum, short = 'p', long)]
39 pub protocol: Option<ProtocolConfig>,
40
41 #[arg(
43 long,
44 conflicts_with = "protocol",
45 conflicts_with = "tcp",
46 conflicts_with = "icmp"
47 )]
48 pub udp: bool,
49
50 #[arg(
52 long,
53 conflicts_with = "protocol",
54 conflicts_with = "udp",
55 conflicts_with = "icmp"
56 )]
57 pub tcp: bool,
58
59 #[arg(
61 long,
62 conflicts_with = "protocol",
63 conflicts_with = "udp",
64 conflicts_with = "tcp"
65 )]
66 pub icmp: bool,
67
68 #[arg(value_enum, short = 'F', long)]
70 pub addr_family: Option<AddressFamilyConfig>,
71
72 #[arg(
74 short = '4',
75 long,
76 conflicts_with = "ipv6",
77 conflicts_with = "addr_family"
78 )]
79 pub ipv4: bool,
80
81 #[arg(
83 short = '6',
84 long,
85 conflicts_with = "ipv4",
86 conflicts_with = "addr_family"
87 )]
88 pub ipv6: bool,
89
90 #[arg(long, short = 'P')]
92 pub target_port: Option<u16>,
93
94 #[arg(long, short = 'S')]
96 pub source_port: Option<u16>,
97
98 #[arg(short = 'A', long, value_parser = parse_addr, conflicts_with = "interface")]
100 pub source_address: Option<IpAddr>,
101
102 #[arg(short = 'I', long)]
104 pub interface: Option<String>,
105
106 #[arg(short = 'i', long, value_parser = parse_duration)]
108 pub min_round_duration: Option<Duration>,
109
110 #[arg(short = 'T', long, value_parser = parse_duration)]
112 pub max_round_duration: Option<Duration>,
113
114 #[arg(short = 'g', long, value_parser = parse_duration)]
117 pub grace_duration: Option<Duration>,
118
119 #[arg(long)]
121 pub initial_sequence: Option<u16>,
122
123 #[arg(value_enum, short = 'R', long)]
125 pub multipath_strategy: Option<MultipathStrategyConfig>,
126
127 #[arg(short = 'U', long)]
129 pub max_inflight: Option<u8>,
130
131 #[arg(short = 'f', long)]
133 pub first_ttl: Option<u8>,
134
135 #[arg(short = 't', long)]
137 pub max_ttl: Option<u8>,
138
139 #[arg(long)]
141 pub packet_size: Option<u16>,
142
143 #[arg(long)]
145 pub payload_pattern: Option<u8>,
146
147 #[arg(short = 'Q', long)]
149 pub tos: Option<u8>,
150
151 #[arg(short = 'e', long)]
153 pub icmp_extensions: bool,
154
155 #[arg(long, value_parser = parse_duration)]
157 pub read_timeout: Option<Duration>,
158
159 #[arg(value_enum, short = 'r', long)]
161 pub dns_resolve_method: Option<DnsResolveMethodConfig>,
162
163 #[arg(short = 'y', long)]
165 pub dns_resolve_all: bool,
166
167 #[arg(long, value_parser = parse_duration)]
169 pub dns_timeout: Option<Duration>,
170
171 #[arg(long, value_parser = parse_duration)]
173 pub dns_ttl: Option<Duration>,
174
175 #[arg(long, short = 'z')]
177 pub dns_lookup_as_info: bool,
178
179 #[arg(long, short = 's')]
181 pub max_samples: Option<usize>,
182
183 #[arg(long)]
185 pub max_flows: Option<usize>,
186
187 #[arg(value_enum, short = 'a', long)]
189 pub tui_address_mode: Option<AddressMode>,
190
191 #[arg(value_enum, long)]
193 pub tui_as_mode: Option<AsMode>,
194
195 #[arg(long)]
197 pub tui_custom_columns: Option<String>,
198
199 #[arg(value_enum, long)]
201 pub tui_icmp_extension_mode: Option<IcmpExtensionMode>,
202
203 #[arg(value_enum, long)]
205 pub tui_geoip_mode: Option<GeoIpMode>,
206
207 #[arg(short = 'M', long)]
209 pub tui_max_addrs: Option<u8>,
210
211 #[arg(long)]
213 pub tui_preserve_screen: bool,
214
215 #[arg(long, value_parser = parse_duration)]
217 pub tui_refresh_rate: Option<Duration>,
218
219 #[arg(long)]
223 pub tui_privacy_max_ttl: Option<u8>,
224
225 #[arg(long)]
227 pub tui_locale: Option<String>,
228
229 #[arg(long)]
233 pub tui_timezone: Option<String>,
234
235 #[arg(long, value_delimiter(','), value_parser = parse_tui_theme_color_value)]
237 pub tui_theme_colors: Vec<(TuiThemeItem, TuiColor)>,
238
239 #[arg(long)]
241 pub print_tui_theme_items: bool,
242
243 #[arg(long, value_delimiter(','), value_parser = parse_tui_binding_value)]
245 pub tui_key_bindings: Vec<(TuiCommandItem, TuiKeyBinding)>,
246
247 #[arg(long)]
249 pub print_tui_binding_commands: bool,
250
251 #[arg(short = 'C', long)]
253 pub report_cycles: Option<usize>,
254
255 #[arg(short = 'G', long, value_hint = clap::ValueHint::FilePath)]
257 pub geoip_mmdb_file: Option<String>,
258
259 #[arg(long)]
261 pub generate: Option<Shell>,
262
263 #[arg(long)]
265 pub generate_man: bool,
266
267 #[arg(long)]
269 pub print_config_template: bool,
270
271 #[arg(long)]
273 pub print_locales: bool,
274
275 #[arg(long)]
277 pub log_format: Option<LogFormat>,
278
279 #[arg(long)]
281 pub log_filter: Option<String>,
282
283 #[arg(long)]
285 pub log_span_events: Option<LogSpanEvents>,
286
287 #[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}