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}