From 50820dd64cb37faef2814f0857f172e452cb264c Mon Sep 17 00:00:00 2001 From: Luis Rubio Date: Wed, 24 Feb 2021 16:29:55 +0100 Subject: [PATCH] refactor(node): add reject_sybil_outbounds_range_limit --- config/src/config.rs | 19 ++++++++--- config/src/defaults.rs | 14 ++++++-- node/src/actors/sessions_manager/actor.rs | 5 +++ node/src/actors/sessions_manager/handlers.rs | 2 +- p2p/src/peers/mod.rs | 34 ++++++++++++++------ p2p/src/sessions/mod.rs | 19 +++++++---- p2p/tests/sessions.rs | 3 +- witnet.toml | 4 +-- 8 files changed, 75 insertions(+), 25 deletions(-) diff --git a/config/src/config.rs b/config/src/config.rs index aadd65b57..523b953ff 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -236,10 +236,14 @@ pub struct Connections { ))] pub bucketing_ice_period: Duration, - /// Reject (tarpit) inbound connections coming from addresses in the same /14 IP range, so as - /// to prevent sybil peers from monopolizing our inbound capacity (128 by default). + /// Reject (tarpit) inbound connections coming from addresses that are alike, so as + /// to prevent sybil peers from monopolizing our inbound capacity. pub reject_sybil_inbounds: bool, + /// Limit to reject (tarpit) inbound connections. If the limit is set to 18, the addresses having + /// the same first 18 bits in the IP will collide, so as to prevent sybil peers from monopolizing our inbound capacity. + pub reject_sybil_inbounds_range_limit: u8, + /// Limit the number of requested blocks that will be processed as one batch pub requested_blocks_batch_limit: u32, } @@ -643,6 +647,10 @@ impl Connections { .reject_sybil_inbounds .to_owned() .unwrap_or_else(|| defaults.connections_reject_sybil_inbounds()), + reject_sybil_inbounds_range_limit: config + .reject_sybil_inbounds_range_limit + .to_owned() + .unwrap_or_else(|| defaults.connections_reject_sybil_inbounds_range_limit()), requested_blocks_batch_limit: config .requested_blocks_batch_limit .to_owned() @@ -669,6 +677,7 @@ impl Connections { bucketing_ice_period: Some(self.bucketing_ice_period), bucketing_update_period: Some(self.bucketing_update_period), reject_sybil_inbounds: Some(self.reject_sybil_inbounds), + reject_sybil_inbounds_range_limit: Some(self.reject_sybil_inbounds_range_limit), requested_blocks_batch_limit: Some(self.requested_blocks_batch_limit), } } @@ -1223,7 +1232,8 @@ mod tests { consensus_c: Some(51), bucketing_ice_period: Some(Duration::from_secs(13200)), bucketing_update_period: Some(200), - reject_sybil_inbounds: Some(false), + reject_sybil_inbounds: Some(true), + reject_sybil_inbounds_range_limit: Some(14), requested_blocks_batch_limit: Some(99), }; let config = Connections::from_partial(&partial_config, &Testnet); @@ -1244,7 +1254,8 @@ mod tests { assert_eq!(config.consensus_c, 51); assert_eq!(config.bucketing_ice_period, Duration::from_secs(13200)); assert_eq!(config.bucketing_update_period, 200); - assert_eq!(config.reject_sybil_inbounds, false); + assert_eq!(config.reject_sybil_inbounds, true); + assert_eq!(config.reject_sybil_inbounds_range_limit, 14); assert_eq!(config.requested_blocks_batch_limit, 99); } diff --git a/config/src/defaults.rs b/config/src/defaults.rs index 97025e4c8..2a0d76a57 100644 --- a/config/src/defaults.rs +++ b/config/src/defaults.rs @@ -92,12 +92,18 @@ pub trait Defaults { 300 } - /// Reject (tarpit) inbound connections coming from addresses in the same /14 IP range, so as - /// to prevent sybil peers from monopolizing our inbound capacity (128 by default). + /// Reject (tarpit) inbound connections coming from addresses that are alike, so as + /// to prevent sybil peers from monopolizing our inbound capacity. fn connections_reject_sybil_inbounds(&self) -> bool { true } + /// Limit to reject (tarpit) inbound connections. If the limit is set to 18, the addresses having + /// the same first 18 bits in the IP will collide, so as to prevent sybil peers from monopolizing our inbound capacity. + fn connections_reject_sybil_inbounds_range_limit(&self) -> u8 { + 18 + } + /// Limit the number of requested blocks that will be processed as one batch fn connections_requested_blocks_batch_limit(&self) -> u32 { // Default: 500 blocks @@ -443,6 +449,10 @@ impl Defaults for Development { fn connections_reject_sybil_inbounds(&self) -> bool { false } + + fn connections_reject_sybil_inbounds_range_limit(&self) -> u8 { + 0 + } } impl Defaults for Mainnet { diff --git a/node/src/actors/sessions_manager/actor.rs b/node/src/actors/sessions_manager/actor.rs index 869b4d747..d1e9f941d 100644 --- a/node/src/actors/sessions_manager/actor.rs +++ b/node/src/actors/sessions_manager/actor.rs @@ -33,6 +33,11 @@ impl Actor for SessionsManager { config.connections.outbound_limit, ); + // Set reject_sybil_outbounds range limit + act.sessions + .inbound_network_ranges + .set_range_limit(config.connections.reject_sybil_inbounds_range_limit); + // Initialized epoch from config let mut checkpoints_period = config.consensus_constants.checkpoints_period; let checkpoint_zero_timestamp = diff --git a/node/src/actors/sessions_manager/handlers.rs b/node/src/actors/sessions_manager/handlers.rs index 8a6349fb7..01f96b071 100644 --- a/node/src/actors/sessions_manager/handlers.rs +++ b/node/src/actors/sessions_manager/handlers.rs @@ -73,7 +73,7 @@ impl Handler for SessionsManager { // condition if config.connections.reject_sybil_inbounds && msg.session_type == SessionType::Inbound { if let Some(range) = self.sessions.is_similar_to_inbound_session(&remote_addr) { - log::trace!("Refusing to accept {} as inbound peer because there is already an inbound session with another peer in IP range {}", remote_addr, ip_range_string(range)); + log::trace!("Refusing to accept {} as inbound peer because there is already an inbound session with another peer in IP range {}", remote_addr, ip_range_string(range, config.connections.reject_sybil_inbounds_range_limit)); return; } }; diff --git a/p2p/src/peers/mod.rs b/p2p/src/peers/mod.rs index 6bb0826c1..682b8c9c9 100644 --- a/p2p/src/peers/mod.rs +++ b/p2p/src/peers/mod.rs @@ -495,19 +495,20 @@ pub fn get_range_address(socket_addr: &SocketAddr, range: u8) -> [u8; 4] { match socket_addr { SocketAddr::V4(addr) => { let ip = addr.ip().octets(); + if range == 0 { + [0, 0, 0, 0] + } else { + let range = 32u8.saturating_sub(range); - let ip_bytes = u32::from(ip[0]) << 24 - | (u32::from(ip[1])) << 16 - | (u32::from(ip[2])) << 8 - | (u32::from(ip[3])); + let ip_bytes = u32::from_be_bytes(ip); + let ip_bytes = ip_bytes >> range; + let ip_bytes = ip_bytes << range; - let ip_bytes = ip_bytes >> range; - let ip_bytes = ip_bytes << range; - - ip_bytes.to_be_bytes() + ip_bytes.to_be_bytes() + } } SocketAddr::V6(_addr) => { - // TODO: This address type is not currently in use + // FIXME(#740) [0, 0, 0, 0] } } @@ -570,3 +571,18 @@ pub fn calculate_index_for_new(sk: u64, src_group: &[u8], group: &[u8], host_id: (bucket * 64) + slot } + +#[test] +fn test_get_range_address() { + let address = "255.255.255.255:8002".parse().unwrap(); + + assert_eq!(get_range_address(&address, 32), [255, 255, 255, 255]); + assert_eq!(get_range_address(&address, 28), [255, 255, 255, 240]); + assert_eq!(get_range_address(&address, 24), [255, 255, 255, 0]); + assert_eq!(get_range_address(&address, 20), [255, 255, 240, 0]); + assert_eq!(get_range_address(&address, 16), [255, 255, 0, 0]); + assert_eq!(get_range_address(&address, 12), [255, 240, 0, 0]); + assert_eq!(get_range_address(&address, 8), [255, 0, 0, 0]); + assert_eq!(get_range_address(&address, 4), [240, 0, 0, 0]); + assert_eq!(get_range_address(&address, 0), [0, 0, 0, 0]); +} diff --git a/p2p/src/sessions/mod.rs b/p2p/src/sessions/mod.rs index c100126db..8249afcaa 100644 --- a/p2p/src/sessions/mod.rs +++ b/p2p/src/sessions/mod.rs @@ -397,12 +397,13 @@ pub struct GetConsolidatedPeersResult { #[derive(Default)] pub struct NetworkRangesCollection { inner: HashSet<[u8; 4]>, + range_limit: u8, } impl NetworkRangesCollection { /// Checks whether a range is present in the collection as derived from a socket address. pub fn contains_address(&self, address: &SocketAddr) -> Option<&[u8; 4]> { - let range_vec = get_range_address(address, 14); + let range_vec = get_range_address(address, self.range_limit); let mut range = [0, 0, 0, 0]; range[..4].copy_from_slice(&range_vec); @@ -416,7 +417,7 @@ impl NetworkRangesCollection { /// Insert a range into the collection as derived from a socket address. pub fn insert_address(&mut self, address: &SocketAddr) -> bool { - let range_vec = get_range_address(address, 14); + let range_vec = get_range_address(address, self.range_limit); let mut range = [0, 0, 0, 0]; range[..4].copy_from_slice(&range_vec); @@ -430,7 +431,7 @@ impl NetworkRangesCollection { /// Remove a range from the collection as derived from a socket address. pub fn remove_address(&mut self, address: &SocketAddr) -> bool { - let range_vec = get_range_address(address, 14); + let range_vec = get_range_address(address, self.range_limit); let mut range = [0, 0, 0, 0]; range[..4].copy_from_slice(&range_vec); @@ -441,14 +442,20 @@ impl NetworkRangesCollection { pub fn remove_range(&mut self, range: [u8; 4]) -> bool { self.inner.remove(&range) } + + /// Set range limit + pub fn set_range_limit(&mut self, range_limit: u8) { + self.range_limit = range_limit; + } } /// Compose a string for representing an IPV4 range -pub fn ip_range_string(range: &[u8]) -> String { +pub fn ip_range_string(range: &[u8], range_limit: u8) -> String { format!( - "{}0.0/16", + "{}/{}", range.iter().fold(String::new(), |acc, &octet| acc + octet.to_string().as_str() - + ".") + + "."), + range_limit, ) } diff --git a/p2p/tests/sessions.rs b/p2p/tests/sessions.rs index 0b93aeec0..ef3766e5f 100644 --- a/p2p/tests/sessions.rs +++ b/p2p/tests/sessions.rs @@ -554,11 +554,12 @@ fn p2p_sessions_register_more_than_limit() { } #[test] -/// Check that peer addresses are considered "similar" if the first three octets of the IP are +/// Check that peer addresses are considered "similar" if the first 14 bits of the IP are /// matching. fn p2p_peer_address_is_similar_to_inbound_session() { // Create sessions struct let mut sessions = Sessions::::default(); + sessions.inbound_network_ranges.set_range_limit(18); let inbound_address_1 = "127.0.0.1:8002".parse().unwrap(); let inbound_address_2 = "127.0.0.1:8003".parse().unwrap(); diff --git a/witnet.toml b/witnet.toml index 65f97c487..0cd4addec 100644 --- a/witnet.toml +++ b/witnet.toml @@ -114,8 +114,8 @@ known_peers = [ outbound_limit = 8 # Period for opening new peer connections while the current number of peers is lower than `outbound_limit`. bootstrap_peers_period_seconds = 1 -# Reject (tarpit) inbound connections coming from addresses in the same /14 IP range, so as to prevent sybil peers from -# monopolizing our inbound capacity (128 by default). +# Reject (tarpit) inbound connections coming from addresses that are alike (i.e. by default having the first 18 bits equal), +# so as to prevent sybil peers from monopolizing our inbound capacity. reject_sybil_inbounds = true [storage]