darkfi/net/settings.rs
1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::collections::HashMap;
20
21use structopt::StructOpt;
22use url::Url;
23
24use crate::error::{Error, Result};
25
26type BlacklistEntry = (String, Vec<String>, Vec<u16>);
27
28/// Ban policies definitions.
29///
30/// If the ban policy is set to `Relaxed` will not ban peers in case
31/// they send a message without a corresponding MessageDispatcher.
32/// This is useful for nodes that may not be subscribed to protocols,
33/// such as Lilith. For most uses this should be set to `Strict`.
34///
35/// TODO: this will be deprecated when we introduce the p2p resource
36/// mananger.
37#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
38#[serde(rename_all = "lowercase")]
39pub enum BanPolicy {
40 #[default]
41 Strict,
42
43 Relaxed,
44}
45
46/// P2P network settings. The scope of this is a P2P network instance
47/// configured by the library user.
48#[derive(Debug, Clone)]
49pub struct Settings {
50 /// Only used for debugging, compromises privacy when set
51 pub node_id: String,
52 /// P2P accept addresses the instance listens on for inbound connections
53 pub inbound_addrs: Vec<Url>,
54 /// P2P external addresses the instance advertises so other peers can
55 /// reach us and connect to us, as long as inbound addrs are configured
56 pub external_addrs: Vec<Url>,
57 /// Peer nodes to manually connect to
58 pub peers: Vec<Url>,
59 /// Seed nodes to connect to for peer discovery and/or advertising our
60 /// own external addresses
61 pub seeds: Vec<Url>,
62 /// Magic bytes should be unique per P2P network.
63 /// Avoid bleeding of networks.
64 pub magic_bytes: MagicBytes,
65 /// Application version, used for convenient protocol matching
66 pub app_version: semver::Version,
67 /// Application Identifier
68 pub app_name: String,
69 /// Whitelisted network transports for outbound connections
70 pub active_profiles: Vec<String>,
71 /// Transports allowed to be mixed (tcp, tcp+tls, tor, tor+tls)
72 /// When transport is added to this list the corresponding transport
73 /// in active_profiles is used to connect to the node.
74 /// Supported mixing scenarios include
75 /// active_profile | mixed_profile
76 /// tor | tcp
77 /// tor+tls | tcp+tls
78 /// socks5 | tor
79 /// socks5 | tcp
80 /// socks5+tls | tor+tls
81 /// socks5+tls | tcp+tls
82 pub mixed_profiles: Vec<String>,
83 /// Tor socks5 proxy to connect to when socks5 or socks5+tls are added to active profiles
84 /// and transport mixing is enabled
85 pub tor_socks5_proxy: Option<Url>,
86 /// Nym socks5 proxy to connect to when socks5 or socks5+tls are added to active profiles
87 /// and transport mixing is enabled
88 pub nym_socks5_proxy: Option<Url>,
89 /// I2p Socks5 proxy to connect to i2p eepsite (hidden services)
90 pub i2p_socks5_proxy: Url,
91 /// Outbound connection slots number, this many connections will be
92 /// attempted. (This does not include manual connections)
93 pub outbound_connections: usize,
94 /// Inbound connection slots number, this many active listening connections
95 /// will be allowed. (This does not include manual connections)
96 pub inbound_connections: usize,
97 /// Allow localnet hosts
98 pub localnet: bool,
99 /// Cooling off time for peer discovery when unsuccessful
100 pub outbound_peer_discovery_cooloff_time: u64,
101 /// Time between peer discovery attempts
102 pub outbound_peer_discovery_attempt_time: u64,
103 /// Maximum number of addresses (with preferred transports) to receive from
104 /// seeds and peers.
105 /// If undefined, `outbound_connections` will be used instead.
106 pub getaddrs_max: Option<u32>,
107 /// P2P datastore path
108 pub p2p_datastore: Option<String>,
109 /// Hostlist storage path
110 pub hostlist: Option<String>,
111 /// Pause interval within greylist refinery process
112 pub greylist_refinery_interval: u64,
113 /// Percent of connections to come from the whitelist
114 pub white_connect_percent: usize,
115 /// Number of goldlist connections
116 pub gold_connect_count: usize,
117 /// If this is true, strictly follow the gold_connect_count and
118 /// white_connect_percent settings. Otherwise, connect to greylist
119 /// entries if we have no white or gold connections.
120 pub slot_preference_strict: bool,
121 /// Number of seconds with no connections after which refinery
122 /// process is paused.
123 pub time_with_no_connections: u64,
124 /// Nodes to avoid interacting with for the duration of the program,
125 /// in the format ["host", ["scheme", "scheme"], [port, port]]
126 /// If scheme is left empty it will default to "tcp+tls".
127 /// If ports are left empty all ports from this peer will be blocked.
128 pub blacklist: Vec<BlacklistEntry>,
129 /// Do not ban nodes that send messages without dispatchers if set
130 /// to `Relaxed`. For most uses, should be set to `Strict`.
131 pub ban_policy: BanPolicy,
132 /// Mapping of transport/scheme to Network Profile
133 pub profiles: HashMap<String, NetworkProfile>,
134}
135
136impl Default for Settings {
137 fn default() -> Self {
138 let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.0.0");
139 let app_version = semver::Version::parse(version).unwrap();
140 let app_name = option_env!("CARGO_PKG_NAME").unwrap_or("").to_string();
141
142 Self {
143 node_id: String::new(),
144 inbound_addrs: vec![],
145 external_addrs: vec![],
146 magic_bytes: Default::default(),
147 peers: vec![],
148 seeds: vec![],
149 app_version,
150 app_name,
151 active_profiles: vec![],
152 mixed_profiles: vec![],
153 tor_socks5_proxy: None,
154 nym_socks5_proxy: None,
155 i2p_socks5_proxy: Url::parse("socks5://127.0.0.1:4447").unwrap(),
156 outbound_connections: 8,
157 inbound_connections: 8,
158 localnet: false,
159 outbound_peer_discovery_cooloff_time: 30,
160 outbound_peer_discovery_attempt_time: 5,
161 getaddrs_max: None,
162 p2p_datastore: None,
163 hostlist: None,
164 greylist_refinery_interval: 15,
165 white_connect_percent: 70,
166 gold_connect_count: 2,
167 slot_preference_strict: false,
168 time_with_no_connections: 30,
169 blacklist: vec![],
170 ban_policy: BanPolicy::Strict,
171 profiles: HashMap::new(),
172 }
173 }
174}
175
176impl Settings {
177 /// Returns `outbound_connect_timeout` for a specific profile.
178 pub fn outbound_connect_timeout(&self, profile: &str) -> u64 {
179 self.profiles.get(profile).unwrap_or(&NetworkProfile::default()).outbound_connect_timeout
180 }
181
182 /// Returns the maximum `outbound_connect_timeout` across all profiles,
183 /// selecting a conservative value suitable for the slowest network profile.
184 pub fn outbound_connect_timeout_max(&self) -> u64 {
185 self.profiles
186 .values()
187 .map(|p| p.outbound_connect_timeout)
188 .max()
189 .unwrap_or(NetworkProfile::default().outbound_connect_timeout)
190 }
191
192 pub fn channel_heartbeat_interval(&self, profile: &str) -> u64 {
193 self.profiles.get(profile).unwrap_or(&NetworkProfile::default()).channel_heartbeat_interval
194 }
195
196 pub fn channel_handshake_timeout(&self, profile: &str) -> u64 {
197 self.profiles.get(profile).unwrap_or(&NetworkProfile::default()).channel_handshake_timeout
198 }
199}
200
201/// Distinguishes distinct P2P networks
202#[derive(serde::Deserialize, Debug, Clone)]
203pub struct MagicBytes(pub [u8; 4]);
204
205impl Default for MagicBytes {
206 fn default() -> Self {
207 Self([0xd9, 0xef, 0xb6, 0x7d])
208 }
209}
210
211/// Defines the network settings so we can have P2P configurations in
212/// TOML files.
213#[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
214#[structopt()]
215pub struct SettingsOpt {
216 /// P2P accept address node listens to for inbound connections
217 #[serde(default)]
218 #[structopt(long = "accept")]
219 pub inbound: Vec<Url>,
220
221 /// Outbound connection slots number
222 #[structopt(long = "outbound-slots")]
223 pub outbound_connections: Option<usize>,
224
225 /// Inbound connection slots number
226 #[structopt(long = "inbound-slots")]
227 pub inbound_connections: Option<usize>,
228
229 #[serde(default)]
230 #[structopt(skip)]
231 /// Magic bytes used to distinguish P2P distinct networks and
232 /// avoid nodes bleeding due to user config error.
233 pub magic_bytes: MagicBytes,
234
235 /// P2P external addresses node advertises so other peers can
236 /// reach us and connect to us, as long as inbound addresses
237 /// are also configured
238 #[serde(default)]
239 #[structopt(long)]
240 pub external_addrs: Vec<Url>,
241
242 /// Peer nodes to manually connect to
243 #[serde(default)]
244 #[structopt(long)]
245 pub peers: Vec<Url>,
246
247 /// Seed nodes to connect to for peers retrieval and/or
248 /// advertising our own external addresses
249 #[serde(default)]
250 #[structopt(long)]
251 pub seeds: Vec<Url>,
252
253 /// Connection establishment timeout in seconds
254 #[structopt(skip)]
255 pub outbound_connect_timeout: Option<u64>,
256
257 /// Exchange versions (handshake) timeout in seconds
258 #[structopt(skip)]
259 pub channel_handshake_timeout: Option<u64>,
260
261 /// Ping-pong exchange execution interval in seconds
262 #[structopt(skip)]
263 pub channel_heartbeat_interval: Option<u64>,
264
265 /// Only used for debugging. Compromises privacy when set.
266 #[serde(default)]
267 #[structopt(skip)]
268 pub node_id: String,
269
270 /// Preferred transports for outbound connections
271 #[serde(default)]
272 #[structopt(long = "network-profiles")]
273 pub active_profiles: Option<Vec<String>>,
274
275 /// Transports allowed to be mixed (tcp, tcp+tls, tor, tor+tls).
276 /// When transport is added to this list the corresponding transport
277 /// in active_profiles is used to connect to the node.
278 /// Supported mixing scenarios include
279 /// tor => tcp, tor+tls => tcp+tls,
280 /// socks5 => tor, socks5 => tcp,
281 /// socks5+tls => tor+tls, socks5+tls => tcp+tls
282 /// where the first one overrides the second.
283 #[serde(default)]
284 #[structopt(long = "mixed-profiles")]
285 pub mixed_profiles: Option<Vec<String>>,
286
287 /// Tor socks5 proxy to connect to when socks5 or socks5+tls are added to active profiles
288 /// and transport mixing is enabled
289 #[structopt(long)]
290 pub tor_socks5_proxy: Option<Url>,
291
292 /// Nym socks5 proxy to connect to when socks5 or socks5+tls are added to active profiles
293 /// and transport mixing is enabled
294 #[structopt(long)]
295 pub nym_socks5_proxy: Option<Url>,
296
297 /// I2p Socks5 proxy to connect to i2p eepsite (hidden services)
298 #[structopt(long)]
299 pub i2p_socks5_proxy: Option<Url>,
300
301 /// If this is true, strictly follow the gold_connect_count and
302 /// white_connect_percent settings. Otherwise, connect to greylist
303 /// entries if we have no white or gold connections.
304 #[serde(default)]
305 #[structopt(long)]
306 pub localnet: bool,
307
308 /// Cooling off time for peer discovery when unsuccessful
309 #[structopt(skip)]
310 pub outbound_peer_discovery_cooloff_time: Option<u64>,
311
312 /// Time between peer discovery attempts
313 #[structopt(skip)]
314 pub outbound_peer_discovery_attempt_time: Option<u64>,
315
316 /// Maximum number of addresses (with preferred transports) to receive from
317 /// seeds and peers.
318 /// If undefined, `outbound_connections` will be used instead.
319 #[structopt(skip)]
320 pub getaddrs_max: Option<u32>,
321
322 /// P2P datastore path
323 #[serde(default)]
324 #[structopt(long)]
325 pub p2p_datastore: Option<String>,
326
327 /// Hosts .tsv file to use
328 #[serde(default)]
329 #[structopt(long)]
330 pub hostlist: Option<String>,
331
332 /// Pause interval within greylist refinery process
333 #[structopt(skip)]
334 pub greylist_refinery_interval: Option<u64>,
335
336 /// Number of whitelist connections
337 #[structopt(skip)]
338 pub white_connect_percent: Option<usize>,
339
340 /// Number of goldlist connections
341 #[structopt(skip)]
342 pub gold_connect_count: Option<usize>,
343
344 /// Allow localnet hosts
345 #[serde(default)]
346 #[structopt(long)]
347 pub slot_preference_strict: bool,
348
349 /// Number of seconds with no connections after which refinery
350 /// process is paused.
351 #[structopt(skip)]
352 pub time_with_no_connections: Option<u64>,
353
354 /// Nodes to avoid interacting with for the duration of the program,
355 /// in the format ["host", ["scheme", "scheme"], [port, port]]
356 /// If scheme is left empty it will default to "tcp+tls".
357 /// If ports are left empty all ports from this peer will be blocked.
358 #[serde(default)]
359 #[structopt(skip)]
360 pub blacklist: Vec<BlacklistEntry>,
361
362 /// Do not ban nodes that send messages without dispatchers if set
363 /// to `Relaxed`. For most uses, should be set to `Strict`.
364 #[serde(default)]
365 #[structopt(skip)]
366 pub ban_policy: BanPolicy,
367
368 /// Network Profile for each transport
369 #[serde(default)]
370 #[structopt(skip)]
371 pub profiles: HashMap<String, NetworkProfileOpt>,
372}
373
374impl TryFrom<(&str, &str, SettingsOpt)> for Settings {
375 type Error = Error;
376 fn try_from(st: (&str, &str, SettingsOpt)) -> Result<Self> {
377 let app_name = st.0.to_string();
378 let app_version = semver::Version::parse(st.1)?;
379 let opt = st.2;
380
381 let def = Settings::default();
382 let mut inbound_addrs = opt.inbound;
383 let mut external_addrs = opt.external_addrs;
384 let mut peers = opt.peers;
385 let mut seeds = opt.seeds;
386 let active_profiles = opt.active_profiles.unwrap_or(def.active_profiles);
387 let mixed_profiles = opt.mixed_profiles.unwrap_or(def.mixed_profiles);
388
389 // check all the active profiles that are not mixed are found in net.profiles
390 for name in &active_profiles {
391 if !mixed_profiles.contains(name) && !opt.profiles.contains_key(name) {
392 return Err(Error::ConfigError(format!(
393 "Active profile '{name}' not defined in net.profiles"
394 )));
395 }
396 }
397
398 let profiles: HashMap<String, NetworkProfile> = opt
399 .profiles
400 .into_iter()
401 .filter(|(k, _)| active_profiles.contains(k) && !mixed_profiles.contains(k))
402 .map(|(k, v)| {
403 inbound_addrs.extend_from_slice(&v.inbound);
404 external_addrs.extend_from_slice(&v.external_addrs);
405 peers.extend_from_slice(&v.peers);
406 seeds.extend_from_slice(&v.seeds);
407 (k.clone(), NetworkProfile::from_with_profile(v, &k))
408 })
409 .collect();
410
411 Ok(Self {
412 node_id: opt.node_id,
413 inbound_addrs,
414 external_addrs,
415 magic_bytes: opt.magic_bytes,
416 peers,
417 seeds,
418 app_version,
419 app_name,
420 active_profiles,
421 mixed_profiles,
422 tor_socks5_proxy: opt.tor_socks5_proxy,
423 nym_socks5_proxy: opt.nym_socks5_proxy,
424 i2p_socks5_proxy: opt.i2p_socks5_proxy.unwrap_or(def.i2p_socks5_proxy),
425 outbound_connections: opt.outbound_connections.unwrap_or(def.outbound_connections),
426 inbound_connections: opt.inbound_connections.unwrap_or(def.inbound_connections),
427 localnet: opt.localnet,
428 outbound_peer_discovery_cooloff_time: opt
429 .outbound_peer_discovery_cooloff_time
430 .unwrap_or(def.outbound_peer_discovery_cooloff_time),
431 outbound_peer_discovery_attempt_time: opt
432 .outbound_peer_discovery_attempt_time
433 .unwrap_or(def.outbound_peer_discovery_attempt_time),
434 getaddrs_max: opt.getaddrs_max,
435 p2p_datastore: opt.p2p_datastore,
436 hostlist: opt.hostlist,
437 greylist_refinery_interval: opt
438 .greylist_refinery_interval
439 .unwrap_or(def.greylist_refinery_interval),
440 white_connect_percent: opt.white_connect_percent.unwrap_or(def.white_connect_percent),
441 gold_connect_count: opt.gold_connect_count.unwrap_or(def.gold_connect_count),
442 slot_preference_strict: opt.slot_preference_strict,
443 time_with_no_connections: opt
444 .time_with_no_connections
445 .unwrap_or(def.time_with_no_connections),
446 blacklist: opt.blacklist,
447 ban_policy: opt.ban_policy,
448 profiles,
449 })
450 }
451}
452
453#[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
454#[structopt()]
455pub struct NetworkProfileOpt {
456 /// P2P accept address node listens to for inbound connections
457 #[serde(default)]
458 #[structopt(long = "accept")]
459 pub inbound: Vec<Url>,
460
461 /// P2P external addresses node advertises so other peers can
462 /// reach us and connect to us, as long as inbound addresses
463 /// are also configured
464 #[serde(default)]
465 #[structopt(long)]
466 pub external_addrs: Vec<Url>,
467
468 /// Peer nodes to manually connect to
469 #[serde(default)]
470 #[structopt(long)]
471 pub peers: Vec<Url>,
472
473 /// Seed nodes to connect to for peers retrieval and/or
474 /// advertising our own external addresses
475 #[serde(default)]
476 #[structopt(long)]
477 pub seeds: Vec<Url>,
478
479 /// Connection establishment timeout in seconds
480 #[structopt(skip)]
481 pub outbound_connect_timeout: Option<u64>,
482
483 /// Exchange versions (handshake) timeout in seconds
484 #[structopt(skip)]
485 pub channel_handshake_timeout: Option<u64>,
486
487 /// Ping-pong exchange execution interval in seconds
488 #[structopt(skip)]
489 pub channel_heartbeat_interval: Option<u64>,
490}
491
492/// Network Profile info unique for each profile/transport
493#[derive(Debug, Clone)]
494pub struct NetworkProfile {
495 /// Outbound connection timeout (in seconds)
496 pub outbound_connect_timeout: u64,
497 /// Exchange versions (handshake) timeout (in seconds)
498 pub channel_handshake_timeout: u64,
499 /// Ping-pong exchange execution interval (in seconds)
500 pub channel_heartbeat_interval: u64,
501}
502
503impl Default for NetworkProfile {
504 fn default() -> Self {
505 Self {
506 outbound_connect_timeout: 15,
507 channel_handshake_timeout: 10,
508 channel_heartbeat_interval: 30,
509 }
510 }
511}
512
513impl NetworkProfile {
514 /// Creates default [`NetworkProfile`] for non-clearnet profiles
515 pub fn tor_default() -> Self {
516 Self {
517 outbound_connect_timeout: 65,
518 channel_handshake_timeout: 55,
519 channel_heartbeat_interval: 90,
520 }
521 }
522
523 /// Creates [`NetworkProfile`] from [`NetworkProfileOpt`] based on the profile
524 fn from_with_profile(opt: NetworkProfileOpt, profile: &str) -> Self {
525 let def = if profile == "tcp" || profile == "tcp+tls" {
526 NetworkProfile::default()
527 } else {
528 NetworkProfile::tor_default()
529 };
530
531 Self {
532 outbound_connect_timeout: opt
533 .outbound_connect_timeout
534 .unwrap_or(def.outbound_connect_timeout),
535 channel_handshake_timeout: opt
536 .channel_handshake_timeout
537 .unwrap_or(def.channel_handshake_timeout),
538 channel_heartbeat_interval: opt
539 .channel_heartbeat_interval
540 .unwrap_or(def.channel_heartbeat_interval),
541 }
542 }
543}