aboutsummaryrefslogtreecommitdiff
path: root/crates/tor-ptmgr/src/config.rs
blob: 5f850157a7c1e9ee1947964deecd0d602f7c979f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//! Configuration logic for tor-ptmgr.

use std::net::SocketAddr;

use derive_deftly::Deftly;
use tor_config::ConfigBuildError;
use tor_config::derive::prelude::*;
use tor_config_path::CfgPath;
use tor_linkspec::PtTransportName;

#[cfg(feature = "tor-channel-factory")]
use {crate::PtClientMethod, tor_socksproto::SocksVersion};

/// A single pluggable transport.
///
/// Pluggable transports are programs that transform and obfuscate traffic on
/// the network between a Tor client and a Tor bridge, so that an adversary
/// cannot recognize it as Tor traffic.
///
/// A pluggable transport can be either _managed_ (run as an external process
/// that we launch and monitor), or _unmanaged_ (running on a local port, not
/// controlled by Arti).
#[derive(Clone, Debug, Deftly, Eq, PartialEq)]
#[derive_deftly(TorConfig)]
#[deftly(tor_config(no_default_trait, pre_build = "Self::validate"))]
pub struct TransportConfig {
    /// Names of the transport protocols that we are willing to use from this transport.
    ///
    /// (These protocols are arbitrary identifiers that describe which protocols
    /// we want. They must match names that the binary knows how to provide.)
    //
    // NOTE(eta): This doesn't use the list builder stuff, because you're not likely to
    //            set this field more than once.
    #[deftly(tor_config(no_magic, no_default))]
    pub(crate) protocols: Vec<PtTransportName>,

    /// The path to the binary to run, if any.
    ///
    /// This needs to be the path to some executable file on disk.
    ///
    /// Present only for managed transports.
    #[deftly(tor_config(default, setter(strip_option)))]
    pub(crate) path: Option<CfgPath>,

    /// One or more command-line arguments to pass to the binary.
    ///
    /// Meaningful only for managed transports.
    // TODO: Should this be OsString? That's a pain to parse...
    //
    // NOTE(eta): This doesn't use the list builder stuff, because you're not likely to
    //            set this field more than once.
    #[deftly(tor_config(no_magic, default))]
    pub(crate) arguments: Vec<String>,

    /// The location at which to contact this transport.
    ///
    /// Present only for unmanaged transports.
    #[deftly(tor_config(default, setter(strip_option)))]
    pub(crate) proxy_addr: Option<SocketAddr>,

    /// If true, launch this transport on startup.  Otherwise, we launch
    /// it on demand.
    ///
    /// Meaningful only for managed transports.
    #[deftly(tor_config(default))]
    pub(crate) run_on_startup: bool,
}

impl TransportConfigBuilder {
    /// Inspect the list of protocols (ie, transport names)
    ///
    /// If none have yet been specified, returns an empty list.
    pub fn get_protocols(&self) -> &[PtTransportName] {
        self.protocols.as_deref().unwrap_or_default()
    }

    /// Make sure that this builder is internally consistent.
    fn validate(&self) -> Result<(), ConfigBuildError> {
        // `path` can only be set if the `managed-pts` feature is enabled
        #[cfg(not(feature = "managed-pts"))]
        if self.path.is_some() {
            return Err(ConfigBuildError::NoCompileTimeSupport {
                field: "path".into(),
                problem:
                    "Indicates a managed transport, but support is not enabled by cargo features"
                        .into(),
            });
        }

        match (&self.path, &self.proxy_addr) {
            (Some(_), Some(_)) => Err(ConfigBuildError::Inconsistent {
                fields: vec!["path".into(), "proxy_addr".into()],
                problem: "Cannot provide both path and proxy_addr".into(),
            }),
            (None, None) => Err(ConfigBuildError::MissingOneOf {
                min_required: 1,
                fields: vec!["path".into(), "proxy_addr".into()],
            }),
            (None, Some(_)) => {
                if self.arguments.as_ref().is_some_and(|v| !v.is_empty()) {
                    Err(ConfigBuildError::Inconsistent {
                        fields: vec!["proxy_addr".into(), "arguments".into()],
                        problem: "Cannot provide arguments for an unmanaged transport".into(),
                    })
                } else if self.run_on_startup.is_some() {
                    Err(ConfigBuildError::Inconsistent {
                        fields: vec!["proxy_addr".into(), "run_on_startup".into()],
                        problem: "run_on_startup is meaningless for an unmanaged transport".into(),
                    })
                } else {
                    Ok(())
                }
            }
            (Some(_), None) => Ok(()),
        }
    }
}

