Skip to main content

rings_node/native/
config.rs

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
36/// Default Rings network identifier for native nodes.
37pub const DEFAULT_NETWORK_ID: u32 = 1;
38/// Default internal JSON-RPC API port.
39pub const DEFAULT_INTERNAL_API_PORT: u16 = 50000;
40/// Default external JSON-RPC listener address.
41pub const DEFAULT_EXTERNAL_API_ADDR: &str = "127.0.0.1:50001";
42/// Default internal endpoint URL used by CLI clients.
43pub const DEFAULT_ENDPOINT_URL: &str = "http://127.0.0.1:50000";
44/// Default WebRTC ICE server list.
45pub const DEFAULT_ICE_SERVERS: &str = "stun://stun.l.google.com:19302";
46/// Default Chord stabilization interval in seconds.
47pub const DEFAULT_STABILIZE_INTERVAL: u64 = 15;
48/// Default storage capacity in bytes for native storage backends.
49pub const DEFAULT_STORAGE_CAPACITY: u32 = 200000000;
50/// Default interval for refreshing gateway status.
51pub const DEFAULT_GATEWAY_STATUS_REFRESH_SECS: u64 = 2;
52
53/// Native foreground-gateway configuration.
54#[derive(Debug, Clone, Deserialize, Serialize)]
55pub struct NativeGatewayConfig {
56    /// Whether `rings run` starts the gateway when this section is present.
57    #[serde(default = "default_true")]
58    pub enabled: bool,
59    /// Platform-neutral routing, TCP, and flow limits.
60    #[serde(flatten)]
61    pub runtime: GatewayConfig,
62    /// Requested Wintun interface name; on Unix the helper's `--interface` is authoritative.
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub interface_name: Option<String>,
65    /// Durable journal used directly on Windows; on Unix the helper's `--ledger` is authoritative.
66    #[serde(default = "default_gateway_route_ledger_path")]
67    pub route_ledger_path: String,
68    /// Foreground `gateway-config-unix` control socket on Linux and macOS.
69    #[serde(default = "default_gateway_unix_helper_socket")]
70    pub unix_helper_socket: String,
71    /// Optional explicit Wintun DLL path on Windows; overrides `RINGS_GATEWAY_WINTUN_DLL`.
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub wintun_dll_path: Option<String>,
74    /// Interval for refreshing Onion exit availability in gateway status.
75    #[serde(default = "default_gateway_status_refresh_secs")]
76    pub status_refresh_secs: u64,
77    /// Onion TCP exit service selected for captured flows.
78    #[serde(default = "OnionServiceName::tcp")]
79    pub onion_service: OnionServiceName,
80    /// Requested onion route hop count; zero selects the node default.
81    #[serde(default)]
82    pub onion_hop_count: usize,
83    /// Permit shorter onion paths when the requested hop count is unavailable.
84    #[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
104/// Builds the default storage path under the user home directory.
105pub 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/// Serializable native-node configuration.
116#[derive(Debug, Clone, Deserialize, Serialize)]
117pub struct Config {
118    /// Rings network identifier this node joins.
119    pub network_id: u32,
120    /// Deprecated ECDSA key field retained for backward-compatible config reads.
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub ecdsa_key: Option<SecretKey>,
123    /// Deprecated session manager field retained for backward-compatible config reads.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub session_manager: Option<String>,
126    /// Session secret key file path or legacy raw session key string.
127    pub session_sk: Option<String>,
128    /// Internal JSON-RPC API port.
129    pub internal_api_port: u16,
130    /// External JSON-RPC listener address.
131    pub external_api_addr: String,
132    /// Internal endpoint URL used by local clients.
133    pub endpoint_url: String,
134    /// WebRTC ICE server list, independent from optional gateway ingress.
135    pub ice_servers: String,
136    /// Chord stabilization interval in seconds.
137    pub stabilize_interval: u64,
138    /// Presence descriptor heartbeat interval in seconds.
139    #[serde(default = "crate::registration::default_online_node_heartbeat_interval_secs")]
140    pub online_node_heartbeat_interval_secs: u64,
141    /// Presence descriptor time-to-live in seconds.
142    #[serde(default = "crate::registration::default_online_node_ttl_secs")]
143    pub online_node_ttl_secs: u64,
144    /// Node type advertised in online-node descriptors.
145    #[serde(default = "crate::registration::default_online_node_type")]
146    pub online_node_type: OnlineNodeType,
147    /// Whether this node publishes online-node descriptors.
148    #[serde(default = "crate::registration::default_advertise_presence")]
149    pub advertise_presence: bool,
150    /// Whether this node advertises onion relay capability.
151    #[serde(default = "crate::onion::default_advertise_onion_relay")]
152    pub advertise_onion_relay: bool,
153    /// Whether this node advertises onion exit capability.
154    #[serde(default = "crate::onion::default_advertise_onion_exit")]
155    pub advertise_onion_exit: bool,
156    /// Onion-exit descriptor heartbeat interval in seconds.
157    #[serde(default = "crate::onion::default_onion_exit_heartbeat_interval_secs")]
158    pub onion_exit_heartbeat_interval_secs: u64,
159    /// Onion-exit descriptor time-to-live in seconds.
160    #[serde(default = "crate::onion::default_onion_exit_ttl_secs")]
161    pub onion_exit_ttl_secs: u64,
162    /// Onion-exit services this node can publish.
163    #[serde(default = "crate::onion::default_onion_exit_services")]
164    pub onion_exit_services: Vec<OnionExitService>,
165    /// Onion-exit target and resource policy.
166    #[serde(default = "crate::onion::default_onion_exit_policy")]
167    pub onion_exit_policy: OnionExitPolicy,
168    /// Optional local HTTP CONNECT proxy listener address.
169    #[serde(default, skip_serializing_if = "Option::is_none")]
170    pub onion_http_proxy_addr: Option<String>,
171    /// Onion service name used by the HTTP CONNECT proxy.
172    #[serde(default = "OnionServiceName::tcp")]
173    pub onion_http_proxy_service: OnionServiceName,
174    /// Requested hop count for HTTP CONNECT proxy routes.
175    #[serde(default)]
176    pub onion_http_proxy_hop_count: usize,
177    /// Whether the HTTP CONNECT proxy may use shorter routes when needed.
178    #[serde(default)]
179    pub onion_http_proxy_allow_short_paths: bool,
180    /// Timeout for reading HTTP CONNECT headers in seconds.
181    #[serde(default = "crate::onion::proxy::http::default_connect_header_timeout_secs")]
182    pub onion_http_proxy_header_timeout_secs: u64,
183    /// Maximum simultaneous HTTP CONNECT proxy connections.
184    #[serde(default = "crate::onion::proxy::http::default_max_connect_connections")]
185    pub onion_http_proxy_max_connections: usize,
186    /// Optional native TUN gateway started in the same foreground lifecycle.
187    #[serde(default, skip_serializing_if = "Option::is_none")]
188    pub gateway: Option<NativeGatewayConfig>,
189    /// Virtual DHT positions per storage owner.
190    #[serde(default = "default_storage_virtual_positions_per_owner")]
191    pub dht_virtual_nodes: u16,
192    /// Optional externally reachable IP address hint.
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub external_ip: Option<String>,
195    /// Optional lower bound for WebRTC UDP port allocation.
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub webrtc_udp_port_min: Option<u16>,
198    /// Optional upper bound for WebRTC UDP port allocation.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub webrtc_udp_port_max: Option<u16>,
201    /// Persistent DHT data storage configuration.
202    pub data_storage: StorageConfig,
203    /// Peer measurement storage configuration.
204    pub measure_storage: StorageConfig,
205}
206
207impl TryFrom<Config> for ProcessorConfigSerialized {
208    type Error = Error;
209    fn try_from(config: Config) -> Result<Self> {
210        // Support old version
211        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    /// Creates a default native-node configuration using the supplied session key path.
276    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    /// Writes this configuration to a YAML file and returns the written path.
320    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    /// Reads a native-node configuration from a YAML file.
334    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/// Configuration for a node storage backend.
345#[derive(Debug, Clone, Deserialize, Serialize)]
346pub struct StorageConfig {
347    /// Storage directory path.
348    pub path: String,
349    /// Storage capacity in bytes.
350    pub capacity: u32,
351}
352
353impl StorageConfig {
354    /// Creates a storage configuration from a path and capacity.
355    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}