diff --git a/nym-api/src/ecash/tests/mod.rs b/nym-api/src/ecash/tests/mod.rs index 3e81637322..4ffe0b4e5f 100644 --- a/nym-api/src/ecash/tests/mod.rs +++ b/nym-api/src/ecash/tests/mod.rs @@ -13,7 +13,7 @@ use crate::node_status_api::handlers::unstable; use crate::node_status_api::NodeStatusCache; use crate::nym_contract_cache::cache::NymContractCache; use crate::support::caching::cache::SharedCache; -use crate::support::http::state::AppState; +use crate::support::http::state::{AppState, ForcedRefresh}; use crate::support::storage::NymApiStorage; use async_trait::async_trait; use axum::Router; @@ -1261,7 +1261,7 @@ struct TestFixture { impl TestFixture { fn build_app_state(storage: NymApiStorage) -> AppState { AppState { - forced_refresh: Default::default(), + forced_refresh: ForcedRefresh::new(true), nym_contract_cache: NymContractCache::new(), node_status_cache: NodeStatusCache::new(), circulating_supply_cache: CirculatingSupplyCache::new("unym".to_owned()), diff --git a/nym-api/src/node_describe_cache/mod.rs b/nym-api/src/node_describe_cache/mod.rs index aa80d831ab..adf25c29f1 100644 --- a/nym-api/src/node_describe_cache/mod.rs +++ b/nym-api/src/node_describe_cache/mod.rs @@ -57,6 +57,9 @@ pub enum NodeDescribeCacheError { // TODO: perhaps include more details here like whether key/signature/payload was malformed #[error("could not verify signed host information for node {node_id}")] MissignedHostInformation { node_id: NodeId }, + + #[error("node {node_id} is announcing an illegal ip address")] + IllegalIpAddress { node_id: NodeId }, } // this exists because I've been moving things around quite a lot and now the place that holds the type @@ -199,13 +202,18 @@ impl DescribedNodes { pub struct NodeDescriptionProvider { contract_cache: NymContractCache, + allow_all_ips: bool, batch_size: usize, } impl NodeDescriptionProvider { - pub(crate) fn new(contract_cache: NymContractCache) -> NodeDescriptionProvider { + pub(crate) fn new( + contract_cache: NymContractCache, + allow_all_ips: bool, + ) -> NodeDescriptionProvider { NodeDescriptionProvider { contract_cache, + allow_all_ips, batch_size: DEFAULT_NODE_DESCRIBE_BATCH_SIZE, } } @@ -270,6 +278,7 @@ async fn try_get_client( async fn try_get_description( data: RefreshData, + allow_all_ips: bool, ) -> Result { let client = try_get_client(&data.host, data.node_id, data.port).await?; @@ -286,6 +295,12 @@ async fn try_get_description( }); } + if !allow_all_ips && !host_info.data.check_ips() { + return Err(NodeDescribeCacheError::IllegalIpAddress { + node_id: data.node_id, + }); + } + let node_info = query_for_described_data(&client, data.node_id).await?; let description = node_info.into_node_description(host_info.data); @@ -357,8 +372,8 @@ impl RefreshData { self.node_id } - pub(crate) async fn try_refresh(self) -> Option { - match try_get_description(self).await { + pub(crate) async fn try_refresh(self, allow_all_ips: bool) -> Option { + match try_get_description(self, allow_all_ips).await { Ok(description) => Some(description), Err(err) => { debug!("failed to obtain node self-described data: {err}"); @@ -412,11 +427,15 @@ impl CacheItemProvider for NodeDescriptionProvider { } } - let nodes = stream::iter(nodes_to_query.into_iter().map(|n| n.try_refresh())) - .buffer_unordered(self.batch_size) - .filter_map(|x| async move { x.map(|d| (d.node_id, d)) }) - .collect::>() - .await; + let nodes = stream::iter( + nodes_to_query + .into_iter() + .map(|n| n.try_refresh(self.allow_all_ips)), + ) + .buffer_unordered(self.batch_size) + .filter_map(|x| async move { x.map(|d| (d.node_id, d)) }) + .collect::>() + .await; info!("refreshed self described data for {} nodes", nodes.len()); @@ -432,8 +451,11 @@ pub(crate) fn new_refresher( ) -> CacheRefresher { CacheRefresher::new( Box::new( - NodeDescriptionProvider::new(contract_cache) - .with_batch_size(config.debug.node_describe_batch_size), + NodeDescriptionProvider::new( + contract_cache, + config.debug.node_describe_allow_illegal_ips, + ) + .with_batch_size(config.debug.node_describe_batch_size), ), config.debug.node_describe_caching_interval, ) @@ -446,8 +468,11 @@ pub(crate) fn new_refresher_with_initial_value( ) -> CacheRefresher { CacheRefresher::new_with_initial_value( Box::new( - NodeDescriptionProvider::new(contract_cache) - .with_batch_size(config.debug.node_describe_batch_size), + NodeDescriptionProvider::new( + contract_cache, + config.debug.node_describe_allow_illegal_ips, + ) + .with_batch_size(config.debug.node_describe_batch_size), ), config.debug.node_describe_caching_interval, initial, diff --git a/nym-api/src/nym_nodes/handlers/mod.rs b/nym-api/src/nym_nodes/handlers/mod.rs index 47530a6c5c..42cfee3c86 100644 --- a/nym-api/src/nym_nodes/handlers/mod.rs +++ b/nym-api/src/nym_nodes/handlers/mod.rs @@ -86,8 +86,9 @@ async fn refresh_described( } // to make sure you can't ddos the endpoint while a request is in progress state.forced_refresh.set_last_refreshed(node_id).await; + let allow_all_ips = state.forced_refresh.allow_all_ip_addresses; - if let Some(updated_data) = refresh_data.try_refresh().await { + if let Some(updated_data) = refresh_data.try_refresh(allow_all_ips).await { let Ok(mut describe_cache) = state.described_nodes_cache.write().await else { return Err(AxumErrorResponse::service_unavailable()); }; diff --git a/nym-api/src/support/cli/run.rs b/nym-api/src/support/cli/run.rs index 493def1c5a..fa08abe376 100644 --- a/nym-api/src/support/cli/run.rs +++ b/nym-api/src/support/cli/run.rs @@ -21,7 +21,9 @@ use crate::status::{ApiStatusState, SignerState}; use crate::support::caching::cache::SharedCache; use crate::support::config::helpers::try_load_current_config; use crate::support::config::Config; -use crate::support::http::state::{AppState, ShutdownHandles, TASK_MANAGER_TIMEOUT_S}; +use crate::support::http::state::{ + AppState, ForcedRefresh, ShutdownHandles, TASK_MANAGER_TIMEOUT_S, +}; use crate::support::http::RouterBuilder; use crate::support::nyxd; use crate::support::storage::NymApiStorage; @@ -188,7 +190,9 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result>>, } impl ForcedRefresh { + pub(crate) fn new(allow_all_ip_addresses: bool) -> ForcedRefresh { + ForcedRefresh { + allow_all_ip_addresses, + refreshes: Arc::new(Default::default()), + } + } + pub(crate) async fn last_refreshed(&self, node_id: NodeId) -> Option { self.refreshes.read().await.get(&node_id).copied() } diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 6979beb949..d0f1e3df03 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -59,6 +59,17 @@ pub struct HostInformation { pub keys: HostKeys, } +impl HostInformation { + pub fn check_ips(&self) -> bool { + for ip in &self.ip_address { + if ip.is_unspecified() || ip.is_loopback() || ip.is_multicast() { + return false; + } + } + true + } +} + #[derive(Serialize)] pub struct LegacyHostInformationV2 { pub ip_address: Vec,