/// The pluggable transport structure used internally. This is more type-safe than working with
/// `TransportConfig` directly, since we can't change `TransportConfig` as it's part of the public
/// API.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum TransportOptions {
    /// Options for a managed PT transport.
    #[cfg(feature = "managed-pts")]
    Managed(ManagedTransportOptions),
    /// Options for an unmanaged PT transport.
    Unmanaged(UnmanagedTransportOptions),
}

impl TryFrom<TransportConfig> for TransportOptions {
    type Error = tor_error::Bug;
    fn try_from(config: TransportConfig) -> Result<Self, Self::Error> {
        // We rely on the validation performed in `TransportConfigBuilder::validate` to ensure that
        // mutually exclusive options were not set. We could do validation again here, but it would
        // be error-prone to duplicate the validation logic. We also couldn't check things like if
        // `run_on_startup` was `Some`/`None`, since that's only available to the builder.

        if let Some(path) = config.path {
            cfg_if::cfg_if! {
                if #[cfg(feature = "managed-pts")] {
                    Ok(TransportOptions::Managed(ManagedTransportOptions {
                        protocols: config.protocols,
                        path,
                        arguments: config.arguments,
                        run_on_startup: config.run_on_startup,
                    }))
                } else {
                    let _ = path;
                    Err(tor_error::internal!(
                        "Path is set but 'managed-pts' feature is not enabled. How did this pass builder validation?"
                    ))
                }
            }
        } else if let Some(proxy_addr) = config.proxy_addr {
            Ok(TransportOptions::Unmanaged(UnmanagedTransportOptions {
                protocols: config.protocols,
                proxy_addr,
            }))
        } else {
            Err(tor_error::internal!(
                "Neither path nor proxy are set. How did this pass builder validation?"
            ))
        }
    }
}

/// A pluggable transport that is run as an external process that we launch and monitor.
#[cfg(feature = "managed-pts")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ManagedTransportOptions {
    /// See [TransportConfig::protocols].
    pub(crate) protocols: Vec<PtTransportName>,

    /// See [TransportConfig::path].
    pub(crate) path: CfgPath,

    /// See [TransportConfig::arguments].
    pub(crate) arguments: Vec<String>,

    /// See [TransportConfig::run_on_startup].
    pub(crate) run_on_startup: bool,
}

/// A pluggable transport running on a local port, not controlled by Arti.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct UnmanagedTransportOptions {
    /// See [TransportConfig::protocols].
    pub(crate) protocols: Vec<PtTransportName>,

    /// See [TransportConfig::proxy_addr].
    pub(crate) proxy_addr: SocketAddr,
}

impl UnmanagedTransportOptions {
    /// A client method that can be used to contact this transport.
    #[cfg(feature = "tor-channel-factory")]
    pub(crate) fn cmethod(&self) -> PtClientMethod {
        PtClientMethod {
            // TODO: Someday we might want to support other protocols;
            // but for now, let's see if we can get away with just socks5.
            kind: SocksVersion::V5,
            endpoint: self.proxy_addr,
        }
    }

    /// Return true if this transport is configured on localhost.
    pub(crate) fn is_localhost(&self) -> bool {
        self.proxy_addr.ip().is_loopback()
    }
}