Compare commits

...

6 Commits

Author SHA1 Message Date
Tommy Verrall 4dc75d3a44 attempt to fix unwrap 2024-03-28 15:26:32 +01:00
Jędrzej Stuczyński de214e9121 fixed NR template 2024-03-28 12:34:30 +00:00
Jędrzej Stuczyński 8a1c537e35 remove explicit deref 2024-03-28 10:15:49 +00:00
Jędrzej Stuczyński 14b2a29083 config migration for removing allow.list 2024-03-28 10:15:49 +00:00
Jędrzej Stuczyński 1c5ae7d8bd moved and renamed NR old config structs 2024-03-28 10:15:49 +00:00
Jędrzej Stuczyński f1737c2bb3 removed the usage of allow.list 2024-03-28 10:15:48 +00:00
37 changed files with 457 additions and 1664 deletions
@@ -546,37 +546,37 @@ impl PacketStatisticsControl {
break;
}
},
// conditional will disable the branch if we're in wasm32-unknown-unknown
// use `_` to calm down clippy when running for wasm
_result = listener.as_ref().unwrap().accept(), if listener.is_some() => {
cfg_if::cfg_if! {
if #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] {
if let Ok((stream, _)) = _result {
let io = TokioIo::new(stream);
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(io, service_fn(serve_metrics))
.await
{
log::warn!("Error serving connection: {:?}", err);
}
});
} else {
log::warn!("Error accepting connection");
_ = async {
if let Some(ref listener) = listener {
if let Ok((stream, _)) = listener.accept().await {
cfg_if::cfg_if! {
if #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] {
let io = TokioIo::new(stream);
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(io, service_fn(serve_metrics))
.await
{
log::warn!("Error serving connection: {:?}", err);
}
});
}
}
}
} else {
let _ = futures::future::pending::<()>().await;
}
}
}, if listener.is_some() => {
},
_ = snapshot_interval.tick() => {
self.update_history();
self.update_rates();
}
},
_ = report_interval.tick() => {
self.report_rates();
self.check_for_notable_events();
self.report_counters();
}
},
_ = shutdown.recv_with_delay() => {
log::trace!("PacketStatisticsControl: Received shutdown");
break;
+1 -8
View File
@@ -124,15 +124,12 @@ pub struct GatewayNetworkRequesterDetails {
pub encryption_key: String,
pub open_proxy: bool,
pub exit_policy: bool,
pub enabled_statistics: bool,
// just a convenience wrapper around all the keys
pub address: String,
pub config_path: String,
pub allow_list_path: String,
pub unknown_list_path: String,
}
impl fmt::Display for GatewayNetworkRequesterDetails {
@@ -146,11 +143,7 @@ impl fmt::Display for GatewayNetworkRequesterDetails {
writeln!(f, "\taddress: {}", self.address)?;
writeln!(f, "\tuses open proxy: {}", self.open_proxy)?;
writeln!(f, "\tuses exit policy: {}", self.exit_policy)?;
writeln!(f, "\tsends statistics: {}", self.enabled_statistics)?;
writeln!(f, "\tallow list path: {}", self.allow_list_path)?;
writeln!(f, "\tunknown list path: {}", self.unknown_list_path)
writeln!(f, "\tsends statistics: {}", self.enabled_statistics)
}
}
-16
View File
@@ -109,7 +109,6 @@ pub(crate) struct OverrideNetworkRequesterConfig {
pub(crate) medium_toggle: bool,
pub(crate) open_proxy: Option<bool>,
pub(crate) enable_exit_policy: Option<bool>,
pub(crate) enable_statistics: Option<bool>,
pub(crate) statistics_recipient: Option<String>,
@@ -229,10 +228,6 @@ pub(crate) fn override_network_requester_config(
nym_network_requester::Config::with_open_proxy,
opts.open_proxy,
)
.with_optional(
nym_network_requester::Config::with_old_allow_list,
opts.enable_exit_policy.map(|e| !e),
)
.with_optional(
nym_network_requester::Config::with_enabled_statistics,
opts.enable_statistics,
@@ -314,21 +309,10 @@ pub(crate) async fn initialise_local_network_requester(
enabled: gateway_config.network_requester.enabled,
identity_key: address.identity().to_string(),
encryption_key: address.encryption_key().to_string(),
exit_policy: !nr_cfg.network_requester.use_deprecated_allow_list,
open_proxy: nr_cfg.network_requester.open_proxy,
enabled_statistics: nr_cfg.network_requester.enabled_statistics,
address: address.to_string(),
config_path: nr_cfg_path.display().to_string(),
allow_list_path: nr_cfg
.storage_paths
.allowed_list_location
.display()
.to_string(),
unknown_list_path: nr_cfg
.storage_paths
.unknown_list_location
.display()
.to_string(),
})
}
-8
View File
@@ -133,12 +133,6 @@ pub struct Init {
)]
medium_toggle: bool,
/// 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)]
with_exit_policy: Option<bool>,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -173,7 +167,6 @@ impl<'a> From<&'a Init> for OverrideNetworkRequesterConfig {
no_cover: value.no_cover,
medium_toggle: value.medium_toggle,
open_proxy: value.open_proxy,
enable_exit_policy: value.with_exit_policy,
enable_statistics: value.enable_statistics,
statistics_recipient: value.statistics_recipient.clone(),
}
@@ -303,7 +296,6 @@ mod tests {
fastmode: false,
no_cover: false,
medium_toggle: false,
with_exit_policy: None,
};
std::env::set_var(BECH32_PREFIX, "n");
-7
View File
@@ -132,12 +132,6 @@ pub struct Run {
#[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.
#[arg(long)]
with_exit_policy: Option<bool>,
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
@@ -175,7 +169,6 @@ impl<'a> From<&'a Run> for OverrideNetworkRequesterConfig {
no_cover: value.no_cover,
medium_toggle: value.medium_toggle,
open_proxy: value.open_proxy,
enable_exit_policy: value.with_exit_policy,
enable_statistics: value.enable_statistics,
statistics_recipient: value.statistics_recipient.clone(),
}
@@ -59,12 +59,6 @@ pub struct CmdArgs {
)]
medium_toggle: bool,
/// 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)]
with_exit_policy: Option<bool>,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -76,7 +70,6 @@ impl<'a> From<&'a CmdArgs> for OverrideNetworkRequesterConfig {
no_cover: value.no_cover,
medium_toggle: value.medium_toggle,
open_proxy: value.open_proxy,
enable_exit_policy: value.with_exit_policy,
enable_statistics: value.enable_statistics,
statistics_recipient: value.statistics_recipient.clone(),
}
+5 -10
View File
@@ -199,13 +199,11 @@ impl<'a> HttpApiBuilder<'a> {
// once we start refreshing it, we'll have to change it, but at that point
// the allow list will be probably be completely removed and thus the pointer management
// will be much easier
let Some(exit_policy) = request_filter.current_exit_policy_filter() else {
warn!("this node does not use an exit policy. no changes will be made");
return self;
};
let upstream = request_filter.current_exit_policy_filter().upstream();
// if there's no upstream (i.e. open proxy), we couldn't have possibly updated it : )
let last_updated = if exit_policy.upstream().is_some() {
let last_updated = if upstream.is_some() {
#[allow(clippy::expect_used)]
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@@ -217,12 +215,9 @@ impl<'a> HttpApiBuilder<'a> {
self.exit_policy = Some(UsedExitPolicy {
enabled: true,
upstream_source: exit_policy
.upstream()
.map(|u| u.to_string())
.unwrap_or_default(),
upstream_source: upstream.map(|u| u.to_string()).unwrap_or_default(),
last_updated,
policy: Some(exit_policy.policy().clone()),
policy: Some(request_filter.current_exit_policy_filter().policy().clone()),
});
self
-3
View File
@@ -64,12 +64,9 @@ pub(crate) async fn node_details(
identity_key: nr_identity_public_key.to_base58_string(),
encryption_key: nr_encryption_key.to_base58_string(),
open_proxy: cfg.network_requester.open_proxy,
exit_policy: !cfg.network_requester.use_deprecated_allow_list,
enabled_statistics: cfg.network_requester.enabled_statistics,
address: address.to_string(),
config_path: display_path(nr_cfg_path),
allow_list_path: display_path(&cfg.storage_paths.allowed_list_location),
unknown_list_path: display_path(&cfg.storage_paths.unknown_list_location),
})
} else {
None
@@ -57,12 +57,6 @@ pub(crate) struct Init {
#[clap(long)]
statistics_recipient: Option<String>,
/// 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)]
with_exit_policy: Option<bool>,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -76,7 +70,6 @@ impl From<Init> for OverrideConfig {
medium_toggle: false,
nyxd_urls: init_config.common_args.nyxd_urls,
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
enable_exit_policy: init_config.with_exit_policy,
open_proxy: init_config.open_proxy,
enable_statistics: init_config.enable_statistics,
statistics_recipient: init_config.statistics_recipient,
@@ -104,7 +104,6 @@ pub(crate) struct OverrideConfig {
medium_toggle: bool,
nyxd_urls: Option<Vec<url::Url>>,
enabled_credentials_mode: Option<bool>,
enable_exit_policy: Option<bool>,
open_proxy: Option<bool>,
enable_statistics: Option<bool>,
@@ -165,10 +164,6 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
args.enabled_credentials_mode.map(|b| !b),
)
.with_optional(Config::with_open_proxy, args.open_proxy)
.with_optional(
Config::with_old_allow_list,
args.enable_exit_policy.map(|e| !e),
)
.with_optional(Config::with_enabled_statistics, args.enable_statistics)
.with_optional(Config::with_statistics_recipient, args.statistics_recipient)
}
@@ -40,12 +40,6 @@ pub(crate) struct Run {
conflicts_with = "fastmode"
)]
medium_toggle: bool,
/// 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)]
with_exit_policy: Option<bool>,
}
impl From<Run> for OverrideConfig {
@@ -57,7 +51,6 @@ impl From<Run> for OverrideConfig {
medium_toggle: run_config.medium_toggle,
nyxd_urls: run_config.common_args.nyxd_urls,
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
enable_exit_policy: run_config.with_exit_policy,
open_proxy: run_config.open_proxy,
enable_statistics: run_config.enable_statistics,
statistics_recipient: run_config.statistics_recipient,
@@ -2,24 +2,26 @@
// SPDX-License-Identifier: Apache-2.0
use crate::config::default_config_filepath;
use crate::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::config::old_config_v1_1_33::ConfigV1_1_33;
use crate::config::old::v5::ConfigV5;
use crate::config::old_config_v1_1_13::OldConfigV1;
use crate::config::old_config_v1_1_20::ConfigV2;
use crate::config::old_config_v1_1_20_2::ConfigV3;
use crate::config::old_config_v1_1_33::ConfigV4;
use crate::config::Config;
use crate::error::NetworkRequesterError;
use log::{info, trace};
use nym_client_core::cli_helpers::CliClientConfig;
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
use std::path::Path;
async fn try_upgrade_v1_1_13_config<P: AsRef<Path>>(
async fn try_upgrade_v1_config<P: AsRef<Path>>(
config_path: P,
) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.13 config");
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
let Ok(old_config) = OldConfigV1_1_13::load_from_filepath(config_path.as_ref()) else {
let Ok(old_config) = OldConfigV1::load_from_filepath(config_path.as_ref()) 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);
@@ -27,31 +29,33 @@ async fn try_upgrade_v1_1_13_config<P: AsRef<Path>>(
info!("It seems the client is using <= v1.1.13 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_20 = old_config.into();
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let updated_step1: ConfigV2 = old_config.into();
let updated_step2: ConfigV3 = updated_step1.into();
let (updated_step3, gateway_config) = updated_step2.upgrade()?;
let old_paths = updated_step3.storage_paths.clone();
let updated = updated_step3.try_upgrade()?;
let updated_step4 = updated_step3.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step4.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated: Config = updated_step4.into();
updated.save_to(config_path)?;
Ok(true)
}
async fn try_upgrade_v1_1_20_config<P: AsRef<Path>>(
async fn try_upgrade_v2_config<P: AsRef<Path>>(
config_path: P,
) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.20 config");
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20::load_from_filepath(config_path.as_ref()) else {
let Ok(old_config) = ConfigV2::load_from_filepath(config_path.as_ref()) 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);
@@ -60,29 +64,31 @@ async fn try_upgrade_v1_1_20_config<P: AsRef<Path>>(
info!("It seems the client is using <= v1.1.20 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let updated_step1: ConfigV3 = old_config.into();
let (updated_step2, gateway_config) = updated_step1.upgrade()?;
let old_paths = updated_step2.storage_paths.clone();
let updated = updated_step2.try_upgrade()?;
let updated_step3 = updated_step2.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step3.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated: Config = updated_step3.into();
updated.save_to(config_path)?;
Ok(true)
}
async fn try_upgrade_v1_1_20_2_config<P: AsRef<Path>>(
async fn try_upgrade_v3_config<P: AsRef<Path>>(
config_path: P,
) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.20_2 config");
// explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20_2::read_from_toml_file(config_path.as_ref()) else {
let Ok(old_config) = ConfigV3::read_from_toml_file(config_path.as_ref()) 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);
@@ -92,26 +98,28 @@ async fn try_upgrade_v1_1_20_2_config<P: AsRef<Path>>(
let (updated_step1, gateway_config) = old_config.upgrade()?;
let old_paths = updated_step1.storage_paths.clone();
let updated = updated_step1.try_upgrade()?;
let updated_step2 = updated_step1.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step2.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated: Config = updated_step2.into();
updated.save_to(config_path)?;
Ok(true)
}
async fn try_upgrade_v1_1_33_config<P: AsRef<Path>>(
async fn try_upgrade_v4_config<P: AsRef<Path>>(
config_path: P,
) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.33 config");
// explicitly load it as v1.1.33 (which is incompatible with the current one, i.e. +1.1.34)
let Ok(old_config) = ConfigV1_1_33::read_from_toml_file(config_path.as_ref()) else {
let Ok(old_config) = ConfigV4::read_from_toml_file(config_path.as_ref()) 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);
@@ -120,33 +128,56 @@ async fn try_upgrade_v1_1_33_config<P: AsRef<Path>>(
info!("It is going to get updated to the current specification.");
let old_paths = old_config.storage_paths.clone();
let updated = old_config.try_upgrade()?;
let updated_step1 = old_config.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step1.storage_paths.common_paths,
None,
)
.await?;
let updated: Config = updated_step1.into();
updated.save_to(config_path)?;
Ok(true)
}
async fn try_upgrade_v5_config<P: AsRef<Path>>(
config_path: P,
) -> Result<bool, NetworkRequesterError> {
// explicitly load it as v5 (which is incompatible with the current one)
let Ok(old_config) = ConfigV5::read_from_toml_file(config_path.as_ref()) 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 client is using <= v5 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
updated.save_to(config_path)?;
Ok(true)
}
pub async fn try_upgrade_config<P: AsRef<Path>>(
config_path: P,
) -> Result<(), NetworkRequesterError> {
trace!("Attempting to upgrade config");
if try_upgrade_v1_1_13_config(config_path.as_ref()).await? {
if try_upgrade_v1_config(config_path.as_ref()).await? {
return Ok(());
}
if try_upgrade_v1_1_20_config(config_path.as_ref()).await? {
if try_upgrade_v2_config(config_path.as_ref()).await? {
return Ok(());
}
if try_upgrade_v1_1_20_2_config(config_path.as_ref()).await? {
if try_upgrade_v3_config(config_path.as_ref()).await? {
return Ok(());
}
if try_upgrade_v1_1_33_config(config_path).await? {
if try_upgrade_v4_config(config_path.as_ref()).await? {
return Ok(());
}
if try_upgrade_v5_config(config_path).await? {
return Ok(());
}
@@ -24,13 +24,16 @@ use url::Url;
pub use nym_client_core::config::Config as BaseClientConfig;
pub mod helpers;
pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_33;
pub mod old;
mod persistence;
mod template;
// aliases for backwards compatibility
pub use old::v1 as old_config_v1_1_13;
pub use old::v2 as old_config_v1_1_20;
pub use old::v3 as old_config_v1_1_20_2;
pub use old::v4 as old_config_v1_1_33;
const DEFAULT_NETWORK_REQUESTERS_DIR: &str = "network-requester";
pub const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 * 60);
@@ -166,12 +169,6 @@ impl Config {
self
}
#[must_use]
pub fn with_old_allow_list(mut self, use_old_allow_list: bool) -> Self {
self.network_requester.use_deprecated_allow_list = use_old_allow_list;
self
}
#[must_use]
pub fn with_enabled_statistics(mut self, enabled_statistics: bool) -> Self {
self.network_requester.enabled_statistics = enabled_statistics;
@@ -248,11 +245,6 @@ pub struct NetworkRequester {
/// This is equivalent to setting debug.traffic.disable_main_poisson_packet_distribution = true,
pub disable_poisson_rate: bool,
/// Specifies whether this network requester should be using the deprecated allow-list,
/// as opposed to the new ExitPolicy.
/// Note: this field will be removed in a near future.
pub use_deprecated_allow_list: bool,
/// Specifies the url for an upstream source of the exit policy used by this node.
#[serde(deserialize_with = "de_maybe_stringified")]
pub upstream_exit_policy_url: Option<Url>,
@@ -265,7 +257,6 @@ impl Default for NetworkRequester {
enabled_statistics: false,
statistics_recipient: None,
disable_poisson_rate: true,
use_deprecated_allow_list: true,
upstream_exit_policy_url: Some(
mainnet::EXIT_POLICY_URL
.parse()
@@ -0,0 +1,8 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod v1;
pub mod v2;
pub mod v3;
pub mod v4;
pub mod v5;
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::config::old_config_v1_1_20::ConfigV2;
use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use serde::{Deserialize, Serialize};
@@ -9,24 +9,24 @@ use std::path::PathBuf;
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13 {
pub struct OldConfigV1 {
#[serde(flatten)]
pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
pub base: OldBaseConfigV1_1_13<OldConfigV1>,
}
impl MigrationNymConfig for OldConfigV1_1_13 {
impl MigrationNymConfig for OldConfigV1 {
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("service-providers")
.join("../../../..")
.join("network-requester")
}
}
impl From<OldConfigV1_1_13> for ConfigV1_1_20 {
fn from(value: OldConfigV1_1_13) -> Self {
ConfigV1_1_20 {
impl From<OldConfigV1> for ConfigV2 {
fn from(value: OldConfigV1) -> Self {
ConfigV2 {
base: value.base.into(),
network_requester: Default::default(),
network_requester_debug: Default::default(),
@@ -1,9 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_20_2::{
ConfigV1_1_20_2, DebugV1_1_20_2, NetworkRequesterPathsV1_1_20_2,
};
use crate::config::old_config_v1_1_20_2::{ConfigV3, DebugV3};
use crate::config::persistence::old::v1::NetworkRequesterPathsV1;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::disk_persistence::old_v1_1_33::ClientKeysPathsV1_1_33;
use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20;
@@ -19,20 +18,20 @@ const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 *
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20 {
pub struct ConfigV2 {
#[serde(flatten)]
pub base: BaseConfigV1_1_20<ConfigV1_1_20>,
pub base: BaseConfigV1_1_20<ConfigV2>,
#[serde(default)]
pub network_requester: NetworkRequster,
#[serde(default)]
pub network_requester_debug: DebugV1_1_20,
pub network_requester_debug: DebugV2,
}
impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
fn from(value: ConfigV1_1_20) -> Self {
ConfigV1_1_20_2 {
impl From<ConfigV2> for ConfigV3 {
fn from(value: ConfigV2) -> Self {
ConfigV3 {
base: BaseClientConfigV1_1_20_2 {
client: ClientV1_1_20_2 {
version: value.base.client.version,
@@ -45,7 +44,7 @@ impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
debug: Default::default(),
},
network_requester: Default::default(),
storage_paths: NetworkRequesterPathsV1_1_20_2 {
storage_paths: NetworkRequesterPathsV1 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPathsV1_1_33 {
private_identity_key_file: value.base.client.private_identity_key_file,
@@ -67,12 +66,12 @@ impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
}
}
impl MigrationNymConfig for ConfigV1_1_20 {
impl MigrationNymConfig for ConfigV2 {
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("service-providers")
.join("../../../..")
.join("network-requester")
}
}
@@ -88,9 +87,9 @@ impl Default for NetworkRequster {
fn default() -> Self {
// same defaults as we had in <= v1.1.13
NetworkRequster {
allowed_list_location: <ConfigV1_1_20 as MigrationNymConfig>::default_root_directory()
allowed_list_location: <ConfigV2 as MigrationNymConfig>::default_root_directory()
.join("allowed.list"),
unknown_list_location: <ConfigV1_1_20 as MigrationNymConfig>::default_root_directory()
unknown_list_location: <ConfigV2 as MigrationNymConfig>::default_root_directory()
.join("unknown.list"),
}
}
@@ -98,22 +97,22 @@ impl Default for NetworkRequster {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugV1_1_20 {
pub struct DebugV2 {
#[serde(with = "humantime_serde")]
pub standard_list_update_interval: Duration,
}
impl From<DebugV1_1_20> for DebugV1_1_20_2 {
fn from(value: DebugV1_1_20) -> Self {
DebugV1_1_20_2 {
impl From<DebugV2> for DebugV3 {
fn from(value: DebugV2) -> Self {
DebugV3 {
standard_list_update_interval: value.standard_list_update_interval,
}
}
}
impl Default for DebugV1_1_20 {
impl Default for DebugV2 {
fn default() -> Self {
DebugV1_1_20 {
DebugV2 {
standard_list_update_interval: DEFAULT_STANDARD_LIST_UPDATE_INTERVAL,
}
}
@@ -1,56 +1,41 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use super::old_config_v1_1_33::{
ConfigV1_1_33, DebugV1_1_33, NetworkRequesterPathsV1_1_33, NetworkRequesterV1_1_33,
};
use super::v4::{ConfigV4, DebugV4, NetworkRequesterV4};
use crate::config::persistence::old::v1::NetworkRequesterPathsV1;
use crate::config::persistence::old::v2::{NetworkRequesterPathsV2, DEFAULT_DESCRIPTION_FILENAME};
use crate::{config::default_config_filepath, error::NetworkRequesterError};
use log::trace;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseClientConfigV1_1_20_2;
use nym_client_core::config::old_config_v1_1_30::ConfigV1_1_30 as BaseConfigV1_1_30;
use nym_client_core::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
use nym_config::read_config_from_toml_file;
use serde::{Deserialize, Serialize};
use std::io;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::time::Duration;
use super::persistence::DEFAULT_DESCRIPTION_FILENAME;
pub const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 * 60);
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct NetworkRequesterPathsV1_1_20_2 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_20_2,
/// Location of the file containing our allow.list
pub allowed_list_location: PathBuf,
/// Location of the file containing our unknown.list
pub unknown_list_location: PathBuf,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20_2 {
pub struct ConfigV3 {
#[serde(flatten)]
pub base: BaseClientConfigV1_1_20_2,
#[serde(default)]
pub network_requester: NetworkRequesterV1_1_20_2,
pub network_requester: NetworkRequesterV3,
pub storage_paths: NetworkRequesterPathsV1_1_20_2,
pub storage_paths: NetworkRequesterPathsV1,
#[serde(default)]
pub network_requester_debug: DebugV1_1_20_2,
pub network_requester_debug: DebugV3,
pub logging: LoggingSettings,
}
impl ConfigV1_1_20_2 {
impl ConfigV3 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
@@ -64,7 +49,7 @@ impl ConfigV1_1_20_2 {
// so its returned to be stored elsewhere.
pub fn upgrade(
self,
) -> Result<(ConfigV1_1_33, OldGatewayEndpointConfigV1_1_33), NetworkRequesterError> {
) -> Result<(ConfigV4, OldGatewayEndpointConfigV1_1_33), NetworkRequesterError> {
trace!("Upgrading from v1.1.20_2");
let gateway_details = self.base.client.gateway_endpoint.clone().into();
let nr_description = self
@@ -75,9 +60,9 @@ impl ConfigV1_1_20_2 {
.parent()
.expect("config paths upgrade failure")
.join(DEFAULT_DESCRIPTION_FILENAME);
let config = ConfigV1_1_33 {
let config = ConfigV4 {
base: BaseConfigV1_1_30::from(self.base).into(),
storage_paths: NetworkRequesterPathsV1_1_33 {
storage_paths: NetworkRequesterPathsV2 {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
allowed_list_location: self.storage_paths.allowed_list_location,
unknown_list_location: self.storage_paths.unknown_list_location,
@@ -94,33 +79,33 @@ impl ConfigV1_1_20_2 {
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct NetworkRequesterV1_1_20_2 {}
pub struct NetworkRequesterV3 {}
impl From<NetworkRequesterV1_1_20_2> for NetworkRequesterV1_1_33 {
fn from(_value: NetworkRequesterV1_1_20_2) -> Self {
NetworkRequesterV1_1_33::default()
impl From<NetworkRequesterV3> for NetworkRequesterV4 {
fn from(_value: NetworkRequesterV3) -> Self {
NetworkRequesterV4::default()
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugV1_1_20_2 {
pub struct DebugV3 {
/// Defines how often the standard allow list should get updated
#[serde(with = "humantime_serde")]
pub standard_list_update_interval: Duration,
}
impl From<DebugV1_1_20_2> for DebugV1_1_33 {
fn from(value: DebugV1_1_20_2) -> Self {
DebugV1_1_33 {
impl From<DebugV3> for DebugV4 {
fn from(value: DebugV3) -> Self {
DebugV4 {
standard_list_update_interval: value.standard_list_update_interval,
}
}
}
impl Default for DebugV1_1_20_2 {
impl Default for DebugV3 {
fn default() -> Self {
DebugV1_1_20_2 {
DebugV3 {
standard_list_update_interval: DEFAULT_STANDARD_LIST_UPDATE_INTERVAL,
}
}
@@ -1,56 +1,41 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::NetworkRequesterPaths;
use crate::config::Config;
use crate::config::{default_config_filepath, Debug, NetworkRequester};
use crate::config::default_config_filepath;
use crate::config::old::v5::{ConfigV5, DebugV5, NetworkRequesterV5};
use crate::config::persistence::old::v2::NetworkRequesterPathsV2;
use crate::config::persistence::old::v3::NetworkRequesterPathsV3;
use crate::error::NetworkRequesterError;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
use nym_client_core::config::old_config_v1_1_33::ConfigV1_1_33 as BaseConfigV1_1_33;
use nym_config::read_config_from_toml_file;
use nym_config::serde_helpers::de_maybe_stringified;
use serde::{Deserialize, Serialize};
use std::io;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::time::Duration;
use url::Url;
pub const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 * 60);
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct NetworkRequesterPathsV1_1_33 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_33,
/// Location of the file containing our allow.list
pub allowed_list_location: PathBuf,
/// Location of the file containing our unknown.list
pub unknown_list_location: PathBuf,
#[serde(default)]
pub nr_description: PathBuf,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_33 {
pub struct ConfigV4 {
#[serde(flatten)]
pub base: BaseConfigV1_1_33,
#[serde(default)]
pub network_requester: NetworkRequesterV1_1_33,
pub network_requester: NetworkRequesterV4,
pub storage_paths: NetworkRequesterPathsV1_1_33,
pub storage_paths: NetworkRequesterPathsV2,
#[serde(default)]
pub network_requester_debug: DebugV1_1_33,
pub network_requester_debug: DebugV4,
pub logging: LoggingSettings,
}
impl ConfigV1_1_33 {
impl ConfigV4 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
@@ -60,11 +45,11 @@ impl ConfigV1_1_33 {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, NetworkRequesterError> {
Ok(Config {
pub fn try_upgrade(self) -> Result<ConfigV5, NetworkRequesterError> {
Ok(ConfigV5 {
base: self.base.into(),
network_requester: self.network_requester.into(),
storage_paths: NetworkRequesterPaths {
storage_paths: NetworkRequesterPathsV3 {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
allowed_list_location: self.storage_paths.allowed_list_location,
unknown_list_location: self.storage_paths.unknown_list_location,
@@ -78,7 +63,7 @@ impl ConfigV1_1_33 {
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct NetworkRequesterV1_1_33 {
pub struct NetworkRequesterV4 {
/// specifies whether this network requester should run in 'open-proxy' mode
/// and thus would attempt to resolve **ANY** request it receives.
pub open_proxy: bool,
@@ -103,9 +88,9 @@ pub struct NetworkRequesterV1_1_33 {
pub upstream_exit_policy_url: Option<Url>,
}
impl From<NetworkRequesterV1_1_33> for NetworkRequester {
fn from(value: NetworkRequesterV1_1_33) -> Self {
NetworkRequester {
impl From<NetworkRequesterV4> for NetworkRequesterV5 {
fn from(value: NetworkRequesterV4) -> Self {
NetworkRequesterV5 {
open_proxy: value.open_proxy,
enabled_statistics: value.enabled_statistics,
statistics_recipient: value.statistics_recipient,
@@ -118,23 +103,23 @@ impl From<NetworkRequesterV1_1_33> for NetworkRequester {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugV1_1_33 {
pub struct DebugV4 {
/// Defines how often the standard allow list should get updated
#[serde(with = "humantime_serde")]
pub standard_list_update_interval: Duration,
}
impl From<DebugV1_1_33> for Debug {
fn from(value: DebugV1_1_33) -> Self {
Debug {
impl From<DebugV4> for DebugV5 {
fn from(value: DebugV4) -> Self {
DebugV5 {
standard_list_update_interval: value.standard_list_update_interval,
}
}
}
impl Default for DebugV1_1_33 {
impl Default for DebugV4 {
fn default() -> Self {
DebugV1_1_33 {
DebugV4 {
standard_list_update_interval: DEFAULT_STANDARD_LIST_UPDATE_INTERVAL,
}
}
@@ -0,0 +1,146 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::old::v3::NetworkRequesterPathsV3;
use crate::config::persistence::NetworkRequesterPaths;
use crate::config::Config;
use crate::config::{default_config_filepath, Debug, NetworkRequester};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::Config as BaseClientConfig;
use nym_config::read_config_from_toml_file;
use nym_config::serde_helpers::de_maybe_stringified;
use nym_network_defaults::mainnet;
use serde::{Deserialize, Serialize};
use std::io;
use std::path::Path;
use std::time::Duration;
use url::Url;
pub const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 * 60);
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV5 {
// *sigh* currently we point to the most recent config because that's the one that's 'correct'
// but the moment we make breaking changes there, we'll have to update this config too.
// I think we should always keep versioned base config, i.e. `ConfigV1`, `ConfigV2`, etc,
// and then just make type alias for the current one, i.e. `type Config = ConfigV2`.
// then in 'old' configs we could simply use the underlying type as opposed to the alias for easier upgrades.
#[serde(flatten)]
pub base: BaseClientConfig,
#[serde(default)]
pub network_requester: NetworkRequesterV5,
pub storage_paths: NetworkRequesterPathsV3,
#[serde(default)]
pub network_requester_debug: DebugV5,
pub logging: LoggingSettings,
}
impl ConfigV5 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
#[allow(dead_code)]
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
}
impl From<ConfigV5> for Config {
fn from(value: ConfigV5) -> Self {
Config {
base: value.base,
network_requester: value.network_requester.into(),
storage_paths: NetworkRequesterPaths {
common_paths: value.storage_paths.common_paths,
},
network_requester_debug: value.network_requester_debug.into(),
logging: value.logging,
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct NetworkRequesterV5 {
/// specifies whether this network requester should run in 'open-proxy' mode
/// and thus would attempt to resolve **ANY** request it receives.
pub open_proxy: bool,
/// specifies whether this network requester would send anonymized statistics to a statistics aggregator server
pub enabled_statistics: bool,
/// in case of enabled statistics, specifies mixnet client address where a statistics aggregator is running
pub statistics_recipient: Option<String>,
/// Disable Poisson sending rate.
/// This is equivalent to setting debug.traffic.disable_main_poisson_packet_distribution = true,
pub disable_poisson_rate: bool,
/// Specifies whether this network requester should be using the deprecated allow-list,
/// as opposed to the new ExitPolicy.
pub use_deprecated_allow_list: bool,
/// Specifies the url for an upstream source of the exit policy used by this node.
#[serde(deserialize_with = "de_maybe_stringified")]
pub upstream_exit_policy_url: Option<Url>,
}
impl Default for NetworkRequesterV5 {
fn default() -> Self {
NetworkRequesterV5 {
open_proxy: false,
enabled_statistics: false,
statistics_recipient: None,
disable_poisson_rate: true,
use_deprecated_allow_list: true,
upstream_exit_policy_url: Some(
mainnet::EXIT_POLICY_URL
.parse()
.expect("invalid default exit policy URL"),
),
}
}
}
impl From<NetworkRequesterV5> for NetworkRequester {
fn from(value: NetworkRequesterV5) -> Self {
NetworkRequester {
open_proxy: value.open_proxy,
enabled_statistics: value.enabled_statistics,
statistics_recipient: value.statistics_recipient,
disable_poisson_rate: value.disable_poisson_rate,
upstream_exit_policy_url: value.upstream_exit_policy_url,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugV5 {
/// Defines how often the standard allow list should get updated
/// Deprecated
#[serde(with = "humantime_serde")]
pub standard_list_update_interval: Duration,
}
impl From<DebugV5> for Debug {
fn from(value: DebugV5) -> Self {
Debug {
standard_list_update_interval: value.standard_list_update_interval,
}
}
}
impl Default for DebugV5 {
fn default() -> Self {
DebugV5 {
standard_list_update_interval: DEFAULT_STANDARD_LIST_UPDATE_INTERVAL,
}
}
}
@@ -1,120 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::old_config_v1_1_20::{
ConfigV1_1_20, DebugV1_1_20, NetworkRequesterPathsV1_1_20,
};
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
use nym_client_core::config::disk_persistence::old_v1_1_20::CommonClientPathsV1_1_20;
use nym_client_core::config::old_config_v1_1_19::ConfigV1_1_19 as BaseConfigV1_1_19;
use nym_client_core::config::old_config_v1_1_20::{
ClientV1_1_20, ConfigV1_1_20 as BaseClientConfigV1_1_20,
};
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Duration;
const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 * 60);
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_19 {
#[serde(flatten)]
pub base: BaseConfigV1_1_19<ConfigV1_1_19>,
#[serde(default)]
pub network_requester: NetworkRequster,
#[serde(default)]
pub network_requester_debug: DebugV1_1_19,
}
impl From<ConfigV1_1_19> for ConfigV1_1_20 {
fn from(value: ConfigV1_1_19) -> Self {
ConfigV1_1_20 {
base: BaseClientConfigV1_1_20 {
client: ClientV1_1_20 {
version: value.base.client.version,
id: value.base.client.id,
disabled_credentials_mode: value.base.client.disabled_credentials_mode,
nyxd_urls: value.base.client.nyxd_urls,
nym_api_urls: value.base.client.nym_api_urls,
gateway_endpoint: value.base.client.gateway_endpoint.into(),
},
debug: Default::default(),
},
network_requester: Default::default(),
storage_paths: NetworkRequesterPathsV1_1_20 {
common_paths: CommonClientPathsV1_1_20 {
keys: ClientKeysPaths {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
private_encryption_key_file: value.base.client.private_encryption_key_file,
public_encryption_key_file: value.base.client.public_encryption_key_file,
gateway_shared_key_file: value.base.client.gateway_shared_key_file,
ack_key_file: value.base.client.ack_key_file,
},
credentials_database: value.base.client.database_path,
reply_surb_database: value.base.client.reply_surb_database_path,
},
allowed_list_location: value.network_requester.allowed_list_location,
unknown_list_location: value.network_requester.unknown_list_location,
},
network_requester_debug: value.network_requester_debug.into(),
logging: Default::default(),
}
}
}
impl MigrationNymConfig for ConfigV1_1_19 {
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("service-providers")
.join("network-requester")
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct NetworkRequster {
pub allowed_list_location: PathBuf,
pub unknown_list_location: PathBuf,
}
impl Default for NetworkRequster {
fn default() -> Self {
// same defaults as we had in <= v1.1.13
NetworkRequster {
allowed_list_location: <ConfigV1_1_19 as MigrationNymConfig>::default_root_directory()
.join("allowed.list"),
unknown_list_location: <ConfigV1_1_19 as MigrationNymConfig>::default_root_directory()
.join("unknown.list"),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugV1_1_19 {
#[serde(with = "humantime_serde")]
pub standard_list_update_interval: Duration,
}
impl From<DebugV1_1_19> for DebugV1_1_20 {
fn from(value: DebugV1_1_19) -> Self {
DebugV1_1_20 {
standard_list_update_interval: value.standard_list_update_interval,
}
}
}
impl Default for DebugV1_1_19 {
fn default() -> Self {
DebugV1_1_19 {
standard_list_update_interval: DEFAULT_STANDARD_LIST_UPDATE_INTERVAL,
}
}
}
@@ -1,40 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_client_core::config::disk_persistence::CommonClientPaths;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub const DEFAULT_ALLOWED_LIST_FILENAME: &str = "allowed.list";
pub const DEFAULT_UNKNOWN_LIST_FILENAME: &str = "unknown.list";
pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml";
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct NetworkRequesterPaths {
#[serde(flatten)]
pub common_paths: CommonClientPaths,
/// Deprecated
/// Location of the file containing our allow.list
pub allowed_list_location: PathBuf,
/// Deprecated
/// Location of the file containing our unknown.list
pub unknown_list_location: PathBuf,
/// Location of the file containing our description
pub nr_description: PathBuf,
}
impl NetworkRequesterPaths {
pub fn new_base<P: AsRef<Path>>(base_data_directory: P) -> Self {
let base_dir = base_data_directory.as_ref();
NetworkRequesterPaths {
common_paths: CommonClientPaths::new_base(base_dir),
allowed_list_location: base_dir.join(DEFAULT_ALLOWED_LIST_FILENAME),
unknown_list_location: base_dir.join(DEFAULT_UNKNOWN_LIST_FILENAME),
nr_description: base_dir.join(DEFAULT_DESCRIPTION_FILENAME),
}
}
}
@@ -0,0 +1,24 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_client_core::config::disk_persistence::CommonClientPaths;
use serde::{Deserialize, Serialize};
use std::path::Path;
pub mod old;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct NetworkRequesterPaths {
#[serde(flatten)]
pub common_paths: CommonClientPaths,
}
impl NetworkRequesterPaths {
pub fn new_base<P: AsRef<Path>>(base_data_directory: P) -> Self {
let base_dir = base_data_directory.as_ref();
NetworkRequesterPaths {
common_paths: CommonClientPaths::new_base(base_dir),
}
}
}
@@ -0,0 +1,6 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod v1;
pub mod v2;
pub mod v3;
@@ -0,0 +1,18 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct NetworkRequesterPathsV1 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_20_2,
/// Location of the file containing our allow.list
pub allowed_list_location: PathBuf,
/// Location of the file containing our unknown.list
pub unknown_list_location: PathBuf,
}
@@ -0,0 +1,22 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml";
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct NetworkRequesterPathsV2 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_33,
/// Location of the file containing our allow.list
pub allowed_list_location: PathBuf,
/// Location of the file containing our unknown.list
pub unknown_list_location: PathBuf,
#[serde(default)]
pub nr_description: PathBuf,
}
@@ -0,0 +1,21 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::disk_persistence::CommonClientPaths;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct NetworkRequesterPathsV3 {
#[serde(flatten)]
pub common_paths: CommonClientPaths,
/// Location of the file containing our allow.list
pub allowed_list_location: PathBuf,
/// Location of the file containing our unknown.list
pub unknown_list_location: PathBuf,
/// Location of the file containing our description
pub nr_description: PathBuf,
}
@@ -91,11 +91,6 @@ statistics_recipient = '{{ network_requester.statistics_recipient }}'
# This is equivalent to setting debug.traffic.disable_main_poisson_packet_distribution = true,
disable_poisson_rate = {{ network_requester.disable_poisson_rate }}
# Specifies whether this network requester should be using the deprecated allow-list,
# as opposed to the new ExitPolicy.
# Note: this field will be removed in a near future.
use_deprecated_allow_list = {{ network_requester.use_deprecated_allow_list }}
# Specifies the url for an upstream source of the exit policy used by this node.
upstream_exit_policy_url = '{{ network_requester.upstream_exit_policy_url }}'
@@ -327,9 +327,6 @@ impl NRServiceProviderBuilder {
});
let request_filter = RequestFilter::new(&self.config).await?;
request_filter
.start_update_tasks(&self.config.network_requester_debug, &shutdown)
.await;
let mut service_provider = NRServiceProvider {
config: self.config,
@@ -605,20 +602,14 @@ impl NRServiceProvider {
QueryResponse::Description("Description (placeholder)".to_string()),
),
QueryRequest::ExitPolicy => {
let response = match self.request_filter.current_exit_policy_filter() {
Some(exit_policy_filter) => QueryResponse::ExitPolicy {
enabled: true,
upstream: exit_policy_filter
.upstream()
.map(|u| u.to_string())
.unwrap_or_default(),
policy: Some(exit_policy_filter.policy().clone()),
},
None => QueryResponse::ExitPolicy {
enabled: false,
upstream: "".to_string(),
policy: None,
},
let exit_policy_filter = self.request_filter.current_exit_policy_filter();
let response = QueryResponse::ExitPolicy {
enabled: true,
upstream: exit_policy_filter
.upstream()
.map(|u| u.to_string())
.unwrap_or_default(),
policy: Some(exit_policy_filter.policy().clone()),
};
Socks5Response::new_query(protocol_version, response)
@@ -1,559 +0,0 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use super::HostsStore;
use crate::request_filter::allowed_hosts::group::HostsGroup;
use crate::request_filter::allowed_hosts::standard_list::StandardList;
use crate::request_filter::allowed_hosts::stored_allowed_hosts::StoredAllowedHosts;
use publicsuffix::Psl;
use std::{
net::{IpAddr, SocketAddr},
str::FromStr,
};
use tokio::sync::Mutex;
use url::Url;
#[derive(Debug)]
enum RequestHost {
IpAddr(IpAddr),
SocketAddr(SocketAddr),
RootDomain(String),
}
/// Filters outbound requests based on what's in an `allowed_hosts` list.
///
/// Requests to unknown hosts are automatically written to an `unknown_hosts`
/// list so that they can be copy/pasted into the `allowed_hosts` list if desired.
/// This may be handy for service provider node operators who want to be able to look in the
/// `unknown_hosts` file and allow new hosts (e.g. if a wallet has added a new outbound request
/// which needs to be allowed).
///
/// We rely on the list of domains at https://publicsuffix.org/ to figure out what the root
/// domain is for a given request. This allows us to distinguish all the rules for e.g.
/// .com, .co.uk, .co.jp, uk.com, etc, so that we can distinguish correct root-ish
/// domains as allowed. That list is loaded once at startup from Mozilla's canonical
/// publicsuffix list.
pub(crate) struct OutboundRequestFilter {
pub(super) allowed_hosts: StoredAllowedHosts,
pub(super) standard_list: StandardList,
root_domain_list: publicsuffix::List,
unknown_hosts: Mutex<HostsStore>,
}
/// The official URL of the list
pub const LIST_URL: &str = "https://publicsuffix.org/list/public_suffix_list.dat";
async fn request(u: Url) -> String {
reqwest::get(u)
.await
.expect("failed to get LIST_URL")
.text()
.await
.expect("failed to get text")
}
async fn fetch_list() -> publicsuffix::List {
let str = request(Url::parse(LIST_URL).unwrap()).await;
publicsuffix::List::from_str(&str).unwrap()
}
impl OutboundRequestFilter {
/// Create a new `OutboundRequestFilter` with the given `allowed_hosts` and `unknown_hosts` lists.
///
/// Automatically fetches the latest https://publicsuffix.org/ domain list from the internet so that
/// the requester can properly parse all of the world's top-level domains in an up-to-date fashion.
///
/// Automatcially fetches the latest standard allowed list from the Nym website, so that all
/// requesters are able to support the same minimal functionality out of the box.
pub(crate) async fn new(
allowed_hosts: StoredAllowedHosts,
standard_list: StandardList,
unknown_hosts: HostsStore,
) -> OutboundRequestFilter {
let domain_list = fetch_list().await;
OutboundRequestFilter {
allowed_hosts,
standard_list,
root_domain_list: domain_list,
unknown_hosts: Mutex::new(unknown_hosts),
}
}
pub(crate) fn standard_list(&self) -> StandardList {
self.standard_list.clone()
}
pub(crate) fn allowed_hosts(&self) -> StoredAllowedHosts {
self.allowed_hosts.clone()
}
async fn check_allowed_hosts(&self, host: &RequestHost) -> bool {
let guard = self.allowed_hosts.get().await;
self.check_group(&guard.data, host)
}
async fn check_standard_list(&self, host: &RequestHost) -> bool {
let guard = self.standard_list.get().await;
self.check_group(&guard, host)
}
fn check_group(&self, group: &HostsGroup, host: &RequestHost) -> bool {
match host {
RequestHost::IpAddr(ip_addr) => group.contains_ip_address(*ip_addr),
RequestHost::SocketAddr(socket_addr) => group.contains_ip_address(socket_addr.ip()),
RequestHost::RootDomain(domain) => group.contains_domain(domain),
}
}
async fn add_to_unknown_hosts(&self, host: RequestHost) {
let mut guard = self.unknown_hosts.lock().await;
match host {
RequestHost::IpAddr(ip_addr) => guard.add_ip(ip_addr),
RequestHost::SocketAddr(socket_addr) => guard.add_ip(socket_addr.ip()),
RequestHost::RootDomain(domain) => guard.add_domain(&domain),
}
}
fn parse_request_host(&self, host: &str) -> Option<RequestHost> {
// first check if it's a socket address (ip:port)
// (this check is performed to not incorrectly strip what we think might be a port
// from ipv6 address, as for example ::1 contains colons but has no port
if let Ok(socketaddr) = host.parse::<SocketAddr>() {
Some(RequestHost::SocketAddr(socketaddr))
// then check if it was an ip address
} else if let Ok(ipaddr) = host.parse::<IpAddr>() {
Some(RequestHost::IpAddr(ipaddr))
// finally, then assume it might be a domain
} else {
// check root
let trimmed = Self::trim_port(host);
// if this failed, it was probably some nonsense
self.get_domain_root(&trimmed).map(RequestHost::RootDomain)
}
}
async fn check_request_host(&self, request_host: &RequestHost) -> bool {
// first check our own allow list
let local_allowed = self.check_allowed_hosts(request_host).await;
// if it's locally allowed, no point in checking the standard list
if local_allowed {
return true;
}
// if that failed, check the standard list
self.check_standard_list(request_host).await
}
/// Returns `true` if a host's root domain is in the `allowed_hosts` list.
///
/// If it's not in the list, return `false` and write it to the `unknown_hosts` storefile.
pub(crate) async fn check(&self, host: &str) -> bool {
let allowed = match self.parse_request_host(host) {
Some(request_host) => {
let res = self.check_request_host(&request_host).await;
if !res {
self.add_to_unknown_hosts(request_host).await
}
res
}
None => false,
};
if !allowed {
log::warn!("Blocked outbound connection to {host}, add it to allowed.list if needed",);
}
allowed
}
fn trim_port(host: &str) -> String {
let mut tmp: Vec<_> = host.split(':').collect();
if tmp.len() > 1 {
tmp.pop(); // get rid of last element (port)
tmp.join(":") //rejoin
} else {
host.to_string()
}
}
/// Attempts to get the root domain, shorn of subdomains, using publicsuffix.
/// If the domain is itself registered in publicsuffix (e.g. s3.amazonaws.com),
/// then just use the full address as root.
fn get_domain_root(&self, host: &str) -> Option<String> {
if let Some(domain) = self.root_domain_list.domain(host.as_bytes()) {
return String::from_utf8(domain.as_bytes().to_vec()).ok();
} else if let Some(suffix) = self.root_domain_list.suffix(host.as_bytes()) {
if suffix.is_known() {
return String::from_utf8(suffix.as_bytes().to_vec()).ok();
} else {
return None;
}
}
log::warn!("Error parsing domain: {:?}", host);
None // domain couldn't be parsed
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::{Deref, DerefMut};
struct HostsStoreFixture {
inner: HostsStore,
// take ownership of temp file that will get cleaned up on drop (i.e. when test finishes)
_tmp_file: tempfile::NamedTempFile,
}
impl Deref for HostsStoreFixture {
type Target = HostsStore;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for HostsStoreFixture {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl HostsStoreFixture {
fn new() -> HostsStoreFixture {
let tmp_file = tempfile::NamedTempFile::new().unwrap();
let inner = HostsStore::new(&tmp_file);
HostsStoreFixture {
inner,
_tmp_file: tmp_file,
}
}
}
struct OutboundRequestFilterFixture {
inner: OutboundRequestFilter,
// take ownership of temp files that will get cleaned up on drop (i.e. when test finishes)
_allow_tmp_file: tempfile::NamedTempFile,
_unknown_tmp_file: tempfile::NamedTempFile,
}
impl Deref for OutboundRequestFilterFixture {
type Target = OutboundRequestFilter;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for OutboundRequestFilterFixture {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl OutboundRequestFilterFixture {
async fn new() -> OutboundRequestFilterFixture {
let allow_tmp_file = tempfile::NamedTempFile::new().unwrap();
let unknown_tmp_file = tempfile::NamedTempFile::new().unwrap();
let allowed = HostsStore::new(&allow_tmp_file);
let unknown = HostsStore::new(&unknown_tmp_file);
let standard = StandardList::new();
let inner = OutboundRequestFilter::new(allowed.into(), standard, unknown).await;
OutboundRequestFilterFixture {
inner,
_allow_tmp_file: allow_tmp_file,
_unknown_tmp_file: unknown_tmp_file,
}
}
}
async fn setup_empty() -> OutboundRequestFilterFixture {
OutboundRequestFilterFixture::new().await
}
async fn setup_with_allowed(allowed: &[&str]) -> OutboundRequestFilterFixture {
let allow_tmp_file = tempfile::NamedTempFile::new().unwrap();
let unknown_tmp_file = tempfile::NamedTempFile::new().unwrap();
let mut allowed_store = HostsStore::new(&allow_tmp_file);
let unknown = HostsStore::new(&unknown_tmp_file);
let standard = StandardList::new();
for allow in allowed {
allowed_store.add_host(allow)
}
let inner = OutboundRequestFilter::new(allowed_store.into(), standard, unknown).await;
OutboundRequestFilterFixture {
inner,
_allow_tmp_file: allow_tmp_file,
_unknown_tmp_file: unknown_tmp_file,
}
}
#[cfg(test)]
mod trimming_port_information {
use super::*;
#[test]
fn happens_when_port_exists() {
let host = "nymtech.net:9999";
assert_eq!("nymtech.net", OutboundRequestFilter::trim_port(host));
}
#[test]
fn doesnt_happen_when_no_port_exists() {
let host = "nymtech.net";
assert_eq!("nymtech.net", OutboundRequestFilter::trim_port(host));
}
}
#[cfg(test)]
mod getting_the_domain_root {
use super::*;
#[tokio::test]
async fn leaves_a_com_alone() {
let filter = setup_empty().await;
assert_eq!(
Some("domain.com".to_string()),
filter.get_domain_root("domain.com")
)
}
#[tokio::test]
async fn trims_subdomains_from_com() {
let filter = setup_empty().await;
assert_eq!(
Some("domain.com".to_string()),
filter.get_domain_root("foomp.domain.com")
)
}
#[tokio::test]
async fn works_for_non_com_roots() {
let filter = setup_empty().await;
assert_eq!(
Some("domain.co.uk".to_string()),
filter.get_domain_root("domain.co.uk")
)
}
#[tokio::test]
async fn works_for_non_com_roots_with_subdomains() {
let filter = setup_empty().await;
assert_eq!(
Some("domain.co.uk".to_string()),
filter.get_domain_root("foomp.domain.co.uk")
)
}
#[tokio::test]
async fn returns_none_on_garbage() {
let filter = setup_empty().await;
assert_eq!(None, filter.get_domain_root("::/&&%@"));
}
#[tokio::test]
async fn returns_full_on_suffix_domains() {
let filter = setup_empty().await;
dbg!(filter.get_domain_root("s3.amazonaws.com"));
assert_eq!(
Some("s3.amazonaws.com".to_string()),
filter.get_domain_root("s3.amazonaws.com")
);
}
}
#[cfg(test)]
mod requests_to_unknown_hosts {
use super::*;
#[tokio::test]
async fn are_not_allowed() {
let host = "unknown.com";
let filter = setup_empty().await;
assert!(!filter.check(host).await);
}
#[tokio::test]
async fn get_appended_once_to_the_unknown_hosts_list() {
let host = "unknown.com";
let filter = setup_empty().await;
filter.check(host).await;
assert_eq!(1, filter.unknown_hosts.lock().await.data.domains.len());
assert!(filter
.unknown_hosts
.lock()
.await
.data
.domains
.contains("unknown.com"));
filter.check(host).await;
assert_eq!(1, filter.unknown_hosts.lock().await.data.domains.len());
assert!(filter
.unknown_hosts
.lock()
.await
.data
.domains
.contains("unknown.com"));
}
}
#[cfg(test)]
mod requests_to_allowed_hosts {
use super::*;
#[tokio::test]
async fn are_allowed() {
let host = "nymtech.net";
let filter = setup_with_allowed(&["nymtech.net"]).await;
assert!(filter.check(host).await);
}
#[tokio::test]
async fn are_allowed_for_subdomains() {
let host = "foomp.nymtech.net";
let filter = setup_with_allowed(&["nymtech.net"]).await;
assert!(filter.check(host).await);
}
#[tokio::test]
async fn are_not_appended_to_file() {
let filter = setup_with_allowed(&["nymtech.net"]).await;
// test initial state
let lines =
HostsStore::load_from_storefile(&filter.allowed_hosts.get().await.storefile)
.unwrap();
assert_eq!(1, lines.len());
assert!(filter.check("nymtech.net").await);
// test state after we've checked to make sure no unexpected changes
let lines =
HostsStore::load_from_storefile(&filter.allowed_hosts.get().await.storefile)
.unwrap();
assert_eq!(1, lines.len());
}
#[tokio::test]
async fn are_allowed_for_ipv4_addresses() {
let address_good = "1.1.1.1";
let address_good_port = "1.1.1.1:1234";
let address_bad = "1.1.1.2";
let filter = setup_with_allowed(&["1.1.1.1"]).await;
assert!(filter.check(address_good).await);
assert!(filter.check(address_good_port).await);
assert!(!filter.check(address_bad).await);
}
#[tokio::test]
async fn are_allowed_for_ipv6_addresses() {
let ip_v6_full = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
let ip_v6_full_rendered = "2001:0db8:85a3::8a2e:0370:7334";
let ip_v6_full_port = "[2001:0db8:85a3::8a2e:0370:7334]:1234";
let ip_v6_semi = "2001:0db8::0001:0000";
let ip_v6_semi_rendered = "2001:db8::1:0";
let ip_v6_loopback_port = "[::1]:1234";
let filter1 = setup_with_allowed(&[ip_v6_full, ip_v6_semi, "::1"]).await;
let filter2 =
setup_with_allowed(&[ip_v6_full_rendered, ip_v6_semi_rendered, "::1"]).await;
assert!(filter1.check(ip_v6_full).await);
assert!(filter1.check(ip_v6_full_rendered).await);
assert!(filter1.check(ip_v6_full_port).await);
assert!(filter1.check(ip_v6_semi).await);
assert!(filter1.check(ip_v6_semi_rendered).await);
assert!(filter1.check(ip_v6_loopback_port).await);
assert!(filter2.check(ip_v6_full).await);
assert!(filter2.check(ip_v6_full_rendered).await);
assert!(filter2.check(ip_v6_full_port).await);
assert!(filter2.check(ip_v6_semi).await);
assert!(filter2.check(ip_v6_semi_rendered).await);
assert!(filter2.check(ip_v6_loopback_port).await);
}
#[tokio::test]
async fn are_allowed_for_ipv4_address_ranges() {
let range1 = "127.0.0.1/32";
let range2 = "1.2.3.4/24";
let bottom_range2 = "1.2.3.0";
let top_range2 = "1.2.3.255";
let outside_range2 = "1.2.2.4";
let filter = setup_with_allowed(&[range1, range2]).await;
assert!(filter.check("127.0.0.1").await);
assert!(filter.check("127.0.0.1:1234").await);
assert!(filter.check(bottom_range2).await);
assert!(filter.check(top_range2).await);
assert!(!filter.check(outside_range2).await);
}
#[tokio::test]
async fn are_allowed_for_ipv6_address_ranges() {
let range = "2620:0:2d0:200::7/32";
let bottom1 = "2620:0:0:0:0:0:0:0";
let bottom2 = "2620::";
let top = "2620:0:ffff:ffff:ffff:ffff:ffff:ffff";
let mid = "2620:0:42::42";
let filter = setup_with_allowed(&[range]).await;
assert!(filter.check(bottom1).await);
assert!(filter.check(bottom2).await);
assert!(filter.check(top).await);
assert!(filter.check(mid).await);
}
}
#[cfg(test)]
mod creating_a_new_host_store {
use super::*;
#[test]
fn loads_its_host_list_from_storefile() {
let mut host_store = HostsStoreFixture::new();
host_store.add_host("nymtech.net");
host_store.add_host("edwardsnowden.com");
host_store.add_host("1.2.3.4");
host_store.add_host("5.6.7.8/16");
host_store.add_host("1:2:3::");
host_store.add_host("5:6:7::/48");
assert!(host_store.data.domains.contains("nymtech.net"));
assert!(host_store.data.domains.contains("edwardsnowden.com"));
assert!(host_store
.data
.ip_nets
.contains(&"1.2.3.4".parse().unwrap()));
assert!(host_store
.data
.ip_nets
.contains(&"5.6.7.8/16".parse().unwrap()));
assert!(host_store
.data
.ip_nets
.contains(&"1:2:3::".parse().unwrap()));
assert!(host_store
.data
.ip_nets
.contains(&"5:6:7::/48".parse().unwrap()));
}
}
}
@@ -1,61 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::request_filter::allowed_hosts::host::Host;
use ipnetwork::IpNetwork;
use std::collections::HashSet;
use std::net::IpAddr;
/// A simpled grouped set of hosts.
/// It ignores any port information.
#[derive(Debug)]
pub(crate) struct HostsGroup {
pub(super) domains: HashSet<String>,
pub(super) ip_nets: HashSet<IpNetwork>,
}
impl HostsGroup {
pub(crate) fn new(raw_hosts: Vec<Host>) -> HostsGroup {
let mut domains = HashSet::new();
let mut ip_nets = HashSet::new();
for host in raw_hosts {
match host {
Host::Domain(domain) => {
domains.insert(domain);
}
Host::IpNetwork(ipnet) => {
ip_nets.insert(ipnet);
}
}
}
HostsGroup { domains, ip_nets }
}
pub(crate) fn contains_domain(&self, host: &str) -> bool {
self.domains.contains(host)
}
pub(super) fn contains_ip_address(&self, address: IpAddr) -> bool {
for ip_net in &self.ip_nets {
if ip_net.contains(address) {
return true;
}
}
false
}
pub(super) fn contains_ip_network(&self, network: IpNetwork) -> bool {
self.ip_nets.contains(&network)
}
pub(super) fn add_ipnet<N: Into<IpNetwork>>(&mut self, network: N) {
self.ip_nets.insert(network.into());
}
pub(super) fn add_domain(&mut self, domain: &str) {
self.domains.insert(domain.to_string());
}
}
@@ -1,35 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use ipnetwork::IpNetwork;
use std::str::FromStr;
// used for parsing file content
#[derive(Debug)]
pub(crate) enum Host {
Domain(String),
IpNetwork(IpNetwork),
}
impl<S: AsRef<str>> From<S> for Host {
fn from(raw: S) -> Self {
// SAFETY: unwrap here is fine as `FromStr` implementation returns `Infallible` error.
raw.as_ref().parse().unwrap()
}
}
impl FromStr for Host {
type Err = core::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(ipnet) = s.parse() {
Ok(Host::IpNetwork(ipnet))
} else {
// TODO: perhaps in the future it should do some domain validation?
//
// So for example if somebody put some nonsense in the whitelist file like "foomp",
// it would get rejected?
Ok(Host::Domain(s.to_string()))
}
}
}
@@ -1,203 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use super::host::Host;
use crate::request_filter::allowed_hosts::group::HostsGroup;
use ipnetwork::IpNetwork;
use std::{
fs::{self, File, OpenOptions},
io::{self, BufRead, BufReader},
net::IpAddr,
path::{Path, PathBuf},
};
/// A simple file-backed store for information about allowed / unknown hosts.
/// It ignores any port information.
#[derive(Debug)]
pub(crate) struct HostsStore {
pub(super) storefile: PathBuf,
pub(super) data: HostsGroup,
}
impl HostsStore {
/// Constructs a new HostsStore. If the storefile does not exist, it will be created.
///
/// You can inject a list of standard hosts that you want to support, in addition to the ones
/// in the user-defined storefile.
pub(crate) fn new<P: AsRef<Path>>(storefile: P) -> HostsStore {
let storefile = storefile.as_ref().to_path_buf();
if storefile.exists() && !storefile.is_file() {
// there's no error handling in here and I'm not going to be changing it right now.
panic!(
"the provided storefile {:?} is not a valid file!",
storefile
)
}
Self::setup_storefile(&storefile);
let hosts = Self::load_from_storefile(&storefile)
.unwrap_or_else(|_| panic!("Could not load hosts from storefile at {storefile:?}"));
HostsStore {
storefile,
data: HostsGroup::new(hosts),
}
}
pub(crate) fn try_reload(&mut self) -> io::Result<()> {
let hosts = Self::load_from_storefile(&self.storefile)?;
self.data = HostsGroup::new(hosts);
Ok(())
}
pub(crate) fn contains_domain(&self, host: &str) -> bool {
self.data.contains_domain(host)
}
pub(super) fn contains_ip_address(&self, address: IpAddr) -> bool {
self.data.contains_ip_address(address)
}
#[allow(unused)]
pub(super) fn contains_ipnetwork(&self, network: IpNetwork) -> bool {
self.data.contains_ip_network(network)
}
#[allow(unused)]
pub(super) fn add_host<H: Into<Host>>(&mut self, host: H) {
match host.into() {
Host::Domain(domain) => self.add_domain(&domain),
Host::IpNetwork(ipnet) => self.add_ipnet(ipnet),
}
}
#[allow(unused)]
pub(super) fn add_ipnet(&mut self, network: IpNetwork) {
if !self.contains_ipnetwork(network) {
self.data.add_ipnet(network);
self.append_to_file(&network.to_string());
}
}
pub(super) fn add_ip(&mut self, ip: IpAddr) {
if !self.contains_ip_address(ip) {
self.data.add_ipnet(ip);
self.append_to_file(&ip.to_string());
}
}
pub(super) fn add_domain(&mut self, domain: &str) {
if !self.contains_domain(domain) {
self.data.add_domain(domain);
self.append_to_file(domain);
}
}
/// Appends a line of `text` to the storefile at `path`
pub(super) fn append(path: &Path, text: &str) {
use std::io::Write;
let mut file = OpenOptions::new().append(true).open(path).unwrap();
if let Err(e) = writeln!(file, "{text}") {
log::error!("Couldn't write to file: {e}");
}
}
fn append_to_file(&self, host: &str) {
HostsStore::append(&self.storefile, host);
}
fn setup_storefile(file: &PathBuf) {
if !file.exists() {
let parent_dir = file
.parent()
.expect("parent dir does not exist for {file:?}");
fs::create_dir_all(parent_dir)
.unwrap_or_else(|_| panic!("could not create storage directory at {parent_dir:?}"));
log::trace!("Creating: {file:?}");
File::create(file).unwrap();
}
}
/// Loads the storefile contents into memory.
pub(super) fn load_from_storefile<P>(filename: P) -> io::Result<Vec<Host>>
where
P: AsRef<Path>,
{
log::trace!("Loading from storefile: {}", filename.as_ref().display());
let file = File::open(filename)?;
let reader = BufReader::new(&file);
let hosts = reader
.lines()
.filter_map(|line| {
let line = line.expect("failed to read input file line!");
trim_comment(&line)
})
.map(Host::from)
.collect();
Ok(hosts)
}
}
fn trim_comment(line: &str) -> Option<String> {
if let Some(content) = line.split('#').next() {
let trim_content = content.trim().to_string();
if trim_content.is_empty() {
None
} else {
Some(trim_content)
}
} else {
None
}
}
#[cfg(test)]
mod constructor_tests {
use super::*;
#[test]
fn works_with_no_standard_hosts() {
let temp_file = tempfile::NamedTempFile::new().unwrap();
let store = HostsStore::new(temp_file);
assert_eq!(store.data.domains.len(), 0);
}
#[test]
fn trim_comments() {
let entries = vec![
"# keybase",
"keybaseapi.com",
"",
"gist.githubusercontent.com",
" ",
"# healthcheck # foo",
"nymtech.net",
"# blockstream green wallet",
"blockstream.info",
"greenaddress.it",
"91.108.56.0/22",
"2001:b28:f23d::/48",
"2001:67c:4e8::/48",
"# nym matrix server",
"# monero desktop - mainnet",
];
let filtered_entries: Vec<_> = entries
.iter()
.filter_map(|line| trim_comment(line))
.collect();
assert_eq!(
filtered_entries,
vec![
"keybaseapi.com",
"gist.githubusercontent.com",
"nymtech.net",
"blockstream.info",
"greenaddress.it",
"91.108.56.0/22",
"2001:b28:f23d::/48",
"2001:67c:4e8::/48",
]
);
}
}
@@ -1,13 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
mod filter;
mod group;
mod host;
mod hosts;
pub(crate) mod standard_list;
pub(crate) mod stored_allowed_hosts;
pub(crate) use filter::OutboundRequestFilter;
pub(crate) use hosts::HostsStore;
pub(crate) use standard_list::StandardList;
@@ -1,113 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::request_filter::allowed_hosts::group::HostsGroup;
use crate::request_filter::allowed_hosts::host::Host;
use nym_task::TaskClient;
use regex::Regex;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{RwLock, RwLockReadGuard};
const STANDARD_LIST_URL: &str =
"https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt";
fn remove_comments(text: String) -> String {
if let Ok(regex) = Regex::new(r"#.*\n") {
regex.replace_all(&text, "").into_owned()
} else {
log::warn!("Failed to strip comments from standard allowed list");
text
}
}
/// Fetch the standard allowed list from nymtech.net
pub(crate) async fn fetch() -> Vec<Host> {
log::info!("Refreshing standard allowed hosts");
let text = get_standard_allowed_list().await;
remove_comments(text)
.split_whitespace()
.map(Into::into)
.collect()
}
async fn get_standard_allowed_list() -> String {
reqwest::get(STANDARD_LIST_URL)
.await
.expect("failed to get allowed hosts")
.text()
.await
.expect("failed to get allowed hosts text")
}
#[derive(Clone, Debug)]
pub(crate) struct StandardList {
inner: Arc<RwLock<HostsGroup>>,
}
impl StandardList {
// note: standard list will be fetched immediately when `StandardListUpdater::run` is called
// (because first `tick()` of tokio interval fires up immediately)
pub(crate) fn new() -> Self {
StandardList {
inner: Arc::new(RwLock::new(HostsGroup::new(Vec::new()))),
}
}
pub(crate) async fn update(&self) {
let raw_standard_list = fetch().await;
log::debug!("fetched allowed hosts: {:?}", raw_standard_list);
let new_data = HostsGroup::new(raw_standard_list);
*self.inner.write().await = new_data
}
pub(crate) async fn get(&self) -> RwLockReadGuard<'_, HostsGroup> {
self.inner.read().await
}
}
pub(crate) struct StandardListUpdater {
update_interval: Duration,
standard_list: StandardList,
// Listens to shutdown commands from higher up
shutdown_listener: TaskClient,
}
impl StandardListUpdater {
pub(crate) fn new(
update_interval: Duration,
standard_list: StandardList,
shutdown_listener: TaskClient,
) -> Self {
Self {
update_interval,
standard_list,
shutdown_listener,
}
}
pub(crate) async fn run(&mut self) {
let mut update_interval = tokio::time::interval(self.update_interval);
while !self.shutdown_listener.is_shutdown() {
tokio::select! {
biased;
_ = self.shutdown_listener.recv() => {
log::trace!("StandardListUpdater: Received shutdown");
}
_ = update_interval.tick() => {
log::debug!("updating standard list");
self.standard_list.update().await
}
}
}
log::debug!("StandardListUpdater: Exiting");
}
pub(crate) fn start(mut self) {
tokio::spawn(async move { self.run().await });
}
}
@@ -1,127 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::request_filter::allowed_hosts::HostsStore;
use futures::channel::mpsc;
use futures::StreamExt;
use nym_async_file_watcher::{AsyncFileWatcher, FileWatcherEventReceiver};
use nym_task::TaskClient;
use std::io;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::{RwLock, RwLockReadGuard};
#[derive(Debug, Clone)]
pub(crate) struct StoredAllowedHosts {
inner: Arc<RwLock<HostsStore>>,
}
impl StoredAllowedHosts {
pub(crate) fn new<P: AsRef<Path>>(path: P) -> Self {
let allowed_hosts = HostsStore::new(path);
StoredAllowedHosts {
inner: Arc::new(RwLock::new(allowed_hosts)),
}
}
pub(crate) async fn reload(&self) -> io::Result<()> {
log::debug!("reloading stored allowed hosts");
self.inner.write().await.try_reload()
}
pub(crate) async fn get(&self) -> RwLockReadGuard<'_, HostsStore> {
self.inner.read().await
}
}
impl From<HostsStore> for StoredAllowedHosts {
fn from(value: HostsStore) -> Self {
StoredAllowedHosts {
inner: Arc::new(RwLock::new(value)),
}
}
}
pub(crate) struct StoredAllowedHostsReloader {
stored_hosts: StoredAllowedHosts,
events_receiver: FileWatcherEventReceiver,
// Listens to shutdown commands from higher up
shutdown_listener: TaskClient,
}
impl StoredAllowedHostsReloader {
pub(crate) fn new(
stored_hosts: StoredAllowedHosts,
events_receiver: FileWatcherEventReceiver,
shutdown_listener: TaskClient,
) -> Self {
StoredAllowedHostsReloader {
events_receiver,
stored_hosts,
shutdown_listener,
}
}
pub(crate) async fn run(&mut self) {
while !self.shutdown_listener.is_shutdown() {
tokio::select! {
biased;
_ = self.shutdown_listener.recv() => {
log::trace!("StoredAllowedHostsReloader: Received shutdown");
}
event = self.events_receiver.next() => {
let Some(event) = event else {
log::trace!("StoredAllowedHostsReloader: sender channel has terminated");
break
};
log::debug!("the file has changed - {event:?}");
log::debug!("reloading stored hosts");
if let Err(err) = self.stored_hosts.reload().await {
log::error!("failed to reload stored hosts: {err}")
}
}
}
}
log::debug!("StoredAllowedHostsReloader: Exiting");
}
pub(crate) fn start(mut self) {
tokio::spawn(async move { self.run().await });
}
}
async fn run_watcher(mut watcher: AsyncFileWatcher, mut shutdown: TaskClient) {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("AsyncFileWatcher: Received shutdown");
}
res = watcher.watch() => {
log::trace!("AsyncFileWatcher: finished with {res:?}");
}
}
log::debug!("AsyncFileWatcher: Exiting");
}
fn start_watcher(watcher: AsyncFileWatcher, shutdown: TaskClient) {
tokio::spawn(async move { run_watcher(watcher, shutdown).await });
}
pub(crate) async fn start_allowed_list_reloader(
stored_list: StoredAllowedHosts,
shutdown_listener: TaskClient,
) {
let (events_sender, events_receiver) = mpsc::unbounded();
let file = stored_list.get().await.storefile.clone();
let watcher = AsyncFileWatcher::new_file_changes_watcher(file, events_sender)
.expect("failed to create file watcher");
let reloader =
StoredAllowedHostsReloader::new(stored_list, events_receiver, shutdown_listener.clone());
start_watcher(watcher, shutdown_listener);
reloader.start()
}
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Config;
use crate::error::NetworkRequesterError;
use log::trace;
use nym_exit_policy::client::get_exit_policy;
@@ -15,6 +16,12 @@ pub struct ExitPolicyRequestFilter {
policy: ExitPolicy,
}
impl From<ExitPolicy> for ExitPolicyRequestFilter {
fn from(value: ExitPolicy) -> Self {
ExitPolicyRequestFilter::new_from_policy(value)
}
}
impl ExitPolicyRequestFilter {
pub(crate) async fn new_upstream(url: impl IntoUrl) -> Result<Self, NetworkRequesterError> {
let url = url
@@ -27,7 +34,21 @@ impl ExitPolicyRequestFilter {
})
}
pub(crate) fn new(policy: ExitPolicy) -> Self {
pub(crate) async fn new(config: &Config) -> Result<Self, NetworkRequesterError> {
let policy_filter = if config.network_requester.open_proxy {
ExitPolicyRequestFilter::new_from_policy(ExitPolicy::new_open())
} else {
let upstream_url = config
.network_requester
.upstream_exit_policy_url
.as_ref()
.ok_or(NetworkRequesterError::NoUpstreamExitPolicy)?;
ExitPolicyRequestFilter::new_upstream(upstream_url.clone()).await?
};
Ok(policy_filter)
}
pub fn new_from_policy(policy: ExitPolicy) -> Self {
ExitPolicyRequestFilter {
upstream: None,
policy,
@@ -1,143 +1,36 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::{self, Config};
use crate::config::Config;
use crate::error::NetworkRequesterError;
use crate::request_filter::allowed_hosts::standard_list::StandardListUpdater;
use crate::request_filter::allowed_hosts::stored_allowed_hosts::{
start_allowed_list_reloader, StoredAllowedHosts,
};
use crate::request_filter::allowed_hosts::{OutboundRequestFilter, StandardList};
use crate::request_filter::exit_policy::ExitPolicyRequestFilter;
use log::{info, warn};
use nym_exit_policy::ExitPolicy;
use log::warn;
use nym_socks5_requests::RemoteAddress;
use nym_task::TaskHandle;
use std::sync::Arc;
/// Old request filtering based on the allowed.list files.
pub mod allowed_hosts;
pub mod exit_policy;
enum RequestFilterInner {
AllowList {
open_proxy: bool,
filter: OutboundRequestFilter,
},
ExitPolicy {
policy_filter: ExitPolicyRequestFilter,
},
}
pub use exit_policy::ExitPolicyRequestFilter;
#[derive(Clone)]
pub struct RequestFilter {
inner: Arc<RequestFilterInner>,
inner: Arc<ExitPolicyRequestFilter>,
}
impl RequestFilter {
pub(crate) async fn new(config: &Config) -> Result<Self, NetworkRequesterError> {
if config.network_requester.use_deprecated_allow_list {
info!("setting up allow-list based 'OutboundRequestFilter'...");
Ok(Self::new_allow_list_request_filter(config).await)
} else {
info!("setting up ExitPolicy based request filter...");
Self::new_exit_policy_filter(config).await
}
}
pub fn current_exit_policy_filter(&self) -> Option<&ExitPolicyRequestFilter> {
match &*self.inner {
RequestFilterInner::AllowList { .. } => None,
RequestFilterInner::ExitPolicy { policy_filter } => Some(policy_filter),
}
}
pub(crate) async fn start_update_tasks(
&self,
config: &config::Debug,
task_handle: &TaskHandle,
) {
match &*self.inner {
RequestFilterInner::AllowList { open_proxy, filter } => {
// if we're running in open proxy, we don't have to spawn any refreshers,
// after all, we're going to be accepting all requests regardless
// of the local allow list or the standard list
if *open_proxy {
return;
}
// start the standard list updater
StandardListUpdater::new(
config.standard_list_update_interval,
filter.standard_list(),
task_handle.get_handle().named("StandardListUpdater"),
)
.start();
// start the allowed.list watcher and updater
start_allowed_list_reloader(
filter.allowed_hosts(),
task_handle
.get_handle()
.named("stored_allowed_hosts_reloader"),
)
.await;
}
RequestFilterInner::ExitPolicy { .. } => {
// nothing to do for the exit policy (yet; we might add a refresher at some point)
}
}
}
async fn new_allow_list_request_filter(config: &Config) -> Self {
let standard_list = StandardList::new();
let allowed_hosts = StoredAllowedHosts::new(&config.storage_paths.allowed_list_location);
let unknown_hosts =
allowed_hosts::HostsStore::new(&config.storage_paths.unknown_list_location);
// TODO: technically if we're running open proxy, we don't have to be loading anything here
RequestFilter {
inner: Arc::new(RequestFilterInner::AllowList {
open_proxy: config.network_requester.open_proxy,
filter: OutboundRequestFilter::new(allowed_hosts, standard_list, unknown_hosts)
.await,
}),
}
}
async fn new_exit_policy_filter(config: &Config) -> Result<Self, NetworkRequesterError> {
let policy_filter = if config.network_requester.open_proxy {
ExitPolicyRequestFilter::new(ExitPolicy::new_open())
} else {
let upstream_url = config
.network_requester
.upstream_exit_policy_url
.as_ref()
.ok_or(NetworkRequesterError::NoUpstreamExitPolicy)?;
ExitPolicyRequestFilter::new_upstream(upstream_url.clone()).await?
};
Ok(RequestFilter {
inner: Arc::new(RequestFilterInner::ExitPolicy { policy_filter }),
inner: Arc::new(ExitPolicyRequestFilter::new(config).await?),
})
}
pub fn current_exit_policy_filter(&self) -> &ExitPolicyRequestFilter {
&self.inner
}
pub(crate) async fn check_address(&self, address: &RemoteAddress) -> bool {
match &*self.inner {
RequestFilterInner::AllowList { open_proxy, filter } => {
if *open_proxy {
return true;
}
filter.check(address).await
}
RequestFilterInner::ExitPolicy { policy_filter } => {
match policy_filter.check(address).await {
Err(err) => {
warn!("failed to validate '{address}' against the exit policy: {err}");
false
}
Ok(res) => res,
}
}
}
self.inner.check(address).await.unwrap_or_else(|err| {
warn!("failed to validate '{address}' against the exit policy: {err}");
false
})
}
}