1
//! A relay binary used to join the Tor network to relay anonymous communication.
2
//!
3
//! NOTE: This binary is still highly experimental as in active development, not stable and
4
//! without any type of guarantee of running or even working.
5
//!
6
//! ## Error handling
7
//!
8
//! We return [`anyhow::Error`] for functions whose errors will always result in an exit and don't
9
//! need to be handled individually.
10
//! When we do need to handle errors, functions should return a more comprehensive error type (for
11
//! example one created with `thiserror`).
12

            
13
// @@ begin lint list maintained by maint/add_warning @@
14
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
15
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
16
#![warn(missing_docs)]
17
#![warn(noop_method_call)]
18
#![warn(unreachable_pub)]
19
#![warn(clippy::all)]
20
#![deny(clippy::await_holding_lock)]
21
#![deny(clippy::cargo_common_metadata)]
22
#![deny(clippy::cast_lossless)]
23
#![deny(clippy::checked_conversions)]
24
#![allow(clippy::cognitive_complexity)] // See arti#2556
25
#![deny(clippy::debug_assert_with_mut_call)]
26
#![deny(clippy::exhaustive_enums)]
27
#![deny(clippy::exhaustive_structs)]
28
#![deny(clippy::expl_impl_clone_on_copy)]
29
#![deny(clippy::fallible_impl_from)]
30
#![deny(clippy::implicit_clone)]
31
#![deny(clippy::large_stack_arrays)]
32
#![warn(clippy::manual_ok_or)]
33
#![deny(clippy::missing_docs_in_private_items)]
34
#![warn(clippy::needless_borrow)]
35
#![warn(clippy::needless_pass_by_value)]
36
#![warn(clippy::option_option)]
37
#![warn(clippy::or_fun_call)]
38
#![deny(clippy::print_stderr)]
39
#![deny(clippy::print_stdout)]
40
#![warn(clippy::rc_buffer)]
41
#![deny(clippy::ref_option_ref)]
42
#![warn(clippy::semicolon_if_nothing_returned)]
43
#![warn(clippy::trait_duplication_in_bounds)]
44
#![deny(clippy::unchecked_time_subtraction)]
45
#![deny(clippy::unnecessary_wraps)]
46
#![warn(clippy::unseparated_literal_suffix)]
47
#![deny(clippy::unwrap_used)]
48
#![deny(clippy::mod_module_files)]
49
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
50
#![allow(clippy::uninlined_format_args)]
51
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
52
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
53
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
54
#![allow(clippy::needless_lifetimes)] // See arti#1765
55
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
56
#![allow(clippy::collapsible_if)] // See arti#2342
57
#![deny(clippy::unused_async)]
58
#![deny(clippy::string_slice)] // See arti#2571
59
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
60
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
61

            
62
#![allow(clippy::redundant_field_names)] // TODO beta clippy bug, rust-clippy/issues/17525
63

            
64
/// Dummy module
65
///
66
/// TODO MSRV 1.100, change this to `[lints.cargo]` in `Cargo.toml`.
67
/// See <https://github.com/rust-lang/cargo/issues/17461>.
68
#[allow(unused, clippy::single_component_path_imports)]
69
mod _suppress_unused_crate_warnings {
70
    // TODO RELAY drop these suppressions (deleting the dep from Cargo.toml too if not needed)
71
    #[cfg(feature = "opentelemetry")]
72
    use {opentelemetry_appender_tracing, otlp_file_exporter, tracing_opentelemetry};
73
}
74

            
75
mod cli;
76
mod client;
77
mod config;
78
mod keys;
79
mod relay;
80
mod stream;
81
mod tasks;
82
mod util;
83

            
84
use std::io::IsTerminal as _;
85

            
86
use anyhow::Context;
87
use cfg_if::cfg_if;
88
use clap::Parser;
89
use futures::FutureExt;
90
use safelog::with_safe_logging_suppressed;
91
use tor_basic_utils::iter_join;
92
use tor_error::warn_report;
93
use tor_rtcompat::SpawnExt;
94
use tor_rtcompat::tokio::TokioRustlsRuntime;
95
use tor_rtcompat::{Runtime, ToplevelRuntime};
96
use tracing::{debug, info, trace, warn};
97
use tracing_subscriber::FmtSubscriber;
98
use tracing_subscriber::filter::EnvFilter;
99
use tracing_subscriber::util::SubscriberInitExt;
100

            
101
use crate::config::{DEFAULT_LOG_LEVEL, TorRelayConfig, base_resolver};
102
use crate::relay::InertTorRelay;
103

            
104
fn main() {
105
    // Will exit if '--help' used or there's a parse error.
106
    let cli = cli::Cli::parse();
107

            
108
    if let Err(e) = main_main(cli) {
109
        // TODO: Use arti_client's `HintableError` here (see `arti::main`)?
110
        // TODO: Why do we suppress safe logging?
111
        // TODO: Do we want to log the error?
112
        // We use anyhow's error formatting here rather than `tor_error::report_and_exit` since the
113
        // latter seems to omit some error info and anyhow's error formatting is nicer.
114
        #[allow(clippy::print_stderr)]
115
        with_safe_logging_suppressed(|| {
116
            eprintln!("Error: {e:?}");
117
            // The 127 is copied from `tor_error::report_and_exit`.
118
            // It's unclear why 127 was chosen there.
119
            std::process::exit(127);
120
        });
121
    }
122
}
123

            
124
/// The real main without the error formatting.
125
fn main_main(cli: cli::Cli) -> anyhow::Result<()> {
126
    // Register a basic stderr logger until we have enough info to configure the main logger.
127
    // Unlike arti, we enable timestamps for this pre-config logger.
128
    // TODO: Consider using timestamps with reduced-granularity (see `LogPrecision`).
129
    let level: tracing::metadata::Level = cli
130
        .global
131
        .log_level
132
        .map(Into::into)
133
        .unwrap_or(DEFAULT_LOG_LEVEL);
134
    let filter = EnvFilter::builder()
135
        .with_default_directive(level.into())
136
        .parse("")
137
        .expect("empty filter directive should be trivially parsable");
138
    FmtSubscriber::builder()
139
        .with_env_filter(filter)
140
        .with_ansi(std::io::stderr().is_terminal())
141
        .with_writer(std::io::stderr)
142
        .finish()
143
        .init();
144

            
145
    match cli.command {
146
        #[allow(clippy::print_stdout)]
147
        cli::Commands::BuildInfo => {
148
            println!("Version: {}", env!("CARGO_PKG_VERSION"));
149
            // these are set by our build script
150
            println!("Features: {}", env!("BUILD_FEATURES"));
151
            println!("Profile: {}", env!("BUILD_PROFILE"));
152
            println!("Debug: {}", env!("BUILD_DEBUG"));
153
            println!("Optimization level: {}", env!("BUILD_OPT_LEVEL"));
154
            println!("Rust version: {}", env!("BUILD_RUSTC_VERSION"));
155
            println!("Target triple: {}", env!("BUILD_TARGET"));
156
            println!("Host triple: {}", env!("BUILD_HOST"));
157
        }
158
        cli::Commands::Run(args) => start_relay(args, cli.global)?,
159
    }
160

            
161
    Ok(())
162
}
163

            
164
/// Initialize and start the relay.
165
// Pass by value so that we don't need to clone fields, which keeps the code simpler.
166
#[allow(clippy::needless_pass_by_value)]
167
fn start_relay(_args: cli::RunArgs, global_args: cli::GlobalArgs) -> anyhow::Result<()> {
168
    // TODO: Warn (or exit?) if running as root; see 'arti::process::running_as_root()'.
169

            
170
    let mut cfg_sources = global_args
171
        .config()
172
        .context("Failed to get configuration sources")?;
173

            
174
    debug!(
175
        "Using override options: {}",
176
        iter_join(", ", cfg_sources.options()),
177
    );
178

            
179
    // A Mistrust object to use for loading our configuration.
180
    // Elsewhere, we use the value _from_ the configuration.
181
    let cfg_mistrust = if global_args.disable_fs_permission_checks {
182
        fs_mistrust::Mistrust::new_dangerously_trust_everyone()
183
    } else {
184
        fs_mistrust::MistrustBuilder::default()
185
            // By default, a `Mistrust` checks an environment variable.
186
            // We do not (at the moment) want this behaviour for relays:
187
            // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/2699#note_3147502
188
            .ignore_environment()
189
            .build()
190
            .expect("default fs-mistrust should be buildable")
191
    };
192

            
193
    cfg_sources.set_mistrust(cfg_mistrust);
194

            
195
    let cfg = cfg_sources
196
        .load()
197
        .context("Failed to load configuration sources")?;
198
    let config =
199
        tor_config::resolve::<TorRelayConfig>(&cfg).context("Failed to resolve configuration")?;
200

            
201
    // TODO: Configure a proper logger, not just a simple stderr logger.
202
    // TODO: We may want this to be the global logger, but if we use arti's `setup_logging` in the
203
    // future, it returns a `LogGuards` which we'd have no way of holding on to until the
204
    // application exits (see https://gitlab.torproject.org/tpo/core/arti/-/issues/1791).
205
    let filter = EnvFilter::builder()
206
        .parse(&config.logging.console)
207
        .with_context(|| {
208
            format!(
209
                "Failed to parse console logging directive {:?}",
210
                config.logging.console,
211
            )
212
        })?;
213
    let logger = tracing_subscriber::FmtSubscriber::builder()
214
        .with_env_filter(filter)
215
        .with_ansi(std::io::stderr().is_terminal())
216
        .with_writer(std::io::stderr)
217
        .finish();
218
    let logger = tracing::Dispatch::new(logger);
219

            
220
    // Disable safe logging if requested.
221
    // This guard will be dropped at the end of this function,
222
    // which means we effectively re-enable safe logging once this function returns.
223
    // TODO: Do we want this guard behaviour?
224
    // I think it would be better to enable safe-logging forever?
225
    let _safelog_guard = if config.logging.log_sensitive_information {
226
        match safelog::disable_safe_logging() {
227
            Ok(guard) => Some(guard),
228
            Err(e) => {
229
                // We don't need to propagate this error;
230
                // it isn't the end of the world if we were unable to disable safe logging.
231
                warn_report!(e, "Unable to disable safe logging");
232
                None
233
            }
234
        }
235
    } else {
236
        None
237
    };
238

            
239
    if let Some(listen) = {
240
        // https://github.com/metrics-rs/metrics/issues/567
241
        config
242
            .metrics
243
            .prometheus
244
            .listen
245
            .single_address_legacy()
246
            .context("can only listen on a single address for Prometheus metrics")?
247
    } {
248
        cfg_if! {
249
            if #[cfg(feature = "metrics")] {
250
                metrics_exporter_prometheus::PrometheusBuilder::new()
251
                    .with_http_listener(listen)
252
                    .install()
253
                    .with_context(|| format!(
254
                        "set up Prometheus metrics exporter on {listen}"
255
                    ))?;
256
                info!("Arti Prometheus metrics export scraper endpoint http://{listen}");
257
            } else {
258
                let _ = listen;
259
                warn!("`metrics.prometheus.listen` config set but `metrics` cargo feature compiled out in `arti-relay` crate");
260
            }
261
        }
262
    }
