Skip to main content

fips_core/config/transport/
tor.rs

1//! Tor transport configuration.
2
3use serde::{Deserialize, Serialize};
4
5/// Default Tor SOCKS5 proxy address.
6const DEFAULT_TOR_SOCKS5_ADDR: &str = "127.0.0.1:9050";
7
8/// Default Tor control port address.
9const DEFAULT_TOR_CONTROL_ADDR: &str = "/run/tor/control";
10
11/// Default Tor control cookie file path (Debian standard location).
12const DEFAULT_TOR_COOKIE_PATH: &str = "/var/run/tor/control.authcookie";
13
14/// Default Tor connect timeout in milliseconds (120s — Tor circuit
15/// establishment can take 30-60s on first connect, plus SOCKS5 handshake).
16const DEFAULT_TOR_CONNECT_TIMEOUT_MS: u64 = 120_000;
17
18/// Default Tor dataplane/path budget (same as TCP).
19const DEFAULT_TOR_MTU: u16 = 1400;
20
21/// Default max inbound connections via onion service.
22const DEFAULT_TOR_MAX_INBOUND: usize = 64;
23
24/// Default HiddenServiceDir hostname file path.
25const DEFAULT_HOSTNAME_FILE: &str = "/var/lib/tor/fips_onion_service/hostname";
26
27/// Default directory mode bind address.
28const DEFAULT_DIRECTORY_BIND_ADDR: &str = "127.0.0.1:8443";
29
30/// Default advertised onion port for Nostr overlay discovery. Matches the
31/// Tor convention of `HiddenServicePort 443 127.0.0.1:<bind_port>` in torrc.
32const DEFAULT_TOR_ADVERTISED_PORT: u16 = 443;
33
34/// Tor transport instance configuration.
35///
36/// Supports three modes:
37/// - `socks5`: Outbound-only connections through a Tor SOCKS5 proxy.
38/// - `control_port`: Full bidirectional support — outbound via SOCKS5
39///   plus inbound via Tor onion service managed through the control port.
40/// - `directory`: Full bidirectional support — outbound via SOCKS5,
41///   inbound via a Tor-managed `HiddenServiceDir` onion service. No
42///   control port needed. Enables Tor `Sandbox 1` mode.
43#[derive(Debug, Clone, Default, Serialize, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct TorConfig {
46    /// Tor access mode: "socks5", "control_port", or "directory".
47    /// Default: "socks5".
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub mode: Option<String>,
50
51    /// SOCKS5 proxy address (host:port). Defaults to "127.0.0.1:9050".
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub socks5_addr: Option<String>,
54
55    /// Outbound connect timeout in milliseconds. Defaults to 120000 (120s).
56    /// Tor circuit establishment can take 30-60s, so this must be generous.
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub connect_timeout_ms: Option<u64>,
59
60    /// Dataplane/path budget advertised for Tor routes. Defaults to 1400.
61    /// Tor byte-stream framing is bounded by the FMP/FSP wire record's u16
62    /// payload length, independently of this budget.
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub mtu: Option<u16>,
65
66    /// Control port address: a Unix socket path (`/run/tor/control`) or
67    /// TCP address (`host:port`). Unix sockets are preferred for security.
68    /// Defaults to "/run/tor/control".
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub control_addr: Option<String>,
71
72    /// Control port authentication method:
73    /// `"cookie"` (read from default path),
74    /// `"cookie:/path/to/cookie"` (read from specified path), or
75    /// `"password:secret"` (password auth). Default: `"cookie"`.
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub control_auth: Option<String>,
78
79    /// Path to the Tor control cookie file. Used when control_auth is "cookie".
80    /// Defaults to "/var/run/tor/control.authcookie".
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub cookie_path: Option<String>,
83
84    /// Maximum number of inbound connections via onion service. Default: 64.
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub max_inbound_connections: Option<usize>,
87
88    /// Directory-mode onion service configuration. Only valid in
89    /// "directory" mode. Tor manages the onion service via HiddenServiceDir
90    /// in torrc; fips reads the .onion hostname from a file.
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub directory_service: Option<DirectoryServiceConfig>,
93
94    /// Whether this transport should be advertised on Nostr overlay discovery.
95    /// Default: false.
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub advertise_on_nostr: Option<bool>,
98
99    /// Public-facing onion port published in Nostr overlay adverts. Must
100    /// match the virtual port in torrc's `HiddenServicePort <port>
101    /// 127.0.0.1:<bind_port>` directive — that is the port other peers
102    /// will use to reach this onion. Default: 443.
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub advertised_port: Option<u16>,
105}
106
107/// Directory-mode onion service configuration.
108///
109/// In `directory` mode, Tor manages the onion service via `HiddenServiceDir`
110/// in torrc. FIPS reads the `.onion` address from the hostname file and
111/// binds a local TCP listener for Tor to forward inbound connections to.
112/// This mode requires no control port and enables Tor's `Sandbox 1`.
113#[derive(Debug, Clone, Default, Serialize, Deserialize)]
114#[serde(deny_unknown_fields)]
115pub struct DirectoryServiceConfig {
116    /// Path to the Tor-managed hostname file containing the .onion address.
117    /// Defaults to "/var/lib/tor/fips_onion_service/hostname".
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub hostname_file: Option<String>,
120
121    /// Local bind address for the listener that Tor forwards inbound
122    /// connections to. Must match the target in torrc's `HiddenServicePort`.
123    /// Defaults to "127.0.0.1:8443".
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub bind_addr: Option<String>,
126}
127
128impl DirectoryServiceConfig {
129    /// Path to the hostname file. Default: "/var/lib/tor/fips_onion_service/hostname".
130    pub fn hostname_file(&self) -> &str {
131        self.hostname_file
132            .as_deref()
133            .unwrap_or(DEFAULT_HOSTNAME_FILE)
134    }
135
136    /// Local bind address for the listener. Default: "127.0.0.1:8443".
137    pub fn bind_addr(&self) -> &str {
138        self.bind_addr
139            .as_deref()
140            .unwrap_or(DEFAULT_DIRECTORY_BIND_ADDR)
141    }
142}
143
144impl TorConfig {
145    /// Get the access mode. Default: "socks5".
146    pub fn mode(&self) -> &str {
147        self.mode.as_deref().unwrap_or("socks5")
148    }
149
150    /// Get the SOCKS5 proxy address. Default: "127.0.0.1:9050".
151    pub fn socks5_addr(&self) -> &str {
152        self.socks5_addr
153            .as_deref()
154            .unwrap_or(DEFAULT_TOR_SOCKS5_ADDR)
155    }
156
157    /// Get the control port address. Default: "/run/tor/control".
158    pub fn control_addr(&self) -> &str {
159        self.control_addr
160            .as_deref()
161            .unwrap_or(DEFAULT_TOR_CONTROL_ADDR)
162    }
163
164    /// Get the control auth string. Default: "cookie".
165    pub fn control_auth(&self) -> &str {
166        self.control_auth.as_deref().unwrap_or("cookie")
167    }
168
169    /// Get the cookie file path. Default: "/var/run/tor/control.authcookie".
170    pub fn cookie_path(&self) -> &str {
171        self.cookie_path
172            .as_deref()
173            .unwrap_or(DEFAULT_TOR_COOKIE_PATH)
174    }
175
176    /// Get the connect timeout in milliseconds. Default: 120000.
177    pub fn connect_timeout_ms(&self) -> u64 {
178        self.connect_timeout_ms
179            .unwrap_or(DEFAULT_TOR_CONNECT_TIMEOUT_MS)
180    }
181
182    /// Get the default MTU. Default: 1400.
183    pub fn mtu(&self) -> u16 {
184        self.mtu.unwrap_or(DEFAULT_TOR_MTU)
185    }
186
187    /// Get the max inbound connections. Default: 64.
188    pub fn max_inbound_connections(&self) -> usize {
189        self.max_inbound_connections
190            .unwrap_or(DEFAULT_TOR_MAX_INBOUND)
191    }
192
193    /// Whether this Tor transport should be advertised on Nostr discovery.
194    pub fn advertise_on_nostr(&self) -> bool {
195        self.advertise_on_nostr.unwrap_or(false)
196    }
197
198    /// Public-facing onion port published in Nostr overlay adverts.
199    /// Default: 443.
200    pub fn advertised_port(&self) -> u16 {
201        self.advertised_port.unwrap_or(DEFAULT_TOR_ADVERTISED_PORT)
202    }
203}