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

            
52
pub mod config;
53
pub mod err;
54

            
55
#[cfg(feature = "managed-pts")]
56
pub mod ipc;
57

            
58
#[cfg(feature = "managed-pts")]
59
mod managed;
60

            
61
use crate::config::{TransportConfig, TransportOptions};
62
use crate::err::PtError;
63
use std::collections::HashMap;
64
use std::net::SocketAddr;
65
use std::path::PathBuf;
66
use std::sync::{Arc, RwLock};
67
use tor_chanmgr::ProxyProtocol;
68
use tor_config_path::CfgPathResolver;
69
use tor_linkspec::PtTransportName;
70
use tor_rtcompat::Runtime;
71
use tor_socksproto::SocksVersion;
72
use tracing::warn;
73
#[cfg(feature = "managed-pts")]
74
use {
75
    crate::managed::{PtReactor, PtReactorMessage},
76
    futures::channel::mpsc::{self, UnboundedSender},
77
    tor_error::error_report,
78
    tor_rtcompat::SpawnExt,
79
};
80
#[cfg(feature = "tor-channel-factory")]
81
use {
82
    async_trait::async_trait,
83
    tor_chanmgr::{
84
        builder::ChanBuilder,
85
        factory::{AbstractPtError, ChannelFactory},
86
        transport::ExternalProxyPlugin,
87
    },
88
    tracing::trace,
89
};
90
#[cfg(all(feature = "managed-pts", feature = "tor-channel-factory"))]
91
use {oneshot_fused_workaround as oneshot, tracing::info};
92

            
93
/// Shared mutable state between the `PtReactor` and `PtMgr`.
94
#[derive(Default, Debug)]
95
struct PtSharedState {
96
    /// Connection information for pluggable transports from currently running binaries.
97
    ///
98
    /// Unmanaged pluggable transports are not included in this map.
99
    #[cfg(feature = "managed-pts")]
100
    managed_cmethods: HashMap<PtTransportName, PtClientMethod>,
101
    /// Current configured set of pluggable transports.
102
    configured: HashMap<PtTransportName, TransportOptions>,
103
    /// The global Tor outbound proxy, if any.
104
    outbound_proxy: Option<ProxyProtocol>,
105
}
106

            
107
/// A pluggable transport manager knows how to make different
108
/// kinds of connections to the Tor network, for censorship avoidance.
109
pub struct PtMgr<R> {
110
    /// An underlying `Runtime`, used to spawn background tasks.
111
    #[allow(dead_code)]
112
    runtime: R,
113
    /// State for this `PtMgr` that's shared with the `PtReactor`.
114
    state: Arc<RwLock<PtSharedState>>,
115
    /// PtReactor channel when the `managed-pts` feature is enabled.
116
    #[cfg(feature = "managed-pts")]
117
    tx: UnboundedSender<PtReactorMessage>,
118
}
119

            
120
impl<R: Runtime> PtMgr<R> {
121
    /// Transform the config into a more useful representation indexed by transport name.
122
    fn transform_config(
123
        binaries: Vec<TransportConfig>,
124
    ) -> Result<HashMap<PtTransportName, TransportOptions>, tor_error::Bug> {
125
        let mut ret = HashMap::new();
126
        // FIXME(eta): You can currently specify overlapping protocols, and it'll
127
        //             just use the last transport specified.
128
        //             I attempted to fix this, but decided I didn't want to stare into the list
129
        //             builder macro void after trying it for 15 minutes.
130
        for thing in binaries {
131
            for tn in thing.protocols.iter() {
132
                ret.insert(tn.clone(), thing.clone().try_into()?);
133
            }
134
        }
135
        for opt in ret.values() {
136
            match opt {
137
                TransportOptions::Unmanaged(u) => {
138
                    if !u.is_localhost() {
139
                        warn!(
140
                            "Configured to connect to a PT on a non-local addresses. This is usually insecure! We recommend running PTs on localhost only."
141
                        );
142
                    }
143
                }
144
                #[cfg(feature = "managed-pts")]
145
                TransportOptions::Managed(_) => {
146
                    // Nothing to check here,
147
                    // since we should spawn the managed PT and
148
                    // we shouldn't know what address it will listen on yet.
149
                }
150
            }
151
        }
152
        Ok(ret)
153
    }
154

            
155
    /// Create a new PtMgr.
156
    // TODO: maybe don't have the Vec directly exposed?
157
    pub fn new(
158
        transports: Vec<TransportConfig>,
159
        #[allow(unused)] state_dir: PathBuf,
160
        #[allow(unused)] path_resolver: Arc<CfgPathResolver>,
161
        outbound_proxy: Option<ProxyProtocol>,
162
        rt: R,
163
    ) -> Result<Self, PtError> {
164
        let state = PtSharedState {
165
            #[cfg(feature = "managed-pts")]
166
            managed_cmethods: Default::default(),
167
            configured: Self::transform_config(transports)?,
168
            outbound_proxy,
169
        };
170
        let state = Arc::new(RwLock::new(state));
171

            
172
        // reactor is only needed if we support managed pts
173
        #[cfg(feature = "managed-pts")]
174
        let tx = {
175
            let (tx, rx) = mpsc::unbounded();
176

            
177
            let mut reactor =
178
                PtReactor::new(rt.clone(), state.clone(), rx, state_dir, path_resolver);
179
            rt.spawn(async move {
180
                loop {
181
                    match reactor.run_one_step().await {
182
                        Ok(true) => return,
183
                        Ok(false) => {}
184
                        Err(e) => {
185
                            error_report!(e, "PtReactor failed");
186
                            return;
187
                        }
188
                    }
189
                }
190
            })
191
            .map_err(|e| PtError::Spawn { cause: Arc::new(e) })?;
192

            
193
            tx
194
        };
195

            
196
        Ok(Self {
197
            runtime: rt,
198
            state,
199
            #[cfg(feature = "managed-pts")]
200
            tx,
201
        })
202
    }
203

            
204
    /// Reload the configuration
205
    pub fn reconfigure(
206
        &self,
207
        how: tor_config::Reconfigure,
208
        transports: Vec<TransportConfig>,
209
        outbound_proxy: Option<ProxyProtocol>,
210
    ) -> Result<(), tor_config::ReconfigureError> {
211
        let configured = Self::transform_config(transports)?;
212
        if how == tor_config::Reconfigure::CheckAllOrNothing {
213
            return Ok(());
214
        }
215
        {
216
            let mut inner = self.state.write().expect("ptmgr poisoned");
217
            inner.configured = configured;
218
            inner.outbound_proxy = outbound_proxy;
219
        }
220
        // We don't have any way of propagating this sanely; the caller will find out the reactor
221
        // has died later on anyway.
222
        // TODO(#2634):
223
        // PT reactor doesn't appear to update its running list when Reconfigured.
224
        // Change logic in PtReactor::run_one_step(...)
225
        // to update the running list upon receiving a reconfigured message.
226
        #[cfg(feature = "managed-pts")]
227
        let _ = self.tx.unbounded_send(PtReactorMessage::Reconfigured);
228
        Ok(())
229
    }
230

            
231
    /// Given a transport name, return a method that we can use to contact that transport.
232
    ///
233
    /// May have to launch a managed transport as needed.
234
    ///
235
    /// Returns Ok(None) if no such transport exists.
236
    #[cfg(feature = "tor-channel-factory")]
237
    async fn get_cmethod_for_transport(
238
        &self,
239
        transport: &PtTransportName,
240
    ) -> Result<Option<PtClientMethod>, PtError> {
241
        let (cfg, managed_cmethod) = {
242
            // NOTE(eta): This is using a RwLock inside async code (but not across an await point).
243
            //            Arguably this is fine since it's just a small read, and nothing should ever
244
            //            hold this lock for very long.
245
            let inner = self.state.read().expect("ptmgr poisoned");
246
            let cfg = inner.configured.get(transport);
247
            let managed_cmethod = inner.managed_cmethods.get(transport);
248
            (cfg.cloned(), managed_cmethod.cloned())
249
        };
250

            
251
        #[cfg(not(feature = "managed-pts"))]
252
        let _ = managed_cmethod; // avoid unused variable warning
253

            
254
        match cfg {
255
            Some(TransportOptions::Unmanaged(cfg)) => {
256
                let cmethod = cfg.cmethod();
257
                trace!(
258
                    "Found configured unmanaged transport {transport} accessible via {cmethod:?}"
259
                );
260
                Ok(Some(cmethod))
261
            }
262
            #[cfg(feature = "managed-pts")]
263
            Some(TransportOptions::Managed(_cfg)) => {
264
                match managed_cmethod {
265
                    // A configured-and-running cmethod.
266
                    Some(cmethod) => {
267
                        trace!(
268
                            "Found configured managed transport {transport} accessible via {cmethod:?}"
269
                        );
270
                        Ok(Some(cmethod))
271
                    }
272
                    // A configured-but-not-running cmethod.
273
                    None => {
274
                        // There is going to be a lot happening "under the hood" here.
275
                        //
276
                        // When we are asked to get a ChannelFactory for a given
277
                        // connection, we will need to:
278
                        //    - launch the binary for that transport if it is not already running*.
279
                        //    - If we launched the binary, talk to it and see which ports it
280
                        //      is listening on.
281
                        //    - Return a ChannelFactory that connects via one of those ports,
282
                        //      using the appropriate version of SOCKS, passing K=V parameters
283
                        //      encoded properly.
284
                        //
285
                        // * As in other managers, we'll need to avoid trying to launch the same
286
                        //   transport twice if we get two concurrent requests.
287
                        //
288
                        // Later if the binary crashes, we should detect that.  We should relaunch
289
                        // it on demand.
290
                        //
291
                        // On reconfigure, we should shut down any no-longer-used transports.
292
                        //
293
                        // Maybe, we should shut down transports that haven't been used
294
                        // for a long time.
295
                        Ok(Some(self.spawn_transport(transport).await?))
296
                    }
297
                }
298
            }
299
            // No configuration for this transport.
300
            None => {
301
                trace!("Got a request for transport {transport}, which is not configured.");
302
                Ok(None)
303
            }
304
        }
305
    }
306

            
307
    /// Communicate with the PT reactor to launch a managed transport.
308
    #[cfg(all(feature = "tor-channel-factory", feature = "managed-pts"))]
309
    async fn spawn_transport(
310
        &self,
311
        transport: &PtTransportName,
312
    ) -> Result<PtClientMethod, PtError> {
313
        // Tell the reactor to spawn the PT, and wait for it.
314
        // (The reactor will handle coalescing multiple requests.)
315
        info!(
316
            "Got a request for transport {transport}, which is not currently running. Launching it."
317
        );
318

            
319
        let (tx, rx) = oneshot::channel();
320
        self.tx
321
            .unbounded_send(PtReactorMessage::Spawn {
322
                pt: transport.clone(),
323
                result: tx,
324
            })
325
            .map_err(|_| {
326
                PtError::Internal(tor_error::internal!("PT reactor closed unexpectedly"))
327
            })?;
328

            
329
        let method = match rx.await {
330
            Err(_) => {
331
                return Err(PtError::Internal(tor_error::internal!(
332
                    "PT reactor closed unexpectedly"
333
                )));
334
            }
335
            Ok(Err(e)) => {
336
                warn!("PT for {transport} failed to launch: {e}");
337
                return Err(e);
338
            }
339
            Ok(Ok(method)) => method,
340
        };
341

            
342
        info!("Successfully launched PT for {transport} at {method:?}.");
343
        Ok(method)
344
    }
345
}
346

            
347
/// A SOCKS endpoint to connect through a pluggable transport.
348
#[derive(Debug, Clone, PartialEq, Eq)]
349
pub struct PtClientMethod {
350
    /// The SOCKS protocol version to use.
351
    pub(crate) kind: SocksVersion,
352
    /// The socket address to connect to.
353
    pub(crate) endpoint: SocketAddr,
354
}
355

            
356
impl PtClientMethod {
357
    /// Get the SOCKS protocol version to use.
358
    pub fn kind(&self) -> SocksVersion {
359
        self.kind
360
    }
361

            
362
    /// Get the socket address to connect to.
363
    pub fn endpoint(&self) -> SocketAddr {
364
        self.endpoint
365
    }
366
}
367

            
368
#[cfg(feature = "tor-channel-factory")]
369
#[async_trait]
370
impl<R: Runtime> tor_chanmgr::factory::AbstractPtMgr for PtMgr<R> {
371
    async fn factory_for_transport(
372
        &self,
373
        transport: &PtTransportName,
374
    ) -> Result<Option<Arc<dyn ChannelFactory + Send + Sync>>, Arc<dyn AbstractPtError>> {
375
        let cmethod = match self.get_cmethod_for_transport(transport).await {
376
            Err(e) => return Err(Arc::new(e)),
377
            Ok(None) => return Ok(None),
378
            Ok(Some(m)) => m,
379
        };
380

            
381
        let proxy = ExternalProxyPlugin::new(self.runtime.clone(), cmethod.endpoint, cmethod.kind);
382
        let factory = ChanBuilder::new_client(self.runtime.clone(), proxy);
383
        // FIXME(eta): Should we cache constructed factories? If no: should this still be an Arc?
384
        // FIXME(eta): Should we track what transports are live somehow, so we can shut them down?
385
        Ok(Some(Arc::new(factory)))
386
    }
387
}