diff --git a/Cargo.lock b/Cargo.lock index 2b0a50c68b..d73cbeec9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6173,6 +6173,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util 0.6.10", + "toml 0.5.11", "tower", "tracing", "yamux", diff --git a/base_layer/p2p/src/initialization.rs b/base_layer/p2p/src/initialization.rs index 7579d5954e..dd2cc4e3e4 100644 --- a/base_layer/p2p/src/initialization.rs +++ b/base_layer/p2p/src/initialization.rs @@ -332,7 +332,7 @@ async fn configure_comms_and_dht( .with_listener_liveness_allowlist_cidrs(listener_liveness_allowlist_cidrs) .with_dial_backoff(ConstantBackoff::new(Duration::from_millis(500))) .with_peer_storage(peer_database, Some(file_lock)) - .with_excluded_dial_addresses(config.dht.excluded_dial_addresses.clone()); + .with_excluded_dial_addresses(config.dht.excluded_dial_addresses.clone().into_vec().clone()); let mut comms = match config.auxiliary_tcp_listener_address { Some(ref addr) => builder.with_auxiliary_tcp_listener_address(addr.clone()).build()?, diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index c8e1164c10..90853f40af 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -5328,7 +5328,7 @@ pub unsafe extern "C" fn comms_config_create( minimum_desired_tcpv4_node_ratio: 0.0, ..Default::default() }, - excluded_dial_addresses: vec![IP4_TCP_TEST_ADDR_RANGE.parse().expect("valid address range")], + excluded_dial_addresses: vec![IP4_TCP_TEST_ADDR_RANGE.parse().expect("valid address range")].into(), ..Default::default() }, allow_test_addresses: true, diff --git a/common/config/presets/c_base_node_c.toml b/common/config/presets/c_base_node_c.toml index 4c7b147950..3d72a8037c 100644 --- a/common/config/presets/c_base_node_c.toml +++ b/common/config/presets/c_base_node_c.toml @@ -303,8 +303,6 @@ database_url = "data/base_node/dht.db" #ban_duration = 21_600 # 6 * 60 * 60 # Length of time to ban a peer for a "short" duration. Default: 60 mins #ban_duration_short = 3_600 # 60 * 60 -# This allows the use of test addresses in the network like 127.0.0.1. Default: false -#allow_test_addresses = false # The maximum number of messages over `flood_ban_timespan` to allow before banning the peer (for `ban_duration_short`) # Default: 100_000 messages #flood_ban_max_msg_count = 100_000 @@ -316,5 +314,9 @@ database_url = "data/base_node/dht.db" # In a situation where a node is not well-connected and many nodes are locally marked as offline, we can retry # peers that were previously tried. Default: 2 hours #offline_peer_cooldown = 7_200 # 2 * 60 * 60 -# Addresses that should never be dialed (default value = []) -#excluded_dial_addresses = ["/ip4/x.x.x.x/tcp/xxxx", "/ip4/x.y.x.y/tcp/xyxy"] +# Addresses that should never be dialed (default value = []). This can be a specific address or an IPv4/TCP range. +# Example: When used in conjunction with `allow_test_addresses = true` (but it could be any other range) +# `excluded_dial_addresses = ["/ip4/127.*.0:49.*/tcp/*", "/ip4/127.*.101:255.*/tcp/*"]` +# or +# `excluded_dial_addresses = ["/ip4/127.0:0.1/tcp/122", "/ip4/127.0:0.1/tcp/1000:2000"]` +#excluded_dial_addresses = [] diff --git a/common/config/presets/d_console_wallet.toml b/common/config/presets/d_console_wallet.toml index 93b5a8f920..6a3c5d5f27 100644 --- a/common/config/presets/d_console_wallet.toml +++ b/common/config/presets/d_console_wallet.toml @@ -347,8 +347,6 @@ network_discovery.initial_peer_sync_delay = 25 #ban_duration = 21_600 # 6 * 60 * 60 # Length of time to ban a peer for a "short" duration. Default: 60 mins #ban_duration_short = 3_600 # 60 * 60 -# This allows the use of test addresses in the network like 127.0.0.1. Default: false -#allow_test_addresses = false # The maximum number of messages over `flood_ban_timespan` to allow before banning the peer (for `ban_duration_short`) # Default: 100_000 messages #flood_ban_max_msg_count = 100_000 @@ -360,5 +358,9 @@ network_discovery.initial_peer_sync_delay = 25 # In a situation where a node is not well-connected and many nodes are locally marked as offline, we can retry # peers that were previously tried. Default: 2 hours #offline_peer_cooldown = 7_200 # 2 * 60 * 60 -# Addresses that should never be dialed (default value = []) -#excluded_dial_addresses = ["/ip4/x.x.x.x/tcp/xxxx", "/ip4/x.y.x.y/tcp/xyxy"] +# Addresses that should never be dialed (default value = []). This can be a specific address or an IPv4/TCP range. +# Example: When used in conjunction with `allow_test_addresses = true` (but it could be any other range) +# `excluded_dial_addresses = ["/ip4/127.*.0:49.*/tcp/*", "/ip4/127.*.101:255.*/tcp/*"]` +# or +# `excluded_dial_addresses = ["/ip4/127.0:0.1/tcp/122", "/ip4/127.0:0.1/tcp/1000:2000"]` +#excluded_dial_addresses = [] diff --git a/comms/core/Cargo.toml b/comms/core/Cargo.toml index fef234ee85..8814759bf4 100644 --- a/comms/core/Cargo.toml +++ b/comms/core/Cargo.toml @@ -52,6 +52,7 @@ zeroize = "1" [dev-dependencies] tari_test_utils = { path = "../../infrastructure/test_utils" } tari_comms_rpc_macros = { path = "../rpc_macros" } +toml = { version = "0.5"} env_logger = "0.7.0" serde_json = "1.0.39" diff --git a/comms/core/src/net_address/mod.rs b/comms/core/src/net_address/mod.rs index 5efd1f557d..34e2645984 100644 --- a/comms/core/src/net_address/mod.rs +++ b/comms/core/src/net_address/mod.rs @@ -29,4 +29,4 @@ mod mutliaddresses_with_stats; pub use mutliaddresses_with_stats::MultiaddressesWithStats; mod multiaddr_range; -pub use multiaddr_range::{MultiaddrRange, IP4_TCP_TEST_ADDR_RANGE}; +pub use multiaddr_range::{serde_multiaddr_range, MultiaddrRange, MultiaddrRangeList, IP4_TCP_TEST_ADDR_RANGE}; diff --git a/comms/core/src/net_address/multiaddr_range.rs b/comms/core/src/net_address/multiaddr_range.rs index 985d598607..f30542b5be 100644 --- a/comms/core/src/net_address/multiaddr_range.rs +++ b/comms/core/src/net_address/multiaddr_range.rs @@ -1,10 +1,16 @@ // Copyright 2022 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use std::{fmt, net::Ipv4Addr, str::FromStr}; +use std::{fmt, net::Ipv4Addr, ops::Deref, slice, str::FromStr}; use multiaddr::{Multiaddr, Protocol}; -use serde_derive::{Deserialize, Serialize}; +use serde::{ + de, + de::{Error, SeqAccess, Visitor}, + Deserialize, + Deserializer, + Serialize, +}; /// A MultiaddrRange for testing purposes that matches any IPv4 address and any port pub const IP4_TCP_TEST_ADDR_RANGE: &str = "/ip4/127.*.*.*/tcp/*"; @@ -12,7 +18,7 @@ pub const IP4_TCP_TEST_ADDR_RANGE: &str = "/ip4/127.*.*.*/tcp/*"; /// ----------------- MultiaddrRange ----------------- /// A struct containing either an Ipv4AddrRange or a Multiaddr. If a range of IP addresses and/or ports needs to be /// specified, the MultiaddrRange can be used, but it only supports IPv4 addresses with the TCP protocol. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct MultiaddrRange { ipv4_addr_range: Option, multiaddr: Option, @@ -65,7 +71,7 @@ impl MultiaddrRange { // ----------------- Ipv4AddrRange ----------------- // A struct containing an Ipv4Range and a PortRange -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] struct Ipv4AddrRange { ip_range: Ipv4Range, port_range: PortRange, @@ -123,7 +129,7 @@ impl fmt::Display for Ipv4AddrRange { // ----------------- Ipv4Range ----------------- // A struct containing the start and end Ipv4Addr -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] struct Ipv4Range { start: Ipv4Addr, end: Ipv4Addr, @@ -141,7 +147,7 @@ impl Ipv4Range { for (i, part) in parts.iter().enumerate() { if i == 0 { - start_octets[i] = part.parse().map_err(|_| "Invalid first octet".to_string())?; + start_octets[i] = part.parse().map_err(|_| "Invalid first IPv4 octet".to_string())?; end_octets[i] = start_octets[i]; } else if part == &"*" { start_octets[i] = 0; @@ -149,12 +155,16 @@ impl Ipv4Range { } else if part.contains(':') { let range_parts: Vec<&str> = part.split(':').collect(); if range_parts.len() != 2 { - return Err("Invalid range format".to_string()); + return Err(format!("Invalid range format for IPv4 octet {}", i)); } - start_octets[i] = range_parts[0].parse().map_err(|_| "Invalid range start".to_string())?; - end_octets[i] = range_parts[1].parse().map_err(|_| "Invalid range end".to_string())?; + start_octets[i] = range_parts[0] + .parse() + .map_err(|_| format!("Invalid range start for IPv4 octet {}", i))?; + end_octets[i] = range_parts[1] + .parse() + .map_err(|_| format!("Invalid range end for IPv4 octet {}", i))?; } else { - start_octets[i] = part.parse().map_err(|_| "Invalid octet".to_string())?; + start_octets[i] = part.parse().map_err(|_| format!("Invalid IPv4 octet {}", i))?; end_octets[i] = start_octets[i]; } } @@ -214,7 +224,7 @@ impl fmt::Display for Ipv4Range { // ----------------- PortRange ----------------- // A struct containing the start and end port -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] struct PortRange { start: u16, end: u16, @@ -234,8 +244,18 @@ impl PortRange { if parts.len() != 2 { return Err("Invalid port range format".to_string()); } - let start = parts[0].parse().map_err(|_| "Invalid port range start".to_string())?; - let end = parts[1].parse().map_err(|_| "Invalid port range end".to_string())?; + let start = parts[0] + .parse() + .map_err(|_| format!("Invalid port range start '{}'", parts[0]))?; + let end = parts[1] + .parse() + .map_err(|_| format!("Invalid port range end '{}'", parts[1]))?; + if end < start { + return Err(format!( + "Invalid port range '{}', end `{}` is less than start `{}`", + range_str, end, start + )); + } return Ok(PortRange { start, end }); } @@ -250,7 +270,7 @@ impl PortRange { impl fmt::Display for PortRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.start == 0 && self.end == u16::MAX { + if self.start <= 1 && self.end == u16::MAX { write!(f, "*") } else if self.start == self.end { write!(f, "{}", self.start) @@ -260,15 +280,202 @@ impl fmt::Display for PortRange { } } +/// Supports deserialization from a sequence of strings or comma-delimited strings +#[derive(Debug, Default, Clone, Serialize, PartialEq, Eq)] +pub struct MultiaddrRangeList(Vec); + +impl MultiaddrRangeList { + pub fn new() -> Self { + Self(vec![]) + } + + pub fn with_capacity(size: usize) -> Self { + Self(Vec::with_capacity(size)) + } + + pub fn into_vec(self) -> Vec { + self.0 + } + + pub fn as_slice(&self) -> &[MultiaddrRange] { + self.0.as_slice() + } +} + +impl Deref for MultiaddrRangeList { + type Target = [MultiaddrRange]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[MultiaddrRange]> for MultiaddrRangeList { + fn as_ref(&self) -> &[MultiaddrRange] { + self.0.as_ref() + } +} + +impl From> for MultiaddrRangeList { + fn from(v: Vec) -> Self { + Self(v) + } +} + +impl IntoIterator for MultiaddrRangeList { + type IntoIter = as IntoIterator>::IntoIter; + type Item = as IntoIterator>::Item; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a MultiaddrRangeList { + type IntoIter = slice::Iter<'a, MultiaddrRange>; + type Item = ::Item; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'de> Deserialize<'de> for MultiaddrRangeList { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + struct MultiaddrRangeListVisitor; + + impl<'de> Visitor<'de> for MultiaddrRangeListVisitor { + type Value = MultiaddrRangeList; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "a comma delimited string or multiple string elements") + } + + fn visit_str(self, v: &str) -> Result + where E: de::Error { + let strings = v + .split(',') + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .collect::>(); + let multiaddr_ranges: Result, _> = strings + .into_iter() + .map(|item| MultiaddrRange::from_str(item).map_err(E::custom)) + .collect(); + Ok(MultiaddrRangeList(multiaddr_ranges.map_err(E::custom)?)) + } + + fn visit_newtype_struct(self, deserializer: D) -> Result + where D: Deserializer<'de> { + deserializer.deserialize_seq(MultiaddrRangeListVisitor) + } + + fn visit_seq(self, mut seq: A) -> Result + where A: SeqAccess<'de> { + let mut buf = seq.size_hint().map(Vec::with_capacity).unwrap_or_default(); + while let Some(v) = seq.next_element::()? { + buf.push(v) + } + Ok(MultiaddrRangeList(buf)) + } + } + + if deserializer.is_human_readable() { + deserializer.deserialize_seq(MultiaddrRangeListVisitor) + } else { + deserializer.deserialize_newtype_struct("MultiaddrRangeList", MultiaddrRangeListVisitor) + } + } +} + +impl<'de> Deserialize<'de> for MultiaddrRange { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + let s = String::deserialize(deserializer)?; + MultiaddrRange::from_str(&s).map_err(D::Error::custom) + } +} + +pub mod serde_multiaddr_range { + use std::str::FromStr; + + use serde::{de::Error, Deserialize, Deserializer, Serializer}; + + use crate::net_address::MultiaddrRange; + + pub fn serialize(value: &[MultiaddrRange], serializer: S) -> Result + where S: Serializer { + let strings: Vec = value.iter().map(|v| v.to_string()).collect(); + serializer.serialize_str(&strings.join(",")) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de> { + let strings: Vec = Vec::deserialize(deserializer)?; + strings + .into_iter() + .map(|item| MultiaddrRange::from_str(&item).map_err(D::Error::custom)) + .collect() + } +} + #[cfg(test)] mod test { - use std::net::{IpAddr, Ipv6Addr}; + use std::{ + net::{IpAddr, Ipv6Addr}, + str::FromStr, + }; + + use serde::Deserialize; use crate::{ multiaddr::Multiaddr, - net_address::{multiaddr_range::IP4_TCP_TEST_ADDR_RANGE, MultiaddrRange}, + net_address::{multiaddr_range::IP4_TCP_TEST_ADDR_RANGE, MultiaddrRange, MultiaddrRangeList}, }; + #[derive(Deserialize)] + struct Test { + something: MultiaddrRangeList, + } + + #[test] + fn it_parses_with_serde() { + // Random tests + let config_str = r#"something = [ + "/ip4/127.*.100:200.*/tcp/18000:19000", + "/ip4/127.0.150.1/tcp/18500", + "/ip4/127.0.0.1/udt/sctp/5678", + "/ip4/127.*.*.*/tcp/*" + ]"#; + let item_vec = toml::from_str::(config_str).unwrap().something.into_vec(); + assert_eq!(item_vec, vec![ + MultiaddrRange::from_str("/ip4/127.*.100:200.*/tcp/18000:19000").unwrap(), + MultiaddrRange::from_str("/ip4/127.0.150.1/tcp/18500").unwrap(), + MultiaddrRange::from_str("/ip4/127.0.0.1/udt/sctp/5678").unwrap(), + MultiaddrRange::from_str(IP4_TCP_TEST_ADDR_RANGE).unwrap() + ]); + + // Allowing only '/ip4/127.0.0.1/tcp/0:18189' + let config_str = r#"something = [ + "/ip4/127.*.*.*/tcp/0:18188", + "/ip4/127.*.*.*/tcp/18190:65535", + "/ip4/127.0.0.0/tcp/18189", + "/ip4/127.1:255.1:255.2:255/tcp/18189" + ]"#; + let item_vec = toml::from_str::(config_str).unwrap().something.into_vec(); + assert_eq!(item_vec, vec![ + MultiaddrRange::from_str("/ip4/127.*.*.*/tcp/0:18188").unwrap(), + MultiaddrRange::from_str("/ip4/127.*.*.*/tcp/18190:65535").unwrap(), + MultiaddrRange::from_str("/ip4/127.0.0.0/tcp/18189").unwrap(), + MultiaddrRange::from_str("/ip4/127.1:255.1:255.2:255/tcp/18189").unwrap(), + ]); + + for item in item_vec { + assert!(!item.contains(&Multiaddr::from_str("/ip4/127.0.0.1/tcp/18189").unwrap())); + } + } + #[test] fn it_parses_properly_and_verify_inclusion() { // MultiaddrRange for ip4 with tcp diff --git a/comms/dht/src/actor.rs b/comms/dht/src/actor.rs index 64c1bebf85..0291e81bc4 100644 --- a/comms/dht/src/actor.rs +++ b/comms/dht/src/actor.rs @@ -419,7 +419,7 @@ impl DhtActor { Box::pin(Self::broadcast_join( node_identity, peer_manager, - excluded_dial_addresses, + excluded_dial_addresses.into_vec(), outbound_requester, )) }, @@ -502,7 +502,7 @@ impl DhtActor { Box::pin(async move { DhtActor::check_if_addresses_excluded( - excluded_dial_addresses, + excluded_dial_addresses.into_vec(), &peer_manager, node_identity.node_id().clone(), ) diff --git a/comms/dht/src/config.rs b/comms/dht/src/config.rs index 796246fe3f..069c77b9f1 100644 --- a/comms/dht/src/config.rs +++ b/comms/dht/src/config.rs @@ -24,7 +24,7 @@ use std::{path::Path, time::Duration}; use serde::{Deserialize, Serialize}; use tari_common::configuration::serializers; -use tari_comms::{net_address::MultiaddrRange, peer_validator::PeerValidatorConfig}; +use tari_comms::{net_address::MultiaddrRangeList, peer_validator::PeerValidatorConfig}; use crate::{ actor::OffenceSeverity, @@ -94,7 +94,6 @@ pub struct DhtConfig { /// Default: 10 mins #[serde(with = "serializers::seconds")] pub ban_duration_short: Duration, - /// The maximum number of messages over `flood_ban_timespan` to allow before banning the peer (for /// `ban_duration_short`) Default: 100_000 messages pub flood_ban_max_msg_count: usize, @@ -115,8 +114,12 @@ pub struct DhtConfig { /// Configuration for peer validation /// See [PeerValidatorConfig] pub peer_validator_config: PeerValidatorConfig, - /// Addresses that should never be dialed - pub excluded_dial_addresses: Vec, + /// Addresses that should never be dialed (default value = []). This can be a specific address or an IPv4/TCP + /// range. Example: When used in conjunction with `allow_test_addresses = true` (but it could be any other + /// range) `excluded_dial_addresses = ["/ip4/127.*.0:49.*/tcp/*", "/ip4/127.*.101:255.*/tcp/*"]` + /// or + /// `excluded_dial_addresses = ["/ip4/127.0:0.1/tcp/122", "/ip4/127.0:0.1/tcp/1000:2000"]` + pub excluded_dial_addresses: MultiaddrRangeList, } impl DhtConfig { @@ -195,7 +198,7 @@ impl Default for DhtConfig { max_permitted_peer_claims: 5, offline_peer_cooldown: Duration::from_secs(24 * 60 * 60), peer_validator_config: Default::default(), - excluded_dial_addresses: vec![], + excluded_dial_addresses: vec![].into(), } } }