arti_testing/
config.rs
1use crate::rt::badtcp::ConditionalAction;
4use crate::{Action, Job, TcpBreakage};
5
6use anyhow::{anyhow, Result};
7use clap::{value_parser, Arg, ArgAction, Command};
8use std::ffi::OsString;
9use std::str::FromStr;
10use std::time::Duration;
11
12use tor_config::{ConfigurationSource, ConfigurationSources};
13
14pub(crate) fn parse_cmdline() -> Result<Job> {
16 let matches = Command::new("Arti testing tool")
17 .version(env!("CARGO_PKG_VERSION"))
18 .author("The Tor Project Developers")
19 .about("Testing program for unusual arti behaviors")
20 .override_usage("arti-testing <SUBCOMMAND> [OPTIONS]")
22 .arg(
23 Arg::new("config-files")
24 .short('c')
25 .long("config")
26 .action(ArgAction::Set)
27 .value_name("FILE")
28 .value_parser(value_parser!(OsString))
29 .action(ArgAction::Append)
30 .global(true),
31 )
32 .arg(
33 Arg::new("option")
34 .short('o')
35 .action(ArgAction::Set)
36 .value_name("KEY=VALUE")
37 .action(ArgAction::Append)
38 .global(true),
39 )
40 .arg(
41 Arg::new("log")
42 .short('l')
43 .long("log")
44 .action(ArgAction::Set)
45 .value_name("FILTER")
46 .default_value("debug")
47 .global(true),
48 )
49 .arg(
50 Arg::new("timeout")
51 .long("timeout")
52 .action(ArgAction::Set)
53 .value_name("SECS")
54 .value_parser(value_parser!(u64))
55 .default_value("30")
56 .global(true),
57 )
58 .arg(
59 Arg::new("expect")
60 .long("expect")
61 .action(ArgAction::Set)
62 .value_name("success|failure|timeout")
63 .global(true),
64 )
65 .arg(
66 Arg::new("tcp-failure")
67 .long("tcp-failure")
68 .action(ArgAction::Set)
69 .value_name("none|timeout|error|blackhole")
70 .global(true),
71 )
72 .arg(
73 Arg::new("tcp-failure-on")
74 .long("tcp-failure-on")
75 .action(ArgAction::Set)
76 .value_name("all|v4|v6|non443")
77 .global(true),
78 )
79 .arg(
80 Arg::new("tcp-failure-stage")
81 .long("tcp-failure-stage")
82 .action(ArgAction::Set)
83 .value_name("bootstrap|connect")
84 .global(true),
85 )
86 .arg(
87 Arg::new("tcp-failure-delay")
88 .long("tcp-failure-delay")
89 .action(ArgAction::Set)
90 .value_name("SECS")
91 .global(true),
92 )
93 .arg(
94 Arg::new("dir-filter")
95 .long("dir-filter")
96 .action(ArgAction::Set)
97 .value_name("FILTER_NAME")
98 .global(true),
99 )
100 .subcommand(
101 Command::new("connect")
102 .about("Try to bootstrap and connect to an address")
103 .arg(
104 Arg::new("target")
105 .long("target")
106 .action(ArgAction::Set)
107 .value_name("ADDR:PORT")
108 .default_value("www.torproject.org:443"),
109 )
110 .arg(
111 Arg::new("retry")
112 .long("retry")
113 .action(ArgAction::Set)
114 .value_name("DELAY")
115 .value_parser(value_parser!(u64)),
116 ),
117 )
118 .subcommand(Command::new("bootstrap").about("Try to bootstrap only"))
119 .subcommand_required(true)
120 .arg_required_else_help(true)
121 .get_matches();
122
123 let config = {
124 let mut cfg_sources = ConfigurationSources::new_empty();
126
127 let config_files = matches
128 .get_many::<OsString>("config-files")
129 .unwrap_or_default();
130
131 if config_files.len() == 0 {
132 return Err(anyhow!("Sorry, you need to give me a configuration file."));
136 } else {
137 config_files.for_each(|f| {
138 cfg_sources.push_source(
139 ConfigurationSource::from_path(f),
140 tor_config::sources::MustRead::MustRead,
141 );
142 });
143 }
144
145 matches
146 .get_many::<String>("option")
147 .unwrap_or_default()
148 .for_each(|s| cfg_sources.push_option(s));
149
150 cfg_sources
151 };
152
153 let timeout = Duration::from_secs(
154 *matches
155 .get_one::<u64>("timeout")
156 .expect("Failed to pick default timeout"),
157 );
158
159 let console_log = matches
160 .get_one::<String>("log")
161 .expect("Failed to get default log level")
162 .clone();
163
164 let expectation = matches
165 .get_one::<String>("expect")
166 .map(|s| crate::Expectation::from_str(s.as_str()))
167 .transpose()?;
168
169 let tcp_breakage = {
170 let action = matches
171 .get_one::<String>("tcp-failure")
172 .unwrap_or(&"none".to_string())
173 .parse()?;
174 let stage = matches
175 .get_one::<String>("tcp-failure-stage")
176 .unwrap_or(&"bootstrap".to_string())
177 .parse()?;
178 let delay = matches
179 .get_one::<String>("tcp-failure-delay")
180 .map(|d| d.parse().map(Duration::from_secs))
181 .transpose()?;
182 let when = matches
183 .get_one::<String>("tcp-failure-on")
184 .unwrap_or(&"all".to_string())
185 .parse()?;
186 let action = ConditionalAction { action, when };
187
188 TcpBreakage {
189 action,
190 stage,
191 delay,
192 }
193 };
194
195 let dir_filter = matches
196 .get_one::<String>("dir-filter")
197 .map(|s| crate::dirfilter::new_filter(s.as_str()))
198 .transpose()?
199 .unwrap_or_else(crate::dirfilter::nil_filter);
200
201 let action = if let Some(_m) = matches.subcommand_matches("bootstrap") {
202 Action::Bootstrap
203 } else if let Some(matches) = matches.subcommand_matches("connect") {
204 let target = matches
205 .get_one::<String>("target")
206 .expect("Failed to set default connect target")
207 .clone();
208 let retry_delay = matches
209 .get_one::<u64>("retry")
210 .map(|d| Duration::from_secs(*d));
211
212 Action::Connect {
213 target,
214 retry_delay,
215 }
216 } else {
217 return Err(anyhow!("No subcommand given?"));
218 };
219
220 Ok(Job {
221 action,
222 config,
223 timeout,
224 tcp_breakage,
225 dir_filter,
226 console_log,
227 expectation,
228 })
229}