| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use std::collections::HashMap;
- use structopt::StructOpt;
- use url::Url;
- use crate::error::{Error, Result};
- type BlacklistEntry = (String, Vec<String>, Vec<u16>);
- /// Ban policies definitions.
- ///
- /// If the ban policy is set to `Relaxed` will not ban peers in case
- /// they send a message without a corresponding MessageDispatcher.
- /// This is useful for nodes that may not be subscribed to protocols,
- /// such as Lilith. For most uses this should be set to `Strict`.
- ///
- /// TODO: this will be deprecated when we introduce the p2p resource
- /// mananger.
- #[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
- #[serde(rename_all = "lowercase")]
- pub enum BanPolicy {
- #[default]
- Strict,
- Relaxed,
- }
- /// P2P network settings. The scope of this is a P2P network instance
- /// configured by the library user.
- #[derive(Debug, Clone)]
- pub struct Settings {
- /// Only used for debugging, compromises privacy when set
- pub node_id: String,
- /// P2P accept addresses the instance listens on for inbound connections
- pub inbound_addrs: Vec<Url>,
- /// P2P external addresses the instance advertises so other peers can
- /// reach us and connect to us, as long as inbound addrs are configured
- pub external_addrs: Vec<Url>,
- /// Peer nodes to manually connect to
- pub peers: Vec<Url>,
- /// Seed nodes to connect to for peer discovery and/or advertising our
- /// own external addresses
- pub seeds: Vec<Url>,
- /// Magic bytes should be unique per P2P network.
- /// Avoid bleeding of networks.
- pub magic_bytes: MagicBytes,
- /// Application version, used for convenient protocol matching
- pub app_version: semver::Version,
- /// Application Identifier
- pub app_name: String,
- /// Whitelisted network transports for outbound connections
- pub active_profiles: Vec<String>,
- /// Transports allowed to be mixed (tcp, tcp+tls, tor, tor+tls)
- /// When transport is added to this list the corresponding transport
- /// in active_profiles is used to connect to the node.
- /// Supported mixing scenarios include
- /// active_profile | mixed_profile
- /// tor | tcp
- /// tor+tls | tcp+tls
- /// socks5 | tor
- /// socks5 | tcp
- /// socks5+tls | tor+tls
- /// socks5+tls | tcp+tls
- pub mixed_profiles: Vec<String>,
- /// Tor socks5 proxy to connect to when socks5 or socks5+tls are added to active profiles
- /// and transport mixing is enabled
- pub tor_socks5_proxy: Option<Url>,
- /// Nym socks5 proxy to connect to when socks5 or socks5+tls are added to active profiles
- /// and transport mixing is enabled
- pub nym_socks5_proxy: Option<Url>,
- /// I2p Socks5 proxy to connect to i2p eepsite (hidden services)
- pub i2p_socks5_proxy: Url,
- /// Outbound connection slots number, this many connections will be
- /// attempted. (This does not include manual connections)
- pub outbound_connections: usize,
- /// Inbound connection slots number, this many active listening connections
- /// will be allowed. (This does not include manual connections)
- pub inbound_connections: usize,
- /// Allow localnet hosts
- pub localnet: bool,
- /// Cooling off time for peer discovery when unsuccessful
- pub outbound_peer_discovery_cooloff_time: u64,
- /// Time between peer discovery attempts
- pub outbound_peer_discovery_attempt_time: u64,
- /// Maximum number of addresses (with preferred transports) to receive from
- /// seeds and peers.
- /// If undefined, `outbound_connections` will be used instead.
- pub getaddrs_max: Option<u32>,
- /// P2P datastore path
- pub p2p_datastore: Option<String>,
- /// Hostlist storage path
- pub hostlist: Option<String>,
- /// Pause interval within greylist refinery process
- pub greylist_refinery_interval: u64,
- /// Percent of connections to come from the whitelist
- pub white_connect_percent: usize,
- /// Number of goldlist connections
- pub gold_connect_count: usize,
- /// If this is true, strictly follow the gold_connect_count and
- /// white_connect_percent settings. Otherwise, connect to greylist
- /// entries if we have no white or gold connections.
- pub slot_preference_strict: bool,
- /// Number of seconds with no connections after which refinery
- /// process is paused.
- pub time_with_no_connections: u64,
- /// Nodes to avoid interacting with for the duration of the program,
- /// in the format ["host", ["scheme", "scheme"], [port, port]]
- /// If scheme is left empty it will default to "tcp+tls".
- /// If ports are left empty all ports from this peer will be blocked.
- pub blacklist: Vec<BlacklistEntry>,
- /// Do not ban nodes that send messages without dispatchers if set
- /// to `Relaxed`. For most uses, should be set to `Strict`.
- pub ban_policy: BanPolicy,
- /// Mapping of transport/scheme to Network Profile
- pub profiles: HashMap<String, NetworkProfile>,
- }
- impl Default for Settings {
- fn default() -> Self {
- let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.0.0");
- let app_version = semver::Version::parse(version).unwrap();
- let app_name = option_env!("CARGO_PKG_NAME").unwrap_or("").to_string();
- Self {
- node_id: String::new(),
- inbound_addrs: vec![],
- external_addrs: vec![],
- magic_bytes: Default::default(),
- peers: vec![],
- seeds: vec![],
- app_version,
- app_name,
- active_profiles: vec![],
- mixed_profiles: vec![],
- tor_socks5_proxy: None,
- nym_socks5_proxy: None,
- i2p_socks5_proxy: Url::parse("socks5://127.0.0.1:4447").unwrap(),
- outbound_connections: 8,
- inbound_connections: 8,
- localnet: false,
- outbound_peer_discovery_cooloff_time: 30,
- outbound_peer_discovery_attempt_time: 5,
- getaddrs_max: None,
- p2p_datastore: None,
- hostlist: None,
- greylist_refinery_interval: 15,
- white_connect_percent: 70,
- gold_connect_count: 2,
- slot_preference_strict: false,
- time_with_no_connections: 30,
- blacklist: vec![],
- ban_policy: BanPolicy::Strict,
- profiles: HashMap::new(),
- }
- }
- }
- impl Settings {
- /// Returns `outbound_connect_timeout` for a specific profile.
- pub fn outbound_connect_timeout(&self, profile: &str) -> u64 {
- self.profiles.get(profile).unwrap_or(&NetworkProfile::default()).outbound_connect_timeout
- }
- /// Returns the maximum `outbound_connect_timeout` across all profiles,
- /// selecting a conservative value suitable for the slowest network profile.
- pub fn outbound_connect_timeout_max(&self) -> u64 {
- self.profiles
- .values()
- .map(|p| p.outbound_connect_timeout)
- .max()
- .unwrap_or(NetworkProfile::default().outbound_connect_timeout)
- }
- pub fn channel_heartbeat_interval(&self, profile: &str) -> u64 {
- self.profiles.get(profile).unwrap_or(&NetworkProfile::default()).channel_heartbeat_interval
- }
- pub fn channel_handshake_timeout(&self, profile: &str) -> u64 {
- self.profiles.get(profile).unwrap_or(&NetworkProfile::default()).channel_handshake_timeout
- }
- }
- /// Distinguishes distinct P2P networks
- #[derive(serde::Deserialize, Debug, Clone)]
- pub struct MagicBytes(pub [u8; 4]);
- impl Default for MagicBytes {
- fn default() -> Self {
- Self([0xd9, 0xef, 0xb6, 0x7d])
- }
- }
- /// Defines the network settings so we can have P2P configurations in
- /// TOML files.
- #[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
- #[structopt()]
- pub struct SettingsOpt {
- /// P2P accept address node listens to for inbound connections
- #[serde(default)]
- #[structopt(long = "accept")]
- pub inbound: Vec<Url>,
- /// Outbound connection slots number
- #[structopt(long = "outbound-slots")]
- pub outbound_connections: Option<usize>,
- /// Inbound connection slots number
- #[structopt(long = "inbound-slots")]
- pub inbound_connections: Option<usize>,
- #[serde(default)]
- #[structopt(skip)]
- /// Magic bytes used to distinguish P2P distinct networks and
- /// avoid nodes bleeding due to user config error.
- pub magic_bytes: MagicBytes,
- /// P2P external addresses node advertises so other peers can
- /// reach us and connect to us, as long as inbound addresses
- /// are also configured
- #[serde(default)]
- #[structopt(long)]
- pub external_addrs: Vec<Url>,
- /// Peer nodes to manually connect to
- #[serde(default)]
- #[structopt(long)]
- pub peers: Vec<Url>,
- /// Seed nodes to connect to for peers retrieval and/or
- /// advertising our own external addresses
- #[serde(default)]
- #[structopt(long)]
- pub seeds: Vec<Url>,
- /// Connection establishment timeout in seconds
- #[structopt(skip)]
- pub outbound_connect_timeout: Option<u64>,
- /// Exchange versions (handshake) timeout in seconds
- #[structopt(skip)]
- pub channel_handshake_timeout: Option<u64>,
- /// Ping-pong exchange execution interval in seconds
- #[structopt(skip)]
- pub channel_heartbeat_interval: Option<u64>,
- /// Only used for debugging. Compromises privacy when set.
- #[serde(default)]
- #[structopt(skip)]
- pub node_id: String,
- /// Preferred transports for outbound connections
- #[serde(default)]
- #[structopt(long = "network-profiles")]
- pub active_profiles: Option<Vec<String>>,
- /// Transports allowed to be mixed (tcp, tcp+tls, tor, tor+tls).
- /// When transport is added to this list the corresponding transport
- /// in active_profiles is used to connect to the node.
- /// Supported mixing scenarios include
- /// tor => tcp, tor+tls => tcp+tls,
- /// socks5 => tor, socks5 => tcp,
- /// socks5+tls => tor+tls, socks5+tls => tcp+tls
- /// where the first one overrides the second.
- #[serde(default)]
- #[structopt(long = "mixed-profiles")]
- pub mixed_profiles: Option<Vec<String>>,
- /// Tor socks5 proxy to connect to when socks5 or socks5+tls are added to active profiles
- /// and transport mixing is enabled
- #[structopt(long)]
- pub tor_socks5_proxy: Option<Url>,
- /// Nym socks5 proxy to connect to when socks5 or socks5+tls are added to active profiles
- /// and transport mixing is enabled
- #[structopt(long)]
- pub nym_socks5_proxy: Option<Url>,
- /// I2p Socks5 proxy to connect to i2p eepsite (hidden services)
- #[structopt(long)]
- pub i2p_socks5_proxy: Option<Url>,
- /// If this is true, strictly follow the gold_connect_count and
- /// white_connect_percent settings. Otherwise, connect to greylist
- /// entries if we have no white or gold connections.
- #[serde(default)]
- #[structopt(long)]
- pub localnet: bool,
- /// Cooling off time for peer discovery when unsuccessful
- #[structopt(skip)]
- pub outbound_peer_discovery_cooloff_time: Option<u64>,
- /// Time between peer discovery attempts
- #[structopt(skip)]
- pub outbound_peer_discovery_attempt_time: Option<u64>,
- /// Maximum number of addresses (with preferred transports) to receive from
- /// seeds and peers.
- /// If undefined, `outbound_connections` will be used instead.
- #[structopt(skip)]
- pub getaddrs_max: Option<u32>,
- /// P2P datastore path
- #[serde(default)]
- #[structopt(long)]
- pub p2p_datastore: Option<String>,
- /// Hosts .tsv file to use
- #[serde(default)]
- #[structopt(long)]
- pub hostlist: Option<String>,
- /// Pause interval within greylist refinery process
- #[structopt(skip)]
- pub greylist_refinery_interval: Option<u64>,
- /// Number of whitelist connections
- #[structopt(skip)]
- pub white_connect_percent: Option<usize>,
- /// Number of goldlist connections
- #[structopt(skip)]
- pub gold_connect_count: Option<usize>,
- /// Allow localnet hosts
- #[serde(default)]
- #[structopt(long)]
- pub slot_preference_strict: bool,
- /// Number of seconds with no connections after which refinery
- /// process is paused.
- #[structopt(skip)]
- pub time_with_no_connections: Option<u64>,
- /// Nodes to avoid interacting with for the duration of the program,
- /// in the format ["host", ["scheme", "scheme"], [port, port]]
- /// If scheme is left empty it will default to "tcp+tls".
- /// If ports are left empty all ports from this peer will be blocked.
- #[serde(default)]
- #[structopt(skip)]
- pub blacklist: Vec<BlacklistEntry>,
- /// Do not ban nodes that send messages without dispatchers if set
- /// to `Relaxed`. For most uses, should be set to `Strict`.
- #[serde(default)]
- #[structopt(skip)]
- pub ban_policy: BanPolicy,
- /// Network Profile for each transport
- #[serde(default)]
- #[structopt(skip)]
- pub profiles: HashMap<String, NetworkProfileOpt>,
- }
- impl TryFrom<(&str, &str, SettingsOpt)> for Settings {
- type Error = Error;
- fn try_from(st: (&str, &str, SettingsOpt)) -> Result<Self> {
- let app_name = st.0.to_string();
- let app_version = semver::Version::parse(st.1)?;
- let opt = st.2;
- let def = Settings::default();
- let mut inbound_addrs = opt.inbound;
- let mut external_addrs = opt.external_addrs;
- let mut peers = opt.peers;
- let mut seeds = opt.seeds;
- let active_profiles = opt.active_profiles.unwrap_or(def.active_profiles);
- let mixed_profiles = opt.mixed_profiles.unwrap_or(def.mixed_profiles);
- // check all the active profiles that are not mixed are found in net.profiles
- for name in &active_profiles {
- if !mixed_profiles.contains(name) && !opt.profiles.contains_key(name) {
- return Err(Error::ConfigError(format!(
- "Active profile '{name}' not defined in net.profiles"
- )));
- }
- }
- let profiles: HashMap<String, NetworkProfile> = opt
- .profiles
- .into_iter()
- .filter(|(k, _)| active_profiles.contains(k) && !mixed_profiles.contains(k))
- .map(|(k, v)| {
- inbound_addrs.extend_from_slice(&v.inbound);
- external_addrs.extend_from_slice(&v.external_addrs);
- peers.extend_from_slice(&v.peers);
- seeds.extend_from_slice(&v.seeds);
- (k.clone(), NetworkProfile::from_with_profile(v, &k))
- })
- .collect();
- Ok(Self {
- node_id: opt.node_id,
- inbound_addrs,
- external_addrs,
- magic_bytes: opt.magic_bytes,
- peers,
- seeds,
- app_version,
- app_name,
- active_profiles,
- mixed_profiles,
- tor_socks5_proxy: opt.tor_socks5_proxy,
- nym_socks5_proxy: opt.nym_socks5_proxy,
- i2p_socks5_proxy: opt.i2p_socks5_proxy.unwrap_or(def.i2p_socks5_proxy),
- outbound_connections: opt.outbound_connections.unwrap_or(def.outbound_connections),
- inbound_connections: opt.inbound_connections.unwrap_or(def.inbound_connections),
- localnet: opt.localnet,
- outbound_peer_discovery_cooloff_time: opt
- .outbound_peer_discovery_cooloff_time
- .unwrap_or(def.outbound_peer_discovery_cooloff_time),
- outbound_peer_discovery_attempt_time: opt
- .outbound_peer_discovery_attempt_time
- .unwrap_or(def.outbound_peer_discovery_attempt_time),
- getaddrs_max: opt.getaddrs_max,
- p2p_datastore: opt.p2p_datastore,
- hostlist: opt.hostlist,
- greylist_refinery_interval: opt
- .greylist_refinery_interval
- .unwrap_or(def.greylist_refinery_interval),
- white_connect_percent: opt.white_connect_percent.unwrap_or(def.white_connect_percent),
- gold_connect_count: opt.gold_connect_count.unwrap_or(def.gold_connect_count),
- slot_preference_strict: opt.slot_preference_strict,
- time_with_no_connections: opt
- .time_with_no_connections
- .unwrap_or(def.time_with_no_connections),
- blacklist: opt.blacklist,
- ban_policy: opt.ban_policy,
- profiles,
- })
- }
- }
- #[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
- #[structopt()]
- pub struct NetworkProfileOpt {
- /// P2P accept address node listens to for inbound connections
- #[serde(default)]
- #[structopt(long = "accept")]
- pub inbound: Vec<Url>,
- /// P2P external addresses node advertises so other peers can
- /// reach us and connect to us, as long as inbound addresses
- /// are also configured
- #[serde(default)]
- #[structopt(long)]
- pub external_addrs: Vec<Url>,
- /// Peer nodes to manually connect to
- #[serde(default)]
- #[structopt(long)]
- pub peers: Vec<Url>,
- /// Seed nodes to connect to for peers retrieval and/or
- /// advertising our own external addresses
- #[serde(default)]
- #[structopt(long)]
- pub seeds: Vec<Url>,
- /// Connection establishment timeout in seconds
- #[structopt(skip)]
- pub outbound_connect_timeout: Option<u64>,
- /// Exchange versions (handshake) timeout in seconds
- #[structopt(skip)]
- pub channel_handshake_timeout: Option<u64>,
- /// Ping-pong exchange execution interval in seconds
- #[structopt(skip)]
- pub channel_heartbeat_interval: Option<u64>,
- }
- /// Network Profile info unique for each profile/transport
- #[derive(Debug, Clone)]
- pub struct NetworkProfile {
- /// Outbound connection timeout (in seconds)
- pub outbound_connect_timeout: u64,
- /// Exchange versions (handshake) timeout (in seconds)
- pub channel_handshake_timeout: u64,
- /// Ping-pong exchange execution interval (in seconds)
- pub channel_heartbeat_interval: u64,
- }
- impl Default for NetworkProfile {
- fn default() -> Self {
- Self {
- outbound_connect_timeout: 15,
- channel_handshake_timeout: 10,
- channel_heartbeat_interval: 30,
- }
- }
- }
- impl NetworkProfile {
- /// Creates default [`NetworkProfile`] for non-clearnet profiles
- pub fn tor_default() -> Self {
- Self {
- outbound_connect_timeout: 65,
- channel_handshake_timeout: 55,
- channel_heartbeat_interval: 90,
- }
- }
- /// Creates [`NetworkProfile`] from [`NetworkProfileOpt`] based on the profile
- fn from_with_profile(opt: NetworkProfileOpt, profile: &str) -> Self {
- let def = if ["tcp", "tcp+tls", "quic"].contains(&profile) {
- NetworkProfile::default()
- } else {
- NetworkProfile::tor_default()
- };
- Self {
- outbound_connect_timeout: opt
- .outbound_connect_timeout
- .unwrap_or(def.outbound_connect_timeout),
- channel_handshake_timeout: opt
- .channel_handshake_timeout
- .unwrap_or(def.channel_handshake_timeout),
- channel_heartbeat_interval: opt
- .channel_heartbeat_interval
- .unwrap_or(def.channel_heartbeat_interval),
- }
- }
- }
|