Files
nym/nym-node/src/config/mod.rs
T
Jędrzej Stuczyński 827c13b69e moved nym-gateway-probe to monorepo and updated rust-edition to 2024 (#6094)
dont build netstack in CI

additional rust 2024 fixes

fixes

removed temp.rs

first round of cleanup

removed duplicated NS types

moved gateway probe to the monorepo
2025-10-08 16:17:43 +01:00

1039 lines
35 KiB
Rust

// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::persistence::{NymNodePaths, ReplayProtectionPaths};
use crate::config::template::CONFIG_TEMPLATE;
use crate::error::NymNodeError;
use celes::Country;
use clap::ValueEnum;
use human_repr::HumanCount;
use nym_bin_common::logging::LoggingSettings;
use nym_config::defaults::{
DEFAULT_MIX_LISTENING_PORT, DEFAULT_NYM_NODE_HTTP_PORT, DEFAULT_VERLOC_LISTENING_PORT,
WG_METADATA_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6, WG_TUNNEL_PORT,
mainnet, var_names,
};
use nym_config::defaults::{WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6};
use nym_config::helpers::{in6addr_any_init, inaddr_any};
use nym_config::serde_helpers::de_maybe_port;
use nym_config::serde_helpers::de_maybe_stringified;
use nym_config::{
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, NymConfigTemplate,
must_get_home, parse_urls, read_config_from_toml_file, save_formatted_config_to_file,
};
use nym_gateway::nym_authenticator;
use serde::{Deserialize, Serialize};
use std::env;
use std::fmt::{Display, Formatter};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::time::Duration;
use sysinfo::System;
use tracing::{debug, error};
use url::Url;
pub mod authenticator;
pub mod gateway_tasks;
pub mod helpers;
pub mod metrics;
mod old_configs;
pub mod persistence;
pub mod service_providers;
mod template;
pub mod upgrade_helpers;
pub use crate::config::gateway_tasks::GatewayTasksConfig;
pub use crate::config::metrics::MetricsConfig;
pub use crate::config::service_providers::ServiceProvidersConfig;
use crate::node::replay_protection::{bitmap_size, items_in_bloomfilter};
const DEFAULT_NYMNODES_DIR: &str = "nym-nodes";
pub const DEFAULT_HTTP_PORT: u16 = DEFAULT_NYM_NODE_HTTP_PORT;
pub const DEFAULT_MIXNET_PORT: u16 = DEFAULT_MIX_LISTENING_PORT;
/// Derive default path to nym-node's config directory.
/// It should get resolved to `$HOME/.nym/nym-nodes/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_NYMNODES_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
/// Derive default path to nym-node's config file.
/// It should get resolved to `$HOME/.nym/nym-nodes/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
// a temporary solution until all "types" are run at the same time
#[derive(Debug, Default, Serialize, Deserialize, ValueEnum, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum NodeMode {
#[default]
#[clap(alias = "mix")]
Mixnode,
#[clap(alias = "entry", alias = "gateway")]
EntryGateway,
// to not break existing behaviour, this means exit capabilities AND entry capabilities
#[clap(alias = "exit")]
ExitGateway,
// will start only SP needed for exit capabilities WITHOUT entry routing
ExitProvidersOnly,
}
impl Display for NodeMode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
NodeMode::Mixnode => "mixnode".fmt(f),
NodeMode::EntryGateway => "entry-gateway".fmt(f),
NodeMode::ExitGateway => "exit-gateway".fmt(f),
NodeMode::ExitProvidersOnly => "exit-providers-only".fmt(f),
}
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy)]
pub struct NodeModes {
/// Specifies whether this node can operate in a mixnode mode.
pub mixnode: bool,
/// Specifies whether this node can operate in an entry mode.
pub entry: bool,
/// Specifies whether this node can operate in an exit mode.
pub exit: bool,
// TODO: would it make sense to also put WG here for completion?
}
impl From<&[NodeMode]> for NodeModes {
fn from(modes: &[NodeMode]) -> Self {
let mut out = NodeModes::default();
for &mode in modes {
out.with_mode(mode);
}
out
}
}
impl NodeModes {
pub fn any_enabled(&self) -> bool {
self.mixnode || self.entry || self.exit
}
pub fn standalone_exit(&self) -> bool {
!self.mixnode && !self.entry && self.exit
}
pub fn with_mode(&mut self, mode: NodeMode) -> &mut Self {
match mode {
NodeMode::Mixnode => self.with_mixnode(),
NodeMode::EntryGateway => self.with_entry(),
NodeMode::ExitGateway => self.with_entry().with_exit(),
NodeMode::ExitProvidersOnly => self.with_exit(),
}
}
pub fn expects_final_hop_traffic(&self) -> bool {
self.entry || self.exit
}
pub fn with_mixnode(&mut self) -> &mut Self {
self.mixnode = true;
self
}
pub fn with_entry(&mut self) -> &mut Self {
self.entry = true;
self
}
pub fn with_exit(&mut self) -> &mut Self {
self.exit = true;
self
}
}
pub struct ConfigBuilder {
pub id: String,
pub config_path: PathBuf,
pub data_dir: PathBuf,
pub modes: NodeModes,
pub mixnet: Option<Mixnet>,
pub host: Option<Host>,
pub http: Option<Http>,
pub verloc: Option<Verloc>,
pub wireguard: Option<Wireguard>,
pub storage_paths: Option<NymNodePaths>,
pub gateway_tasks: Option<GatewayTasksConfig>,
pub service_providers: Option<ServiceProvidersConfig>,
pub metrics: Option<MetricsConfig>,
pub logging: Option<LoggingSettings>,
}
impl ConfigBuilder {
pub fn new(id: String, config_path: PathBuf, data_dir: PathBuf) -> Self {
ConfigBuilder {
id,
config_path,
data_dir,
host: None,
http: None,
mixnet: None,
verloc: None,
wireguard: None,
modes: NodeModes::default(),
storage_paths: None,
gateway_tasks: None,
service_providers: None,
metrics: None,
logging: None,
}
}
pub fn with_modes(mut self, mode: impl Into<NodeModes>) -> Self {
self.modes = mode.into();
self
}
pub fn with_host(mut self, section: impl Into<Option<Host>>) -> Self {
self.host = section.into();
self
}
pub fn with_http(mut self, section: impl Into<Option<Http>>) -> Self {
self.http = section.into();
self
}
pub fn with_verloc(mut self, section: impl Into<Option<Verloc>>) -> Self {
self.verloc = section.into();
self
}
pub fn with_mixnet(mut self, section: impl Into<Option<Mixnet>>) -> Self {
self.mixnet = section.into();
self
}
pub fn with_wireguard(mut self, section: impl Into<Option<Wireguard>>) -> Self {
self.wireguard = section.into();
self
}
pub fn with_storage_paths(mut self, section: impl Into<Option<NymNodePaths>>) -> Self {
self.storage_paths = section.into();
self
}
pub fn with_metrics(mut self, section: impl Into<Option<MetricsConfig>>) -> Self {
self.metrics = section.into();
self
}
pub fn with_gateway_tasks(mut self, section: impl Into<Option<GatewayTasksConfig>>) -> Self {
self.gateway_tasks = section.into();
self
}
pub fn with_service_providers(
mut self,
section: impl Into<Option<ServiceProvidersConfig>>,
) -> Self {
self.service_providers = section.into();
self
}
pub fn build(self) -> Config {
Config {
id: self.id,
modes: self.modes,
host: self.host.unwrap_or_default(),
http: self.http.unwrap_or_default(),
mixnet: self
.mixnet
.unwrap_or_else(|| Mixnet::new_default(&self.data_dir)),
verloc: self.verloc.unwrap_or_default(),
wireguard: self
.wireguard
.unwrap_or_else(|| Wireguard::new_default(&self.data_dir)),
storage_paths: self
.storage_paths
.unwrap_or_else(|| NymNodePaths::new(&self.data_dir)),
metrics: self.metrics.unwrap_or_default(),
gateway_tasks: self
.gateway_tasks
.unwrap_or_else(|| GatewayTasksConfig::new_default(&self.data_dir)),
service_providers: self
.service_providers
.unwrap_or_else(|| ServiceProvidersConfig::new_default(&self.data_dir)),
logging: self.logging.unwrap_or_default(),
save_path: Some(self.config_path),
debug: Default::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
// additional metadata holding on-disk location of this config file
#[serde(skip)]
pub(crate) save_path: Option<PathBuf>,
/// Human-readable ID of this particular node.
pub id: String,
/// Current modes of this nym-node.
pub modes: NodeModes,
pub host: Host,
pub mixnet: Mixnet,
/// Storage paths to persistent nym-node data, such as its long term keys.
pub storage_paths: NymNodePaths,
#[serde(default)]
pub http: Http,
#[serde(default)]
pub verloc: Verloc,
pub wireguard: Wireguard,
#[serde(alias = "entry_gateway")]
pub gateway_tasks: GatewayTasksConfig,
#[serde(alias = "exit_gateway")]
pub service_providers: ServiceProvidersConfig,
#[serde(default)]
pub metrics: MetricsConfig,
#[serde(default)]
pub logging: LoggingSettings,
#[serde(default)]
pub debug: Debug,
}
impl NymConfigTemplate for Config {
fn template(&self) -> &'static str {
CONFIG_TEMPLATE
}
}
impl Config {
pub fn save(&self) -> Result<(), NymNodeError> {
let save_location = self.save_location();
debug!(
"attempting to save config file to '{}'",
save_location.display()
);
save_formatted_config_to_file(self, &save_location).map_err(|source| {
NymNodeError::ConfigSaveFailure {
id: self.id.clone(),
path: save_location,
source,
}
})
}
pub fn save_location(&self) -> PathBuf {
self.save_path
.clone()
.unwrap_or(self.default_save_location())
}
pub fn default_save_location(&self) -> PathBuf {
default_config_filepath(&self.id)
}
pub fn default_data_directory<P: AsRef<Path>>(config_path: P) -> Result<PathBuf, NymNodeError> {
let config_path = config_path.as_ref();
// we got a proper path to the .toml file
let Some(config_dir) = config_path.parent() else {
error!(
"'{}' does not have a parent directory. Have you pointed to the fs root?",
config_path.display()
);
return Err(NymNodeError::DataDirDerivationFailure);
};
let Some(config_dir_name) = config_dir.file_name() else {
error!(
"could not obtain parent directory name of '{}'. Have you used relative paths?",
config_path.display()
);
return Err(NymNodeError::DataDirDerivationFailure);
};
if config_dir_name != DEFAULT_CONFIG_DIR {
error!(
"the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported",
config_path.display(),
config_dir_name.to_str().unwrap_or("UNKNOWN")
);
return Err(NymNodeError::DataDirDerivationFailure);
}
let Some(node_dir) = config_dir.parent() else {
error!(
"'{}' does not have a parent directory. Have you pointed to the fs root?",
config_dir.display()
);
return Err(NymNodeError::DataDirDerivationFailure);
};
Ok(node_dir.join(DEFAULT_DATA_DIR))
}
// simple wrapper that reads config file and assigns path location
fn read_from_path<P: AsRef<Path>>(path: P) -> Result<Self, NymNodeError> {
let path = path.as_ref();
let mut loaded: Config =
read_config_from_toml_file(path).map_err(|source| NymNodeError::ConfigLoadFailure {
path: path.to_path_buf(),
source,
})?;
loaded.save_path = Some(path.to_path_buf());
debug!("loaded config file from {}", path.display());
Ok(loaded)
}
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> Result<Self, NymNodeError> {
Self::read_from_path(path)
}
pub fn validate(&self) -> Result<(), NymNodeError> {
self.mixnet.validate()?;
// it's not allowed to run mixnode mode alongside entry mode
if self.modes.mixnode && self.modes.entry {
return Err(NymNodeError::config_validation_failure(
"illegal modes configuration - node cannot run as a mixnode and an entry gateway",
));
}
// nor it's allowed to run mixnode mode alongside exit mode
// (use two separate checks for better error messages)
if self.modes.mixnode && self.modes.exit {
return Err(NymNodeError::config_validation_failure(
"illegal modes configuration - node cannot run as a mixnode and an exit gateway",
));
}
Ok(())
}
}
// TODO: this is very much a WIP. we need proper ssl certificate support here
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct Host {
/// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections.
/// If no values are provided, when this node gets included in the network,
/// its ip addresses will be populated by whatever value is resolved by associated nym-api.
pub public_ips: Vec<IpAddr>,
/// Optional hostname of this node, for example nymtech.net.
// TODO: this is temporary. to be replaced by pulling the data directly from the certs.
#[serde(deserialize_with = "de_maybe_stringified")]
pub hostname: Option<String>,
/// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location
#[serde(deserialize_with = "de_maybe_stringified")]
pub location: Option<Country>,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct Http {
/// Socket address this node will use for binding its http API.
/// default: `[::]:8080`
pub bind_address: SocketAddr,
/// Path to assets directory of custom landing page of this node.
#[serde(deserialize_with = "de_maybe_stringified")]
pub landing_page_assets_path: Option<PathBuf>,
/// An optional bearer token for accessing certain http endpoints.
/// Currently only used for obtaining mixnode's stats.
#[serde(default)]
#[serde(deserialize_with = "de_maybe_stringified")]
pub access_token: Option<String>,
/// Specify whether basic system information should be exposed.
/// default: true
pub expose_system_info: bool,
/// Specify whether basic system hardware information should be exposed.
/// This option is superseded by `expose_system_info`
/// default: true
pub expose_system_hardware: bool,
/// Specify whether detailed system crypto hardware information should be exposed.
/// This option is superseded by `expose_system_hardware`
/// default: true
pub expose_crypto_hardware: bool,
/// Specify the cache ttl of the node load.
/// default: 30s
#[serde(with = "humantime_serde")]
pub node_load_cache_ttl: Duration,
}
impl Http {
pub const DEFAULT_NODE_LOAD_CACHE_TTL: Duration = Duration::from_secs(30);
}
impl Default for Http {
fn default() -> Self {
Http {
bind_address: SocketAddr::new(in6addr_any_init(), DEFAULT_HTTP_PORT),
landing_page_assets_path: None,
access_token: None,
expose_system_info: true,
expose_system_hardware: true,
expose_crypto_hardware: true,
node_load_cache_ttl: Self::DEFAULT_NODE_LOAD_CACHE_TTL,
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Mixnet {
/// Address this node will bind to for listening for mixnet packets
/// default: `[::]:1789`
pub bind_address: SocketAddr,
/// If applicable, custom port announced in the self-described API that other clients and nodes
/// will use.
/// Useful when the node is behind a proxy.
#[serde(deserialize_with = "de_maybe_port")]
#[serde(default)]
pub announce_port: Option<u16>,
/// Addresses to nym APIs from which the node gets the view of the network.
pub nym_api_urls: Vec<Url>,
/// Addresses to nyxd which the node uses to interact with the nyx chain.
pub nyxd_urls: Vec<Url>,
/// Settings for controlling replay detection
pub replay_protection: ReplayProtection,
#[serde(default)]
pub key_rotation: KeyRotation,
#[serde(default)]
pub debug: MixnetDebug,
}
impl Mixnet {
pub fn validate(&self) -> Result<(), NymNodeError> {
if self.nym_api_urls.is_empty() {
return Err(NymNodeError::config_validation_failure(
"no nym api urls provided",
));
}
if self.nyxd_urls.is_empty() {
return Err(NymNodeError::config_validation_failure(
"no nyxd urls provided",
));
}
self.replay_protection.validate()?;
Ok(())
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct MixnetDebug {
/// Specifies the duration of time this node is willing to delay a forward packet for.
#[serde(with = "humantime_serde")]
pub maximum_forward_packet_delay: Duration,
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
#[serde(with = "humantime_serde")]
pub initial_connection_timeout: Duration,
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
pub maximum_connection_buffer_size: usize,
/// Specify whether any framed packets between nodes should use the legacy format (v7)
/// as opposed to the current (v8) one.
/// The legacy format has to be used until sufficient number of nodes on the network has upgraded and understand the new variant.
/// This will allow for optimisations to indicate which [sphinx] key is meant to be used when
/// processing received packets.
pub use_legacy_packet_encoding: bool,
/// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented)
pub unsafe_disable_noise: bool,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct ReplayProtection {
/// Paths for current bloomfilters
pub storage_paths: persistence::ReplayProtectionPaths,
#[serde(default)]
pub debug: ReplayProtectionDebug,
}
impl ReplayProtection {
pub fn new_default<P: AsRef<Path>>(data_dir: P) -> Self {
ReplayProtection {
storage_paths: ReplayProtectionPaths::new(data_dir),
debug: Default::default(),
}
}
}
impl ReplayProtection {
pub fn validate(&self) -> Result<(), NymNodeError> {
self.debug.validate()?;
Ok(())
}
}
#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct ReplayProtectionDebug {
/// Specifies whether this node should **NOT** use replay protection
pub unsafe_disabled: bool,
/// How long the processing task is willing to skip mutex acquisition before it will block the thread
/// until it actually obtains it
pub maximum_replay_detection_deferral: Duration,
/// How many packets the processing task is willing to queue before it will block the thread
/// until it obtains the mutex
pub maximum_replay_detection_pending_packets: usize,
/// Probability of false positives, fraction between 0 and 1 or a number indicating 1-in-p
pub false_positive_rate: f64,
/// Defines initial expected number of packets this node will process a second,
/// so that an initial bloomfilter could be established.
/// As the node is running and BF are cleared, the value will be adjusted dynamically
pub initial_expected_packets_per_second: usize,
/// Defines minimum expected number of packets this node will process a second
/// when used for calculating the BF size after reset.
/// This is to avoid degenerate cases where node receives 0 packets (because say it's misconfigured)
/// and it constructs an empty bloomfilter.
pub bloomfilter_minimum_packets_per_second_size: usize,
/// Specifies the amount the bloomfilter size is going to get multiplied by after each reset.
/// It's performed in case the traffic rates increase before the next bloomfilter update.
pub bloomfilter_size_multiplier: f64,
/// Specifies how often the bloomfilter is flushed to disk for recovery in case of a crash
#[serde(with = "humantime_serde")]
pub bloomfilter_disk_flushing_rate: Duration,
}
impl ReplayProtectionDebug {
pub const DEFAULT_MAXIMUM_REPLAY_DETECTION_DEFERRAL: Duration = Duration::from_millis(50);
pub const DEFAULT_MAXIMUM_REPLAY_DETECTION_PENDING_PACKETS: usize = 100;
// 12% (completely arbitrary)
pub const DEFAULT_BLOOMFILTER_SIZE_MULTIPLIER: f64 = 1.12;
// 10^-5
pub const DEFAULT_REPLAY_DETECTION_FALSE_POSITIVE_RATE: f64 = 1e-5;
// we must have some reasonable balance between losing values and trashing the disk.
// since on average HDD it would take ~30s to save a 2GB bloomfilter
pub const DEFAULT_BF_DISK_FLUSHING_RATE: Duration = Duration::from_secs(10 * 60);
// this value will have to be adjusted in the future
pub const DEFAULT_INITIAL_EXPECTED_PACKETS_PER_SECOND: usize = 2000;
pub const DEFAULT_BLOOMFILTER_MINIMUM_PACKETS_PER_SECOND_SIZE: usize = 200;
pub fn validate(&self) -> Result<(), NymNodeError> {
if self.false_positive_rate >= 1.0 || self.false_positive_rate <= 0.0 {
return Err(NymNodeError::config_validation_failure(
"false positive rate for replay detection can't be larger than (or equal to) 1 or smaller than (or equal to) 0",
));
}
// ideally we would have pulled the exact information from the network,
// but making async calls really doesn't play around with this method
// so we do second best: assume 24h rotation with 1h overlap (which realistically won't ever change)
let items_in_filter = items_in_bloomfilter(
Duration::from_secs(25 * 60 * 60),
self.initial_expected_packets_per_second,
);
let bitmap_size = bitmap_size(self.false_positive_rate, items_in_filter);
let bloomfilter_size = bitmap_size / 8;
let mut sys_info = System::new();
sys_info.refresh_memory();
// we'll need 2x size of the bloomfilter
// as during key transition we'll have to simultaneously use two filters
// plus we also need to make a memcopy during disk flush
let required_memory = 2 * bloomfilter_size;
let memory = sys_info.available_memory();
if (memory as usize) < required_memory {
return Err(NymNodeError::config_validation_failure(format!(
"system does not have sufficient memory to allocate required replay protection bloomfilters. {} is available whilst at least {} is needed",
memory.human_count_bytes(),
required_memory.human_count_bytes()
)));
}
Ok(())
}
}
impl Default for ReplayProtectionDebug {
fn default() -> Self {
ReplayProtectionDebug {
unsafe_disabled: false,
maximum_replay_detection_deferral: Self::DEFAULT_MAXIMUM_REPLAY_DETECTION_DEFERRAL,
maximum_replay_detection_pending_packets:
Self::DEFAULT_MAXIMUM_REPLAY_DETECTION_PENDING_PACKETS,
false_positive_rate: Self::DEFAULT_REPLAY_DETECTION_FALSE_POSITIVE_RATE,
initial_expected_packets_per_second: Self::DEFAULT_INITIAL_EXPECTED_PACKETS_PER_SECOND,
bloomfilter_minimum_packets_per_second_size:
Self::DEFAULT_BLOOMFILTER_MINIMUM_PACKETS_PER_SECOND_SIZE,
bloomfilter_size_multiplier: Self::DEFAULT_BLOOMFILTER_SIZE_MULTIPLIER,
bloomfilter_disk_flushing_rate: Self::DEFAULT_BF_DISK_FLUSHING_RATE,
}
}
}
#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct KeyRotation {
pub debug: KeyRotationDebug,
}
#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct KeyRotationDebug {
/// Specifies how often the node should poll for any changes in the key rotation global state.
#[serde(with = "humantime_serde")]
pub rotation_state_poling_interval: Duration,
}
impl KeyRotationDebug {
pub const DEFAULT_ROTATION_STATE_POLLING_INTERVAL: Duration = Duration::from_secs(4 * 60 * 60);
}
impl Default for KeyRotationDebug {
fn default() -> Self {
KeyRotationDebug {
rotation_state_poling_interval: Self::DEFAULT_ROTATION_STATE_POLLING_INTERVAL,
}
}
}
impl MixnetDebug {
// given that genuine clients are using mean delay of 50ms,
// the probability of them delaying for over 10s is 10^-87
// which for all intents and purposes will never happen
const DEFAULT_MAXIMUM_FORWARD_PACKET_DELAY: Duration = Duration::from_secs(10);
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
}
impl Default for MixnetDebug {
fn default() -> Self {
MixnetDebug {
maximum_forward_packet_delay: Self::DEFAULT_MAXIMUM_FORWARD_PACKET_DELAY,
packet_forwarding_initial_backoff: Self::DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT,
maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
// TODO: update this in few releases...
use_legacy_packet_encoding: true,
unsafe_disable_noise: false,
}
}
}
impl Mixnet {
pub fn new_default<P: AsRef<Path>>(data_dir: P) -> Self {
// SAFETY:
// our hardcoded values should always be valid
#[allow(clippy::expect_used)]
// is if there's anything set in the environment, otherwise fallback to mainnet
let nym_api_urls = if let Ok(env_value) = env::var(var_names::NYM_API) {
parse_urls(&env_value)
} else {
vec![mainnet::NYM_API.parse().expect("Invalid default API URL")]
};
#[allow(clippy::expect_used)]
let nyxd_urls = if let Ok(env_value) = env::var(var_names::NYXD) {
parse_urls(&env_value)
} else {
vec![mainnet::NYXD_URL.parse().expect("Invalid default nyxd URL")]
};
Mixnet {
bind_address: SocketAddr::new(in6addr_any_init(), DEFAULT_MIXNET_PORT),
announce_port: None,
nym_api_urls,
nyxd_urls,
replay_protection: ReplayProtection::new_default(data_dir),
key_rotation: Default::default(),
debug: Default::default(),
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Verloc {
/// Socket address this node will use for binding its verloc API.
/// default: `[::]:1790`
pub bind_address: SocketAddr,
/// If applicable, custom port announced in the self-described API that other clients and nodes
/// will use.
/// Useful when the node is behind a proxy.
#[serde(deserialize_with = "de_maybe_port")]
#[serde(default)]
pub announce_port: Option<u16>,
#[serde(default)]
pub debug: VerlocDebug,
}
impl Verloc {
pub const DEFAULT_VERLOC_PORT: u16 = DEFAULT_VERLOC_LISTENING_PORT;
}
impl Default for Verloc {
fn default() -> Self {
Verloc {
bind_address: SocketAddr::new(in6addr_any_init(), Self::DEFAULT_VERLOC_PORT),
announce_port: None,
debug: Default::default(),
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct VerlocDebug {
/// Specifies number of echo packets sent to each node during a measurement run.
pub packets_per_node: usize,
/// Specifies maximum amount of time to wait for the connection to get established.
#[serde(with = "humantime_serde")]
pub connection_timeout: Duration,
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
#[serde(with = "humantime_serde")]
pub packet_timeout: Duration,
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
#[serde(with = "humantime_serde")]
pub delay_between_packets: Duration,
/// Specifies number of nodes being tested at once.
pub tested_nodes_batch_size: usize,
/// Specifies delay between subsequent test runs.
#[serde(with = "humantime_serde")]
pub testing_interval: Duration,
/// Specifies delay between attempting to run the measurement again if the previous run failed
/// due to being unable to get the list of nodes.
#[serde(with = "humantime_serde")]
pub retry_timeout: Duration,
}
impl VerlocDebug {
const DEFAULT_PACKETS_PER_NODE: usize = 100;
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50);
const DEFAULT_BATCH_SIZE: usize = 50;
const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12);
const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
}
impl Default for VerlocDebug {
fn default() -> Self {
VerlocDebug {
packets_per_node: Self::DEFAULT_PACKETS_PER_NODE,
connection_timeout: Self::DEFAULT_CONNECTION_TIMEOUT,
packet_timeout: Self::DEFAULT_PACKET_TIMEOUT,
delay_between_packets: Self::DEFAULT_DELAY_BETWEEN_PACKETS,
tested_nodes_batch_size: Self::DEFAULT_BATCH_SIZE,
testing_interval: Self::DEFAULT_TESTING_INTERVAL,
retry_timeout: Self::DEFAULT_RETRY_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Wireguard {
/// Specifies whether the wireguard service is enabled on this node.
pub enabled: bool,
/// Socket address this node will use for binding its wireguard interface.
/// default: `[::]:51822`
pub bind_address: SocketAddr,
/// Private IPv4 address of the wireguard gateway.
/// default: `10.1.0.1`
pub private_ipv4: Ipv4Addr,
/// Private IPv6 address of the wireguard gateway.
/// default: `fc01::1`
pub private_ipv6: Ipv6Addr,
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
/// Useful in the instances where the node is behind a proxy.
pub announced_tunnel_port: u16,
/// Metadata port announced to external clients wishing to connect to the metadata endpoint.
/// Useful in the instances where the node is behind a proxy.
pub announced_metadata_port: u16,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
/// The maximum value for IPv4 is 32
pub private_network_prefix_v4: u8,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv6.
/// The maximum value for IPv6 is 128
pub private_network_prefix_v6: u8,
/// Paths for wireguard keys, client registries, etc.
pub storage_paths: persistence::WireguardPaths,
}
impl Wireguard {
pub fn new_default<P: AsRef<Path>>(data_dir: P) -> Self {
Wireguard {
enabled: false,
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_TUNNEL_PORT),
private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4,
private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6,
announced_tunnel_port: WG_TUNNEL_PORT,
announced_metadata_port: WG_METADATA_PORT,
private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4,
private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6,
storage_paths: persistence::WireguardPaths::new(data_dir),
}
}
}
impl From<Wireguard> for nym_wireguard_types::Config {
fn from(value: Wireguard) -> Self {
nym_wireguard_types::Config {
bind_address: value.bind_address,
private_ipv4: value.private_ipv4,
private_ipv6: value.private_ipv6,
announced_tunnel_port: value.announced_tunnel_port,
announced_metadata_port: value.announced_metadata_port,
private_network_prefix_v4: value.private_network_prefix_v4,
private_network_prefix_v6: value.private_network_prefix_v6,
}
}
}
impl From<Wireguard> for nym_authenticator::config::Authenticator {
fn from(value: Wireguard) -> Self {
nym_authenticator::config::Authenticator {
bind_address: value.bind_address,
private_ipv4: value.private_ipv4,
private_ipv6: value.private_ipv6,
tunnel_announced_port: value.announced_tunnel_port,
private_network_prefix_v4: value.private_network_prefix_v4,
private_network_prefix_v6: value.private_network_prefix_v6,
}
}
}
#[derive(Debug, Clone)]
pub struct LocalWireguardOpts {
#[allow(dead_code)]
pub config: Wireguard,
#[allow(dead_code)]
pub custom_mixnet_path: Option<PathBuf>,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Debug {
/// Specifies the time to live of the internal topology provider cache.
#[serde(with = "humantime_serde")]
pub topology_cache_ttl: Duration,
/// Specifies the time between attempting to resolve any pending unknown nodes in the routing filter
#[serde(with = "humantime_serde")]
pub routing_nodes_check_interval: Duration,
/// Specifies whether this node runs in testnet mode thus allowing it to route packets on local interfaces
pub testnet: bool,
}
impl Debug {
pub const DEFAULT_TOPOLOGY_CACHE_TTL: Duration = Duration::from_secs(10 * 60);
pub const DEFAULT_ROUTING_NODES_CHECK_INTERVAL: Duration = Duration::from_secs(5 * 60);
}
impl Default for Debug {
fn default() -> Self {
Debug {
topology_cache_ttl: Self::DEFAULT_TOPOLOGY_CACHE_TTL,
routing_nodes_check_interval: Self::DEFAULT_ROUTING_NODES_CHECK_INTERVAL,
testnet: false,
}
}
}