diff --git a/Cargo.lock b/Cargo.lock index 388c0697fa..416ee3963c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6540,6 +6540,7 @@ dependencies = [ "nym-credentials", "nym-crypto", "nym-gateway-requests", + "nym-ip-packet-router", "nym-mixnet-client", "nym-mixnode-common", "nym-network-defaults", @@ -6644,14 +6645,16 @@ dependencies = [ ] [[package]] -name = "nym-ip-forwarder" +name = "nym-ip-packet-router" version = "0.1.0" dependencies = [ "futures", "log", + "nym-bin-common", "nym-client-core", "nym-config", "nym-sdk", + "nym-service-providers-common", "nym-sphinx", "nym-task", "serde", diff --git a/Cargo.toml b/Cargo.toml index 802b81f89e..3d114f689a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ members = [ "sdk/lib/socks5-listener", "sdk/rust/nym-sdk", "service-providers/common", - "service-providers/ip-forwarder", + "service-providers/ip-packet-router", "service-providers/network-requester", "service-providers/network-statistics", "nym-api", diff --git a/common/types/src/gateway.rs b/common/types/src/gateway.rs index c57dee8001..3b546320b6 100644 --- a/common/types/src/gateway.rs +++ b/common/types/src/gateway.rs @@ -146,3 +146,28 @@ impl fmt::Display for GatewayNetworkRequesterDetails { writeln!(f, "\tunknown list path: {}", self.unknown_list_path) } } + +#[derive(Serialize, Deserialize)] +pub struct GatewayIpPacketRouterDetails { + pub enabled: bool, + + pub identity_key: String, + pub encryption_key: String, + + // just a convenience wrapper around all the keys + pub address: String, + + pub config_path: String, +} + +impl fmt::Display for GatewayIpPacketRouterDetails { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "IP packet router:")?; + writeln!(f, "\tenabled: {}", self.enabled)?; + writeln!(f, "\tconfig path: {}", self.config_path)?; + + writeln!(f, "\tidentity key: {}", self.identity_key)?; + writeln!(f, "\tencryption key: {}", self.encryption_key)?; + writeln!(f, "\taddress: {}", self.address) + } +} diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index c1d6503199..ec75ebf6db 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -76,6 +76,7 @@ nym-task = { path = "../common/task" } nym-types = { path = "../common/types" } nym-validator-client = { path = "../common/client-libs/validator-client" } nym-wireguard = { path = "../common/wireguard", optional = true } +nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } [dev-dependencies] tower = "0.4.13" diff --git a/gateway/src/commands/helpers.rs b/gateway/src/commands/helpers.rs index 9029c14a7a..f4ef8640e9 100644 --- a/gateway/src/commands/helpers.rs +++ b/gateway/src/commands/helpers.rs @@ -3,7 +3,9 @@ use crate::commands::upgrade_helpers; use crate::config::default_config_filepath; -use crate::config::persistence::paths::default_network_requester_data_dir; +use crate::config::persistence::paths::{ + default_ip_packet_router_data_dir, default_network_requester_data_dir, +}; use crate::config::Config; use crate::error::GatewayError; use log::{error, info}; @@ -17,7 +19,7 @@ use nym_network_requester::config::BaseClientConfig; use nym_network_requester::{ setup_gateway, GatewaySelectionSpecification, GatewaySetup, OnDiskGatewayDetails, OnDiskKeys, }; -use nym_types::gateway::GatewayNetworkRequesterDetails; +use nym_types::gateway::{GatewayIpPacketRouterDetails, GatewayNetworkRequesterDetails}; use nym_validator_client::nyxd::AccountId; use std::net::IpAddr; use std::path::PathBuf; @@ -39,6 +41,7 @@ pub(crate) struct OverrideConfig { pub(crate) nyxd_urls: Option>, pub(crate) only_coconut_credentials: Option, pub(crate) with_network_requester: Option, + pub(crate) with_ip_packet_router: Option, } impl OverrideConfig { @@ -76,12 +79,20 @@ impl OverrideConfig { .with_optional( Config::with_enabled_network_requester, self.with_network_requester, + ) + .with_optional( + Config::with_enabled_ip_packet_router, + self.with_ip_packet_router, ); if config.network_requester.enabled && config.storage_paths.network_requester_config.is_none() { Ok(config.with_default_network_requester_config_path()) + } else if config.ip_packet_router.enabled + && config.storage_paths.ip_packet_router_config.is_none() + { + Ok(config.with_default_ip_packet_router_config_path()) } else { Ok(config) } @@ -101,6 +112,11 @@ pub(crate) struct OverrideNetworkRequesterConfig { pub(crate) statistics_recipient: Option, } +#[derive(Default)] +pub(crate) struct OverrideIpPacketRouterConfig { + // TODO +} + /// Ensures that a given bech32 address is valid pub(crate) fn ensure_correct_bech32_prefix(address: &AccountId) -> Result<(), GatewayError> { let expected_prefix = @@ -161,6 +177,10 @@ fn make_nr_id(gateway_id: &str) -> String { format!("{gateway_id}-network-requester") } +fn make_ip_id(gateway_id: &str) -> String { + format!("{gateway_id}-ip-packet-router") +} + // NOTE: make sure this is in sync with service-providers/network-requester/src/cli/mod.rs::override_config pub(crate) fn override_network_requester_config( mut cfg: nym_network_requester::Config, @@ -220,6 +240,13 @@ pub(crate) fn override_network_requester_config( ) } +pub(crate) fn override_ip_packet_router_config( + cfg: nym_ip_packet_router::Config, + _opts: Option, +) -> nym_ip_packet_router::Config { + cfg +} + pub(crate) async fn initialise_local_network_requester( gateway_config: &Config, opts: OverrideNetworkRequesterConfig, @@ -291,3 +318,63 @@ pub(crate) async fn initialise_local_network_requester( .to_string(), }) } + +pub(crate) async fn initialise_local_ip_packet_router( + gateway_config: &Config, + opts: OverrideIpPacketRouterConfig, + identity: identity::PublicKey, +) -> Result { + info!("initialising ip packet router..."); + let Some(ip_cfg_path) = gateway_config.storage_paths.ip_packet_router_config() else { + return Err(GatewayError::UnspecifiedIpPacketRouterConfig); + }; + + let id = &gateway_config.gateway.id; + let ip_id = make_ip_id(id); + let ip_data_dir = default_ip_packet_router_data_dir(id); + let mut ip_cfg = nym_ip_packet_router::Config::new(&ip_id).with_data_directory(ip_data_dir); + ip_cfg = override_ip_packet_router_config(ip_cfg, Some(opts)); + + let key_store = OnDiskKeys::new(ip_cfg.storage_paths.common_paths.keys.clone()); + let details_store = + OnDiskGatewayDetails::new(&ip_cfg.storage_paths.common_paths.gateway_details); + + // gateway setup here is way simpler as we're 'connecting' to ourselves + let init_res = setup_gateway( + GatewaySetup::New { + specification: GatewaySelectionSpecification::Custom { + gateway_identity: identity.to_base58_string(), + additional_data: Default::default(), + }, + available_gateways: vec![], + overwrite_data: false, + }, + &key_store, + &details_store, + ) + .await?; + + let address = init_res.client_address()?; + + if let Err(err) = save_formatted_config_to_file(&ip_cfg, ip_cfg_path) { + log::error!("Failed to save the ip packet router config file: {err}"); + return Err(GatewayError::ConfigSaveFailure { + id: ip_id, + path: ip_cfg_path.to_path_buf(), + source: err, + }); + } else { + eprintln!( + "Saved ip packet router configuration file to {}", + ip_cfg_path.display() + ) + } + + Ok(GatewayIpPacketRouterDetails { + enabled: gateway_config.ip_packet_router.enabled, + identity_key: address.identity().to_string(), + encryption_key: address.encryption_key().to_string(), + address: address.to_string(), + config_path: ip_cfg_path.display().to_string(), + }) +} diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index e5e14068a8..8bcfca93b7 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::commands::helpers::{ - initialise_local_network_requester, OverrideNetworkRequesterConfig, + initialise_local_ip_packet_router, initialise_local_network_requester, + OverrideNetworkRequesterConfig, }; use crate::config::{default_config_directory, default_config_filepath, default_data_directory}; use crate::node::helpers::node_details; @@ -14,6 +15,8 @@ use std::net::IpAddr; use std::path::PathBuf; use std::{fs, io}; +use super::helpers::OverrideIpPacketRouterConfig; + #[derive(Args, Clone)] pub struct Init { /// Id of the gateway we want to create config for @@ -79,9 +82,13 @@ pub struct Init { statistics_service_url: Option, /// Allows this gateway to run an embedded network requester for minimal network overhead - #[clap(long)] + #[clap(long, conflicts_with = "with_ip_packet_router")] with_network_requester: bool, + /// Allows this gateway to run an embedded network requester for minimal network overhead + #[clap(long, hide = true, conflicts_with = "with_network_requester")] + with_ip_packet_router: bool, + // ##### NETWORK REQUESTER FLAGS ##### /// Specifies whether this network requester should run in 'open-proxy' mode #[clap(long, requires = "with_network_requester")] @@ -154,6 +161,7 @@ impl From for OverrideConfig { nyxd_urls: init_config.nyxd_urls, only_coconut_credentials: init_config.only_coconut_credentials, with_network_requester: Some(init_config.with_network_requester), + with_ip_packet_router: Some(init_config.with_ip_packet_router), } } } @@ -172,6 +180,12 @@ impl<'a> From<&'a Init> for OverrideNetworkRequesterConfig { } } +impl From<&Init> for OverrideIpPacketRouterConfig { + fn from(_value: &Init) -> Self { + OverrideIpPacketRouterConfig {} + } +} + fn init_paths(id: &str) -> io::Result<()> { fs::create_dir_all(default_data_directory(id))?; fs::create_dir_all(default_config_directory(id)) @@ -196,6 +210,7 @@ pub async fn execute(args: Init) -> anyhow::Result<()> { // Initialising the config structure is just overriding a default constructed one let fresh_config = Config::new(&args.id); let nr_opts = (&args).into(); + let ip_opts = (&args).into(); let mut config = OverrideConfig::from(args).do_override(fresh_config)?; // if gateway was already initialised, don't generate new keys, et al. @@ -228,6 +243,9 @@ pub async fn execute(args: Init) -> anyhow::Result<()> { if config.network_requester.enabled { initialise_local_network_requester(&config, nr_opts, *identity_keys.public_key()) .await?; + } else if config.ip_packet_router.enabled { + initialise_local_ip_packet_router(&config, ip_opts, *identity_keys.public_key()) + .await?; } eprintln!("Saved identity and mixnet sphinx keypairs"); @@ -276,6 +294,7 @@ mod tests { only_coconut_credentials: None, output: Default::default(), with_network_requester: false, + with_ip_packet_router: false, open_proxy: None, enable_statistics: None, statistics_recipient: None, @@ -303,6 +322,7 @@ mod tests { let _gateway = Gateway::new_from_keys_and_storage( config, None, + None, identity_keys, sphinx_keys, InMemStorage, diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 0df249edb1..99c10869a5 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -12,6 +12,7 @@ pub(crate) mod helpers; pub(crate) mod init; pub(crate) mod node_details; pub(crate) mod run; +pub(crate) mod setup_ip_packet_router; pub(crate) mod setup_network_requester; pub(crate) mod sign; mod upgrade_helpers; @@ -31,6 +32,11 @@ pub(crate) enum Commands { // essentially an option to include NR without having to setup fresh gateway SetupNetworkRequester(setup_network_requester::CmdArgs), + /// Add ip packet router support to this gateway + // essentially an option to include ip packet router without having to setup fresh gateway + #[command(hide = true)] + SetupIpPacketRouter(setup_ip_packet_router::CmdArgs), + /// Sign text to prove ownership of this mixnode Sign(sign::Sign), @@ -52,6 +58,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box node_details::execute(m).await?, Commands::Run(m) => run::execute(m).await?, Commands::SetupNetworkRequester(m) => setup_network_requester::execute(m).await?, + Commands::SetupIpPacketRouter(m) => setup_ip_packet_router::execute(m).await?, Commands::Sign(m) => sign::execute(m)?, Commands::BuildInfo(m) => build_info::execute(m), Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name), diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 185bf75a53..8e57b7b998 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -15,39 +15,41 @@ use nym_node::error::NymNodeError; use std::net::IpAddr; use std::path::PathBuf; +use super::helpers::OverrideIpPacketRouterConfig; + #[derive(Args, Clone)] pub struct Run { /// Id of the gateway we want to run - #[clap(long)] + #[arg(long)] id: String, /// The custom listening address on which the gateway will be running for receiving sphinx packets - #[clap(long, alias = "host")] + #[arg(long, alias = "host")] listening_address: Option, /// Comma separated list of public ip addresses that will announced to the nym-api and subsequently to the clients. /// In nearly all circumstances, it's going to be identical to the address you're going to use for bonding. - #[clap(long, value_delimiter = ',')] + #[arg(long, value_delimiter = ',')] public_ips: Option>, /// Optional hostname associated with this gateway that will announced to the nym-api and subsequently to the clients - #[clap(long)] + #[arg(long)] hostname: Option, /// The port on which the gateway will be listening for sphinx packets - #[clap(long)] + #[arg(long)] mix_port: Option, /// The port on which the gateway will be listening for clients gateway-requests - #[clap(long)] + #[arg(long)] clients_port: Option, /// Path to sqlite database containing all gateway persistent data - #[clap(long)] + #[arg(long)] datastore: Option, /// Comma separated list of endpoints of nym APIs - #[clap( + #[arg( long, alias = "validator_apis", value_delimiter = ',', @@ -57,7 +59,7 @@ pub struct Run { nym_apis: Option>, /// Comma separated list of endpoints of the validator - #[clap( + #[arg( long, alias = "validators", alias = "nyxd_validators", @@ -68,26 +70,30 @@ pub struct Run { nyxd_urls: Option>, /// Cosmos wallet mnemonic - #[clap(long)] + #[arg(long)] mnemonic: Option, /// Set this gateway to work only with coconut credentials; that would disallow clients to /// bypass bandwidth credential requirement - #[clap(long, hide = true)] + #[arg(long, hide = true)] only_coconut_credentials: Option, /// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server - #[clap(long)] + #[arg(long)] enabled_statistics: Option, /// URL where a statistics aggregator is running. The default value is a Nym aggregator server - #[clap(long)] + #[arg(long)] statistics_service_url: Option, /// Allows this gateway to run an embedded network requester for minimal network overhead - #[clap(long)] + #[arg(long, conflicts_with = "with_ip_packet_router")] with_network_requester: Option, + /// Allows this gateway to run an embedded network requester for minimal network overhead + #[arg(long, hide = true, conflicts_with = "with_network_requester")] + with_ip_packet_router: Option, + // ##### NETWORK REQUESTER FLAGS ##### /// Specifies whether this network requester should run in 'open-proxy' mode #[arg(long)] @@ -123,20 +129,20 @@ pub struct Run { /// Path to .json file containing custom network specification. /// Only usable when local network requester is enabled. - #[clap(long, group = "network", hide = true)] + #[arg(long, group = "network", hide = true)] custom_mixnet: Option, /// Specifies whether this network requester will run using the default ExitPolicy /// as opposed to the allow list. /// Note: this setting will become the default in the future releases. - #[clap(long)] + #[arg(long)] with_exit_policy: Option, - #[clap(short, long, default_value_t = OutputFormat::default())] + #[arg(short, long, default_value_t = OutputFormat::default())] output: OutputFormat, /// Flag specifying this node will be running in a local setting. - #[clap(long)] + #[arg(long)] local: bool, } @@ -157,6 +163,7 @@ impl From for OverrideConfig { nyxd_urls: run_config.nyxd_urls, only_coconut_credentials: run_config.only_coconut_credentials, with_network_requester: run_config.with_network_requester, + with_ip_packet_router: run_config.with_ip_packet_router, } } } @@ -175,6 +182,12 @@ impl<'a> From<&'a Run> for OverrideNetworkRequesterConfig { } } +impl From<&Run> for OverrideIpPacketRouterConfig { + fn from(_value: &Run) -> Self { + OverrideIpPacketRouterConfig {} + } +} + fn show_binding_warning(address: IpAddr) { eprintln!("\n##### NOTE #####"); eprintln!( @@ -217,6 +230,7 @@ pub async fn execute(args: Run) -> anyhow::Result<()> { let output = args.output; let custom_mixnet = args.custom_mixnet.clone(); let nr_opts = (&args).into(); + let ip_opts = (&args).into(); let config = build_config(id, args)?; ensure_config_version_compatibility(&config)?; @@ -235,7 +249,8 @@ pub async fn execute(args: Run) -> anyhow::Result<()> { } let node_details = node_details(&config)?; - let gateway = crate::node::create_gateway(config, Some(nr_opts), custom_mixnet).await?; + let gateway = + crate::node::create_gateway(config, Some(nr_opts), Some(ip_opts), custom_mixnet).await?; eprintln!( "\nTo bond your gateway you will need to install the Nym wallet, go to https://nymtech.net/get-involved and select the Download button.\n\ Select the correct version and install it to your machine. You will need to provide some of the following: \n "); diff --git a/gateway/src/commands/setup_ip_packet_router.rs b/gateway/src/commands/setup_ip_packet_router.rs new file mode 100644 index 0000000000..633c2a4bab --- /dev/null +++ b/gateway/src/commands/setup_ip_packet_router.rs @@ -0,0 +1,67 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::commands::helpers::{ + initialise_local_ip_packet_router, try_load_current_config, OverrideIpPacketRouterConfig, +}; +use crate::node::helpers::load_public_key; +use clap::Args; +use nym_bin_common::output_format::OutputFormat; +use std::path::PathBuf; + +#[derive(Args, Clone)] +pub struct CmdArgs { + /// The id of the gateway you want to initialise local ip packet router for. + #[arg(long)] + id: String, + + /// Path to custom location for ip packet routers' config. + #[arg(long)] + custom_config_path: Option, + + /// Specify whether the ip packet router should be enabled. + // (you might want to create all the configs, generate keys, etc. but not actually run the NR just yet) + #[arg(long)] + enabled: Option, + + #[arg(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +impl From<&CmdArgs> for OverrideIpPacketRouterConfig { + fn from(_value: &CmdArgs) -> Self { + OverrideIpPacketRouterConfig {} + } +} + +pub async fn execute(args: CmdArgs) -> anyhow::Result<()> { + let mut config = try_load_current_config(&args.id)?; + let opts = (&args).into(); + + // if somebody provided config file of a custom NR, that's fine + // but in 90% cases, I'd assume, it won't work due to invalid gateway configuration + // but it might be nice to be able to move files around. + if let Some(custom_config_path) = args.custom_config_path { + // if you specified anything as the argument, overwrite whatever was already in the config file + config.storage_paths.ip_packet_router_config = Some(custom_config_path); + } + + if let Some(override_enabled) = args.enabled { + config.ip_packet_router.enabled = override_enabled; + } + + if config.storage_paths.ip_packet_router_config.is_none() { + config = config.with_default_ip_packet_router_config_path() + } + + let identity_public_key = load_public_key( + &config.storage_paths.keys.public_identity_key_file, + "gateway identity", + )?; + let details = initialise_local_ip_packet_router(&config, opts, identity_public_key).await?; + config.try_save()?; + + args.output.to_stdout(&details); + + Ok(()) +} diff --git a/gateway/src/commands/upgrade_helpers.rs b/gateway/src/commands/upgrade_helpers.rs index 7b13cc3f53..e09e6f827d 100644 --- a/gateway/src/commands/upgrade_helpers.rs +++ b/gateway/src/commands/upgrade_helpers.rs @@ -4,6 +4,7 @@ use crate::config::old_config_v1_1_20::ConfigV1_1_20; use crate::config::old_config_v1_1_28::ConfigV1_1_28; use crate::config::old_config_v1_1_29::ConfigV1_1_29; +use crate::config::old_config_v1_1_31::ConfigV1_1_31; use crate::config::{default_config_filepath, Config}; use crate::error::GatewayError; use log::info; @@ -22,7 +23,8 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result { let updated_step1: ConfigV1_1_28 = old_config.into(); let updated_step2: ConfigV1_1_29 = updated_step1.into(); - let updated: Config = updated_step2.into(); + let updated_step3: ConfigV1_1_31 = updated_step2.into(); + let updated: Config = updated_step3.into(); updated .save_to_default_location() .map_err(|err| GatewayError::ConfigSaveFailure { @@ -45,7 +47,8 @@ fn try_upgrade_v1_1_28_config(id: &str) -> Result { info!("It is going to get updated to the current specification."); let updated_step1: ConfigV1_1_29 = old_config.into(); - let updated: Config = updated_step1.into(); + let updated_step2: ConfigV1_1_31 = updated_step1.into(); + let updated: Config = updated_step2.into(); updated .save_to_default_location() .map_err(|err| GatewayError::ConfigSaveFailure { @@ -67,6 +70,29 @@ fn try_upgrade_v1_1_29_config(id: &str) -> Result { info!("It seems the gateway is using <= v1.1.29 config template."); info!("It is going to get updated to the current specification."); + let updated_step1: ConfigV1_1_31 = old_config.into(); + let updated: Config = updated_step1.into(); + updated + .save_to_default_location() + .map_err(|err| GatewayError::ConfigSaveFailure { + path: default_config_filepath(id), + id: id.to_string(), + source: err, + })?; + + Ok(true) +} + +fn try_upgrade_v1_1_31_config(id: &str) -> Result { + // explicitly load it as v1.1.30 (which is incompatible with the current, i.e. 1.1.31+) + let Ok(old_config) = ConfigV1_1_31::read_from_default_path(id) else { + // if we failed to load it, there might have been nothing to upgrade + // or maybe it was an even older file. in either way. just ignore it and carry on with our day + return Ok(false); + }; + info!("It seems the gateway is using <= v1.1.30 config template."); + info!("It is going to get updated to the current specification."); + let updated: Config = old_config.into(); updated .save_to_default_location() @@ -89,6 +115,9 @@ pub(crate) fn try_upgrade_config(id: &str) -> Result<(), GatewayError> { if try_upgrade_v1_1_29_config(id)? { return Ok(()); } + if try_upgrade_v1_1_31_config(id)? { + return Ok(()); + } Ok(()) } diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 134964fe94..4737f38c58 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -24,6 +24,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; pub(crate) mod old_config_v1_1_20; pub(crate) mod old_config_v1_1_28; pub(crate) mod old_config_v1_1_29; +pub(crate) mod old_config_v1_1_31; pub mod persistence; mod template; @@ -100,6 +101,9 @@ pub struct Config { pub network_requester: NetworkRequester, + #[serde(default)] + pub ip_packet_router: IpPacketRouter, + #[serde(default)] pub logging: LoggingSettings, @@ -128,6 +132,7 @@ impl Config { wireguard: Default::default(), storage_paths: GatewayPaths::new_default(id.as_ref()), network_requester: Default::default(), + ip_packet_router: Default::default(), logging: Default::default(), debug: Default::default(), } @@ -195,6 +200,18 @@ impl Config { self } + pub fn with_enabled_ip_packet_router(mut self, enabled_ip_packet_router: bool) -> Self { + self.ip_packet_router.enabled = enabled_ip_packet_router; + self + } + + pub fn with_default_ip_packet_router_config_path(mut self) -> Self { + self.storage_paths = self + .storage_paths + .with_default_ip_packet_router_config(&self.gateway.id); + self + } + pub fn with_only_coconut_credentials(mut self, only_coconut_credentials: bool) -> Self { self.gateway.only_coconut_credentials = only_coconut_credentials; self @@ -360,6 +377,20 @@ impl Default for NetworkRequester { } } +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct IpPacketRouter { + /// Specifies whether ip packet router service is enabled in this process. + pub enabled: bool, +} + +#[allow(clippy::derivable_impls)] +impl Default for IpPacketRouter { + fn default() -> Self { + Self { enabled: false } + } +} + #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default)] pub struct Debug { diff --git a/gateway/src/config/old_config_v1_1_29.rs b/gateway/src/config/old_config_v1_1_29.rs index e38ff2a57c..bbd939b16a 100644 --- a/gateway/src/config/old_config_v1_1_29.rs +++ b/gateway/src/config/old_config_v1_1_29.rs @@ -1,9 +1,6 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::persistence::paths::{GatewayPaths, KeysPaths}; -use crate::config::{Config, Debug, Gateway, NetworkRequester}; -use nym_bin_common::logging::LoggingSettings; use nym_config::{ must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR, }; @@ -14,6 +11,11 @@ use std::path::{Path, PathBuf}; use std::time::Duration; use url::Url; +use super::old_config_v1_1_31::{ + ConfigV1_1_31, DebugV1_1_31, GatewayPathsV1_1_31, GatewayV1_1_31, KeysPathsV1_1_31, + LoggingSettingsV1_1_31, NetworkRequesterV1_1_31, +}; + const DEFAULT_GATEWAYS_DIR: &str = "gateways"; // 'DEBUG' @@ -105,9 +107,9 @@ impl ConfigV1_1_29 { } } -impl From for Config { +impl From for ConfigV1_1_31 { fn from(value: ConfigV1_1_29) -> Self { - Config { + ConfigV1_1_31 { save_path: value.save_path, // \/ ADDED @@ -121,7 +123,7 @@ impl From for Config { // \/ ADDED http: Default::default(), // /\ ADDED - gateway: Gateway { + gateway: GatewayV1_1_31 { version: value.gateway.version, id: value.gateway.id, only_coconut_credentials: value.gateway.only_coconut_credentials, @@ -141,8 +143,8 @@ impl From for Config { // \/ ADDED wireguard: Default::default(), // /\ ADDED - storage_paths: GatewayPaths { - keys: KeysPaths { + storage_paths: GatewayPathsV1_1_31 { + keys: KeysPathsV1_1_31 { private_identity_key_file: value.storage_paths.keys.private_identity_key_file, public_identity_key_file: value.storage_paths.keys.public_identity_key_file, private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file, @@ -151,11 +153,11 @@ impl From for Config { clients_storage: value.storage_paths.clients_storage, network_requester_config: value.storage_paths.network_requester_config, }, - network_requester: NetworkRequester { + network_requester: NetworkRequesterV1_1_31 { enabled: value.network_requester.enabled, }, - logging: LoggingSettings {}, - debug: Debug { + logging: LoggingSettingsV1_1_31 {}, + debug: DebugV1_1_31 { packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff, packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff, initial_connection_timeout: value.debug.initial_connection_timeout, diff --git a/gateway/src/config/old_config_v1_1_31.rs b/gateway/src/config/old_config_v1_1_31.rs new file mode 100644 index 0000000000..6391b18115 --- /dev/null +++ b/gateway/src/config/old_config_v1_1_31.rs @@ -0,0 +1,362 @@ +// Copyright 2020-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::paths::GatewayPaths; +use nym_bin_common::logging::LoggingSettings; +use nym_config::{ + must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR, +}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::io; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use url::Url; + +use super::persistence::paths::KeysPaths; +use super::{Config, Debug, Gateway, NetworkRequester}; + +const DEFAULT_GATEWAYS_DIR: &str = "gateways"; + +// 'DEBUG' +// where applicable, the below are defined in milliseconds +const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000); +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; + +const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; +const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; + +fn de_maybe_port<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let port = u16::deserialize(deserializer)?; + if port == 0 { + Ok(None) + } else { + Ok(Some(port)) + } +} + +fn de_maybe_path<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let path = PathBuf::deserialize(deserializer)?; + if path.as_os_str().is_empty() { + Ok(None) + } else { + Ok(Some(path)) + } +} + +/// Derive default path to gateway's config directory. +/// It should get resolved to `$HOME/.nym/gateways//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_GATEWAYS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to gateways's config file. +/// It should get resolved to `$HOME/.nym/gateways//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_31 { + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, + + pub host: nym_node::config::Host, + + #[serde(default)] + pub http: nym_node::config::Http, + + pub gateway: GatewayV1_1_31, + + #[serde(default)] + // currently not really used for anything useful + pub wireguard: WireguardV1_1_31, + + pub storage_paths: GatewayPathsV1_1_31, + + pub network_requester: NetworkRequesterV1_1_31, + + #[serde(default)] + pub logging: LoggingSettingsV1_1_31, + + #[serde(default)] + pub debug: DebugV1_1_31, +} + +impl ConfigV1_1_31 { + pub fn read_from_default_path>(id: P) -> io::Result { + read_config_from_toml_file(default_config_filepath(id)) + } +} + +impl From for Config { + fn from(value: ConfigV1_1_31) -> Self { + Self { + save_path: value.save_path, + host: value.host, + http: value.http, + gateway: Gateway { + version: value.gateway.version, + id: value.gateway.id, + only_coconut_credentials: value.gateway.only_coconut_credentials, + listening_address: value.gateway.listening_address, + mix_port: value.gateway.mix_port, + clients_port: value.gateway.clients_port, + clients_wss_port: value.gateway.clients_wss_port, + enabled_statistics: value.gateway.enabled_statistics, + statistics_service_url: value.gateway.statistics_service_url, + nym_api_urls: value.gateway.nym_api_urls, + nyxd_urls: value.gateway.nyxd_urls, + cosmos_mnemonic: value.gateway.cosmos_mnemonic, + }, + wireguard: nym_node::config::Wireguard { + enabled: value.wireguard.enabled, + bind_address: value.wireguard.bind_address, + announced_port: value.wireguard.announced_port, + storage_paths: nym_node::config::persistence::WireguardPaths { + // no fields (yet) + }, + }, + storage_paths: GatewayPaths { + keys: KeysPaths { + private_identity_key_file: value.storage_paths.keys.private_identity_key_file, + public_identity_key_file: value.storage_paths.keys.public_identity_key_file, + private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file, + public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file, + }, + clients_storage: value.storage_paths.clients_storage, + network_requester_config: value.storage_paths.network_requester_config, + // \/ ADDED + ip_packet_router_config: Default::default(), + // /\ ADDED + }, + network_requester: NetworkRequester { + enabled: value.network_requester.enabled, + }, + // \/ ADDED + ip_packet_router: Default::default(), + // /\ ADDED + logging: LoggingSettings { + // no fields (yet) + }, + debug: Debug { + packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff, + initial_connection_timeout: value.debug.initial_connection_timeout, + maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size, + presence_sending_delay: value.debug.presence_sending_delay, + stored_messages_filename_length: value.debug.stored_messages_filename_length, + message_retrieval_limit: value.debug.message_retrieval_limit, + use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version, + }, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct GatewayV1_1_31 { + /// Version of the gateway for which this configuration was created. + pub version: String, + + /// ID specifies the human readable ID of this particular gateway. + pub id: String, + + /// Indicates whether this gateway is accepting only coconut credentials for accessing the + /// the mixnet, or if it also accepts non-paying clients + #[serde(default)] + pub only_coconut_credentials: bool, + + /// Address to which this mixnode will bind to and will be listening for packets. + pub listening_address: IpAddr, + + /// Port used for listening for all mixnet traffic. + /// (default: 1789) + pub mix_port: u16, + + /// Port used for listening for all client-related traffic. + /// (default: 9000) + pub clients_port: u16, + + /// If applicable, announced port for listening for secure websocket client traffic. + /// (default: None) + #[serde(deserialize_with = "de_maybe_port")] + pub clients_wss_port: Option, + + /// Whether gateway collects and sends anonymized statistics + pub enabled_statistics: bool, + + /// Domain address of the statistics service + pub statistics_service_url: Url, + + /// Addresses to APIs from which the node gets the view of the network. + #[serde(alias = "validator_api_urls")] + pub nym_api_urls: Vec, + + /// Addresses to validators which the node uses to check for double spending of ERC20 tokens. + #[serde(alias = "validator_nymd_urls")] + pub nyxd_urls: Vec, + + /// Mnemonic of a cosmos wallet used in checking for double spending. + // #[deprecated(note = "move to storage")] + // TODO: I don't think this should be stored directly in the config... + pub cosmos_mnemonic: bip39::Mnemonic, +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct WireguardV1_1_31 { + /// 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: `0.0.0.0:51820` + pub bind_address: SocketAddr, + + /// 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_port: u16, + + /// Paths for wireguard keys, client registries, etc. + pub storage_paths: WireguardPathsV1_1_31, +} + +impl Default for WireguardV1_1_31 { + fn default() -> Self { + Self { + enabled: false, + bind_address: SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + nym_node::config::DEFAULT_WIREGUARD_PORT, + ), + announced_port: nym_node::config::DEFAULT_WIREGUARD_PORT, + storage_paths: WireguardPathsV1_1_31 {}, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardPathsV1_1_31 { + // pub keys: +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct GatewayPathsV1_1_31 { + pub keys: KeysPathsV1_1_31, + + /// Path to sqlite database containing all persistent data: messages for offline clients, + /// derived shared keys and available client bandwidths. + #[serde(alias = "persistent_storage")] + pub clients_storage: PathBuf, + + /// Path to the configuration of the embedded network requester. + #[serde(deserialize_with = "de_maybe_path")] + pub network_requester_config: Option, + // pub node_description: PathBuf, + + // pub cosmos_bip39_mnemonic: PathBuf, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +pub struct KeysPathsV1_1_31 { + /// Path to file containing private identity key. + pub private_identity_key_file: PathBuf, + + /// Path to file containing public identity key. + pub public_identity_key_file: PathBuf, + + /// Path to file containing private sphinx key. + pub private_sphinx_key_file: PathBuf, + + /// Path to file containing public sphinx key. + pub public_sphinx_key_file: PathBuf, +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct NetworkRequesterV1_1_31 { + /// Specifies whether network requester service is enabled in this process. + pub enabled: bool, +} + +#[allow(clippy::derivable_impls)] +impl Default for NetworkRequesterV1_1_31 { + fn default() -> Self { + Self { enabled: false } + } +} + +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct LoggingSettingsV1_1_31 { + // well, we need to implement something here at some point... +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct DebugV1_1_31 { + /// 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, + + /// Delay between each subsequent presence data being sent. + #[serde(with = "humantime_serde")] + pub presence_sending_delay: Duration, + + /// Length of filenames for new client messages. + pub stored_messages_filename_length: u16, + + /// Number of messages from offline client that can be pulled at once from the storage. + pub message_retrieval_limit: i64, + + /// Specifies whether the mixnode should be using the legacy framing for the sphinx packets. + // it's set to true by default. The reason for that decision is to preserve compatibility with the + // existing nodes whilst everyone else is upgrading and getting the code for handling the new field. + // It shall be disabled in the subsequent releases. + pub use_legacy_framed_packet_version: bool, +} + +impl Default for DebugV1_1_31 { + fn default() -> Self { + Self { + packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, + packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, + initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, + presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, + maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH, + message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + use_legacy_framed_packet_version: false, + } + } +} diff --git a/gateway/src/config/persistence/paths.rs b/gateway/src/config/persistence/paths.rs index 8d3be49335..340b4a6495 100644 --- a/gateway/src/config/persistence/paths.rs +++ b/gateway/src/config/persistence/paths.rs @@ -15,12 +15,19 @@ pub const DEFAULT_CLIENTS_STORAGE_FILENAME: &str = "db.sqlite"; pub const DEFAULT_NETWORK_REQUESTER_CONFIG_FILENAME: &str = "network_requester_config.toml"; pub const DEFAULT_NETWORK_REQUESTER_DATA_DIR: &str = "network-requester-data"; +pub const DEFAULT_IP_PACKET_ROUTER_CONFIG_FILENAME: &str = "ip_packet_router_config.toml"; +pub const DEFAULT_IP_PACKET_ROUTER_DATA_DIR: &str = "ip-packet-router-data"; + // pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml"; pub fn default_network_requester_data_dir>(id: P) -> PathBuf { default_data_directory(id).join(DEFAULT_NETWORK_REQUESTER_DATA_DIR) } +pub fn default_ip_packet_router_data_dir>(id: P) -> PathBuf { + default_data_directory(id).join(DEFAULT_IP_PACKET_ROUTER_DATA_DIR) +} + /// makes sure that an empty path is converted into a `None` as opposed to `Some("")` fn de_maybe_path<'de, D>(deserializer: D) -> Result, D::Error> where @@ -50,6 +57,9 @@ pub struct GatewayPaths { // pub node_description: PathBuf, // pub cosmos_bip39_mnemonic: PathBuf, + /// Path to the configuration of the embedded ip packet router. + #[serde(deserialize_with = "de_maybe_path")] + pub ip_packet_router_config: Option, } impl GatewayPaths { @@ -59,6 +69,7 @@ impl GatewayPaths { clients_storage: default_data_directory(id).join(DEFAULT_CLIENTS_STORAGE_FILENAME), // node_description: default_config_filepath(id).join(DEFAULT_DESCRIPTION_FILENAME), network_requester_config: None, + ip_packet_router_config: None, } } @@ -75,10 +86,27 @@ impl GatewayPaths { ) } + #[must_use] + pub fn with_ip_packet_router_config>(mut self, path: P) -> Self { + self.ip_packet_router_config = Some(path.as_ref().into()); + self + } + + #[must_use] + pub fn with_default_ip_packet_router_config>(self, id: P) -> Self { + self.with_ip_packet_router_config( + default_config_directory(id).join(DEFAULT_IP_PACKET_ROUTER_CONFIG_FILENAME), + ) + } + pub fn network_requester_config(&self) -> &Option { &self.network_requester_config } + pub fn ip_packet_router_config(&self) -> &Option { + &self.ip_packet_router_config + } + pub fn private_identity_key(&self) -> &Path { self.keys.private_identity_key() } diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index 0f77d499fc..040a92bddd 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -82,6 +82,10 @@ landing_page_assets_path = '{{ http.landing_page_assets_path }}' # Specifies whether network requester service is enabled in this process. enabled = {{ network_requester.enabled }} +[ip_packet_router] +# Specifies whether ip packet router service is enabled in this process. +enabled = {{ ip_packet_router.enabled }} + [storage_paths] # Path to file containing private identity key. @@ -103,6 +107,9 @@ clients_storage = '{{ storage_paths.clients_storage }}' # Path to the configuration of the embedded network requester. network_requester_config = '{{ storage_paths.network_requester_config }}' +# Path to the configuration of the embedded ip packet router. +ip_packet_router_config = '{{ storage_paths.ip_packet_router_config }}' + ##### logging configuration options ##### [logging] diff --git a/gateway/src/error.rs b/gateway/src/error.rs index 438b684981..d69b8eb480 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::storage::error::StorageError; +use nym_ip_packet_router::error::IpForwarderError; use nym_network_requester::error::{ClientCoreError, NetworkRequesterError}; use nym_validator_client::nyxd::error::NyxdError; use nym_validator_client::nyxd::AccountId; @@ -48,6 +49,17 @@ pub(crate) enum GatewayError { source: io::Error, }, + #[error( + "failed to load config file for ip packet router (gateway-id: '{id}') using path '{}'. detailed message: {source}", + path.display() + )] + IpPacketRouterConfigLoadFailure { + id: String, + path: PathBuf, + #[source] + source: io::Error, + }, + #[error( "failed to save config file for id {id} using path '{}'. detailed message: {source}", path.display() )] @@ -86,15 +98,27 @@ pub(crate) enum GatewayError { #[error("Path to network requester configuration file hasn't been specified. Perhaps try to run `setup-network-requester`?")] UnspecifiedNetworkRequesterConfig, + #[error("Path to ip packet router configuration file hasn't been specified. Perhaps try to run `setup-ip-packet-router`?")] + UnspecifiedIpPacketRouterConfig, + #[error("there was an issue with the local network requester: {source}")] NetworkRequesterFailure { #[from] source: NetworkRequesterError, }, + #[error("there was an issue with the local ip packet router: {source}")] + IpPacketRouterFailure { + #[from] + source: IpForwarderError, + }, + #[error("failed to startup local network requester")] NetworkRequesterStartupFailure, + #[error("failed to startup local ip packet router")] + IpPacketRouterStartupFailure, + #[error("there are no nym API endpoints available")] NoNymApisAvailable, diff --git a/gateway/src/node/client_handling/embedded_network_requester/mod.rs b/gateway/src/node/client_handling/embedded_network_requester/mod.rs index e088535cd9..8ed7e2f978 100644 --- a/gateway/src/node/client_handling/embedded_network_requester/mod.rs +++ b/gateway/src/node/client_handling/embedded_network_requester/mod.rs @@ -28,6 +28,17 @@ impl LocalNetworkRequesterHandle { } } + // TODO: generalize this whole thing to be general. And change the name(s). + pub(crate) fn new_ip( + start_data: nym_ip_packet_router::OnStartData, + mix_message_sender: MixMessageSender, + ) -> Self { + Self { + address: start_data.address, + mix_message_sender, + } + } + pub(crate) fn client_destination(&self) -> DestinationAddressBytes { self.address.identity().derive_destination_address() } diff --git a/gateway/src/node/helpers.rs b/gateway/src/node/helpers.rs index 9535519fce..0b8fc9c994 100644 --- a/gateway/src/node/helpers.rs +++ b/gateway/src/node/helpers.rs @@ -96,6 +96,20 @@ pub(crate) fn load_network_requester_config>( }) } +pub(crate) fn load_ip_packet_router_config>( + id: &str, + path: P, +) -> Result { + let path = path.as_ref(); + nym_ip_packet_router::Config::read_from_toml_file(path).map_err(|err| { + GatewayError::IpPacketRouterConfigLoadFailure { + id: id.to_string(), + path: path.to_path_buf(), + source: err, + } + }) +} + pub(crate) async fn initialise_main_storage( config: &Config, ) -> Result { diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index a51180ba35..d3851a671c 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -1,8 +1,12 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use self::helpers::load_ip_packet_router_config; use self::storage::PersistentStorage; -use crate::commands::helpers::{override_network_requester_config, OverrideNetworkRequesterConfig}; +use crate::commands::helpers::{ + override_ip_packet_router_config, override_network_requester_config, + OverrideIpPacketRouterConfig, OverrideNetworkRequesterConfig, +}; use crate::config::Config; use crate::error::GatewayError; use crate::http::HttpApiBuilder; @@ -54,7 +58,8 @@ struct StartedNetworkRequester { pub(crate) async fn create_gateway( config: Config, nr_config_override: Option, - custom_nr_mixnet: Option, + ip_config_override: Option, + custom_mixnet: Option, ) -> Result { // don't attempt to read config if NR is disabled let network_requester_config = if config.network_requester.enabled { @@ -69,14 +74,32 @@ pub(crate) async fn create_gateway( None }; + // don't attempt to read config if NR is disabled + let ip_packet_router_config = if config.ip_packet_router.enabled { + if let Some(path) = &config.storage_paths.ip_packet_router_config { + let cfg = load_ip_packet_router_config(&config.gateway.id, path)?; + Some(override_ip_packet_router_config(cfg, ip_config_override)) + } else { + // if NR is enabled, the config path must be specified + return Err(GatewayError::UnspecifiedIpPacketRouterConfig); + } + } else { + None + }; + let storage = initialise_main_storage(&config).await?; let nr_opts = network_requester_config.map(|config| LocalNetworkRequesterOpts { - config, - custom_mixnet_path: custom_nr_mixnet, + config: config.clone(), + custom_mixnet_path: custom_mixnet.clone(), }); - Gateway::new(config, nr_opts, storage) + let ip_opts = ip_packet_router_config.map(|config| LocalIpPacketRouterOpts { + config, + custom_mixnet_path: custom_mixnet, + }); + + Gateway::new(config, nr_opts, ip_opts, storage) } #[derive(Debug, Clone)] @@ -86,11 +109,20 @@ pub struct LocalNetworkRequesterOpts { custom_mixnet_path: Option, } +#[derive(Debug, Clone)] +pub struct LocalIpPacketRouterOpts { + config: nym_ip_packet_router::Config, + + custom_mixnet_path: Option, +} + pub(crate) struct Gateway { config: Config, network_requester_opts: Option, + ip_packet_router_opts: Option, + /// ed25519 keypair used to assert one's identity. identity_keypair: Arc, @@ -106,6 +138,7 @@ impl Gateway { pub fn new( config: Config, network_requester_opts: Option, + ip_packet_router_opts: Option, storage: St, ) -> Result { Ok(Gateway { @@ -114,6 +147,7 @@ impl Gateway { sphinx_keypair: Arc::new(helpers::load_sphinx_keys(&config)?), config, network_requester_opts, + ip_packet_router_opts, client_registry: Arc::new(DashMap::new()), }) } @@ -122,6 +156,7 @@ impl Gateway { pub async fn new_from_keys_and_storage( config: Config, network_requester_opts: Option, + ip_packet_router_opts: Option, identity_keypair: identity::KeyPair, sphinx_keypair: encryption::KeyPair, storage: St, @@ -129,6 +164,7 @@ impl Gateway { Gateway { config, network_requester_opts, + ip_packet_router_opts, identity_keypair: Arc::new(identity_keypair), sphinx_keypair: Arc::new(sphinx_keypair), storage, @@ -283,6 +319,73 @@ impl Gateway { }) } + async fn start_ip_packet_router( + &self, + forwarding_channel: MixForwardingSender, + shutdown: TaskClient, + ) -> Result { + info!("Starting IP packet provider..."); + + // if network requester is enabled, configuration file must be provided! + let Some(ip_opts) = &self.ip_packet_router_opts else { + log::error!("IP packet router is enabled but no configuration file was provided!"); + return Err(GatewayError::UnspecifiedIpPacketRouterConfig); + }; + + // this gateway, whenever it has anything to send to its local NR will use fake_client_tx + let (nr_mix_sender, nr_mix_receiver) = mpsc::unbounded(); + let router_shutdown = shutdown.fork("message_router"); + + let (router_tx, mut router_rx) = oneshot::channel(); + + let transceiver = LocalGateway::new( + *self.identity_keypair.public_key(), + forwarding_channel, + router_tx, + ); + + // TODO: well, wire it up internally to gateway traffic, shutdowns, etc. + let (on_start_tx, on_start_rx) = oneshot::channel(); + let mut ip_builder = nym_ip_packet_router::IpForwarderBuilder::new(ip_opts.config.clone()) + .with_shutdown(shutdown) + .with_custom_gateway_transceiver(Box::new(transceiver)) + .with_wait_for_gateway(true) + .with_on_start(on_start_tx); + + if let Some(custom_mixnet) = &ip_opts.custom_mixnet_path { + ip_builder = ip_builder.with_stored_topology(custom_mixnet)? + } + + tokio::spawn(async move { + if let Err(err) = ip_builder.run_service_provider().await { + // no need to panic as we have passed a task client to the ip packet router so + // we're most likely already in the process of shutting down + error!("ip packet router has failed: {err}") + } + }); + + let start_data = on_start_rx + .await + .map_err(|_| GatewayError::IpPacketRouterStartupFailure)?; + + // this should be instantaneous since the data is sent on this channel before the on start is called; + // the failure should be impossible + let Ok(Some(packet_router)) = router_rx.try_recv() else { + return Err(GatewayError::IpPacketRouterStartupFailure); + }; + + MessageRouter::new(nr_mix_receiver, packet_router).start_with_shutdown(router_shutdown); + info!( + "the local ip packet router is running on {}", + start_data.address + ); + + Ok(LocalNetworkRequesterHandle::new_ip( + start_data, + nr_mix_sender, + )) + } + async fn wait_for_interrupt(shutdown: TaskManager) -> Result<(), Box> { let res = shutdown.catch_interrupt().await; log::info!("Stopping nym gateway"); @@ -387,6 +490,18 @@ impl Gateway { None }; + // NOTE: this is mutually exclusive with the network requester (for now). This is reflected + // in the command line arguments as well. + if self.config.ip_packet_router.enabled { + let embedded_ip_sp = self + .start_ip_packet_router( + mix_forwarding_channel.clone(), + shutdown.subscribe().named("ip_service_provider"), + ) + .await?; + active_clients_store.insert_embedded(embedded_ip_sp); + } + HttpApiBuilder::new( &self.config, self.identity_keypair.as_ref(), diff --git a/service-providers/ip-forwarder/src/config/mod.rs b/service-providers/ip-forwarder/src/config/mod.rs deleted file mode 100644 index 1792793107..0000000000 --- a/service-providers/ip-forwarder/src/config/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::{io, path::Path}; - -pub use nym_client_core::config::Config as BaseClientConfig; -use serde::{Deserialize, Serialize}; - -use crate::config::persistence::IpForwarderPaths; - -mod persistence; - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Config { - #[serde(flatten)] - pub base: BaseClientConfig, - - pub storage_paths: IpForwarderPaths, -} - -impl Config { - pub fn read_from_toml_file>(path: P) -> io::Result { - nym_config::read_config_from_toml_file(path) - } -} diff --git a/service-providers/ip-forwarder/Cargo.toml b/service-providers/ip-packet-router/Cargo.toml similarity index 81% rename from service-providers/ip-forwarder/Cargo.toml rename to service-providers/ip-packet-router/Cargo.toml index 777de65c28..c01c13cfb4 100644 --- a/service-providers/ip-forwarder/Cargo.toml +++ b/service-providers/ip-packet-router/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nym-ip-forwarder" +name = "nym-ip-packet-router" version = "0.1.0" authors.workspace = true repository.workspace = true @@ -11,9 +11,11 @@ license.workspace = true [dependencies] futures = { workspace = true } log = { workspace = true } +nym-bin-common = { path = "../../common/bin-common" } nym-client-core = { path = "../../common/client-core" } nym-config = { path = "../../common/config" } nym-sdk = { path = "../../sdk/rust/nym-sdk" } +nym-service-providers-common = { path = "../common" } nym-sphinx = { path = "../../common/nymsphinx" } nym-task = { path = "../../common/task" } serde = { workspace = true, features = ["derive"] } diff --git a/service-providers/ip-packet-router/src/config/mod.rs b/service-providers/ip-packet-router/src/config/mod.rs new file mode 100644 index 0000000000..19bcaedc49 --- /dev/null +++ b/service-providers/ip-packet-router/src/config/mod.rs @@ -0,0 +1,104 @@ +pub use nym_client_core::config::Config as BaseClientConfig; + +use nym_bin_common::logging::LoggingSettings; +use nym_config::{ + must_get_home, save_formatted_config_to_file, NymConfigTemplate, DEFAULT_CONFIG_DIR, + DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, +}; +use nym_service_providers_common::DEFAULT_SERVICE_PROVIDERS_DIR; +use serde::{Deserialize, Serialize}; +use std::{ + io, + path::{Path, PathBuf}, +}; + +use crate::config::persistence::IpForwarderPaths; + +use self::template::CONFIG_TEMPLATE; + +mod persistence; +mod template; + +const DEFAULT_IP_FORWARDERS_DIR: &str = "ip-forwarder"; + +/// Derive default path to ip forwarder's config directory. +/// It should get resolved to `$HOME/.nym/service-providers/ip-forwareder//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_SERVICE_PROVIDERS_DIR) + .join(DEFAULT_IP_FORWARDERS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to ip forwarder's config file. +/// It should get resolved to `$HOME/.nym/service-providers/ip-forwarder//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +/// Derive default path to network requester's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/service-providers/network-requester//data` +pub fn default_data_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_SERVICE_PROVIDERS_DIR) + .join(DEFAULT_IP_FORWARDERS_DIR) + .join(id) + .join(DEFAULT_DATA_DIR) +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + #[serde(flatten)] + pub base: BaseClientConfig, + + pub storage_paths: IpForwarderPaths, + + pub logging: LoggingSettings, +} + +impl NymConfigTemplate for Config { + fn template(&self) -> &'static str { + CONFIG_TEMPLATE + } +} + +impl Config { + pub fn new>(id: S) -> Self { + Config { + base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")), + storage_paths: IpForwarderPaths::new_base(default_data_directory(id.as_ref())), + logging: Default::default(), + } + } + + pub fn with_data_directory>(mut self, data_directory: P) -> Self { + self.storage_paths = IpForwarderPaths::new_base(data_directory); + self + } + + pub fn read_from_toml_file>(path: P) -> io::Result { + nym_config::read_config_from_toml_file(path) + } + + pub fn read_from_default_path>(id: P) -> io::Result { + Self::read_from_toml_file(default_config_filepath(id)) + } + + pub fn default_location(&self) -> PathBuf { + default_config_filepath(&self.base.client.id) + } + + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = self.default_location(); + save_formatted_config_to_file(self, config_save_location) + } + + pub fn validate(&self) -> bool { + // no other sections have explicit requirements (yet) + self.base.validate() + } +} diff --git a/service-providers/ip-forwarder/src/config/persistence.rs b/service-providers/ip-packet-router/src/config/persistence.rs similarity index 100% rename from service-providers/ip-forwarder/src/config/persistence.rs rename to service-providers/ip-packet-router/src/config/persistence.rs diff --git a/service-providers/ip-packet-router/src/config/template.rs b/service-providers/ip-packet-router/src/config/template.rs new file mode 100644 index 0000000000..2da118a580 --- /dev/null +++ b/service-providers/ip-packet-router/src/config/template.rs @@ -0,0 +1,105 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) const CONFIG_TEMPLATE: &str = + // While using normal toml marshalling would have been way simpler with less overhead, + // I think it's useful to have comments attached to the saved config file to explain behaviour of + // particular fields. + // Note: any changes to the template must be reflected in the appropriate structs. + r#" +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base client config options ##### + +[client] +# Version of the client for which this configuration was created. +version = '{{ client.version }}' + +# Human readable ID of this particular client. +id = '{{ client.id }}' + +# Indicates whether this client is running in a disabled credentials mode, thus attempting +# to claim bandwidth without presenting bandwidth credentials. +disabled_credentials_mode = {{ client.disabled_credentials_mode }} + +# Addresses to nyxd validators via which the client can communicate with the chain. +nyxd_urls = [ + {{#each client.nyxd_urls }} + '{{this}}', + {{/each}} +] + +# Addresses to APIs running on validator from which the client gets the view of the network. +nym_api_urls = [ + {{#each client.nym_api_urls }} + '{{this}}', + {{/each}} +] + +[storage_paths] + +# Path to file containing private identity key. +keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' + +# Path to file containing public identity key. +keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}' + +# Path to file containing private encryption key. +keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}' + +# Path to file containing public encryption key. +keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}' + +# A gateway specific, optional, base58 stringified shared key used for +# communication with particular gateway. +keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}' + +# Path to file containing key used for encrypting and decrypting the content of an +# acknowledgement so that nobody besides the client knows which packet it refers to. +keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}' + +# Path to the database containing bandwidth credentials +credentials_database = '{{ storage_paths.credentials_database }}' + +# Path to the file containing information about gateway used by this client, +# i.e. details such as its public key, owner address or the network information. +gateway_details = '{{ storage_paths.gateway_details }}' + +# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. +reply_surb_database = '{{ storage_paths.reply_surb_database }}' + +# Location of the file containing our allow.list +allowed_list_location = '{{ storage_paths.allowed_list_location }}' + +# Location of the file containing our unknown.list +unknown_list_location = '{{ storage_paths.unknown_list_location }}' + +# Path to file containing description of this network-requester. +ip_forwarder_description = '{{ storage_paths.ip_forwarder_description }}' + + +##### logging configuration options ##### + +[logging] + +# TODO + + +##### debug configuration options ##### +# The following options should not be modified unless you know EXACTLY what you are doing +# as if set incorrectly, they may impact your anonymity. + +[debug] + +[debug.traffic] +average_packet_delay = '{{ debug.traffic.average_packet_delay }}' +message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}' + +[debug.acknowledgements] +average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}' + +[debug.cover_traffic] +loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}' + +"#; diff --git a/service-providers/ip-forwarder/src/error.rs b/service-providers/ip-packet-router/src/error.rs similarity index 100% rename from service-providers/ip-forwarder/src/error.rs rename to service-providers/ip-packet-router/src/error.rs diff --git a/service-providers/ip-forwarder/src/lib.rs b/service-providers/ip-packet-router/src/lib.rs similarity index 100% rename from service-providers/ip-forwarder/src/lib.rs rename to service-providers/ip-packet-router/src/lib.rs