diff --git a/config/src/config.rs b/config/src/config.rs index 716064904..bd26b2317 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -22,6 +22,8 @@ use std::io::prelude::*; use std::io::BufReader; use std::path::PathBuf; +use p2p::types::{TESTNET_PEER_PORT, USERNET_PEER_PORT}; + use crate::comments::insert_comments; use crate::core::global; use crate::p2p; @@ -42,10 +44,7 @@ pub const FOREIGN_API_SECRET_FILE_NAME: &str = ".foreign_api_secret"; fn get_grin_path(chain_type: &global::ChainTypes) -> Result { // Check if grin dir exists - let mut grin_path = match dirs::home_dir() { - Some(p) => p, - None => PathBuf::new(), - }; + let mut grin_path = dirs::home_dir().unwrap_or_else(|| PathBuf::new()); grin_path.push(GRIN_HOME); grin_path.push(chain_type.shortname()); // Create if the default path doesn't exist @@ -113,7 +112,7 @@ fn check_api_secret_files( pub fn initial_setup_server(chain_type: &global::ChainTypes) -> Result { check_api_secret_files(chain_type, API_SECRET_FILE_NAME)?; check_api_secret_files(chain_type, FOREIGN_API_SECRET_FILE_NAME)?; - // Use config file if current directory if it exists, .grin home otherwise + // Use config file in current directory if it exists, .grin home otherwise if let Some(p) = check_config_current_dir(SERVER_CONFIG_FILE_NAME) { GlobalConfig::new(p.to_str().unwrap()) } else { @@ -168,7 +167,7 @@ impl GlobalConfig { global::ChainTypes::Mainnet => {} global::ChainTypes::Testnet => { defaults.api_http_addr = "127.0.0.1:13413".to_owned(); - defaults.p2p_config.port = 13414; + defaults.p2p_config.port = TESTNET_PEER_PORT; defaults .stratum_mining_config .as_mut() @@ -182,7 +181,7 @@ impl GlobalConfig { } global::ChainTypes::UserTesting => { defaults.api_http_addr = "127.0.0.1:23413".to_owned(); - defaults.p2p_config.port = 23414; + defaults.p2p_config.port = USERNET_PEER_PORT; defaults.p2p_config.seeding_type = p2p::Seeding::None; defaults .stratum_mining_config @@ -234,14 +233,12 @@ impl GlobalConfig { match decoded { Ok(gc) => { self.members = Some(gc); - return Ok(self); - } - Err(e) => { - return Err(ConfigError::ParseError( - self.config_file_path.unwrap().to_str().unwrap().to_string(), - format!("{}", e), - )); + Ok(self) } + Err(e) => Err(ConfigError::ParseError( + self.config_file_path.unwrap().to_str().unwrap().to_string(), + format!("{}", e), + )), } } @@ -275,8 +272,7 @@ impl GlobalConfig { /// Enable mining pub fn stratum_enabled(&mut self) -> bool { - return self - .members + self.members .as_mut() .unwrap() .server @@ -284,7 +280,7 @@ impl GlobalConfig { .as_mut() .unwrap() .enable_stratum_server - .unwrap(); + .unwrap() } /// Serialize config @@ -292,10 +288,8 @@ impl GlobalConfig { let encoded: Result = toml::to_string(self.members.as_mut().unwrap()); match encoded { - Ok(enc) => return Ok(enc), - Err(e) => { - return Err(ConfigError::SerializationError(format!("{}", e))); - } + Ok(enc) => Ok(enc), + Err(e) => Err(ConfigError::SerializationError(format!("{}", e))), } } diff --git a/p2p/src/peers.rs b/p2p/src/peers.rs index e62a2724f..9758051d9 100644 --- a/p2p/src/peers.rs +++ b/p2p/src/peers.rs @@ -32,8 +32,8 @@ use crate::msg::PeerAddrs; use crate::peer::Peer; use crate::store::{PeerData, PeerStore, State}; use crate::types::{ - Capabilities, ChainAdapter, Error, NetAdapter, P2PConfig, PeerAddr, PeerInfo, ReasonForBan, - TxHashSetRead, MAX_PEER_ADDRS, + is_private_ip, Capabilities, ChainAdapter, Error, NetAdapter, P2PConfig, PeerAddr, PeerInfo, + ReasonForBan, TxHashSetRead, MAX_PEER_ADDRS, }; use crate::util::secp::pedersen::RangeProof; use chrono::prelude::*; @@ -86,9 +86,14 @@ impl Peers { peers.insert(peer_data.addr, peer); } } - debug!("Saving newly connected peer {}.", peer_data.addr); - if let Err(e) = self.save_peer(&peer_data) { - error!("Could not save connected peer address: {:?}", e); + // Do not save private peer. + if !is_private_ip(&peer_data.addr.0.ip()) { + debug!("Saving newly connected peer {}.", peer_data.addr); + if let Err(e) = self.save_peer(&peer_data) { + error!("Could not save connected peer address: {:?}", e); + } + } else { + debug!("Do not save connected private peer {}.", peer_data.addr); } Ok(()) } @@ -180,7 +185,13 @@ impl Peers { // check if peer exist self.get_peer(peer_addr)?; if self.is_banned(peer_addr) { - self.update_state(peer_addr, State::Healthy) + // delete banned private peer + if is_private_ip(&peer_addr.0.ip()) { + let batch = self.store.iter_batch()?; + batch.delete_peer(peer_addr).map_err(|e| Error::Store(e)) + } else { + self.update_state(peer_addr, State::Healthy) + } } else { Err(Error::PeerNotBanned) } @@ -328,7 +339,11 @@ impl Peers { /// Saves updated information about mulitple peers in batch pub fn save_peers(&self, p: Vec) -> Result<(), Error> { - self.store.save_peers(p).map_err(From::from) + let public_peers = p + .iter() + .filter(|p| !is_private_ip(&p.addr.0.ip())) + .collect::>(); + self.store.save_peers(public_peers).map_err(From::from) } /// Updates the state of a peer in store diff --git a/p2p/src/store.rs b/p2p/src/store.rs index 675d31b8b..72ef25863 100644 --- a/p2p/src/store.rs +++ b/p2p/src/store.rs @@ -138,7 +138,7 @@ impl PeerStore { batch.commit() } - pub fn save_peers(&self, p: Vec) -> Result<(), Error> { + pub fn save_peers(&self, p: Vec<&PeerData>) -> Result<(), Error> { let mut batch = self.db.batch()?; for pd in p { debug!("save_peers: {:?} marked {:?}", pd.addr, pd.flags); @@ -233,6 +233,14 @@ impl<'a> PeersIterBatch<'a> { Ok(peers) } + /// Delete a peer by provided address. + pub fn delete_peer(mut self, peer_addr: PeerAddr) -> Result<(), Error> { + let key = peer_addr.as_key(); + self.db.delete(Some(PEER_PREFIX), key.as_bytes())?; + self.db.commit()?; + Ok(()) + } + /// Deletes peers from the storage that satisfy some condition `predicate` pub fn delete_peers(mut self, predicate: F) -> Result<(), Error> where diff --git a/p2p/src/types.rs b/p2p/src/types.rs index 8d0bdd1a5..1bccc8c4c 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -37,6 +37,13 @@ use crate::msg::PeerAddrs; use crate::util::secp::pedersen::RangeProof; use crate::util::RwLock; +/// Default main network peer port. +pub const MAINNET_PEER_PORT: u16 = 3414; +/// Default test network peer port. +pub const TESTNET_PEER_PORT: u16 = 13414; +/// Default user network peer port. +pub const USERNET_PEER_PORT: u16 = 23414; + /// Maximum number of block headers a peer should ever send pub const MAX_BLOCK_HEADERS: u32 = 512; @@ -207,10 +214,10 @@ impl<'de> Deserialize<'de> for PeerAddrs { } impl std::hash::Hash for PeerAddr { - /// If loopback address then we care about ip and port. + /// If private address then we care about ip and port. /// If regular address then we only care about the ip and ignore the port. fn hash(&self, state: &mut H) { - if self.0.ip().is_loopback() { + if is_private_ip(&self.0.ip()) { self.0.hash(state); } else { self.0.ip().hash(state); @@ -219,10 +226,10 @@ impl std::hash::Hash for PeerAddr { } impl PartialEq for PeerAddr { - /// If loopback address then we care about ip and port. + /// If private address then we care about ip and port. /// If regular address then we only care about the ip and ignore the port. fn eq(&self, other: &PeerAddr) -> bool { - if self.0.ip().is_loopback() { + if is_private_ip(&self.0.ip()) { self.0 == other.0 } else { self.0.ip() == other.0.ip() @@ -230,26 +237,78 @@ impl PartialEq for PeerAddr { } } +/// Check if IP address is private. +/// Implementation taken from `core::net:ip_addr` while `is_global` is unstable. +pub fn is_private_ip(ip: &IpAddr) -> bool { + match ip { + IpAddr::V4(ip) => { + ip.is_private() || ip.is_loopback() || ip.is_link_local() || ip.is_documentation() + // addresses reserved for future protocols (`192.0.0.0/24`) + // .9 and .10 are documented as globally reachable so they're excluded + || ( + ip.octets()[0] == 192 && ip.octets()[1] == 0 && ip.octets()[2] == 0 + && ip.octets()[3] != 9 && ip.octets()[3] != 10 + // this address is part of the Shared Address Space defined in + // [IETF RFC 6598] (`100.64.0.0/10`). + || ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000) + ) + } + IpAddr::V6(ip) => { + ip.is_loopback() || ip.is_unspecified() + // IPv4-mapped Address (`::ffff:0:0/96`) + || matches!(ip.segments(), [0, 0, 0, 0, 0, 0xffff, _, _]) + // IPv4-IPv6 Translat. (`64:ff9b:1::/48`) + || matches!(ip.segments(), [0x64, 0xff9b, 1, _, _, _, _, _]) + // Discard-Only Address Block (`100::/64`) + || matches!(ip.segments(), [0x100, 0, 0, 0, _, _, _, _]) + // IETF Protocol Assignments (`2001::/23`) + || (matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200) + && !( + // Port Control Protocol Anycast (`2001:1::1`) + u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001 + // Traversal Using Relays around NAT Anycast (`2001:1::2`) + || u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002 + // AMT (`2001:3::/32`) + || matches!(ip.segments(), [0x2001, 3, _, _, _, _, _, _]) + // AS112-v6 (`2001:4:112::/48`) + || matches!(ip.segments(), [0x2001, 4, 0x112, _, _, _, _, _]) + // ORCHIDv2 (`2001:20::/28`) + // Drone Remote ID Protocol Entity Tags (DETs) Prefix (`2001:30::/28`)` + || matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b >= 0x20 && b <= 0x3F) + )) + // 6to4 (`2002::/16`) – it's not explicitly documented as globally reachable, + // IANA says N/A. + || matches!(ip.segments(), [0x2002, _, _, _, _, _, _, _]) + // Segment Routing (SRv6) SIDs (`5f00::/16`) + || matches!(ip.segments(), [0x5f00, ..]) + || ip.is_unique_local() + || ip.is_unicast_link_local() + } + } +} + impl Eq for PeerAddr {} -impl std::fmt::Display for PeerAddr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl fmt::Display for PeerAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } - impl PeerAddr { - /// Convenient way of constructing a new peer_addr from an ip_addr - /// defaults to port 3414 on mainnet and 13414 on testnet. + /// Convenient way of constructing a new peer address from an ip address. pub fn from_ip(addr: IpAddr) -> PeerAddr { - let port = if global::is_testnet() { 13414 } else { 3414 }; + let port = if global::is_testnet() { + TESTNET_PEER_PORT + } else { + MAINNET_PEER_PORT + }; PeerAddr(SocketAddr::new(addr, port)) } - /// If the ip is loopback then our key is "ip:port" (mainly for local usernet testing). - /// Otherwise we only care about the ip (we disallow multiple peers on the same ip address). + /// If the ip is private then our key is "ip:port". + /// Otherwise, we only care about the ip (we disallow multiple peers on the same ip address). pub fn as_key(&self) -> String { - if self.0.ip().is_loopback() { + if is_private_ip(&self.0.ip()) { format!("{}:{}", self.0.ip(), self.0.port()) } else { format!("{}", self.0.ip()) @@ -296,7 +355,7 @@ impl Default for P2PConfig { let ipaddr = "::".parse().unwrap(); P2PConfig { host: ipaddr, - port: 3414, + port: MAINNET_PEER_PORT, seeding_type: Seeding::default(), seeds: None, peers_allow: None, @@ -317,42 +376,31 @@ impl Default for P2PConfig { impl P2PConfig { /// return ban window pub fn ban_window(&self) -> i64 { - match self.ban_window { - Some(n) => n, - None => BAN_WINDOW, - } + self.ban_window.unwrap_or_else(|| BAN_WINDOW) } /// return maximum inbound peer connections count pub fn peer_max_inbound_count(&self) -> u32 { - match self.peer_max_inbound_count { - Some(n) => n, - None => PEER_MAX_INBOUND_COUNT, - } + self.peer_max_inbound_count + .unwrap_or_else(|| PEER_MAX_INBOUND_COUNT) } /// return maximum outbound peer connections count pub fn peer_max_outbound_count(&self) -> u32 { - match self.peer_max_outbound_count { - Some(n) => n, - None => PEER_MAX_OUTBOUND_COUNT, - } + self.peer_max_outbound_count + .unwrap_or_else(|| PEER_MAX_OUTBOUND_COUNT) } /// return minimum preferred outbound peer count pub fn peer_min_preferred_outbound_count(&self) -> u32 { - match self.peer_min_preferred_outbound_count { - Some(n) => n, - None => PEER_MIN_PREFERRED_OUTBOUND_COUNT, - } + self.peer_min_preferred_outbound_count + .unwrap_or_else(|| PEER_MIN_PREFERRED_OUTBOUND_COUNT) } /// return peer buffer count for listener pub fn peer_listener_buffer_count(&self) -> u32 { - match self.peer_listener_buffer_count { - Some(n) => n, - None => PEER_LISTENER_BUFFER_COUNT, - } + self.peer_listener_buffer_count + .unwrap_or_else(|| PEER_LISTENER_BUFFER_COUNT) } } diff --git a/p2p/tests/peer_addr.rs b/p2p/tests/peer_addr.rs index 645d2c8e2..597775ba8 100644 --- a/p2p/tests/peer_addr.rs +++ b/p2p/tests/peer_addr.rs @@ -24,17 +24,17 @@ use crate::p2p::types::PeerAddr; fn test_peer_addr_hashing() { let mut peers: HashMap = HashMap::new(); - let socket_addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), 8080); + let socket_addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(185, 147, 152, 14)), 8080); let peer_addr1 = PeerAddr(socket_addr1); peers.insert(peer_addr1, "peer1".into()); assert!(peers.contains_key(&peer_addr1)); assert_eq!(peers.len(), 1); - let socket_addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), 8081); + let socket_addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(185, 147, 152, 14)), 8081); let peer_addr2 = PeerAddr(socket_addr2); - // Expected behavior here is to ignore the port when hashing peer_addr. + // Expected behavior here is to ignore the port when hashing non-private peer_addr. // This means the two peer_addr instances above are seen as the same addr. assert!(peers.contains_key(&peer_addr1)); assert!(peers.contains_key(&peer_addr2)); @@ -52,4 +52,31 @@ fn test_peer_addr_hashing() { assert_eq!(peer_addr2.0, socket_addr2); assert_eq!(peer_addr1.0.port(), 8080); assert_eq!(peer_addr2.0.port(), 8081); + + peers = HashMap::new(); + + let socket_addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)), 8080); + let peer_addr1 = PeerAddr(socket_addr1); + peers.insert(peer_addr1, "peer1".into()); + + assert!(peers.contains_key(&peer_addr1)); + assert_eq!(peers.len(), 1); + + let socket_addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)), 8081); + let peer_addr2 = PeerAddr(socket_addr2); + + // Expected behavior here is to not ignore the port when hashing private peer_addr. + // This means the two peer_addr instances above are seen as not the same addr. + assert!(peers.contains_key(&peer_addr1)); + assert!(!peers.contains_key(&peer_addr2)); + + peers.insert(peer_addr2, "peer2".into()); + + // Inserting the second instance is a valid operation as they are treated as not the same addr. + assert!(peers.contains_key(&peer_addr1)); + assert!(peers.contains_key(&peer_addr2)); + assert_eq!(peers.len(), 2); + + // Check they are treated as not the same even though their underlying ports are different. + assert_ne!(peer_addr1, peer_addr2); }