263

            
264
    tracing::dispatcher::with_default(&logger, || {
265
        let runtime = init_runtime().context("Failed to initialize the runtime")?;
266

            
267
        // Configure tor-log-ratelim early before we begin logging.
268
        tor_log_ratelim::install_runtime(runtime.clone())
269
            .context("Failed to initialize tor-log-ratelim")?;
270

            
271
        let path_resolver = base_resolver();
272
        let relay =
273
            InertTorRelay::new(config, path_resolver).context("Failed to initialize the relay")?;
274

            
275
        match mainloop(&runtime, run_relay(runtime.clone(), relay))? {
276
            MainloopStatus::Finished(Err(e)) => Err(e),
277
            MainloopStatus::CtrlC => {
278
                info!("Received a ctrl-c; stopping the relay");
279
                Ok(())
280
            }
281
        }
282
    })
283
}
284

            
285
/// A helper to drive a future using a runtime.
286
///
287
/// This calls `block_on` on the runtime.
288
/// The future will be cancelled on a ctrl-c event.
289
fn mainloop<T: Send + 'static>(
290
    runtime: &impl ToplevelRuntime,
291
    fut: impl Future<Output = T> + Send + 'static,
292
) -> anyhow::Result<MainloopStatus<T>> {
293
    trace!("Starting runtime");
294

            
295
    let rv = runtime.block_on(async {
296
        // Code running in 'block_on' runs slower than in a task (in tokio at least),
297
        // so the future is run on a task.
298
        let mut handle = runtime
299
            .spawn_with_handle(fut)
300
            .context("Failed to spawn task")?
301
            .fuse();
302

            
303
        futures::select!(
304
            // Signal handler is registered on the first poll.
305
            res = tokio::signal::ctrl_c().fuse() => {
306
                let () = res.context("Failed to listen for ctrl-c event")?;
307
                trace!("Received a ctrl-c");
308
                // Dropping the handle will cancel the task, so we do that explicitly here.
309
                drop(handle);
310
                Ok(MainloopStatus::CtrlC)
311
            }
312
            x = handle => Ok(MainloopStatus::Finished(x)),
313
        )
314
    });
315

            
316
    trace!("Finished runtime");
317
    rv
318
}
319

            
320
/// Run the relay.
321
///
322
/// This blocks until the relay stops.
323
async fn run_relay<R: Runtime>(
324
    runtime: R,
325
    inert_relay: InertTorRelay,
326
) -> anyhow::Result<void::Void> {
327
    let relay = inert_relay
328
        .init(runtime)
329
        .await
330
        .context("Failed to bootstrap")?;
331

            
332
    // This blocks until end of time or an error.
333
    relay.run().await
334
}
335

            
336
/// Initialize a runtime.
337
///
338
/// Any cli commands that need a runtime should call this so that we use a consistent runtime.
339
fn init_runtime() -> std::io::Result<impl ToplevelRuntime> {
340
    // Use the tokio runtime from tor_rtcompat unless we later find a reason to use tokio directly.
341
    // See https://gitlab.torproject.org/tpo/core/arti/-/work_items/1744.
342
    // Relays must use rustls as native-tls doesn't support
343
    // `CertifiedConn::export_keying_material()`.
344

            
345
    // Note: See comments in `tor_rtcompat::impls::rustls::RustlsProvider`
346
    // about choice of default crypto provider.
347
    let _idempotent_ignore = rustls::crypto::CryptoProvider::install_default(
348
        rustls::crypto::aws_lc_rs::default_provider(),
349
    );
350

            
351
    TokioRustlsRuntime::create()
352
}
353

            
354
/// The result of [`mainloop`].
355
enum MainloopStatus<T> {
356
    /// The result from the completed future.
357
    Finished(T),
358
    /// The future was cancelled due to a ctrl-c event.
359
    CtrlC,
360
}