1use std::env;
2use std::fs;
3use std::io;
4use std::path::PathBuf;
5
6use rings_gateway::GatewayConfig;
7use serde::Deserialize;
8use serde::Serialize;
9
10use crate::error::Error;
11use crate::error::Result;
12use crate::onion::OnionExitPolicy;
13use crate::onion::OnionExitService;
14use crate::onion::OnionServiceName;
15use crate::online::OnlineNodeType;
16use crate::prelude::rings_core::dht::default_storage_virtual_positions_per_owner;
17use crate::prelude::rings_core::dht::DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER;
18use crate::prelude::rings_core::ecc::SecretKey;
19use crate::prelude::SessionSk;
20use crate::processor::ProcessorConfig;
21use crate::processor::ProcessorConfigSerialized;
22use crate::util::ensure_parent_dir;
23use crate::util::expand_home;
24
25lazy_static::lazy_static! {
26 static ref DEFAULT_DATA_STORAGE_CONFIG: StorageConfig = StorageConfig {
27 path: get_storage_location(".rings", "data"),
28 capacity: DEFAULT_STORAGE_CAPACITY,
29 };
30 static ref DEFAULT_MEASURE_STORAGE_CONFIG: StorageConfig = StorageConfig {
31 path: get_storage_location(".rings", "measure"),
32 capacity: DEFAULT_STORAGE_CAPACITY,
33 };
34}
35
36pub const DEFAULT_NETWORK_ID: u32 = 1;
38pub const DEFAULT_INTERNAL_API_PORT: u16 = 50000;
40pub const DEFAULT_EXTERNAL_API_ADDR: &str = "127.0.0.1:50001";
42pub const DEFAULT_ENDPOINT_URL: &str = "http://127.0.0.1:50000";
44pub const DEFAULT_ICE_SERVERS: &str = "stun://stun.l.google.com:19302";
46pub const DEFAULT_STABILIZE_INTERVAL: u64 = 15;
48pub const DEFAULT_STORAGE_CAPACITY: u32 = 200000000;
50pub const DEFAULT_GATEWAY_STATUS_REFRESH_SECS: u64 = 2;
52
53#[derive(Debug, Clone, Deserialize, Serialize)]
55pub struct NativeGatewayConfig {
56 #[serde(default = "default_true")]
58 pub enabled: bool,
59 #[serde(flatten)]
61 pub runtime: GatewayConfig,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub interface_name: Option<String>,
65 #[serde(default = "default_gateway_route_ledger_path")]
67 pub route_ledger_path: String,
68 #[serde(default = "default_gateway_unix_helper_socket")]
70 pub unix_helper_socket: String,
71 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub wintun_dll_path: Option<String>,
74 #[serde(default = "default_gateway_status_refresh_secs")]
76 pub status_refresh_secs: u64,
77 #[serde(default = "OnionServiceName::tcp")]
79 pub onion_service: OnionServiceName,
80 #[serde(default)]
82 pub onion_hop_count: usize,
83 #[serde(default)]
85 pub onion_allow_short_paths: bool,
86}
87
88const fn default_true() -> bool {
89 true
90}
91
92const fn default_gateway_status_refresh_secs() -> u64 {
93 DEFAULT_GATEWAY_STATUS_REFRESH_SECS
94}
95
96fn default_gateway_route_ledger_path() -> String {
97 get_storage_location(".rings", "gateway-routes.json")
98}
99
100fn default_gateway_unix_helper_socket() -> String {
101 get_storage_location(".rings", "gateway-helper.sock")
102}
103
104pub fn get_storage_location<P>(prefix: P, path: P) -> String
106where P: AsRef<std::path::Path> {
107 let home_dir = env::var_os("HOME").map(PathBuf::from);
108 let storage_path = match home_dir {
109 Some(dir) => dir.join(prefix).join(path),
110 None => std::path::Path::new("data").join(prefix).join(path),
111 };
112 storage_path.to_string_lossy().to_string()
113}
114
115#[derive(Debug, Clone, Deserialize, Serialize)]
117pub struct Config {
118 pub network_id: u32,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub ecdsa_key: Option<SecretKey>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub session_manager: Option<String>,
126 pub session_sk: Option<String>,
128 pub internal_api_port: u16,
130 pub external_api_addr: String,
132 pub endpoint_url: String,
134 pub ice_servers: String,
136 pub stabilize_interval: u64,
138 #[serde(default = "crate::registration::default_online_node_heartbeat_interval_secs")]
140 pub online_node_heartbeat_interval_secs: u64,
141 #[serde(default = "crate::registration::default_online_node_ttl_secs")]
143 pub online_node_ttl_secs: u64,
144 #[serde(default = "crate::registration::default_online_node_type")]
146 pub online_node_type: OnlineNodeType,
147 #[serde(default = "crate::registration::default_advertise_presence")]
149 pub advertise_presence: bool,
150 #[serde(default = "crate::onion::default_advertise_onion_relay")]
152 pub advertise_onion_relay: bool,
153 #[serde(default = "crate::onion::default_advertise_onion_exit")]
155 pub advertise_onion_exit: bool,
156 #[serde(default = "crate::onion::default_onion_exit_heartbeat_interval_secs")]
158 pub onion_exit_heartbeat_interval_secs: u64,
159 #[serde(default = "crate::onion::default_onion_exit_ttl_secs")]
161 pub onion_exit_ttl_secs: u64,
162 #[serde(default = "crate::onion::default_onion_exit_services")]
164 pub onion_exit_services: Vec<OnionExitService>,
165 #[serde(default = "crate::onion::default_onion_exit_policy")]
167 pub onion_exit_policy: OnionExitPolicy,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
170 pub onion_http_proxy_addr: Option<String>,
171 #[serde(default = "OnionServiceName::tcp")]
173 pub onion_http_proxy_service: OnionServiceName,
174 #[serde(default)]
176 pub onion_http_proxy_hop_count: usize,
177 #[serde(default)]
179 pub onion_http_proxy_allow_short_paths: bool,
180 #[serde(default = "crate::onion::proxy::http::default_connect_header_timeout_secs")]
182 pub onion_http_proxy_header_timeout_secs: u64,
183 #[serde(default = "crate::onion::proxy::http::default_max_connect_connections")]
185 pub onion_http_proxy_max_connections: usize,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
188 pub gateway: Option<NativeGatewayConfig>,
189 #[serde(default = "default_storage_virtual_positions_per_owner")]
191 pub dht_virtual_nodes: u16,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub external_ip: Option<String>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub webrtc_udp_port_min: Option<u16>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub webrtc_udp_port_max: Option<u16>,
201 pub data_storage: StorageConfig,
203 pub measure_storage: StorageConfig,
205}
206
207impl TryFrom<Config> for ProcessorConfigSerialized {
208 type Error = Error;
209 fn try_from(config: Config) -> Result<Self> {
210 let session_sk: String = if let Some(sk) = config.ecdsa_key {
212 tracing::warn!("Field `ecdsa_key` is deprecated, use `session_sk` instead.");
213 SessionSk::new_with_seckey(&sk)
214 .and_then(|session_sk| session_sk.dump())
215 .map_err(|e| Error::VerifyError(e.to_string()))?
216 } else if let Some(ssk) = config.session_manager {
217 tracing::warn!("Field `session_manager` is deprecated, use `session_sk` instead.");
218 ssk
219 } else {
220 let Some(ssk_file) = config.session_sk else {
221 return Err(Error::InvalidData);
222 };
223 let ssk_file_expand_home = expand_home(&ssk_file)?;
224 fs::read_to_string(ssk_file_expand_home).unwrap_or_else(|e| {
225 tracing::warn!("Read session_sk file failed: {e:?}. Handling it as raw session_sk string. This mode is deprecated. please use a file path.");
226 ssk_file
227 })
228 };
229
230 let mut cs = Self::new(
231 config.network_id,
232 config.ice_servers,
233 session_sk,
234 config.stabilize_interval,
235 )
236 .online_node_heartbeat_interval_secs(config.online_node_heartbeat_interval_secs)
237 .online_node_ttl_secs(config.online_node_ttl_secs)
238 .online_node_type(config.online_node_type)
239 .advertise_presence(config.advertise_presence)
240 .advertise_onion_relay(config.advertise_onion_relay)
241 .advertise_onion_exit(config.advertise_onion_exit)
242 .onion_exit_heartbeat_interval_secs(config.onion_exit_heartbeat_interval_secs)
243 .onion_exit_ttl_secs(config.onion_exit_ttl_secs)
244 .onion_exit_services(config.onion_exit_services)
245 .onion_exit_policy(config.onion_exit_policy)
246 .dht_virtual_nodes(config.dht_virtual_nodes);
247
248 cs = if let Some(ext_ip) = config.external_ip {
249 cs.external_address(ext_ip)
250 } else {
251 cs
252 };
253 let udp_range = crate::processor::parse_webrtc_udp_port_range(
254 config.webrtc_udp_port_min,
255 config.webrtc_udp_port_max,
256 )?;
257 cs = if let Some(range) = udp_range {
258 cs.webrtc_udp_port_range(range)
259 } else {
260 cs
261 };
262
263 Ok(cs)
264 }
265}
266
267impl TryFrom<Config> for ProcessorConfig {
268 type Error = Error;
269 fn try_from(config: Config) -> Result<Self> {
270 ProcessorConfigSerialized::try_from(config).and_then(Self::try_from)
271 }
272}
273
274impl Config {
275 pub fn new<P>(session_sk: P) -> Self
277 where P: AsRef<std::path::Path> {
278 let session_sk = session_sk.as_ref().to_string_lossy().to_string();
279 Self {
280 network_id: DEFAULT_NETWORK_ID,
281 ecdsa_key: None,
282 session_manager: None,
283 session_sk: Some(session_sk),
284 internal_api_port: DEFAULT_INTERNAL_API_PORT,
285 external_api_addr: DEFAULT_EXTERNAL_API_ADDR.to_string(),
286 endpoint_url: DEFAULT_ENDPOINT_URL.to_string(),
287 ice_servers: DEFAULT_ICE_SERVERS.to_string(),
288 stabilize_interval: DEFAULT_STABILIZE_INTERVAL,
289 online_node_heartbeat_interval_secs:
290 crate::registration::default_online_node_heartbeat_interval_secs(),
291 online_node_ttl_secs: crate::registration::default_online_node_ttl_secs(),
292 online_node_type: crate::registration::default_online_node_type(),
293 advertise_presence: crate::registration::default_advertise_presence(),
294 advertise_onion_relay: crate::onion::default_advertise_onion_relay(),
295 advertise_onion_exit: crate::onion::default_advertise_onion_exit(),
296 onion_exit_heartbeat_interval_secs:
297 crate::onion::default_onion_exit_heartbeat_interval_secs(),
298 onion_exit_ttl_secs: crate::onion::default_onion_exit_ttl_secs(),
299 onion_exit_services: crate::onion::default_onion_exit_services(),
300 onion_exit_policy: crate::onion::default_onion_exit_policy(),
301 onion_http_proxy_addr: None,
302 onion_http_proxy_service: OnionServiceName::tcp(),
303 onion_http_proxy_hop_count: 0,
304 onion_http_proxy_allow_short_paths: false,
305 onion_http_proxy_header_timeout_secs:
306 crate::onion::proxy::http::default_connect_header_timeout_secs(),
307 onion_http_proxy_max_connections:
308 crate::onion::proxy::http::default_max_connect_connections(),
309 gateway: None,
310 dht_virtual_nodes: DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER,
311 external_ip: None,
312 webrtc_udp_port_min: None,
313 webrtc_udp_port_max: None,
314 data_storage: DEFAULT_DATA_STORAGE_CONFIG.clone(),
315 measure_storage: DEFAULT_MEASURE_STORAGE_CONFIG.clone(),
316 }
317 }
318
319 pub fn write_fs<P>(&self, path: P) -> Result<String>
321 where P: AsRef<std::path::Path> {
322 let path = expand_home(path)?;
323 ensure_parent_dir(&path)?;
324 let f =
325 fs::File::create(path.as_path()).map_err(|e| Error::CreateFileError(e.to_string()))?;
326 let f_writer = io::BufWriter::new(f);
327 serde_yaml::to_writer(f_writer, self).map_err(|_| Error::EncodeError)?;
328 path.to_str()
329 .map(str::to_owned)
330 .ok_or_else(|| Error::PathUtf8Error(path.display().to_string()))
331 }
332
333 pub fn read_fs<P>(path: P) -> Result<Config>
335 where P: AsRef<std::path::Path> {
336 let path = expand_home(path)?;
337 tracing::debug!("Read config from: {:?}", path);
338 let f = fs::File::open(path).map_err(|e| Error::OpenFileError(e.to_string()))?;
339 let f_rdr = io::BufReader::new(f);
340 serde_yaml::from_reader(f_rdr).map_err(|_| Error::EncodeError)
341 }
342}
343
344#[derive(Debug, Clone, Deserialize, Serialize)]
346pub struct StorageConfig {
347 pub path: String,
349 pub capacity: u32,
351}
352
353impl StorageConfig {
354 pub fn new(path: &str, capacity: u32) -> Self {
356 Self {
357 path: path.to_string(),
358 capacity,
359 }
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 fn dumped_session_sk() -> String {
368 let key = SecretKey::random();
369 let session = match SessionSk::new_with_seckey(&key) {
370 Ok(session) => session,
371 Err(error) => panic!("session key construction failed: {error}"),
372 };
373 match session.dump() {
374 Ok(dump) => dump,
375 Err(error) => panic!("session key dump failed: {error}"),
376 }
377 }
378
379 #[test]
380 fn test_deserialization_defaults_online_registration_fields() {
381 let yaml = r#"
382network_id: 1
383session_sk: session_sk
384internal_api_port: 50000
385external_api_addr: 127.0.0.1:50001
386endpoint_url: http://127.0.0.1:50000
387ice_servers: stun://stun.l.google.com:19302
388stabilize_interval: 15
389external_ip: null
390webrtc_udp_port_min: null
391webrtc_udp_port_max: null
392data_storage:
393 path: /Users/foo/.rings/data
394 capacity: 200000000
395measure_storage:
396 path: /Users/foo/.rings/measure
397 capacity: 200000000
398"#;
399 let cfg: Config = serde_yaml::from_str(yaml).unwrap();
400 assert_eq!(cfg.network_id, 1);
401 assert_eq!(
402 cfg.dht_virtual_nodes,
403 DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER
404 );
405 assert!(cfg.advertise_presence);
406 assert!(!cfg.advertise_onion_relay);
407 assert!(!cfg.advertise_onion_exit);
408 assert_eq!(cfg.onion_http_proxy_addr, None);
409 assert_eq!(cfg.onion_http_proxy_service, OnionServiceName::tcp());
410 assert_eq!(cfg.onion_http_proxy_hop_count, 0);
411 assert!(!cfg.onion_http_proxy_allow_short_paths);
412 assert_eq!(
413 cfg.onion_http_proxy_header_timeout_secs,
414 crate::onion::proxy::http::default_connect_header_timeout_secs()
415 );
416 assert_eq!(
417 cfg.onion_http_proxy_max_connections,
418 crate::onion::proxy::http::default_max_connect_connections()
419 );
420 assert_eq!(
421 cfg.onion_exit_services,
422 crate::onion::default_onion_exit_services()
423 );
424 assert!(cfg.gateway.is_none());
425 }
426
427 #[test]
428 fn test_deserialization_preserves_explicit_disabled_dht_virtual_nodes() {
429 let yaml = r#"
430network_id: 1
431session_sk: session_sk
432internal_api_port: 50000
433external_api_addr: 127.0.0.1:50001
434endpoint_url: http://127.0.0.1:50000
435ice_servers: stun://stun.l.google.com:19302
436stabilize_interval: 15
437dht_virtual_nodes: 0
438external_ip: null
439webrtc_udp_port_min: null
440webrtc_udp_port_max: null
441data_storage:
442 path: /Users/foo/.rings/data
443 capacity: 200000000
444measure_storage:
445 path: /Users/foo/.rings/measure
446 capacity: 200000000
447"#;
448
449 let cfg: Config = serde_yaml::from_str(yaml).unwrap();
450
451 assert_eq!(cfg.dht_virtual_nodes, 0);
452 }
453
454 #[test]
455 fn test_config_with_valid_webrtc_udp_range_builds_processor_config() {
456 let mut config = Config::new(dumped_session_sk());
457 config.webrtc_udp_port_min = Some(49160);
458 config.webrtc_udp_port_max = Some(49200);
459
460 let processor_config = ProcessorConfig::try_from(config);
461
462 assert!(matches!(
463 processor_config.and_then(|config| config.webrtc_udp_port_range()),
464 Ok(Some(range)) if range.min() == 49160 && range.max() == 49200
465 ));
466 }
467
468 #[test]
469 fn test_config_with_partial_webrtc_udp_range_is_rejected() {
470 let mut config = Config::new(dumped_session_sk());
471 config.webrtc_udp_port_min = Some(49160);
472
473 let processor_config = ProcessorConfig::try_from(config);
474
475 assert!(matches!(
476 processor_config,
477 Err(Error::IncompleteWebrtcUdpPortRange {
478 min: Some(49160),
479 max: None
480 })
481 ));
482 }
483}