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}