arti_testing/
config.rs

1//! Reading configuration and command line issues in arti-testing.
2
3use 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
14/// Parse the command line into a Job description.
15pub(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        // HACK: see note in arti/src/main.rs
21        .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        // TODO: this is mostly duplicate code.
125        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            // Not using the regular default here; we don't want interference
133            // from the user's regular setup.
134            // Maybe change this later on if we decide it's silly.
135            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}