Merge pull request #4064 from nymtech/jon/embedded-ip-forwarder-in-gateway

Run nym-ip-packet-router in gateway
This commit is contained in:
Jon Häggblad
2023-10-31 14:48:21 +01:00
committed by GitHub
26 changed files with 1103 additions and 67 deletions
Generated
+4 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+25
View File
@@ -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)
}
}
+1
View File
@@ -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"
+89 -2
View File
@@ -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<Vec<url::Url>>,
pub(crate) only_coconut_credentials: Option<bool>,
pub(crate) with_network_requester: Option<bool>,
pub(crate) with_ip_packet_router: Option<bool>,
}
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<String>,
}
#[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<OverrideIpPacketRouterConfig>,
) -> 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<GatewayIpPacketRouterDetails, GatewayError> {
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(),
})
}
+22 -2
View File
@@ -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<url::Url>,
/// 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<Init> 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,
+7
View File
@@ -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<dyn Error + Send + Sync
Commands::NodeDetails(m) => 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),
+34 -19
View File
@@ -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<IpAddr>,
/// 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<Vec<IpAddr>>,
/// Optional hostname associated with this gateway that will announced to the nym-api and subsequently to the clients
#[clap(long)]
#[arg(long)]
hostname: Option<String>,
/// The port on which the gateway will be listening for sphinx packets
#[clap(long)]
#[arg(long)]
mix_port: Option<u16>,
/// The port on which the gateway will be listening for clients gateway-requests
#[clap(long)]
#[arg(long)]
clients_port: Option<u16>,
/// Path to sqlite database containing all gateway persistent data
#[clap(long)]
#[arg(long)]
datastore: Option<PathBuf>,
/// 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<Vec<url::Url>>,
/// 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<Vec<url::Url>>,
/// Cosmos wallet mnemonic
#[clap(long)]
#[arg(long)]
mnemonic: Option<bip39::Mnemonic>,
/// 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<bool>,
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
#[clap(long)]
#[arg(long)]
enabled_statistics: Option<bool>,
/// URL where a statistics aggregator is running. The default value is a Nym aggregator server
#[clap(long)]
#[arg(long)]
statistics_service_url: Option<url::Url>,
/// 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<bool>,
/// 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<bool>,
// ##### 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<PathBuf>,
/// 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<bool>,
#[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<Run> 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 ");
@@ -0,0 +1,67 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// 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<PathBuf>,
/// 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<bool>,
#[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(())
}
+31 -2
View File
@@ -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<bool, GatewayError> {
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<bool, GatewayError> {
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<bool, GatewayError> {
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<bool, GatewayError> {
// 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(())
}
+31
View File
@@ -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 {
+13 -11
View File
@@ -1,9 +1,6 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// 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<ConfigV1_1_29> for Config {
impl From<ConfigV1_1_29> 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<ConfigV1_1_29> 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<ConfigV1_1_29> 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<ConfigV1_1_29> 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,
+362
View File
@@ -0,0 +1,362 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// 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<Option<u16>, 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<Option<PathBuf>, 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/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(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/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(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<PathBuf>,
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<P: AsRef<Path>>(id: P) -> io::Result<Self> {
read_config_from_toml_file(default_config_filepath(id))
}
}
impl From<ConfigV1_1_31> 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<u16>,
/// 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<Url>,
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
#[serde(alias = "validator_nymd_urls")]
pub nyxd_urls: Vec<Url>,
/// 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<PathBuf>,
// 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,
}
}
}
+28
View File
@@ -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<P: AsRef<Path>>(id: P) -> PathBuf {
default_data_directory(id).join(DEFAULT_NETWORK_REQUESTER_DATA_DIR)
}
pub fn default_ip_packet_router_data_dir<P: AsRef<Path>>(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<Option<PathBuf>, 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<PathBuf>,
}
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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<PathBuf> {
&self.network_requester_config
}
pub fn ip_packet_router_config(&self) -> &Option<PathBuf> {
&self.ip_packet_router_config
}
pub fn private_identity_key(&self) -> &Path {
self.keys.private_identity_key()
}
+7
View File
@@ -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]
+24
View File
@@ -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,
@@ -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()
}
+14
View File
@@ -96,6 +96,20 @@ pub(crate) fn load_network_requester_config<P: AsRef<Path>>(
})
}
pub(crate) fn load_ip_packet_router_config<P: AsRef<Path>>(
id: &str,
path: P,
) -> Result<nym_ip_packet_router::Config, GatewayError> {
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<PersistentStorage, GatewayError> {
+120 -5
View File
@@ -1,8 +1,12 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// 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<OverrideNetworkRequesterConfig>,
custom_nr_mixnet: Option<PathBuf>,
ip_config_override: Option<OverrideIpPacketRouterConfig>,
custom_mixnet: Option<PathBuf>,
) -> Result<Gateway, GatewayError> {
// 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<PathBuf>,
}
#[derive(Debug, Clone)]
pub struct LocalIpPacketRouterOpts {
config: nym_ip_packet_router::Config,
custom_mixnet_path: Option<PathBuf>,
}
pub(crate) struct Gateway<St = PersistentStorage> {
config: Config,
network_requester_opts: Option<LocalNetworkRequesterOpts>,
ip_packet_router_opts: Option<LocalIpPacketRouterOpts>,
/// ed25519 keypair used to assert one's identity.
identity_keypair: Arc<identity::KeyPair>,
@@ -106,6 +138,7 @@ impl<St> Gateway<St> {
pub fn new(
config: Config,
network_requester_opts: Option<LocalNetworkRequesterOpts>,
ip_packet_router_opts: Option<LocalIpPacketRouterOpts>,
storage: St,
) -> Result<Self, GatewayError> {
Ok(Gateway {
@@ -114,6 +147,7 @@ impl<St> Gateway<St> {
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<St> Gateway<St> {
pub async fn new_from_keys_and_storage(
config: Config,
network_requester_opts: Option<LocalNetworkRequesterOpts>,
ip_packet_router_opts: Option<LocalIpPacketRouterOpts>,
identity_keypair: identity::KeyPair,
sphinx_keypair: encryption::KeyPair,
storage: St,
@@ -129,6 +164,7 @@ impl<St> Gateway<St> {
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<St> Gateway<St> {
})
}
async fn start_ip_packet_router(
&self,
forwarding_channel: MixForwardingSender,
shutdown: TaskClient,
) -> Result<LocalNetworkRequesterHandle, GatewayError> {
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<dyn Error + Send + Sync>> {
let res = shutdown.catch_interrupt().await;
log::info!("Stopping nym gateway");
@@ -387,6 +490,18 @@ impl<St> Gateway<St> {
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(),
@@ -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<P: AsRef<Path>>(path: P) -> io::Result<Self> {
nym_config::read_config_from_toml_file(path)
}
}
@@ -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"] }
@@ -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/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(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/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(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/<id>/data`
pub fn default_data_directory<P: AsRef<Path>>(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<S: AsRef<str>>(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<P: AsRef<Path>>(mut self, data_directory: P) -> Self {
self.storage_paths = IpForwarderPaths::new_base(data_directory);
self
}
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
nym_config::read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
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()
}
}
@@ -0,0 +1,105 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// 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 }}'
"#;