From 90cd7d6f8efce6d20ef081c6d4295432eafabe23 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Tue, 1 Aug 2023 20:43:14 +0800 Subject: [PATCH 01/35] feat: memory based connection limits --- Cargo.lock | 48 +- misc/connection-limits/CHANGELOG.md | 15 + misc/connection-limits/Cargo.toml | 5 +- .../src/connection_limits/memory.rs | 172 ++++++++ .../src/connection_limits/mod.rs | 57 +++ .../src/connection_limits/static.rs | 357 +++++++++++++++ misc/connection-limits/src/lib.rs | 412 ++---------------- 7 files changed, 670 insertions(+), 396 deletions(-) create mode 100644 misc/connection-limits/src/connection_limits/memory.rs create mode 100644 misc/connection-limits/src/connection_limits/mod.rs create mode 100644 misc/connection-limits/src/connection_limits/static.rs diff --git a/Cargo.lock b/Cargo.lock index 57bca1723d4..687a1d334a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1363,6 +1363,12 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "deranged" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" + [[package]] name = "derive_builder" version = "0.11.2" @@ -1592,9 +1598,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -2593,7 +2599,7 @@ dependencies = [ [[package]] name = "libp2p-connection-limits" -version = "0.2.1" +version = "0.2.2" dependencies = [ "async-std", "libp2p-core", @@ -2603,6 +2609,9 @@ dependencies = [ "libp2p-swarm", "libp2p-swarm-derive", "libp2p-swarm-test", + "log", + "memory-stats", + "parking_lot", "quickcheck-ext", "rand 0.8.5", "void", @@ -3427,9 +3436,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" @@ -3528,6 +3537,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memory-stats" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f79cf9964c5c9545493acda1263f1912f8d2c56c8a2ffee2606cb960acaacc" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "metrics-example" version = "0.1.0" @@ -4828,7 +4847,7 @@ dependencies = [ "bitflags 2.3.3", "errno", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys 0.4.5", "windows-sys", ] @@ -5067,18 +5086,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.179" +version = "1.0.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" +checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.179" +version = "1.0.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" +checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" dependencies = [ "proc-macro2", "quote", @@ -5563,10 +5582,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -5581,9 +5601,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] diff --git a/misc/connection-limits/CHANGELOG.md b/misc/connection-limits/CHANGELOG.md index a8bd071e6fe..5839ed76228 100644 --- a/misc/connection-limits/CHANGELOG.md +++ b/misc/connection-limits/CHANGELOG.md @@ -1,3 +1,18 @@ +## 0.2.2 +- Rename `ConnectionLimits` to `StaticConnectionLimits` + + See [PR XX] + +- Refactor `Behaviour` to take a generic `ConnectionLimitsChecker` + + See [PR XX] + +- Add `MemoryBasedConnectionLimits` + + See [PR XX] + +[PR XX]: https://github.com/libp2p/rust-libp2p/pull/XX + ## 0.2.1 - Do not count a connection as established when it is denied by another sibling `NetworkBehaviour`. diff --git a/misc/connection-limits/Cargo.toml b/misc/connection-limits/Cargo.toml index 25347e29b16..6d88021b406 100644 --- a/misc/connection-limits/Cargo.toml +++ b/misc/connection-limits/Cargo.toml @@ -3,16 +3,19 @@ name = "libp2p-connection-limits" edition = "2021" rust-version = { workspace = true } description = "Connection limits for libp2p." -version = "0.2.1" +version = "0.2.2" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] +memory-stats = "1" libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true, features = ["peerid"] } +log = "0.4" +parking_lot = "0.12" void = "1" [dev-dependencies] diff --git a/misc/connection-limits/src/connection_limits/memory.rs b/misc/connection-limits/src/connection_limits/memory.rs new file mode 100644 index 00000000000..e4e49b0a5c5 --- /dev/null +++ b/misc/connection-limits/src/connection_limits/memory.rs @@ -0,0 +1,172 @@ +// Copyright 2023 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::*; +use parking_lot::RwLock; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +/// The configurable memory usage based connection limits. +#[derive(Debug)] +pub struct MemoryUsageBasedConnectionLimits { + max_process_memory_usage_bytes: Option, + max_process_memory_usage_percentage: Option, + system_physical_memory_bytes: usize, + mem_tracker: ProcessMemoryUsageTracker, +} + +impl MemoryUsageBasedConnectionLimits { + pub fn new() -> Self { + MemoryUsageBasedConnectionLimits { + max_process_memory_usage_bytes: None, + max_process_memory_usage_percentage: None, + system_physical_memory_bytes: usize::MAX, + mem_tracker: ProcessMemoryUsageTracker::new(), + } + } + pub fn with_max_bytes(mut self, bytes: usize) -> Self { + self.max_process_memory_usage_bytes = Some(bytes); + self + } + + pub fn with_max_percentage(mut self, percentage: f64) -> Self { + self.max_process_memory_usage_percentage = Some(percentage); + self + } + + pub fn max_allowed_bytes(&self) -> Option { + self.max_process_memory_usage_bytes.min( + self.max_process_memory_usage_percentage + .map(|p| (self.system_physical_memory_bytes as f64 * p).round() as usize), + ) + } +} + +impl Default for MemoryUsageBasedConnectionLimits { + fn default() -> Self { + Self::new() + } +} + +impl ConnectionLimitsChecker for MemoryUsageBasedConnectionLimits { + fn check_limit(&self, kind: ConnectionKind, _current: usize) -> Result<(), ConnectionDenied> { + use ConnectionKind::*; + + match kind { + PendingIncoming | PendingOutgoing => { + if let Some(max_allowed_bytes) = self.max_allowed_bytes() { + self.mem_tracker.refresh_if_needed(); + let process_memory_bytes = self.mem_tracker.total_bytes(); + if process_memory_bytes > max_allowed_bytes { + return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { + process_memory_bytes, + max_allowed_bytes, + })); + } + } + } + EstablishedIncoming | EstablishedOutgoing | EstablishedPerPeer | EstablishedTotal => {} + }; + + Ok(()) + } +} + +/// A connection limit has been exceeded. +#[derive(Debug, Clone, Copy)] +pub struct MemoryUsageLimitExceeded { + pub process_memory_bytes: usize, + pub max_allowed_bytes: usize, +} + +impl std::error::Error for MemoryUsageLimitExceeded {} + +impl fmt::Display for MemoryUsageLimitExceeded { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "pending connections are dropped, process memory usage limit exceeded: process memory: {} bytes, max allowed: {} bytes", + self.process_memory_bytes, + self.max_allowed_bytes, + ) + } +} + +#[derive(Debug)] +struct ProcessMemoryUsageTracker(Arc>); + +impl ProcessMemoryUsageTracker { + fn new() -> Self { + Self(Arc::new(RwLock::new(ProcessMemoryUsageTrackerInner::new()))) + } + + fn refresh_if_needed(&self) { + let need_refresh = self.0.read().need_refresh(); + if need_refresh { + self.0.write().refresh(); + } + } + + fn total_bytes(&self) -> usize { + let guard = self.0.read(); + guard.physical_bytes + guard.virtual_bytes + } +} + +#[derive(Debug)] +struct ProcessMemoryUsageTrackerInner { + last_checked: Instant, + physical_bytes: usize, + virtual_bytes: usize, +} + +impl ProcessMemoryUsageTrackerInner { + fn new() -> Self { + let stats = memory_stats::memory_stats(); + if stats.is_none() { + log::warn!("Failed to retrive process memory stats"); + } + Self { + last_checked: Instant::now(), + physical_bytes: stats.map(|s| s.physical_mem).unwrap_or_default(), + virtual_bytes: stats + .map(|s: memory_stats::MemoryStats| s.virtual_mem) + .unwrap_or_default(), + } + } + + fn need_refresh(&self) -> bool { + const PROCESS_MEMORY_USAGE_CHECK_INTERVAL: Duration = Duration::from_millis(1000); + + self.last_checked + PROCESS_MEMORY_USAGE_CHECK_INTERVAL < Instant::now() + } + + fn refresh(&mut self) { + if let Some(stats) = memory_stats::memory_stats() { + self.physical_bytes = stats.physical_mem; + self.virtual_bytes = stats.virtual_mem; + } else { + log::warn!("Failed to retrive process memory stats"); + } + self.last_checked = Instant::now(); + } +} diff --git a/misc/connection-limits/src/connection_limits/mod.rs b/misc/connection-limits/src/connection_limits/mod.rs new file mode 100644 index 00000000000..99a8f5a8504 --- /dev/null +++ b/misc/connection-limits/src/connection_limits/mod.rs @@ -0,0 +1,57 @@ +// Copyright 2023 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +mod memory; +pub use memory::{MemoryUsageBasedConnectionLimits, MemoryUsageLimitExceeded}; + +mod r#static; +pub use r#static::{StaticConnectionLimits, StaticLimitExceeded}; + +use libp2p_swarm::ConnectionDenied; +use std::fmt; + +pub trait ConnectionLimitsChecker { + fn check_limit(&self, kind: ConnectionKind, current: usize) -> Result<(), ConnectionDenied>; +} + +#[derive(Debug, Clone, Copy)] +pub enum ConnectionKind { + PendingIncoming, + PendingOutgoing, + EstablishedIncoming, + EstablishedOutgoing, + EstablishedPerPeer, + EstablishedTotal, +} + +impl fmt::Display for ConnectionKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use ConnectionKind::*; + + match self { + PendingIncoming => write!(f, "pending incoming connections"), + PendingOutgoing => write!(f, "pending outgoing connections"), + EstablishedIncoming => write!(f, "established incoming connections"), + EstablishedOutgoing => write!(f, "established outgoing connections"), + EstablishedPerPeer => write!(f, "established connections per peer"), + EstablishedTotal => write!(f, "established connections"), + } + } +} diff --git a/misc/connection-limits/src/connection_limits/static.rs b/misc/connection-limits/src/connection_limits/static.rs new file mode 100644 index 00000000000..63963baceb2 --- /dev/null +++ b/misc/connection-limits/src/connection_limits/static.rs @@ -0,0 +1,357 @@ +// Copyright 2023 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::*; + +/// The configurable static connection limits. +#[derive(Debug, Clone, Default)] +pub struct StaticConnectionLimits { + pub(crate) max_pending_incoming: Option, + pub(crate) max_pending_outgoing: Option, + pub(crate) max_established_incoming: Option, + pub(crate) max_established_outgoing: Option, + pub(crate) max_established_per_peer: Option, + pub(crate) max_established_total: Option, +} + +impl StaticConnectionLimits { + /// Configures the maximum number of concurrently incoming connections being established. + pub fn with_max_pending_incoming(mut self, limit: Option) -> Self { + self.max_pending_incoming = limit; + self + } + + /// Configures the maximum number of concurrently outgoing connections being established. + pub fn with_max_pending_outgoing(mut self, limit: Option) -> Self { + self.max_pending_outgoing = limit; + self + } + + /// Configures the maximum number of concurrent established inbound connections. + pub fn with_max_established_incoming(mut self, limit: Option) -> Self { + self.max_established_incoming = limit; + self + } + + /// Configures the maximum number of concurrent established outbound connections. + pub fn with_max_established_outgoing(mut self, limit: Option) -> Self { + self.max_established_outgoing = limit; + self + } + + /// Configures the maximum number of concurrent established connections (both + /// inbound and outbound). + /// + /// Note: This should be used in conjunction with + /// [`ConnectionLimits::with_max_established_incoming`] to prevent possible + /// eclipse attacks (all connections being inbound). + pub fn with_max_established(mut self, limit: Option) -> Self { + self.max_established_total = limit; + self + } + + /// Configures the maximum number of concurrent established connections per peer, + /// regardless of direction (incoming or outgoing). + pub fn with_max_established_per_peer(mut self, limit: Option) -> Self { + self.max_established_per_peer = limit; + self + } +} + +impl ConnectionLimitsChecker for StaticConnectionLimits { + fn check_limit(&self, kind: ConnectionKind, current: usize) -> Result<(), ConnectionDenied> { + use ConnectionKind::*; + + let limit = match kind { + PendingIncoming => self.max_pending_incoming.unwrap_or(u32::MAX), + PendingOutgoing => self.max_pending_outgoing.unwrap_or(u32::MAX), + EstablishedIncoming => self.max_established_incoming.unwrap_or(u32::MAX), + EstablishedOutgoing => self.max_established_outgoing.unwrap_or(u32::MAX), + EstablishedPerPeer => self.max_established_per_peer.unwrap_or(u32::MAX), + EstablishedTotal => self.max_established_total.unwrap_or(u32::MAX), + }; + + if current as u32 >= limit { + return Err(ConnectionDenied::new(StaticLimitExceeded { limit, kind })); + } + + Ok(()) + } +} + +/// A connection limit has been exceeded. +#[derive(Debug, Clone, Copy)] +pub struct StaticLimitExceeded { + limit: u32, + kind: ConnectionKind, +} + +impl StaticLimitExceeded { + pub fn limit(&self) -> u32 { + self.limit + } +} + +impl std::error::Error for StaticLimitExceeded {} + +impl fmt::Display for StaticLimitExceeded { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "connection limit exceeded: at most {} {} are allowed", + self.limit, self.kind + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::*; + use libp2p_swarm::{ + behaviour::toggle::Toggle, dial_opts::DialOpts, DialError, ListenError, Swarm, SwarmEvent, + }; + use libp2p_swarm_test::SwarmExt; + use quickcheck::*; + + #[test] + fn max_outgoing() { + use rand::Rng; + + let outgoing_limit = rand::thread_rng().gen_range(1..10); + + let mut network = Swarm::new_ephemeral(|_| { + Behaviour::new( + StaticConnectionLimits::default().with_max_pending_outgoing(Some(outgoing_limit)), + ) + }); + + let addr: Multiaddr = "/memory/1234".parse().unwrap(); + let target = PeerId::random(); + + for _ in 0..outgoing_limit { + network + .dial( + DialOpts::peer_id(target) + .addresses(vec![addr.clone()]) + .build(), + ) + .expect("Unexpected connection limit."); + } + + match network + .dial(DialOpts::peer_id(target).addresses(vec![addr]).build()) + .expect_err("Unexpected dialing success.") + { + DialError::Denied { cause } => { + let exceeded = cause + .downcast::() + .expect("connection denied because of limit"); + + assert_eq!(exceeded.limit(), outgoing_limit); + } + e => panic!("Unexpected error: {e:?}"), + } + + let info = network.network_info(); + assert_eq!(info.num_peers(), 0); + assert_eq!( + info.connection_counters().num_pending_outgoing(), + outgoing_limit + ); + } + + #[test] + fn max_established_incoming() { + fn prop(Limit(limit): Limit) { + let mut swarm1 = Swarm::new_ephemeral(|_| { + Behaviour::new( + StaticConnectionLimits::default().with_max_established_incoming(Some(limit)), + ) + }); + let mut swarm2 = Swarm::new_ephemeral(|_| { + Behaviour::new( + StaticConnectionLimits::default().with_max_established_incoming(Some(limit)), + ) + }); + + async_std::task::block_on(async { + let (listen_addr, _) = swarm1.listen().await; + + for _ in 0..limit { + swarm2.connect(&mut swarm1).await; + } + + swarm2.dial(listen_addr).unwrap(); + + async_std::task::spawn(swarm2.loop_on_next()); + + let cause = swarm1 + .wait(|event| match event { + SwarmEvent::IncomingConnectionError { + error: ListenError::Denied { cause }, + .. + } => Some(cause), + _ => None, + }) + .await; + + assert_eq!( + cause.downcast::().unwrap().limit, + limit + ); + }); + } + + #[derive(Debug, Clone)] + struct Limit(u32); + + impl Arbitrary for Limit { + fn arbitrary(g: &mut Gen) -> Self { + Self(g.gen_range(1..10)) + } + } + + quickcheck(prop as fn(_)); + } + + /// Another sibling [`NetworkBehaviour`] implementation might deny established connections in + /// [`handle_established_outbound_connection`] or [`handle_established_inbound_connection`]. + /// [`Behaviour`] must not increase the established counters in + /// [`handle_established_outbound_connection`] or [`handle_established_inbound_connection`], but + /// in [`SwarmEvent::ConnectionEstablished`] as the connection might still be denied by a + /// sibling [`NetworkBehaviour`] in the former case. Only in the latter case + /// ([`SwarmEvent::ConnectionEstablished`]) can the connection be seen as established. + #[test] + fn support_other_behaviour_denying_connection() { + let mut swarm1 = Swarm::new_ephemeral(|_| { + Behaviour::new_with_connection_denier(StaticConnectionLimits::default()) + }); + let mut swarm2 = + Swarm::new_ephemeral(|_| Behaviour::new(StaticConnectionLimits::default())); + + async_std::task::block_on(async { + // Have swarm2 dial swarm1. + let (listen_addr, _) = swarm1.listen().await; + swarm2.dial(listen_addr).unwrap(); + async_std::task::spawn(swarm2.loop_on_next()); + + // Wait for the ConnectionDenier of swarm1 to deny the established connection. + let cause = swarm1 + .wait(|event| match event { + SwarmEvent::IncomingConnectionError { + error: ListenError::Denied { cause }, + .. + } => Some(cause), + _ => None, + }) + .await; + + cause.downcast::().unwrap(); + + assert_eq!( + 0, + swarm1 + .behaviour_mut() + .limits + .established_inbound_connections + .len(), + "swarm1 connection limit behaviour to not count denied established connection as established connection" + ) + }); + } + + #[derive(libp2p_swarm_derive::NetworkBehaviour)] + #[behaviour(prelude = "libp2p_swarm::derive_prelude")] + struct Behaviour { + limits: crate::Behaviour, + keep_alive: libp2p_swarm::keep_alive::Behaviour, + connection_denier: Toggle, + } + + impl Behaviour { + fn new(limits: StaticConnectionLimits) -> Self { + Self { + limits: crate::Behaviour::new(limits), + keep_alive: libp2p_swarm::keep_alive::Behaviour, + connection_denier: None.into(), + } + } + fn new_with_connection_denier(limits: StaticConnectionLimits) -> Self { + Self { + limits: crate::Behaviour::new(limits), + keep_alive: libp2p_swarm::keep_alive::Behaviour, + connection_denier: Some(ConnectionDenier {}).into(), + } + } + } + + struct ConnectionDenier {} + + impl NetworkBehaviour for ConnectionDenier { + type ConnectionHandler = dummy::ConnectionHandler; + type ToSwarm = Void; + + fn handle_established_inbound_connection( + &mut self, + _connection_id: ConnectionId, + _peer: PeerId, + _local_addr: &Multiaddr, + _remote_addr: &Multiaddr, + ) -> Result, ConnectionDenied> { + Err(ConnectionDenied::new(std::io::Error::new( + std::io::ErrorKind::Other, + "ConnectionDenier", + ))) + } + + fn handle_established_outbound_connection( + &mut self, + _connection_id: ConnectionId, + _peer: PeerId, + _addr: &Multiaddr, + _role_override: Endpoint, + ) -> Result, ConnectionDenied> { + Err(ConnectionDenied::new(std::io::Error::new( + std::io::ErrorKind::Other, + "ConnectionDenier", + ))) + } + + fn on_swarm_event(&mut self, _event: FromSwarm) {} + + fn on_connection_handler_event( + &mut self, + _peer_id: PeerId, + _connection_id: ConnectionId, + event: THandlerOutEvent, + ) { + void::unreachable(event) + } + + fn poll( + &mut self, + _cx: &mut Context<'_>, + _params: &mut impl PollParameters, + ) -> Poll>> { + Poll::Pending + } + } +} diff --git a/misc/connection-limits/src/lib.rs b/misc/connection-limits/src/lib.rs index e4723dd95c6..1d573899a46 100644 --- a/misc/connection-limits/src/lib.rs +++ b/misc/connection-limits/src/lib.rs @@ -18,6 +18,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +mod connection_limits; +pub use connection_limits::*; + use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ @@ -26,11 +29,14 @@ use libp2p_swarm::{ PollParameters, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; use std::collections::{HashMap, HashSet}; -use std::fmt; use std::task::{Context, Poll}; use void::Void; -/// A [`NetworkBehaviour`] that enforces a set of [`ConnectionLimits`]. +/// Alias type for maintaining backward compatibility. +#[deprecated(note = "Renamed to `StaticConnectionLimits`.")] +pub type ConnectionLimits = StaticConnectionLimits; + +/// A [`NetworkBehaviour`] that enforces a set of [`StaticConnectionLimits`]. /// /// For these limits to take effect, this needs to be composed into the behaviour tree of your application. /// @@ -55,11 +61,12 @@ use void::Void; /// struct MyBehaviour { /// identify: identify::Behaviour, /// ping: ping::Behaviour, -/// limits: connection_limits::Behaviour +/// limits: connection_limits::Behaviour /// } /// ``` -pub struct Behaviour { - limits: ConnectionLimits, +// Default type for maintaining backward compatibility. +pub struct Behaviour { + limits: C, pending_inbound_connections: HashSet, pending_outbound_connections: HashSet, @@ -68,8 +75,8 @@ pub struct Behaviour { established_per_peer: HashMap>, } -impl Behaviour { - pub fn new(limits: ConnectionLimits) -> Self { +impl Behaviour { + pub fn new(limits: C) -> Self { Self { limits, pending_inbound_connections: Default::default(), @@ -79,128 +86,9 @@ impl Behaviour { established_per_peer: Default::default(), } } - - fn check_limit( - &mut self, - limit: Option, - current: usize, - kind: Kind, - ) -> Result<(), ConnectionDenied> { - let limit = limit.unwrap_or(u32::MAX); - let current = current as u32; - - if current >= limit { - return Err(ConnectionDenied::new(Exceeded { limit, kind })); - } - - Ok(()) - } -} - -/// A connection limit has been exceeded. -#[derive(Debug, Clone, Copy)] -pub struct Exceeded { - limit: u32, - kind: Kind, -} - -impl Exceeded { - pub fn limit(&self) -> u32 { - self.limit - } -} - -impl fmt::Display for Exceeded { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "connection limit exceeded: at most {} {} are allowed", - self.limit, self.kind - ) - } -} - -#[derive(Debug, Clone, Copy)] -enum Kind { - PendingIncoming, - PendingOutgoing, - EstablishedIncoming, - EstablishedOutgoing, - EstablishedPerPeer, - EstablishedTotal, -} - -impl fmt::Display for Kind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Kind::PendingIncoming => write!(f, "pending incoming connections"), - Kind::PendingOutgoing => write!(f, "pending outgoing connections"), - Kind::EstablishedIncoming => write!(f, "established incoming connections"), - Kind::EstablishedOutgoing => write!(f, "established outgoing connections"), - Kind::EstablishedPerPeer => write!(f, "established connections per peer"), - Kind::EstablishedTotal => write!(f, "established connections"), - } - } } -impl std::error::Error for Exceeded {} - -/// The configurable connection limits. -#[derive(Debug, Clone, Default)] -pub struct ConnectionLimits { - max_pending_incoming: Option, - max_pending_outgoing: Option, - max_established_incoming: Option, - max_established_outgoing: Option, - max_established_per_peer: Option, - max_established_total: Option, -} - -impl ConnectionLimits { - /// Configures the maximum number of concurrently incoming connections being established. - pub fn with_max_pending_incoming(mut self, limit: Option) -> Self { - self.max_pending_incoming = limit; - self - } - - /// Configures the maximum number of concurrently outgoing connections being established. - pub fn with_max_pending_outgoing(mut self, limit: Option) -> Self { - self.max_pending_outgoing = limit; - self - } - - /// Configures the maximum number of concurrent established inbound connections. - pub fn with_max_established_incoming(mut self, limit: Option) -> Self { - self.max_established_incoming = limit; - self - } - - /// Configures the maximum number of concurrent established outbound connections. - pub fn with_max_established_outgoing(mut self, limit: Option) -> Self { - self.max_established_outgoing = limit; - self - } - - /// Configures the maximum number of concurrent established connections (both - /// inbound and outbound). - /// - /// Note: This should be used in conjunction with - /// [`ConnectionLimits::with_max_established_incoming`] to prevent possible - /// eclipse attacks (all connections being inbound). - pub fn with_max_established(mut self, limit: Option) -> Self { - self.max_established_total = limit; - self - } - - /// Configures the maximum number of concurrent established connections per peer, - /// regardless of direction (incoming or outgoing). - pub fn with_max_established_per_peer(mut self, limit: Option) -> Self { - self.max_established_per_peer = limit; - self - } -} - -impl NetworkBehaviour for Behaviour { +impl NetworkBehaviour for Behaviour { type ConnectionHandler = dummy::ConnectionHandler; type ToSwarm = Void; @@ -210,10 +98,9 @@ impl NetworkBehaviour for Behaviour { _: &Multiaddr, _: &Multiaddr, ) -> Result<(), ConnectionDenied> { - self.check_limit( - self.limits.max_pending_incoming, + self.limits.check_limit( + ConnectionKind::PendingIncoming, self.pending_inbound_connections.len(), - Kind::PendingIncoming, )?; self.pending_inbound_connections.insert(connection_id); @@ -230,24 +117,21 @@ impl NetworkBehaviour for Behaviour { ) -> Result, ConnectionDenied> { self.pending_inbound_connections.remove(&connection_id); - self.check_limit( - self.limits.max_established_incoming, + self.limits.check_limit( + ConnectionKind::EstablishedIncoming, self.established_inbound_connections.len(), - Kind::EstablishedIncoming, )?; - self.check_limit( - self.limits.max_established_per_peer, + self.limits.check_limit( + ConnectionKind::EstablishedPerPeer, self.established_per_peer .get(&peer) .map(|connections| connections.len()) .unwrap_or(0), - Kind::EstablishedPerPeer, )?; - self.check_limit( - self.limits.max_established_total, + self.limits.check_limit( + ConnectionKind::EstablishedTotal, self.established_inbound_connections.len() + self.established_outbound_connections.len(), - Kind::EstablishedTotal, )?; Ok(dummy::ConnectionHandler) @@ -260,10 +144,9 @@ impl NetworkBehaviour for Behaviour { _: &[Multiaddr], _: Endpoint, ) -> Result, ConnectionDenied> { - self.check_limit( - self.limits.max_pending_outgoing, + self.limits.check_limit( + ConnectionKind::PendingOutgoing, self.pending_outbound_connections.len(), - Kind::PendingOutgoing, )?; self.pending_outbound_connections.insert(connection_id); @@ -280,24 +163,21 @@ impl NetworkBehaviour for Behaviour { ) -> Result, ConnectionDenied> { self.pending_outbound_connections.remove(&connection_id); - self.check_limit( - self.limits.max_established_outgoing, + self.limits.check_limit( + ConnectionKind::EstablishedOutgoing, self.established_outbound_connections.len(), - Kind::EstablishedOutgoing, )?; - self.check_limit( - self.limits.max_established_per_peer, + self.limits.check_limit( + ConnectionKind::EstablishedPerPeer, self.established_per_peer .get(&peer) .map(|connections| connections.len()) .unwrap_or(0), - Kind::EstablishedPerPeer, )?; - self.check_limit( - self.limits.max_established_total, + self.limits.check_limit( + ConnectionKind::EstablishedTotal, self.established_inbound_connections.len() + self.established_outbound_connections.len(), - Kind::EstablishedTotal, )?; Ok(dummy::ConnectionHandler) @@ -372,233 +252,3 @@ impl NetworkBehaviour for Behaviour { Poll::Pending } } - -#[cfg(test)] -mod tests { - use super::*; - use libp2p_swarm::{ - behaviour::toggle::Toggle, dial_opts::DialOpts, DialError, ListenError, Swarm, SwarmEvent, - }; - use libp2p_swarm_test::SwarmExt; - use quickcheck::*; - - #[test] - fn max_outgoing() { - use rand::Rng; - - let outgoing_limit = rand::thread_rng().gen_range(1..10); - - let mut network = Swarm::new_ephemeral(|_| { - Behaviour::new( - ConnectionLimits::default().with_max_pending_outgoing(Some(outgoing_limit)), - ) - }); - - let addr: Multiaddr = "/memory/1234".parse().unwrap(); - let target = PeerId::random(); - - for _ in 0..outgoing_limit { - network - .dial( - DialOpts::peer_id(target) - .addresses(vec![addr.clone()]) - .build(), - ) - .expect("Unexpected connection limit."); - } - - match network - .dial(DialOpts::peer_id(target).addresses(vec![addr]).build()) - .expect_err("Unexpected dialing success.") - { - DialError::Denied { cause } => { - let exceeded = cause - .downcast::() - .expect("connection denied because of limit"); - - assert_eq!(exceeded.limit(), outgoing_limit); - } - e => panic!("Unexpected error: {e:?}"), - } - - let info = network.network_info(); - assert_eq!(info.num_peers(), 0); - assert_eq!( - info.connection_counters().num_pending_outgoing(), - outgoing_limit - ); - } - - #[test] - fn max_established_incoming() { - fn prop(Limit(limit): Limit) { - let mut swarm1 = Swarm::new_ephemeral(|_| { - Behaviour::new( - ConnectionLimits::default().with_max_established_incoming(Some(limit)), - ) - }); - let mut swarm2 = Swarm::new_ephemeral(|_| { - Behaviour::new( - ConnectionLimits::default().with_max_established_incoming(Some(limit)), - ) - }); - - async_std::task::block_on(async { - let (listen_addr, _) = swarm1.listen().await; - - for _ in 0..limit { - swarm2.connect(&mut swarm1).await; - } - - swarm2.dial(listen_addr).unwrap(); - - async_std::task::spawn(swarm2.loop_on_next()); - - let cause = swarm1 - .wait(|event| match event { - SwarmEvent::IncomingConnectionError { - error: ListenError::Denied { cause }, - .. - } => Some(cause), - _ => None, - }) - .await; - - assert_eq!(cause.downcast::().unwrap().limit, limit); - }); - } - - #[derive(Debug, Clone)] - struct Limit(u32); - - impl Arbitrary for Limit { - fn arbitrary(g: &mut Gen) -> Self { - Self(g.gen_range(1..10)) - } - } - - quickcheck(prop as fn(_)); - } - - /// Another sibling [`NetworkBehaviour`] implementation might deny established connections in - /// [`handle_established_outbound_connection`] or [`handle_established_inbound_connection`]. - /// [`Behaviour`] must not increase the established counters in - /// [`handle_established_outbound_connection`] or [`handle_established_inbound_connection`], but - /// in [`SwarmEvent::ConnectionEstablished`] as the connection might still be denied by a - /// sibling [`NetworkBehaviour`] in the former case. Only in the latter case - /// ([`SwarmEvent::ConnectionEstablished`]) can the connection be seen as established. - #[test] - fn support_other_behaviour_denying_connection() { - let mut swarm1 = Swarm::new_ephemeral(|_| { - Behaviour::new_with_connection_denier(ConnectionLimits::default()) - }); - let mut swarm2 = Swarm::new_ephemeral(|_| Behaviour::new(ConnectionLimits::default())); - - async_std::task::block_on(async { - // Have swarm2 dial swarm1. - let (listen_addr, _) = swarm1.listen().await; - swarm2.dial(listen_addr).unwrap(); - async_std::task::spawn(swarm2.loop_on_next()); - - // Wait for the ConnectionDenier of swarm1 to deny the established connection. - let cause = swarm1 - .wait(|event| match event { - SwarmEvent::IncomingConnectionError { - error: ListenError::Denied { cause }, - .. - } => Some(cause), - _ => None, - }) - .await; - - cause.downcast::().unwrap(); - - assert_eq!( - 0, - swarm1 - .behaviour_mut() - .limits - .established_inbound_connections - .len(), - "swarm1 connection limit behaviour to not count denied established connection as established connection" - ) - }); - } - - #[derive(libp2p_swarm_derive::NetworkBehaviour)] - #[behaviour(prelude = "libp2p_swarm::derive_prelude")] - struct Behaviour { - limits: super::Behaviour, - keep_alive: libp2p_swarm::keep_alive::Behaviour, - connection_denier: Toggle, - } - - impl Behaviour { - fn new(limits: ConnectionLimits) -> Self { - Self { - limits: super::Behaviour::new(limits), - keep_alive: libp2p_swarm::keep_alive::Behaviour, - connection_denier: None.into(), - } - } - fn new_with_connection_denier(limits: ConnectionLimits) -> Self { - Self { - limits: super::Behaviour::new(limits), - keep_alive: libp2p_swarm::keep_alive::Behaviour, - connection_denier: Some(ConnectionDenier {}).into(), - } - } - } - - struct ConnectionDenier {} - - impl NetworkBehaviour for ConnectionDenier { - type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = Void; - - fn handle_established_inbound_connection( - &mut self, - _connection_id: ConnectionId, - _peer: PeerId, - _local_addr: &Multiaddr, - _remote_addr: &Multiaddr, - ) -> Result, ConnectionDenied> { - Err(ConnectionDenied::new(std::io::Error::new( - std::io::ErrorKind::Other, - "ConnectionDenier", - ))) - } - - fn handle_established_outbound_connection( - &mut self, - _connection_id: ConnectionId, - _peer: PeerId, - _addr: &Multiaddr, - _role_override: Endpoint, - ) -> Result, ConnectionDenied> { - Err(ConnectionDenied::new(std::io::Error::new( - std::io::ErrorKind::Other, - "ConnectionDenier", - ))) - } - - fn on_swarm_event(&mut self, _event: FromSwarm) {} - - fn on_connection_handler_event( - &mut self, - _peer_id: PeerId, - _connection_id: ConnectionId, - event: THandlerOutEvent, - ) { - void::unreachable(event) - } - - fn poll( - &mut self, - _cx: &mut Context<'_>, - _params: &mut impl PollParameters, - ) -> Poll>> { - Poll::Pending - } - } -} From 501450093b7af779dead3a5bb8c6023ee40f4916 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Tue, 1 Aug 2023 21:06:16 +0800 Subject: [PATCH 02/35] sets system ram --- Cargo.lock | 25 +++++++++++++++++++ misc/connection-limits/Cargo.toml | 1 + .../src/connection_limits/memory.rs | 13 ++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 687a1d334a3..b77ca5e2ab8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2614,6 +2614,7 @@ dependencies = [ "parking_lot", "quickcheck-ext", "rand 0.8.5", + "sysinfo", "void", ] @@ -3777,6 +3778,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -5469,6 +5479,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "sysinfo" +version = "0.29.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165d6d8539689e3d3bc8b98ac59541e1f21c7de7c85d60dc80e43ae0ed2113db" +dependencies = [ + "cfg-if 1.0.0", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "system-configuration" version = "0.5.1" diff --git a/misc/connection-limits/Cargo.toml b/misc/connection-limits/Cargo.toml index 6d88021b406..9a9819545db 100644 --- a/misc/connection-limits/Cargo.toml +++ b/misc/connection-limits/Cargo.toml @@ -16,6 +16,7 @@ libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true, features = ["peerid"] } log = "0.4" parking_lot = "0.12" +sysinfo = "0.29" void = "1" [dev-dependencies] diff --git a/misc/connection-limits/src/connection_limits/memory.rs b/misc/connection-limits/src/connection_limits/memory.rs index e4e49b0a5c5..959c958f202 100644 --- a/misc/connection-limits/src/connection_limits/memory.rs +++ b/misc/connection-limits/src/connection_limits/memory.rs @@ -36,24 +36,33 @@ pub struct MemoryUsageBasedConnectionLimits { impl MemoryUsageBasedConnectionLimits { pub fn new() -> Self { + use sysinfo::{RefreshKind, SystemExt}; + + let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); + MemoryUsageBasedConnectionLimits { max_process_memory_usage_bytes: None, max_process_memory_usage_percentage: None, - system_physical_memory_bytes: usize::MAX, + system_physical_memory_bytes: system_info.total_memory() as usize, mem_tracker: ProcessMemoryUsageTracker::new(), } } + + /// Sets the process memory usage threshold in bytes, + /// all pending connections will be dropped when the threshold is exeeded pub fn with_max_bytes(mut self, bytes: usize) -> Self { self.max_process_memory_usage_bytes = Some(bytes); self } + /// Sets the process memory usage threshold in the percentage of the total physical memory, + /// all pending connections will be dropped when the threshold is exeeded pub fn with_max_percentage(mut self, percentage: f64) -> Self { self.max_process_memory_usage_percentage = Some(percentage); self } - pub fn max_allowed_bytes(&self) -> Option { + fn max_allowed_bytes(&self) -> Option { self.max_process_memory_usage_bytes.min( self.max_process_memory_usage_percentage .map(|p| (self.system_physical_memory_bytes as f64 * p).round() as usize), From e5519d7030b51a4d1ad3953e16fc606a45a67620 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Tue, 1 Aug 2023 21:20:02 +0800 Subject: [PATCH 03/35] code golf --- .../src/connection_limits/static.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/misc/connection-limits/src/connection_limits/static.rs b/misc/connection-limits/src/connection_limits/static.rs index 63963baceb2..c6dd9ed7da0 100644 --- a/misc/connection-limits/src/connection_limits/static.rs +++ b/misc/connection-limits/src/connection_limits/static.rs @@ -80,13 +80,14 @@ impl ConnectionLimitsChecker for StaticConnectionLimits { use ConnectionKind::*; let limit = match kind { - PendingIncoming => self.max_pending_incoming.unwrap_or(u32::MAX), - PendingOutgoing => self.max_pending_outgoing.unwrap_or(u32::MAX), - EstablishedIncoming => self.max_established_incoming.unwrap_or(u32::MAX), - EstablishedOutgoing => self.max_established_outgoing.unwrap_or(u32::MAX), - EstablishedPerPeer => self.max_established_per_peer.unwrap_or(u32::MAX), - EstablishedTotal => self.max_established_total.unwrap_or(u32::MAX), - }; + PendingIncoming => self.max_pending_incoming, + PendingOutgoing => self.max_pending_outgoing, + EstablishedIncoming => self.max_established_incoming, + EstablishedOutgoing => self.max_established_outgoing, + EstablishedPerPeer => self.max_established_per_peer, + EstablishedTotal => self.max_established_total, + } + .unwrap_or(u32::MAX); if current as u32 >= limit { return Err(ConnectionDenied::new(StaticLimitExceeded { limit, kind })); From 024da42eec4d7e8ddbf5825792158f1535ebe60b Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 1 Aug 2023 15:53:38 +0200 Subject: [PATCH 04/35] fix(examples/file-sharing): set Kademlia `Mode::Server` Explicitly set `libp2p-kad` `Kademlia::set_mode` to `Mode::Server` to reduce complexity of example, i.e. not having to explicitly set external addresses. Pull-Request: #4197. --- examples/file-sharing/src/network.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/file-sharing/src/network.rs b/examples/file-sharing/src/network.rs index db781a27a93..7ddd0afb0cc 100644 --- a/examples/file-sharing/src/network.rs +++ b/examples/file-sharing/src/network.rs @@ -7,7 +7,8 @@ use libp2p::{ core::Multiaddr, identity, kad::{ - record::store::MemoryStore, GetProvidersOk, Kademlia, KademliaEvent, QueryId, QueryResult, + self, record::store::MemoryStore, GetProvidersOk, Kademlia, KademliaEvent, QueryId, + QueryResult, }, multiaddr::Protocol, noise, @@ -52,7 +53,7 @@ pub(crate) async fn new( // Build the Swarm, connecting the lower layer transport logic with the // higher layer network behaviour logic. - let swarm = SwarmBuilder::with_async_std_executor( + let mut swarm = SwarmBuilder::with_async_std_executor( transport, ComposedBehaviour { kademlia: Kademlia::new(peer_id, MemoryStore::new(peer_id)), @@ -68,6 +69,11 @@ pub(crate) async fn new( ) .build(); + swarm + .behaviour_mut() + .kademlia + .set_mode(Some(kad::Mode::Server)); + let (command_sender, command_receiver) = mpsc::channel(0); let (event_sender, event_receiver) = mpsc::channel(0); From 065efb1b7cd1689aea03d30d0ff827e20bda13f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A9n=C3=A9chal?= <44696378+thomas-senechal@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:43:37 +0200 Subject: [PATCH 05/35] docs: fix last OutboundQueryCompleted in kad doc `OutboundQueryCompleted` hasn't been fully replaced by `OutboundQueryProgressed` in kad doc. Pull-Request: #4257. --- protocols/kad/CHANGELOG.md | 5 +++++ protocols/kad/src/behaviour.rs | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index c729e5ea0f9..e3da07294fb 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -12,6 +12,11 @@ [PR 4224]: https://github.com/libp2p/rust-libp2p/pull/4224 +- Rename missed `KademliaEvent::OutboundQueryCompleted` to `KademliaEvent::OutboundQueryProgressed` in documentation. + See [PR 4257]. + +[PR 4257]: https://github.com/libp2p/rust-libp2p/pull/4257 + ## 0.44.2 - Allow to explicitly set `Mode::{Client,Server}`. diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index c0a11a100ec..fa582496a9d 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -665,7 +665,7 @@ where /// Initiates an iterative query for the closest peers to the given key. /// /// The result of the query is delivered in a - /// [`KademliaEvent::OutboundQueryCompleted{QueryResult::GetClosestPeers}`]. + /// [`KademliaEvent::OutboundQueryProgressed{QueryResult::GetClosestPeers}`]. pub fn get_closest_peers(&mut self, key: K) -> QueryId where K: Into> + Into> + Clone, @@ -692,7 +692,7 @@ where /// Performs a lookup for a record in the DHT. /// /// The result of this operation is delivered in a - /// [`KademliaEvent::OutboundQueryCompleted{QueryResult::GetRecord}`]. + /// [`KademliaEvent::OutboundQueryProgressed{QueryResult::GetRecord}`]. pub fn get_record(&mut self, key: record_priv::Key) -> QueryId { let record = if let Some(record) = self.store.get(&key) { if record.is_expired(Instant::now()) { @@ -753,7 +753,7 @@ where /// Returns `Ok` if a record has been stored locally, providing the /// `QueryId` of the initial query that replicates the record in the DHT. /// The result of the query is eventually reported as a - /// [`KademliaEvent::OutboundQueryCompleted{QueryResult::PutRecord}`]. + /// [`KademliaEvent::OutboundQueryProgressed{QueryResult::PutRecord}`]. /// /// The record is always stored locally with the given expiration. If the record's /// expiration is `None`, the common case, it does not expire in local storage @@ -869,7 +869,7 @@ where /// /// Returns `Ok` if bootstrapping has been initiated with a self-lookup, providing the /// `QueryId` for the entire bootstrapping process. The progress of bootstrapping is - /// reported via [`KademliaEvent::OutboundQueryCompleted{QueryResult::Bootstrap}`] events, + /// reported via [`KademliaEvent::OutboundQueryProgressed{QueryResult::Bootstrap}`] events, /// with one such event per bootstrapping query. /// /// Returns `Err` if bootstrapping is impossible due an empty routing table. @@ -913,7 +913,7 @@ where /// of the libp2p Kademlia provider API. /// /// The results of the (repeated) provider announcements sent by this node are - /// reported via [`KademliaEvent::OutboundQueryCompleted{QueryResult::StartProviding}`]. + /// reported via [`KademliaEvent::OutboundQueryProgressed{QueryResult::StartProviding}`]. pub fn start_providing(&mut self, key: record_priv::Key) -> Result { // Note: We store our own provider records locally without local addresses // to avoid redundant storage and outdated addresses. Instead these are @@ -950,7 +950,7 @@ where /// Performs a lookup for providers of a value to the given key. /// /// The result of this operation is delivered in a - /// reported via [`KademliaEvent::OutboundQueryCompleted{QueryResult::GetProviders}`]. + /// reported via [`KademliaEvent::OutboundQueryProgressed{QueryResult::GetProviders}`]. pub fn get_providers(&mut self, key: record_priv::Key) -> QueryId { let providers: HashSet<_> = self .store From 2a8e3718780e3afbdf0446e7490126e640f41668 Mon Sep 17 00:00:00 2001 From: quininer Date: Wed, 2 Aug 2023 21:46:27 +0800 Subject: [PATCH 06/35] fix(relay): remove unconditional `async-std` dependency `libp2p-relay` crate specifies the `libp2p-swarm/async-std` feature, which causes an `async-std` dependency to always be introduced. Pull-Request: #4283. --- protocols/relay/CHANGELOG.md | 8 ++++++-- protocols/relay/Cargo.toml | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/protocols/relay/CHANGELOG.md b/protocols/relay/CHANGELOG.md index 46e27bc7bf9..c79034c24d4 100644 --- a/protocols/relay/CHANGELOG.md +++ b/protocols/relay/CHANGELOG.md @@ -1,15 +1,19 @@ ## 0.16.1 - unreleased -- Export `RateLimiter` type. +- Export `RateLimiter` type. See [PR 3742]. - Add functions to access data within `Limit`. See [PR 4162]. +- Remove unconditional `async-std` dependency. + See [PR 4283]. + [PR 3742]: https://github.com/libp2p/rust-libp2p/pull/3742 [PR 4162]: https://github.com/libp2p/rust-libp2p/pull/4162 +[PR 4283]: https://github.com/libp2p/rust-libp2p/pull/4283 -## 0.16.0 +## 0.16.0 - Raise MSRV to 1.65. See [PR 3715]. diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index a749e6f2483..31f6cc16d1e 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -18,7 +18,7 @@ futures = "0.3.28" futures-timer = "3" instant = "0.1.12" libp2p-core = { workspace = true } -libp2p-swarm = { workspace = true, features = ["async-std"] } +libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } log = "0.4" quick-protobuf = "0.8" @@ -32,7 +32,7 @@ void = "1" env_logger = "0.10.0" libp2p-ping = { workspace = true } libp2p-plaintext = { workspace = true } -libp2p-swarm = { workspace = true, features = ["macros"] } +libp2p-swarm = { workspace = true, features = ["macros", "async-std"] } libp2p-yamux = { workspace = true } quickcheck = { workspace = true } From 94dc11b6cd834382e6deb0de54a7e78fdd73cf13 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 2 Aug 2023 17:02:54 +0200 Subject: [PATCH 07/35] deps(yamux): update yamux to `v0.12` Pull-Request: #3013. --- Cargo.lock | 7 +-- Cargo.toml | 2 +- interop-tests/Cargo.toml | 4 +- interop-tests/src/lib.rs | 10 +++- muxers/yamux/CHANGELOG.md | 8 +++ muxers/yamux/Cargo.toml | 4 +- muxers/yamux/src/lib.rs | 85 +++++++++++------------------- protocols/kad/tests/client_mode.rs | 4 +- 8 files changed, 57 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b77ca5e2ab8..95127fdd93d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3364,7 +3364,7 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.44.0" +version = "0.44.1" dependencies = [ "async-std", "futures", @@ -6774,14 +6774,15 @@ dependencies = [ [[package]] name = "yamux" -version = "0.10.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +checksum = "0329ef377816896f014435162bb3711ea7a07729c23d0960e6f8048b21b8fe91" dependencies = [ "futures", "log", "nohash-hasher", "parking_lot", + "pin-project", "rand 0.8.5", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index 0a23bc54815..42d4375cb1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ libp2p-wasm-ext = { version = "0.40.0", path = "transports/wasm-ext" } libp2p-webrtc = { version = "0.6.0-alpha", path = "transports/webrtc" } libp2p-websocket = { version = "0.42.0", path = "transports/websocket" } libp2p-webtransport-websys = { version = "0.1.0", path = "transports/webtransport-websys" } -libp2p-yamux = { version = "0.44.0", path = "muxers/yamux" } +libp2p-yamux = { version = "0.44.1", path = "muxers/yamux" } multistream-select = { version = "0.13.0", path = "misc/multistream-select" } quick-protobuf-codec = { version = "0.2.0", path = "misc/quick-protobuf-codec" } quickcheck = { package = "quickcheck-ext", path = "misc/quickcheck-ext" } diff --git a/interop-tests/Cargo.toml b/interop-tests/Cargo.toml index a2e281dbb87..2d0d8ea25b7 100644 --- a/interop-tests/Cargo.toml +++ b/interop-tests/Cargo.toml @@ -19,7 +19,7 @@ rand = "0.8.5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] axum = "0.6" -libp2p = { path = "../libp2p", features = ["ping", "noise", "tls", "rsa", "macros", "websocket", "tokio", "yamux", "tcp", "dns"] } +libp2p = { path = "../libp2p", features = ["ping", "noise", "tls", "rsa", "macros", "websocket", "tokio", "yamux", "tcp", "dns", "identify"] } libp2p-quic = { workspace = true, features = ["tokio"] } libp2p-webrtc = { workspace = true, features = ["tokio"] } libp2p-mplex = { path = "../muxers/mplex" } @@ -34,7 +34,7 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } [target.'cfg(target_arch = "wasm32")'.dependencies] -libp2p = { path = "../libp2p", features = ["ping", "macros", "webtransport-websys", "wasm-bindgen"] } +libp2p = { path = "../libp2p", features = ["ping", "macros", "webtransport-websys", "wasm-bindgen", "identify"] } wasm-bindgen = { version = "0.2" } wasm-bindgen-futures = { version = "0.4" } wasm-logger = { version = "0.2.0" } diff --git a/interop-tests/src/lib.rs b/interop-tests/src/lib.rs index beb7c91c63d..57ce636367b 100644 --- a/interop-tests/src/lib.rs +++ b/interop-tests/src/lib.rs @@ -4,7 +4,7 @@ use std::time::Duration; use anyhow::{bail, Context, Result}; use futures::{FutureExt, StreamExt}; use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmEvent}; -use libp2p::{identity, ping, Multiaddr, PeerId}; +use libp2p::{identify, identity, ping, Multiaddr, PeerId}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -29,12 +29,17 @@ pub async fn run_test( let redis_client = RedisClient::new(redis_addr).context("Could not connect to redis")?; // Build the transport from the passed ENV var. - let (boxed_transport, local_addr) = build_transport(local_key, ip, transport)?; + let (boxed_transport, local_addr) = build_transport(local_key.clone(), ip, transport)?; let mut swarm = swarm_builder( boxed_transport, Behaviour { ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), keep_alive: keep_alive::Behaviour, + // Need to include identify until https://github.com/status-im/nim-libp2p/issues/924 is resolved. + identify: identify::Behaviour::new(identify::Config::new( + "/interop-tests".to_owned(), + local_key.public(), + )), }, local_peer_id, ) @@ -237,6 +242,7 @@ impl FromStr for SecProtocol { struct Behaviour { ping: ping::Behaviour, keep_alive: keep_alive::Behaviour, + identify: identify::Behaviour, } /// Helper function to get a ENV variable into an test parameter like `Transport`. diff --git a/muxers/yamux/CHANGELOG.md b/muxers/yamux/CHANGELOG.md index f0055c3fe4f..caea4e5359e 100644 --- a/muxers/yamux/CHANGELOG.md +++ b/muxers/yamux/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.44.1 - unreleased + +- Update to `yamux` `v0.12` which brings performance improvements and introduces an ACK backlog of 256 inbound streams. + When interacting with other libp2p nodes that are also running this or a newer version, the creation of inbound streams will be backpressured once the ACK backlog is hit. + See [PR 3013]. + +[PR 3013]: https://github.com/libp2p/rust-libp2p/pull/3013 + ## 0.44.0 - Raise MSRV to 1.65. diff --git a/muxers/yamux/Cargo.toml b/muxers/yamux/Cargo.toml index 50a9e97d1d0..11b94ac9738 100644 --- a/muxers/yamux/Cargo.toml +++ b/muxers/yamux/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-yamux" edition = "2021" rust-version = { workspace = true } description = "Yamux multiplexing protocol for libp2p" -version = "0.44.0" +version = "0.44.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -14,7 +14,7 @@ categories = ["network-programming", "asynchronous"] futures = "0.3.28" libp2p-core = { workspace = true } thiserror = "1.0" -yamux = "0.10.0" +yamux = "0.12" log = "0.4" [dev-dependencies] diff --git a/muxers/yamux/src/lib.rs b/muxers/yamux/src/lib.rs index b24c976ebf2..12e5dd8c1ff 100644 --- a/muxers/yamux/src/lib.rs +++ b/muxers/yamux/src/lib.rs @@ -22,14 +22,14 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use futures::{future, prelude::*, ready, stream::BoxStream}; +use futures::{future, prelude::*, ready}; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use std::collections::VecDeque; use std::io::{IoSlice, IoSliceMut}; use std::task::Waker; use std::{ - fmt, io, iter, mem, + io, iter, pin::Pin, task::{Context, Poll}, }; @@ -37,14 +37,12 @@ use thiserror::Error; use yamux::ConnectionError; /// A Yamux connection. +#[derive(Debug)] pub struct Muxer { - /// The [`futures::stream::Stream`] of incoming substreams. - incoming: BoxStream<'static, Result>, - /// Handle to control the connection. - control: yamux::Control, + connection: yamux::Connection, /// Temporarily buffers inbound streams in case our node is performing backpressure on the remote. /// - /// The only way how yamux can make progress is by driving the stream. However, the + /// The only way how yamux can make progress is by calling [`yamux::Connection::poll_next_inbound`]. However, the /// [`StreamMuxer`] interface is designed to allow a caller to selectively make progress via /// [`StreamMuxer::poll_inbound`] and [`StreamMuxer::poll_outbound`] whilst the more general /// [`StreamMuxer::poll`] is designed to make progress on existing streams etc. @@ -54,17 +52,13 @@ pub struct Muxer { inbound_stream_buffer: VecDeque, /// Waker to be called when new inbound streams are available. inbound_stream_waker: Option, - - _phantom: std::marker::PhantomData, } -const MAX_BUFFERED_INBOUND_STREAMS: usize = 25; - -impl fmt::Debug for Muxer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Yamux") - } -} +/// How many streams to buffer before we start resetting them. +/// +/// This is equal to the ACK BACKLOG in `rust-yamux`. +/// Thus, for peers running on a recent version of `rust-libp2p`, we should never need to reset streams because they'll voluntarily stop opening them once they hit the ACK backlog. +const MAX_BUFFERED_INBOUND_STREAMS: usize = 256; impl Muxer where @@ -72,22 +66,17 @@ where { /// Create a new Yamux connection. fn new(io: C, cfg: yamux::Config, mode: yamux::Mode) -> Self { - let conn = yamux::Connection::new(io, cfg, mode); - let ctrl = conn.control(); - - Self { - incoming: yamux::into_stream(conn).err_into().boxed(), - control: ctrl, + Muxer { + connection: yamux::Connection::new(io, cfg, mode), inbound_stream_buffer: VecDeque::default(), inbound_stream_waker: None, - _phantom: Default::default(), } } } impl StreamMuxer for Muxer where - C: AsyncRead + AsyncWrite + Send + Unpin + 'static, + C: AsyncRead + AsyncWrite + Unpin + 'static, { type Substream = Stream; type Error = Error; @@ -112,10 +101,15 @@ where mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - Pin::new(&mut self.control) - .poll_open_stream(cx) - .map_ok(Stream) - .map_err(Error) + let stream = ready!(self.connection.poll_new_outbound(cx).map_err(Error)?); + + Poll::Ready(Ok(Stream(stream))) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + ready!(self.connection.poll_close(cx).map_err(Error)?); + + Poll::Ready(Ok(())) } fn poll( @@ -141,23 +135,6 @@ where cx.waker().wake_by_ref(); Poll::Pending } - - fn poll_close(mut self: Pin<&mut Self>, c: &mut Context<'_>) -> Poll> { - if let Poll::Ready(()) = Pin::new(&mut self.control).poll_close(c).map_err(Error)? { - return Poll::Ready(Ok(())); - } - - while let Poll::Ready(maybe_inbound_stream) = - self.incoming.poll_next_unpin(c).map_err(Error)? - { - match maybe_inbound_stream { - Some(inbound_stream) => mem::drop(inbound_stream), - None => return Poll::Ready(Ok(())), - } - } - - Poll::Pending - } } /// A stream produced by the yamux multiplexer. @@ -210,18 +187,16 @@ impl AsyncWrite for Stream { impl Muxer where - C: AsyncRead + AsyncWrite + Send + Unpin + 'static, + C: AsyncRead + AsyncWrite + Unpin + 'static, { fn poll_inner(&mut self, cx: &mut Context<'_>) -> Poll> { - self.incoming.poll_next_unpin(cx).map(|maybe_stream| { - let stream = maybe_stream - .transpose() - .map_err(Error)? - .map(Stream) - .ok_or(Error(ConnectionError::Closed))?; - - Ok(stream) - }) + let stream = ready!(self.connection.poll_next_inbound(cx)) + .transpose() + .map_err(Error)? + .map(Stream) + .ok_or(Error(ConnectionError::Closed))?; + + Poll::Ready(Ok(stream)) } } diff --git a/protocols/kad/tests/client_mode.rs b/protocols/kad/tests/client_mode.rs index b2530569518..30fd4d972a8 100644 --- a/protocols/kad/tests/client_mode.rs +++ b/protocols/kad/tests/client_mode.rs @@ -59,11 +59,11 @@ async fn two_servers_add_each_other_to_routing_table() { match libp2p_swarm_test::drive(&mut server2, &mut server1).await { ( - [Identify(_), Kad(UnroutablePeer { .. }), Identify(_), Kad(RoutingUpdated { peer: peer2, .. }), Identify(_)], + [Identify(_), Kad(RoutingUpdated { peer: peer2, .. }), Identify(_)], [Identify(_), Identify(_)], ) | ( - [Identify(_), Kad(UnroutablePeer { .. }), Identify(_), Identify(_), Kad(RoutingUpdated { peer: peer2, .. })], + [Identify(_), Identify(_), Kad(RoutingUpdated { peer: peer2, .. })], [Identify(_), Identify(_)], ) => { assert_eq!(peer2, server1_peer_id); From 51a91b7ee94f04c55a6a0da13ab7ff8f0b225213 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:22:47 +0000 Subject: [PATCH 08/35] deps: bump Swatinem/rust-cache from 2.5.1 to 2.6.0 Pull-Request: #4288. --- .github/workflows/cache-factory.yml | 2 +- .github/workflows/ci.yml | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cache-factory.yml b/.github/workflows/cache-factory.yml index 56c42416005..8d25eb9acab 100644 --- a/.github/workflows/cache-factory.yml +++ b/.github/workflows/cache-factory.yml @@ -22,7 +22,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 with: shared-key: stable-cache diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14cd04f4299..c61e673b855 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 with: shared-key: stable-cache save-if: false @@ -136,7 +136,7 @@ jobs: - uses: r7kamura/rust-problem-matchers@d58b70c4a13c4866d96436315da451d8106f8f08 #v1.3.0 - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 with: key: ${{ matrix.target }} save-if: ${{ github.ref == 'refs/heads/master' }} @@ -161,7 +161,7 @@ jobs: - uses: r7kamura/rust-problem-matchers@d58b70c4a13c4866d96436315da451d8106f8f08 #v1.3.0 - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 with: save-if: ${{ github.ref == 'refs/heads/master' }} @@ -182,7 +182,7 @@ jobs: - uses: r7kamura/rust-problem-matchers@d58b70c4a13c4866d96436315da451d8106f8f08 #v1.3.0 - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 with: key: ${{ matrix.features }} save-if: ${{ github.ref == 'refs/heads/master' }} @@ -199,7 +199,7 @@ jobs: - uses: r7kamura/rust-problem-matchers@d58b70c4a13c4866d96436315da451d8106f8f08 #v1.3.0 - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 with: save-if: ${{ github.ref == 'refs/heads/master' }} @@ -225,7 +225,7 @@ jobs: - uses: r7kamura/rust-problem-matchers@d58b70c4a13c4866d96436315da451d8106f8f08 #v1.3.0 - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 with: save-if: ${{ github.ref == 'refs/heads/master' }} @@ -242,7 +242,7 @@ jobs: - uses: r7kamura/rust-problem-matchers@d58b70c4a13c4866d96436315da451d8106f8f08 #v1.3.0 - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 with: save-if: ${{ github.ref == 'refs/heads/master' }} @@ -258,7 +258,7 @@ jobs: - uses: r7kamura/rust-problem-matchers@d58b70c4a13c4866d96436315da451d8106f8f08 #v1.3.0 - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 with: shared-key: stable-cache save-if: false @@ -326,7 +326,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 - run: cargo install --version 0.10.0 pb-rs --locked @@ -352,5 +352,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1 + - uses: Swatinem/rust-cache@b8a6852b4f997182bdea832df3f9e153038b5191 # v2.6.0 - run: cargo metadata --locked --format-version=1 > /dev/null From 6581822aad0957488224b9765de1e56333318c68 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 3 Aug 2023 19:59:16 +0800 Subject: [PATCH 09/35] move to new crate move --- Cargo.lock | 26 +- Cargo.toml | 2 + misc/connection-limits/CHANGELOG.md | 15 - misc/connection-limits/Cargo.toml | 6 +- .../src/connection_limits/mod.rs | 57 --- .../src/connection_limits/static.rs | 358 --------------- misc/connection-limits/src/lib.rs | 412 ++++++++++++++++-- misc/memory-connection-limits/CHANGELOG.md | 3 + misc/memory-connection-limits/Cargo.toml | 28 ++ .../src/connection_limits.rs} | 111 +---- misc/memory-connection-limits/src/lib.rs | 172 ++++++++ 11 files changed, 628 insertions(+), 562 deletions(-) delete mode 100644 misc/connection-limits/src/connection_limits/mod.rs delete mode 100644 misc/connection-limits/src/connection_limits/static.rs create mode 100644 misc/memory-connection-limits/CHANGELOG.md create mode 100644 misc/memory-connection-limits/Cargo.toml rename misc/{connection-limits/src/connection_limits/memory.rs => memory-connection-limits/src/connection_limits.rs} (55%) create mode 100644 misc/memory-connection-limits/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 95127fdd93d..cfe7a388197 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2599,7 +2599,7 @@ dependencies = [ [[package]] name = "libp2p-connection-limits" -version = "0.2.2" +version = "0.2.1" dependencies = [ "async-std", "libp2p-core", @@ -2609,12 +2609,8 @@ dependencies = [ "libp2p-swarm", "libp2p-swarm-derive", "libp2p-swarm-test", - "log", - "memory-stats", - "parking_lot", "quickcheck-ext", "rand 0.8.5", - "sysinfo", "void", ] @@ -2880,6 +2876,26 @@ dependencies = [ "void", ] +[[package]] +name = "libp2p-memory-connection-limits" +version = "0.1.0" +dependencies = [ + "async-std", + "libp2p-core", + "libp2p-identify", + "libp2p-identity", + "libp2p-ping", + "libp2p-swarm", + "libp2p-swarm-derive", + "libp2p-swarm-test", + "log", + "memory-stats", + "quickcheck-ext", + "rand 0.8.5", + "sysinfo", + "void", +] + [[package]] name = "libp2p-metrics" version = "0.13.1" diff --git a/Cargo.toml b/Cargo.toml index 42d4375cb1c..55476381acf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "misc/allow-block-list", "misc/connection-limits", "misc/keygen", + "misc/memory-connection-limits", "misc/metrics", "misc/multistream-select", "misc/quick-protobuf-codec", @@ -75,6 +76,7 @@ libp2p-identify = { version = "0.43.0", path = "protocols/identify" } libp2p-identity = { version = "0.2.2" } libp2p-kad = { version = "0.44.4", path = "protocols/kad" } libp2p-mdns = { version = "0.44.0", path = "protocols/mdns" } +libp2p-memory-connection-limits = { version = "0.1.0", path = "misc/memory-connection-limits" } libp2p-metrics = { version = "0.13.1", path = "misc/metrics" } libp2p-mplex = { version = "0.40.0", path = "muxers/mplex" } libp2p-muxer-test-harness = { path = "muxers/test-harness" } diff --git a/misc/connection-limits/CHANGELOG.md b/misc/connection-limits/CHANGELOG.md index 5839ed76228..a8bd071e6fe 100644 --- a/misc/connection-limits/CHANGELOG.md +++ b/misc/connection-limits/CHANGELOG.md @@ -1,18 +1,3 @@ -## 0.2.2 -- Rename `ConnectionLimits` to `StaticConnectionLimits` - - See [PR XX] - -- Refactor `Behaviour` to take a generic `ConnectionLimitsChecker` - - See [PR XX] - -- Add `MemoryBasedConnectionLimits` - - See [PR XX] - -[PR XX]: https://github.com/libp2p/rust-libp2p/pull/XX - ## 0.2.1 - Do not count a connection as established when it is denied by another sibling `NetworkBehaviour`. diff --git a/misc/connection-limits/Cargo.toml b/misc/connection-limits/Cargo.toml index 9a9819545db..25347e29b16 100644 --- a/misc/connection-limits/Cargo.toml +++ b/misc/connection-limits/Cargo.toml @@ -3,20 +3,16 @@ name = "libp2p-connection-limits" edition = "2021" rust-version = { workspace = true } description = "Connection limits for libp2p." -version = "0.2.2" +version = "0.2.1" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -memory-stats = "1" libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true, features = ["peerid"] } -log = "0.4" -parking_lot = "0.12" -sysinfo = "0.29" void = "1" [dev-dependencies] diff --git a/misc/connection-limits/src/connection_limits/mod.rs b/misc/connection-limits/src/connection_limits/mod.rs deleted file mode 100644 index 99a8f5a8504..00000000000 --- a/misc/connection-limits/src/connection_limits/mod.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2023 Protocol Labs. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -mod memory; -pub use memory::{MemoryUsageBasedConnectionLimits, MemoryUsageLimitExceeded}; - -mod r#static; -pub use r#static::{StaticConnectionLimits, StaticLimitExceeded}; - -use libp2p_swarm::ConnectionDenied; -use std::fmt; - -pub trait ConnectionLimitsChecker { - fn check_limit(&self, kind: ConnectionKind, current: usize) -> Result<(), ConnectionDenied>; -} - -#[derive(Debug, Clone, Copy)] -pub enum ConnectionKind { - PendingIncoming, - PendingOutgoing, - EstablishedIncoming, - EstablishedOutgoing, - EstablishedPerPeer, - EstablishedTotal, -} - -impl fmt::Display for ConnectionKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use ConnectionKind::*; - - match self { - PendingIncoming => write!(f, "pending incoming connections"), - PendingOutgoing => write!(f, "pending outgoing connections"), - EstablishedIncoming => write!(f, "established incoming connections"), - EstablishedOutgoing => write!(f, "established outgoing connections"), - EstablishedPerPeer => write!(f, "established connections per peer"), - EstablishedTotal => write!(f, "established connections"), - } - } -} diff --git a/misc/connection-limits/src/connection_limits/static.rs b/misc/connection-limits/src/connection_limits/static.rs deleted file mode 100644 index c6dd9ed7da0..00000000000 --- a/misc/connection-limits/src/connection_limits/static.rs +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright 2023 Protocol Labs. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::*; - -/// The configurable static connection limits. -#[derive(Debug, Clone, Default)] -pub struct StaticConnectionLimits { - pub(crate) max_pending_incoming: Option, - pub(crate) max_pending_outgoing: Option, - pub(crate) max_established_incoming: Option, - pub(crate) max_established_outgoing: Option, - pub(crate) max_established_per_peer: Option, - pub(crate) max_established_total: Option, -} - -impl StaticConnectionLimits { - /// Configures the maximum number of concurrently incoming connections being established. - pub fn with_max_pending_incoming(mut self, limit: Option) -> Self { - self.max_pending_incoming = limit; - self - } - - /// Configures the maximum number of concurrently outgoing connections being established. - pub fn with_max_pending_outgoing(mut self, limit: Option) -> Self { - self.max_pending_outgoing = limit; - self - } - - /// Configures the maximum number of concurrent established inbound connections. - pub fn with_max_established_incoming(mut self, limit: Option) -> Self { - self.max_established_incoming = limit; - self - } - - /// Configures the maximum number of concurrent established outbound connections. - pub fn with_max_established_outgoing(mut self, limit: Option) -> Self { - self.max_established_outgoing = limit; - self - } - - /// Configures the maximum number of concurrent established connections (both - /// inbound and outbound). - /// - /// Note: This should be used in conjunction with - /// [`ConnectionLimits::with_max_established_incoming`] to prevent possible - /// eclipse attacks (all connections being inbound). - pub fn with_max_established(mut self, limit: Option) -> Self { - self.max_established_total = limit; - self - } - - /// Configures the maximum number of concurrent established connections per peer, - /// regardless of direction (incoming or outgoing). - pub fn with_max_established_per_peer(mut self, limit: Option) -> Self { - self.max_established_per_peer = limit; - self - } -} - -impl ConnectionLimitsChecker for StaticConnectionLimits { - fn check_limit(&self, kind: ConnectionKind, current: usize) -> Result<(), ConnectionDenied> { - use ConnectionKind::*; - - let limit = match kind { - PendingIncoming => self.max_pending_incoming, - PendingOutgoing => self.max_pending_outgoing, - EstablishedIncoming => self.max_established_incoming, - EstablishedOutgoing => self.max_established_outgoing, - EstablishedPerPeer => self.max_established_per_peer, - EstablishedTotal => self.max_established_total, - } - .unwrap_or(u32::MAX); - - if current as u32 >= limit { - return Err(ConnectionDenied::new(StaticLimitExceeded { limit, kind })); - } - - Ok(()) - } -} - -/// A connection limit has been exceeded. -#[derive(Debug, Clone, Copy)] -pub struct StaticLimitExceeded { - limit: u32, - kind: ConnectionKind, -} - -impl StaticLimitExceeded { - pub fn limit(&self) -> u32 { - self.limit - } -} - -impl std::error::Error for StaticLimitExceeded {} - -impl fmt::Display for StaticLimitExceeded { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "connection limit exceeded: at most {} {} are allowed", - self.limit, self.kind - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::*; - use libp2p_swarm::{ - behaviour::toggle::Toggle, dial_opts::DialOpts, DialError, ListenError, Swarm, SwarmEvent, - }; - use libp2p_swarm_test::SwarmExt; - use quickcheck::*; - - #[test] - fn max_outgoing() { - use rand::Rng; - - let outgoing_limit = rand::thread_rng().gen_range(1..10); - - let mut network = Swarm::new_ephemeral(|_| { - Behaviour::new( - StaticConnectionLimits::default().with_max_pending_outgoing(Some(outgoing_limit)), - ) - }); - - let addr: Multiaddr = "/memory/1234".parse().unwrap(); - let target = PeerId::random(); - - for _ in 0..outgoing_limit { - network - .dial( - DialOpts::peer_id(target) - .addresses(vec![addr.clone()]) - .build(), - ) - .expect("Unexpected connection limit."); - } - - match network - .dial(DialOpts::peer_id(target).addresses(vec![addr]).build()) - .expect_err("Unexpected dialing success.") - { - DialError::Denied { cause } => { - let exceeded = cause - .downcast::() - .expect("connection denied because of limit"); - - assert_eq!(exceeded.limit(), outgoing_limit); - } - e => panic!("Unexpected error: {e:?}"), - } - - let info = network.network_info(); - assert_eq!(info.num_peers(), 0); - assert_eq!( - info.connection_counters().num_pending_outgoing(), - outgoing_limit - ); - } - - #[test] - fn max_established_incoming() { - fn prop(Limit(limit): Limit) { - let mut swarm1 = Swarm::new_ephemeral(|_| { - Behaviour::new( - StaticConnectionLimits::default().with_max_established_incoming(Some(limit)), - ) - }); - let mut swarm2 = Swarm::new_ephemeral(|_| { - Behaviour::new( - StaticConnectionLimits::default().with_max_established_incoming(Some(limit)), - ) - }); - - async_std::task::block_on(async { - let (listen_addr, _) = swarm1.listen().await; - - for _ in 0..limit { - swarm2.connect(&mut swarm1).await; - } - - swarm2.dial(listen_addr).unwrap(); - - async_std::task::spawn(swarm2.loop_on_next()); - - let cause = swarm1 - .wait(|event| match event { - SwarmEvent::IncomingConnectionError { - error: ListenError::Denied { cause }, - .. - } => Some(cause), - _ => None, - }) - .await; - - assert_eq!( - cause.downcast::().unwrap().limit, - limit - ); - }); - } - - #[derive(Debug, Clone)] - struct Limit(u32); - - impl Arbitrary for Limit { - fn arbitrary(g: &mut Gen) -> Self { - Self(g.gen_range(1..10)) - } - } - - quickcheck(prop as fn(_)); - } - - /// Another sibling [`NetworkBehaviour`] implementation might deny established connections in - /// [`handle_established_outbound_connection`] or [`handle_established_inbound_connection`]. - /// [`Behaviour`] must not increase the established counters in - /// [`handle_established_outbound_connection`] or [`handle_established_inbound_connection`], but - /// in [`SwarmEvent::ConnectionEstablished`] as the connection might still be denied by a - /// sibling [`NetworkBehaviour`] in the former case. Only in the latter case - /// ([`SwarmEvent::ConnectionEstablished`]) can the connection be seen as established. - #[test] - fn support_other_behaviour_denying_connection() { - let mut swarm1 = Swarm::new_ephemeral(|_| { - Behaviour::new_with_connection_denier(StaticConnectionLimits::default()) - }); - let mut swarm2 = - Swarm::new_ephemeral(|_| Behaviour::new(StaticConnectionLimits::default())); - - async_std::task::block_on(async { - // Have swarm2 dial swarm1. - let (listen_addr, _) = swarm1.listen().await; - swarm2.dial(listen_addr).unwrap(); - async_std::task::spawn(swarm2.loop_on_next()); - - // Wait for the ConnectionDenier of swarm1 to deny the established connection. - let cause = swarm1 - .wait(|event| match event { - SwarmEvent::IncomingConnectionError { - error: ListenError::Denied { cause }, - .. - } => Some(cause), - _ => None, - }) - .await; - - cause.downcast::().unwrap(); - - assert_eq!( - 0, - swarm1 - .behaviour_mut() - .limits - .established_inbound_connections - .len(), - "swarm1 connection limit behaviour to not count denied established connection as established connection" - ) - }); - } - - #[derive(libp2p_swarm_derive::NetworkBehaviour)] - #[behaviour(prelude = "libp2p_swarm::derive_prelude")] - struct Behaviour { - limits: crate::Behaviour, - keep_alive: libp2p_swarm::keep_alive::Behaviour, - connection_denier: Toggle, - } - - impl Behaviour { - fn new(limits: StaticConnectionLimits) -> Self { - Self { - limits: crate::Behaviour::new(limits), - keep_alive: libp2p_swarm::keep_alive::Behaviour, - connection_denier: None.into(), - } - } - fn new_with_connection_denier(limits: StaticConnectionLimits) -> Self { - Self { - limits: crate::Behaviour::new(limits), - keep_alive: libp2p_swarm::keep_alive::Behaviour, - connection_denier: Some(ConnectionDenier {}).into(), - } - } - } - - struct ConnectionDenier {} - - impl NetworkBehaviour for ConnectionDenier { - type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = Void; - - fn handle_established_inbound_connection( - &mut self, - _connection_id: ConnectionId, - _peer: PeerId, - _local_addr: &Multiaddr, - _remote_addr: &Multiaddr, - ) -> Result, ConnectionDenied> { - Err(ConnectionDenied::new(std::io::Error::new( - std::io::ErrorKind::Other, - "ConnectionDenier", - ))) - } - - fn handle_established_outbound_connection( - &mut self, - _connection_id: ConnectionId, - _peer: PeerId, - _addr: &Multiaddr, - _role_override: Endpoint, - ) -> Result, ConnectionDenied> { - Err(ConnectionDenied::new(std::io::Error::new( - std::io::ErrorKind::Other, - "ConnectionDenier", - ))) - } - - fn on_swarm_event(&mut self, _event: FromSwarm) {} - - fn on_connection_handler_event( - &mut self, - _peer_id: PeerId, - _connection_id: ConnectionId, - event: THandlerOutEvent, - ) { - void::unreachable(event) - } - - fn poll( - &mut self, - _cx: &mut Context<'_>, - _params: &mut impl PollParameters, - ) -> Poll>> { - Poll::Pending - } - } -} diff --git a/misc/connection-limits/src/lib.rs b/misc/connection-limits/src/lib.rs index 1d573899a46..e4723dd95c6 100644 --- a/misc/connection-limits/src/lib.rs +++ b/misc/connection-limits/src/lib.rs @@ -18,9 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -mod connection_limits; -pub use connection_limits::*; - use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ @@ -29,14 +26,11 @@ use libp2p_swarm::{ PollParameters, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; use std::collections::{HashMap, HashSet}; +use std::fmt; use std::task::{Context, Poll}; use void::Void; -/// Alias type for maintaining backward compatibility. -#[deprecated(note = "Renamed to `StaticConnectionLimits`.")] -pub type ConnectionLimits = StaticConnectionLimits; - -/// A [`NetworkBehaviour`] that enforces a set of [`StaticConnectionLimits`]. +/// A [`NetworkBehaviour`] that enforces a set of [`ConnectionLimits`]. /// /// For these limits to take effect, this needs to be composed into the behaviour tree of your application. /// @@ -61,12 +55,11 @@ pub type ConnectionLimits = StaticConnectionLimits; /// struct MyBehaviour { /// identify: identify::Behaviour, /// ping: ping::Behaviour, -/// limits: connection_limits::Behaviour +/// limits: connection_limits::Behaviour /// } /// ``` -// Default type for maintaining backward compatibility. -pub struct Behaviour { - limits: C, +pub struct Behaviour { + limits: ConnectionLimits, pending_inbound_connections: HashSet, pending_outbound_connections: HashSet, @@ -75,8 +68,8 @@ pub struct Behaviour { established_per_peer: HashMap>, } -impl Behaviour { - pub fn new(limits: C) -> Self { +impl Behaviour { + pub fn new(limits: ConnectionLimits) -> Self { Self { limits, pending_inbound_connections: Default::default(), @@ -86,9 +79,128 @@ impl Behaviour { established_per_peer: Default::default(), } } + + fn check_limit( + &mut self, + limit: Option, + current: usize, + kind: Kind, + ) -> Result<(), ConnectionDenied> { + let limit = limit.unwrap_or(u32::MAX); + let current = current as u32; + + if current >= limit { + return Err(ConnectionDenied::new(Exceeded { limit, kind })); + } + + Ok(()) + } +} + +/// A connection limit has been exceeded. +#[derive(Debug, Clone, Copy)] +pub struct Exceeded { + limit: u32, + kind: Kind, +} + +impl Exceeded { + pub fn limit(&self) -> u32 { + self.limit + } +} + +impl fmt::Display for Exceeded { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "connection limit exceeded: at most {} {} are allowed", + self.limit, self.kind + ) + } +} + +#[derive(Debug, Clone, Copy)] +enum Kind { + PendingIncoming, + PendingOutgoing, + EstablishedIncoming, + EstablishedOutgoing, + EstablishedPerPeer, + EstablishedTotal, +} + +impl fmt::Display for Kind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Kind::PendingIncoming => write!(f, "pending incoming connections"), + Kind::PendingOutgoing => write!(f, "pending outgoing connections"), + Kind::EstablishedIncoming => write!(f, "established incoming connections"), + Kind::EstablishedOutgoing => write!(f, "established outgoing connections"), + Kind::EstablishedPerPeer => write!(f, "established connections per peer"), + Kind::EstablishedTotal => write!(f, "established connections"), + } + } } -impl NetworkBehaviour for Behaviour { +impl std::error::Error for Exceeded {} + +/// The configurable connection limits. +#[derive(Debug, Clone, Default)] +pub struct ConnectionLimits { + max_pending_incoming: Option, + max_pending_outgoing: Option, + max_established_incoming: Option, + max_established_outgoing: Option, + max_established_per_peer: Option, + max_established_total: Option, +} + +impl ConnectionLimits { + /// Configures the maximum number of concurrently incoming connections being established. + pub fn with_max_pending_incoming(mut self, limit: Option) -> Self { + self.max_pending_incoming = limit; + self + } + + /// Configures the maximum number of concurrently outgoing connections being established. + pub fn with_max_pending_outgoing(mut self, limit: Option) -> Self { + self.max_pending_outgoing = limit; + self + } + + /// Configures the maximum number of concurrent established inbound connections. + pub fn with_max_established_incoming(mut self, limit: Option) -> Self { + self.max_established_incoming = limit; + self + } + + /// Configures the maximum number of concurrent established outbound connections. + pub fn with_max_established_outgoing(mut self, limit: Option) -> Self { + self.max_established_outgoing = limit; + self + } + + /// Configures the maximum number of concurrent established connections (both + /// inbound and outbound). + /// + /// Note: This should be used in conjunction with + /// [`ConnectionLimits::with_max_established_incoming`] to prevent possible + /// eclipse attacks (all connections being inbound). + pub fn with_max_established(mut self, limit: Option) -> Self { + self.max_established_total = limit; + self + } + + /// Configures the maximum number of concurrent established connections per peer, + /// regardless of direction (incoming or outgoing). + pub fn with_max_established_per_peer(mut self, limit: Option) -> Self { + self.max_established_per_peer = limit; + self + } +} + +impl NetworkBehaviour for Behaviour { type ConnectionHandler = dummy::ConnectionHandler; type ToSwarm = Void; @@ -98,9 +210,10 @@ impl NetworkBehaviour for Behaviour { _: &Multiaddr, _: &Multiaddr, ) -> Result<(), ConnectionDenied> { - self.limits.check_limit( - ConnectionKind::PendingIncoming, + self.check_limit( + self.limits.max_pending_incoming, self.pending_inbound_connections.len(), + Kind::PendingIncoming, )?; self.pending_inbound_connections.insert(connection_id); @@ -117,21 +230,24 @@ impl NetworkBehaviour for Behaviour { ) -> Result, ConnectionDenied> { self.pending_inbound_connections.remove(&connection_id); - self.limits.check_limit( - ConnectionKind::EstablishedIncoming, + self.check_limit( + self.limits.max_established_incoming, self.established_inbound_connections.len(), + Kind::EstablishedIncoming, )?; - self.limits.check_limit( - ConnectionKind::EstablishedPerPeer, + self.check_limit( + self.limits.max_established_per_peer, self.established_per_peer .get(&peer) .map(|connections| connections.len()) .unwrap_or(0), + Kind::EstablishedPerPeer, )?; - self.limits.check_limit( - ConnectionKind::EstablishedTotal, + self.check_limit( + self.limits.max_established_total, self.established_inbound_connections.len() + self.established_outbound_connections.len(), + Kind::EstablishedTotal, )?; Ok(dummy::ConnectionHandler) @@ -144,9 +260,10 @@ impl NetworkBehaviour for Behaviour { _: &[Multiaddr], _: Endpoint, ) -> Result, ConnectionDenied> { - self.limits.check_limit( - ConnectionKind::PendingOutgoing, + self.check_limit( + self.limits.max_pending_outgoing, self.pending_outbound_connections.len(), + Kind::PendingOutgoing, )?; self.pending_outbound_connections.insert(connection_id); @@ -163,21 +280,24 @@ impl NetworkBehaviour for Behaviour { ) -> Result, ConnectionDenied> { self.pending_outbound_connections.remove(&connection_id); - self.limits.check_limit( - ConnectionKind::EstablishedOutgoing, + self.check_limit( + self.limits.max_established_outgoing, self.established_outbound_connections.len(), + Kind::EstablishedOutgoing, )?; - self.limits.check_limit( - ConnectionKind::EstablishedPerPeer, + self.check_limit( + self.limits.max_established_per_peer, self.established_per_peer .get(&peer) .map(|connections| connections.len()) .unwrap_or(0), + Kind::EstablishedPerPeer, )?; - self.limits.check_limit( - ConnectionKind::EstablishedTotal, + self.check_limit( + self.limits.max_established_total, self.established_inbound_connections.len() + self.established_outbound_connections.len(), + Kind::EstablishedTotal, )?; Ok(dummy::ConnectionHandler) @@ -252,3 +372,233 @@ impl NetworkBehaviour for Behaviour { Poll::Pending } } + +#[cfg(test)] +mod tests { + use super::*; + use libp2p_swarm::{ + behaviour::toggle::Toggle, dial_opts::DialOpts, DialError, ListenError, Swarm, SwarmEvent, + }; + use libp2p_swarm_test::SwarmExt; + use quickcheck::*; + + #[test] + fn max_outgoing() { + use rand::Rng; + + let outgoing_limit = rand::thread_rng().gen_range(1..10); + + let mut network = Swarm::new_ephemeral(|_| { + Behaviour::new( + ConnectionLimits::default().with_max_pending_outgoing(Some(outgoing_limit)), + ) + }); + + let addr: Multiaddr = "/memory/1234".parse().unwrap(); + let target = PeerId::random(); + + for _ in 0..outgoing_limit { + network + .dial( + DialOpts::peer_id(target) + .addresses(vec![addr.clone()]) + .build(), + ) + .expect("Unexpected connection limit."); + } + + match network + .dial(DialOpts::peer_id(target).addresses(vec![addr]).build()) + .expect_err("Unexpected dialing success.") + { + DialError::Denied { cause } => { + let exceeded = cause + .downcast::() + .expect("connection denied because of limit"); + + assert_eq!(exceeded.limit(), outgoing_limit); + } + e => panic!("Unexpected error: {e:?}"), + } + + let info = network.network_info(); + assert_eq!(info.num_peers(), 0); + assert_eq!( + info.connection_counters().num_pending_outgoing(), + outgoing_limit + ); + } + + #[test] + fn max_established_incoming() { + fn prop(Limit(limit): Limit) { + let mut swarm1 = Swarm::new_ephemeral(|_| { + Behaviour::new( + ConnectionLimits::default().with_max_established_incoming(Some(limit)), + ) + }); + let mut swarm2 = Swarm::new_ephemeral(|_| { + Behaviour::new( + ConnectionLimits::default().with_max_established_incoming(Some(limit)), + ) + }); + + async_std::task::block_on(async { + let (listen_addr, _) = swarm1.listen().await; + + for _ in 0..limit { + swarm2.connect(&mut swarm1).await; + } + + swarm2.dial(listen_addr).unwrap(); + + async_std::task::spawn(swarm2.loop_on_next()); + + let cause = swarm1 + .wait(|event| match event { + SwarmEvent::IncomingConnectionError { + error: ListenError::Denied { cause }, + .. + } => Some(cause), + _ => None, + }) + .await; + + assert_eq!(cause.downcast::().unwrap().limit, limit); + }); + } + + #[derive(Debug, Clone)] + struct Limit(u32); + + impl Arbitrary for Limit { + fn arbitrary(g: &mut Gen) -> Self { + Self(g.gen_range(1..10)) + } + } + + quickcheck(prop as fn(_)); + } + + /// Another sibling [`NetworkBehaviour`] implementation might deny established connections in + /// [`handle_established_outbound_connection`] or [`handle_established_inbound_connection`]. + /// [`Behaviour`] must not increase the established counters in + /// [`handle_established_outbound_connection`] or [`handle_established_inbound_connection`], but + /// in [`SwarmEvent::ConnectionEstablished`] as the connection might still be denied by a + /// sibling [`NetworkBehaviour`] in the former case. Only in the latter case + /// ([`SwarmEvent::ConnectionEstablished`]) can the connection be seen as established. + #[test] + fn support_other_behaviour_denying_connection() { + let mut swarm1 = Swarm::new_ephemeral(|_| { + Behaviour::new_with_connection_denier(ConnectionLimits::default()) + }); + let mut swarm2 = Swarm::new_ephemeral(|_| Behaviour::new(ConnectionLimits::default())); + + async_std::task::block_on(async { + // Have swarm2 dial swarm1. + let (listen_addr, _) = swarm1.listen().await; + swarm2.dial(listen_addr).unwrap(); + async_std::task::spawn(swarm2.loop_on_next()); + + // Wait for the ConnectionDenier of swarm1 to deny the established connection. + let cause = swarm1 + .wait(|event| match event { + SwarmEvent::IncomingConnectionError { + error: ListenError::Denied { cause }, + .. + } => Some(cause), + _ => None, + }) + .await; + + cause.downcast::().unwrap(); + + assert_eq!( + 0, + swarm1 + .behaviour_mut() + .limits + .established_inbound_connections + .len(), + "swarm1 connection limit behaviour to not count denied established connection as established connection" + ) + }); + } + + #[derive(libp2p_swarm_derive::NetworkBehaviour)] + #[behaviour(prelude = "libp2p_swarm::derive_prelude")] + struct Behaviour { + limits: super::Behaviour, + keep_alive: libp2p_swarm::keep_alive::Behaviour, + connection_denier: Toggle, + } + + impl Behaviour { + fn new(limits: ConnectionLimits) -> Self { + Self { + limits: super::Behaviour::new(limits), + keep_alive: libp2p_swarm::keep_alive::Behaviour, + connection_denier: None.into(), + } + } + fn new_with_connection_denier(limits: ConnectionLimits) -> Self { + Self { + limits: super::Behaviour::new(limits), + keep_alive: libp2p_swarm::keep_alive::Behaviour, + connection_denier: Some(ConnectionDenier {}).into(), + } + } + } + + struct ConnectionDenier {} + + impl NetworkBehaviour for ConnectionDenier { + type ConnectionHandler = dummy::ConnectionHandler; + type ToSwarm = Void; + + fn handle_established_inbound_connection( + &mut self, + _connection_id: ConnectionId, + _peer: PeerId, + _local_addr: &Multiaddr, + _remote_addr: &Multiaddr, + ) -> Result, ConnectionDenied> { + Err(ConnectionDenied::new(std::io::Error::new( + std::io::ErrorKind::Other, + "ConnectionDenier", + ))) + } + + fn handle_established_outbound_connection( + &mut self, + _connection_id: ConnectionId, + _peer: PeerId, + _addr: &Multiaddr, + _role_override: Endpoint, + ) -> Result, ConnectionDenied> { + Err(ConnectionDenied::new(std::io::Error::new( + std::io::ErrorKind::Other, + "ConnectionDenier", + ))) + } + + fn on_swarm_event(&mut self, _event: FromSwarm) {} + + fn on_connection_handler_event( + &mut self, + _peer_id: PeerId, + _connection_id: ConnectionId, + event: THandlerOutEvent, + ) { + void::unreachable(event) + } + + fn poll( + &mut self, + _cx: &mut Context<'_>, + _params: &mut impl PollParameters, + ) -> Poll>> { + Poll::Pending + } + } +} diff --git a/misc/memory-connection-limits/CHANGELOG.md b/misc/memory-connection-limits/CHANGELOG.md new file mode 100644 index 00000000000..951a5a3f138 --- /dev/null +++ b/misc/memory-connection-limits/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +- Initial release. diff --git a/misc/memory-connection-limits/Cargo.toml b/misc/memory-connection-limits/Cargo.toml new file mode 100644 index 00000000000..cd0aeadf497 --- /dev/null +++ b/misc/memory-connection-limits/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "libp2p-memory-connection-limits" +edition = "2021" +rust-version = { workspace = true } +description = "Memory usage based connection limits for libp2p." +version = "0.1.0" +license = "MIT" +repository = "https://github.com/libp2p/rust-libp2p" +keywords = ["peer-to-peer", "libp2p", "networking"] +categories = ["network-programming", "asynchronous"] + +[dependencies] +memory-stats = "1" +libp2p-core = { workspace = true } +libp2p-swarm = { workspace = true } +libp2p-identity = { workspace = true, features = ["peerid"] } +log = "0.4" +sysinfo = "0.29" +void = "1" + +[dev-dependencies] +async-std = { version = "1.12.0", features = ["attributes"] } +libp2p-identify = { workspace = true } +libp2p-ping = { workspace = true } +libp2p-swarm-derive = { path = "../../swarm-derive" } +libp2p-swarm-test = { path = "../../swarm-test" } +quickcheck = { workspace = true } +rand = "0.8.5" diff --git a/misc/connection-limits/src/connection_limits/memory.rs b/misc/memory-connection-limits/src/connection_limits.rs similarity index 55% rename from misc/connection-limits/src/connection_limits/memory.rs rename to misc/memory-connection-limits/src/connection_limits.rs index 959c958f202..b2f03a6e236 100644 --- a/misc/connection-limits/src/connection_limits/memory.rs +++ b/misc/memory-connection-limits/src/connection_limits.rs @@ -19,11 +19,8 @@ // DEALINGS IN THE SOFTWARE. use super::*; -use parking_lot::RwLock; -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; +use libp2p_swarm::ConnectionDenied; +use std::fmt; /// The configurable memory usage based connection limits. #[derive(Debug)] @@ -31,7 +28,6 @@ pub struct MemoryUsageBasedConnectionLimits { max_process_memory_usage_bytes: Option, max_process_memory_usage_percentage: Option, system_physical_memory_bytes: usize, - mem_tracker: ProcessMemoryUsageTracker, } impl MemoryUsageBasedConnectionLimits { @@ -44,7 +40,6 @@ impl MemoryUsageBasedConnectionLimits { max_process_memory_usage_bytes: None, max_process_memory_usage_percentage: None, system_physical_memory_bytes: system_info.total_memory() as usize, - mem_tracker: ProcessMemoryUsageTracker::new(), } } @@ -62,6 +57,24 @@ impl MemoryUsageBasedConnectionLimits { self } + pub(crate) fn check_limit( + &self, + mem_tracker: &mut ProcessMemoryUsageTracker, + ) -> Result<(), ConnectionDenied> { + if let Some(max_allowed_bytes) = self.max_allowed_bytes() { + mem_tracker.refresh_if_needed(); + let process_memory_bytes = mem_tracker.total_bytes(); + if process_memory_bytes > max_allowed_bytes { + return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { + process_memory_bytes, + max_allowed_bytes, + })); + } + } + + Ok(()) + } + fn max_allowed_bytes(&self) -> Option { self.max_process_memory_usage_bytes.min( self.max_process_memory_usage_percentage @@ -76,30 +89,6 @@ impl Default for MemoryUsageBasedConnectionLimits { } } -impl ConnectionLimitsChecker for MemoryUsageBasedConnectionLimits { - fn check_limit(&self, kind: ConnectionKind, _current: usize) -> Result<(), ConnectionDenied> { - use ConnectionKind::*; - - match kind { - PendingIncoming | PendingOutgoing => { - if let Some(max_allowed_bytes) = self.max_allowed_bytes() { - self.mem_tracker.refresh_if_needed(); - let process_memory_bytes = self.mem_tracker.total_bytes(); - if process_memory_bytes > max_allowed_bytes { - return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { - process_memory_bytes, - max_allowed_bytes, - })); - } - } - } - EstablishedIncoming | EstablishedOutgoing | EstablishedPerPeer | EstablishedTotal => {} - }; - - Ok(()) - } -} - /// A connection limit has been exceeded. #[derive(Debug, Clone, Copy)] pub struct MemoryUsageLimitExceeded { @@ -119,63 +108,3 @@ impl fmt::Display for MemoryUsageLimitExceeded { ) } } - -#[derive(Debug)] -struct ProcessMemoryUsageTracker(Arc>); - -impl ProcessMemoryUsageTracker { - fn new() -> Self { - Self(Arc::new(RwLock::new(ProcessMemoryUsageTrackerInner::new()))) - } - - fn refresh_if_needed(&self) { - let need_refresh = self.0.read().need_refresh(); - if need_refresh { - self.0.write().refresh(); - } - } - - fn total_bytes(&self) -> usize { - let guard = self.0.read(); - guard.physical_bytes + guard.virtual_bytes - } -} - -#[derive(Debug)] -struct ProcessMemoryUsageTrackerInner { - last_checked: Instant, - physical_bytes: usize, - virtual_bytes: usize, -} - -impl ProcessMemoryUsageTrackerInner { - fn new() -> Self { - let stats = memory_stats::memory_stats(); - if stats.is_none() { - log::warn!("Failed to retrive process memory stats"); - } - Self { - last_checked: Instant::now(), - physical_bytes: stats.map(|s| s.physical_mem).unwrap_or_default(), - virtual_bytes: stats - .map(|s: memory_stats::MemoryStats| s.virtual_mem) - .unwrap_or_default(), - } - } - - fn need_refresh(&self) -> bool { - const PROCESS_MEMORY_USAGE_CHECK_INTERVAL: Duration = Duration::from_millis(1000); - - self.last_checked + PROCESS_MEMORY_USAGE_CHECK_INTERVAL < Instant::now() - } - - fn refresh(&mut self) { - if let Some(stats) = memory_stats::memory_stats() { - self.physical_bytes = stats.physical_mem; - self.virtual_bytes = stats.virtual_mem; - } else { - log::warn!("Failed to retrive process memory stats"); - } - self.last_checked = Instant::now(); - } -} diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs new file mode 100644 index 00000000000..93e5eed9b77 --- /dev/null +++ b/misc/memory-connection-limits/src/lib.rs @@ -0,0 +1,172 @@ +// Copyright 2023 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +mod connection_limits; +pub use connection_limits::{MemoryUsageBasedConnectionLimits, MemoryUsageLimitExceeded}; + +use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + dummy, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, PollParameters, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, +}; +use void::Void; + +use std::{ + task::{Context, Poll}, + time::{Duration, Instant}, +}; + +pub struct Behaviour { + limits: MemoryUsageBasedConnectionLimits, + mem_tracker: ProcessMemoryUsageTracker, +} + +impl Behaviour { + pub fn new(limits: MemoryUsageBasedConnectionLimits) -> Self { + Self { + limits, + mem_tracker: ProcessMemoryUsageTracker::new(), + } + } +} + +impl NetworkBehaviour for Behaviour { + type ConnectionHandler = dummy::ConnectionHandler; + type ToSwarm = Void; + + fn handle_pending_inbound_connection( + &mut self, + _: ConnectionId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result<(), ConnectionDenied> { + self.limits.check_limit(&mut self.mem_tracker) + } + + fn handle_pending_outbound_connection( + &mut self, + _: ConnectionId, + _: Option, + _: &[Multiaddr], + _: Endpoint, + ) -> Result, ConnectionDenied> { + self.limits.check_limit(&mut self.mem_tracker)?; + Ok(vec![]) + } + + fn handle_established_inbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result, ConnectionDenied> { + Ok(dummy::ConnectionHandler) + } + + fn handle_established_outbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: Endpoint, + ) -> Result, ConnectionDenied> { + Ok(dummy::ConnectionHandler) + } + + fn on_swarm_event(&mut self, _: FromSwarm) {} + + fn on_connection_handler_event( + &mut self, + _id: PeerId, + _: ConnectionId, + event: THandlerOutEvent, + ) { + void::unreachable(event) + } + + fn poll( + &mut self, + _: &mut Context<'_>, + _: &mut impl PollParameters, + ) -> Poll>> { + Poll::Pending + } +} + +#[derive(Debug, Clone, Copy)] +pub enum ConnectionKind { + PendingIncoming, + PendingOutgoing, + EstablishedIncoming, + EstablishedOutgoing, + EstablishedPerPeer, + EstablishedTotal, +} + +#[derive(Debug)] +struct ProcessMemoryUsageTracker { + last_checked: Instant, + physical_bytes: usize, + virtual_bytes: usize, +} + +impl ProcessMemoryUsageTracker { + fn new() -> Self { + let stats = memory_stats::memory_stats(); + if stats.is_none() { + log::warn!("Failed to retrive process memory stats"); + } + Self { + last_checked: Instant::now(), + physical_bytes: stats.map(|s| s.physical_mem).unwrap_or_default(), + virtual_bytes: stats + .map(|s: memory_stats::MemoryStats| s.virtual_mem) + .unwrap_or_default(), + } + } + + fn need_refresh(&self) -> bool { + const PROCESS_MEMORY_USAGE_CHECK_INTERVAL: Duration = Duration::from_millis(1000); + + self.last_checked + PROCESS_MEMORY_USAGE_CHECK_INTERVAL < Instant::now() + } + + fn refresh(&mut self) { + if let Some(stats) = memory_stats::memory_stats() { + self.physical_bytes = stats.physical_mem; + self.virtual_bytes = stats.virtual_mem; + } else { + log::warn!("Failed to retrive process memory stats"); + } + self.last_checked = Instant::now(); + } + + fn refresh_if_needed(&mut self) { + if self.need_refresh() { + self.refresh(); + } + } + + fn total_bytes(&self) -> usize { + self.physical_bytes + self.virtual_bytes + } +} From 6066b2e2d6ab9072de2214d9f02e8379ba0b9230 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 3 Aug 2023 20:31:09 +0800 Subject: [PATCH 10/35] doc --- misc/memory-connection-limits/src/lib.rs | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 93e5eed9b77..d5ecdb4737f 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -34,6 +34,34 @@ use std::{ time::{Duration, Instant}, }; +/// A [`NetworkBehaviour`] that enforces a set of memory usage based [`ConnectionLimits`]. +/// +/// For these limits to take effect, this needs to be composed into the behaviour tree of your application. +/// +/// If a connection is denied due to a limit, either a [`SwarmEvent::IncomingConnectionError`](libp2p_swarm::SwarmEvent::IncomingConnectionError) +/// or [`SwarmEvent::OutgoingConnectionError`](libp2p_swarm::SwarmEvent::OutgoingConnectionError) will be emitted. +/// The [`ListenError::Denied`](libp2p_swarm::ListenError::Denied) and respectively the [`DialError::Denied`](libp2p_swarm::DialError::Denied) variant +/// contain a [`ConnectionDenied`](libp2p_swarm::ConnectionDenied) type that can be downcast to [`MemoryUsageLimitExceeded`] error if (and only if) **this** +/// behaviour denied the connection. +/// +/// If you employ multiple [`NetworkBehaviour`]s that manage connections, it may also be a different error. +/// +/// # Example +/// +/// ```rust +/// # use libp2p_identify as identify; +/// # use libp2p_ping as ping; +/// # use libp2p_swarm_derive::NetworkBehaviour; +/// # use libp2p_memory_connection_limits as connection_limits; +/// +/// #[derive(NetworkBehaviour)] +/// # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] +/// struct MyBehaviour { +/// identify: identify::Behaviour, +/// ping: ping::Behaviour, +/// limits: connection_limits::Behaviour +/// } +/// ``` pub struct Behaviour { limits: MemoryUsageBasedConnectionLimits, mem_tracker: ProcessMemoryUsageTracker, From 1fa32f08b6ca34badcca7bf267289fb71925737f Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 3 Aug 2023 20:32:57 +0800 Subject: [PATCH 11/35] lock --- Cargo.lock | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfe7a388197..c647aeebee4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1363,12 +1363,6 @@ dependencies = [ "rusticata-macros", ] -[[package]] -name = "deranged" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" - [[package]] name = "derive_builder" version = "0.11.2" @@ -1598,9 +1592,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", @@ -3453,9 +3447,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] name = "lock_api" @@ -4873,7 +4867,7 @@ dependencies = [ "bitflags 2.3.3", "errno", "libc", - "linux-raw-sys 0.4.5", + "linux-raw-sys 0.4.3", "windows-sys", ] @@ -5112,18 +5106,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.180" +version = "1.0.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.180" +version = "1.0.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" dependencies = [ "proc-macro2", "quote", @@ -5623,11 +5617,10 @@ dependencies = [ [[package]] name = "time" -version = "0.3.24" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ - "deranged", "itoa", "serde", "time-core", @@ -5642,9 +5635,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] From 05cbf5ef9643c3aec159fa64df33a5d1a8a2b055 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 3 Aug 2023 23:21:32 +0800 Subject: [PATCH 12/35] doc and tests --- Cargo.lock | 2 - misc/memory-connection-limits/Cargo.toml | 2 - .../src/connection_limits.rs | 27 ++-- misc/memory-connection-limits/src/lib.rs | 28 ++-- .../tests/max_bytes.rs | 82 ++++++++++++ .../tests/max_percentage.rs | 80 ++++++++++++ misc/memory-connection-limits/tests/util.rs | 122 ++++++++++++++++++ 7 files changed, 320 insertions(+), 23 deletions(-) create mode 100644 misc/memory-connection-limits/tests/max_bytes.rs create mode 100644 misc/memory-connection-limits/tests/max_percentage.rs create mode 100644 misc/memory-connection-limits/tests/util.rs diff --git a/Cargo.lock b/Cargo.lock index c647aeebee4..43654713404 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2878,13 +2878,11 @@ dependencies = [ "libp2p-core", "libp2p-identify", "libp2p-identity", - "libp2p-ping", "libp2p-swarm", "libp2p-swarm-derive", "libp2p-swarm-test", "log", "memory-stats", - "quickcheck-ext", "rand 0.8.5", "sysinfo", "void", diff --git a/misc/memory-connection-limits/Cargo.toml b/misc/memory-connection-limits/Cargo.toml index cd0aeadf497..3efe3406f9b 100644 --- a/misc/memory-connection-limits/Cargo.toml +++ b/misc/memory-connection-limits/Cargo.toml @@ -21,8 +21,6 @@ void = "1" [dev-dependencies] async-std = { version = "1.12.0", features = ["attributes"] } libp2p-identify = { workspace = true } -libp2p-ping = { workspace = true } libp2p-swarm-derive = { path = "../../swarm-derive" } libp2p-swarm-test = { path = "../../swarm-test" } -quickcheck = { workspace = true } rand = "0.8.5" diff --git a/misc/memory-connection-limits/src/connection_limits.rs b/misc/memory-connection-limits/src/connection_limits.rs index b2f03a6e236..b37e23d4033 100644 --- a/misc/memory-connection-limits/src/connection_limits.rs +++ b/misc/memory-connection-limits/src/connection_limits.rs @@ -63,10 +63,9 @@ impl MemoryUsageBasedConnectionLimits { ) -> Result<(), ConnectionDenied> { if let Some(max_allowed_bytes) = self.max_allowed_bytes() { mem_tracker.refresh_if_needed(); - let process_memory_bytes = mem_tracker.total_bytes(); - if process_memory_bytes > max_allowed_bytes { + if mem_tracker.physical_bytes > max_allowed_bytes { return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { - process_memory_bytes, + process_physical_memory_bytes: mem_tracker.physical_bytes, max_allowed_bytes, })); } @@ -76,10 +75,18 @@ impl MemoryUsageBasedConnectionLimits { } fn max_allowed_bytes(&self) -> Option { - self.max_process_memory_usage_bytes.min( - self.max_process_memory_usage_percentage - .map(|p| (self.system_physical_memory_bytes as f64 * p).round() as usize), - ) + let max_process_memory_usage_percentage = self + .max_process_memory_usage_percentage + .map(|p| (self.system_physical_memory_bytes as f64 * p).round() as usize); + match ( + self.max_process_memory_usage_bytes, + max_process_memory_usage_percentage, + ) { + (None, None) => None, + (Some(a), Some(b)) => Some(a.min(b)), + (Some(a), None) => Some(a), + (None, Some(b)) => Some(b), + } } } @@ -92,7 +99,7 @@ impl Default for MemoryUsageBasedConnectionLimits { /// A connection limit has been exceeded. #[derive(Debug, Clone, Copy)] pub struct MemoryUsageLimitExceeded { - pub process_memory_bytes: usize, + pub process_physical_memory_bytes: usize, pub max_allowed_bytes: usize, } @@ -102,8 +109,8 @@ impl fmt::Display for MemoryUsageLimitExceeded { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "pending connections are dropped, process memory usage limit exceeded: process memory: {} bytes, max allowed: {} bytes", - self.process_memory_bytes, + "pending connections are dropped, process physical memory usage limit exceeded: process memory: {} bytes, max allowed: {} bytes", + self.process_physical_memory_bytes, self.max_allowed_bytes, ) } diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index d5ecdb4737f..10ff106ca55 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -50,7 +50,6 @@ use std::{ /// /// ```rust /// # use libp2p_identify as identify; -/// # use libp2p_ping as ping; /// # use libp2p_swarm_derive::NetworkBehaviour; /// # use libp2p_memory_connection_limits as connection_limits; /// @@ -58,7 +57,6 @@ use std::{ /// # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] /// struct MyBehaviour { /// identify: identify::Behaviour, -/// ping: ping::Behaviour, /// limits: connection_limits::Behaviour /// } /// ``` @@ -74,6 +72,16 @@ impl Behaviour { mem_tracker: ProcessMemoryUsageTracker::new(), } } + + pub fn update_limits(&mut self, limits: MemoryUsageBasedConnectionLimits) { + self.limits = limits; + } + + /// Sets memory usage refresh interval, default is 1s. Use `None` to always refresh + pub fn with_memory_usage_refresh_interval(mut self, interval: Option) -> Self { + self.mem_tracker.refresh_interval = interval; + self + } } impl NetworkBehaviour for Behaviour { @@ -152,6 +160,7 @@ pub enum ConnectionKind { #[derive(Debug)] struct ProcessMemoryUsageTracker { + refresh_interval: Option, last_checked: Instant, physical_bytes: usize, virtual_bytes: usize, @@ -159,11 +168,14 @@ struct ProcessMemoryUsageTracker { impl ProcessMemoryUsageTracker { fn new() -> Self { + const DEFAULT_MEMORY_USAGE_REFRESH_INTERVAL: Duration = Duration::from_millis(1000); + let stats = memory_stats::memory_stats(); if stats.is_none() { log::warn!("Failed to retrive process memory stats"); } Self { + refresh_interval: Some(DEFAULT_MEMORY_USAGE_REFRESH_INTERVAL), last_checked: Instant::now(), physical_bytes: stats.map(|s| s.physical_mem).unwrap_or_default(), virtual_bytes: stats @@ -173,9 +185,11 @@ impl ProcessMemoryUsageTracker { } fn need_refresh(&self) -> bool { - const PROCESS_MEMORY_USAGE_CHECK_INTERVAL: Duration = Duration::from_millis(1000); - - self.last_checked + PROCESS_MEMORY_USAGE_CHECK_INTERVAL < Instant::now() + if let Some(refresh_interval) = self.refresh_interval { + self.last_checked + refresh_interval < Instant::now() + } else { + true + } } fn refresh(&mut self) { @@ -193,8 +207,4 @@ impl ProcessMemoryUsageTracker { self.refresh(); } } - - fn total_bytes(&self) -> usize { - self.physical_bytes + self.virtual_bytes - } } diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs new file mode 100644 index 00000000000..91759043d9c --- /dev/null +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -0,0 +1,82 @@ +// Copyright 2023 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +mod util; +use libp2p_core::Multiaddr; +use libp2p_identity::PeerId; +use libp2p_memory_connection_limits::*; +use util::*; + +use libp2p_swarm::{dial_opts::DialOpts, DialError, Swarm}; +use libp2p_swarm_test::SwarmExt; +use rand::{rngs::OsRng, Rng}; + +#[test] +fn max_bytes() { + let connection_limit = OsRng.gen_range(1..30); + let max_allowed_bytes = connection_limit * 1024 * 1024; + + let mut network = Swarm::new_ephemeral(|_| TestBehaviour { + connection_limits: Behaviour::new( + MemoryUsageBasedConnectionLimits::default().with_max_bytes(max_allowed_bytes), + ) + .with_memory_usage_refresh_interval(None), + mem: Default::default(), + }); + + // Adds current mem usage to the limit and update + let current_mem = memory_stats::memory_stats().unwrap().physical_mem; + let max_allowed_bytes_plus_base_usage = max_allowed_bytes + current_mem; + network.behaviour_mut().connection_limits.update_limits( + MemoryUsageBasedConnectionLimits::default() + .with_max_bytes(max_allowed_bytes_plus_base_usage), + ); + + let addr: Multiaddr = "/memory/1234".parse().unwrap(); + let target = PeerId::random(); + + for _ in 0..connection_limit { + network + .dial( + DialOpts::peer_id(target) + .addresses(vec![addr.clone()]) + .build(), + ) + .expect("Unexpected connection limit."); + } + + match network + .dial(DialOpts::peer_id(target).addresses(vec![addr]).build()) + .expect_err("Unexpected dialing success.") + { + DialError::Denied { cause } => { + let exceeded = cause + .downcast::() + .expect("connection denied because of limit"); + + assert_eq!( + exceeded.max_allowed_bytes, + max_allowed_bytes_plus_base_usage + ); + assert!(exceeded.process_physical_memory_bytes >= exceeded.max_allowed_bytes); + } + e => panic!("Unexpected error: {e:?}"), + } +} diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs new file mode 100644 index 00000000000..fe5097d0df6 --- /dev/null +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -0,0 +1,80 @@ +// Copyright 2023 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +mod util; +use libp2p_core::Multiaddr; +use libp2p_identity::PeerId; +use libp2p_memory_connection_limits::*; +use sysinfo::{RefreshKind, SystemExt}; +use util::*; + +use libp2p_swarm::{dial_opts::DialOpts, DialError, Swarm}; +use libp2p_swarm_test::SwarmExt; +use rand::{rngs::OsRng, Rng}; + +#[test] +fn max_percentage() { + let connection_limit = OsRng.gen_range(1..30); + let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); + + let mut network = Swarm::new_ephemeral(|_| TestBehaviour { + connection_limits: Behaviour::new( + MemoryUsageBasedConnectionLimits::default().with_max_percentage(0.1), + ) + .with_memory_usage_refresh_interval(None), + mem: Default::default(), + }); + + // Adds current mem usage to the limit and update + let current_mem = memory_stats::memory_stats().unwrap().physical_mem; + let max_allowed_bytes = current_mem + connection_limit * 1024 * 1024; + network.behaviour_mut().connection_limits.update_limits( + MemoryUsageBasedConnectionLimits::default() + .with_max_percentage(max_allowed_bytes as f64 / system_info.total_memory() as f64), + ); + + let addr: Multiaddr = "/memory/1234".parse().unwrap(); + let target = PeerId::random(); + + for _ in 0..connection_limit { + network + .dial( + DialOpts::peer_id(target) + .addresses(vec![addr.clone()]) + .build(), + ) + .expect("Unexpected connection limit."); + } + + match network + .dial(DialOpts::peer_id(target).addresses(vec![addr]).build()) + .expect_err("Unexpected dialing success.") + { + DialError::Denied { cause } => { + let exceeded = cause + .downcast::() + .expect("connection denied because of limit"); + + assert_eq!(exceeded.max_allowed_bytes, max_allowed_bytes); + assert!(exceeded.process_physical_memory_bytes >= exceeded.max_allowed_bytes); + } + e => panic!("Unexpected error: {e:?}"), + } +} diff --git a/misc/memory-connection-limits/tests/util.rs b/misc/memory-connection-limits/tests/util.rs new file mode 100644 index 00000000000..b03459636dd --- /dev/null +++ b/misc/memory-connection-limits/tests/util.rs @@ -0,0 +1,122 @@ +// Copyright 2023 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use std::task::{Context, Poll}; + +use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + dummy, ConnectionClosed, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, + PollParameters, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, +}; +use void::Void; + +#[derive(libp2p_swarm_derive::NetworkBehaviour)] +#[behaviour(prelude = "libp2p_swarm::derive_prelude")] +pub struct TestBehaviour { + pub connection_limits: libp2p_memory_connection_limits::Behaviour, + pub mem: ConsumeMemoryBehaviour, +} + +#[derive(Default)] +pub struct ConsumeMemoryBehaviour { + map: Vec>, +} + +impl ConsumeMemoryBehaviour { + fn handle_connection(&mut self) { + // 1MB + self.map.push(vec![1; 1024 * 1024]); + } +} + +impl NetworkBehaviour for ConsumeMemoryBehaviour { + type ConnectionHandler = dummy::ConnectionHandler; + type ToSwarm = Void; + + fn handle_pending_inbound_connection( + &mut self, + _: ConnectionId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result<(), ConnectionDenied> { + self.handle_connection(); + Ok(()) + } + + fn handle_pending_outbound_connection( + &mut self, + _: ConnectionId, + _: Option, + _: &[Multiaddr], + _: Endpoint, + ) -> Result, ConnectionDenied> { + self.handle_connection(); + Ok(vec![]) + } + + fn handle_established_inbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result, ConnectionDenied> { + self.handle_connection(); + Ok(dummy::ConnectionHandler) + } + + fn handle_established_outbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: Endpoint, + ) -> Result, ConnectionDenied> { + self.handle_connection(); + Ok(dummy::ConnectionHandler) + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::ConnectionClosed(ConnectionClosed { .. }) => { + self.map.pop(); + } + _ => {} + } + } + + fn on_connection_handler_event( + &mut self, + _id: PeerId, + _: ConnectionId, + event: THandlerOutEvent, + ) { + void::unreachable(event) + } + + fn poll( + &mut self, + _: &mut Context<'_>, + _: &mut impl PollParameters, + ) -> Poll>> { + Poll::Pending + } +} From 7d05ee5d9cb38fbaab98a345a696fa37df59cea9 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 3 Aug 2023 23:25:49 +0800 Subject: [PATCH 13/35] Apply suggestions from code review Co-authored-by: Thomas Eizinger --- misc/memory-connection-limits/CHANGELOG.md | 2 +- misc/memory-connection-limits/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/memory-connection-limits/CHANGELOG.md b/misc/memory-connection-limits/CHANGELOG.md index 951a5a3f138..5dac7afbf35 100644 --- a/misc/memory-connection-limits/CHANGELOG.md +++ b/misc/memory-connection-limits/CHANGELOG.md @@ -1,3 +1,3 @@ -## 0.1.0 +## 0.1.0 - unreleased - Initial release. diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 10ff106ca55..d8d9a9df707 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -51,7 +51,7 @@ use std::{ /// ```rust /// # use libp2p_identify as identify; /// # use libp2p_swarm_derive::NetworkBehaviour; -/// # use libp2p_memory_connection_limits as connection_limits; +/// # use libp2p_memory_connection_limits as memory_connection_limits; /// /// #[derive(NetworkBehaviour)] /// # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] From 9ba799eb3dcee3500e414127a5294bc4f2082517 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 3 Aug 2023 23:40:39 +0800 Subject: [PATCH 14/35] hide MemoryUsageBasedConnectionLimits --- .../src/connection_limits.rs | 23 +---------- misc/memory-connection-limits/src/error.rs | 41 +++++++++++++++++++ misc/memory-connection-limits/src/lib.rs | 32 ++++++++++++--- .../tests/max_bytes.rs | 14 +++---- .../tests/max_percentage.rs | 14 +++---- 5 files changed, 80 insertions(+), 44 deletions(-) create mode 100644 misc/memory-connection-limits/src/error.rs diff --git a/misc/memory-connection-limits/src/connection_limits.rs b/misc/memory-connection-limits/src/connection_limits.rs index b37e23d4033..4f452ae2daf 100644 --- a/misc/memory-connection-limits/src/connection_limits.rs +++ b/misc/memory-connection-limits/src/connection_limits.rs @@ -20,11 +20,10 @@ use super::*; use libp2p_swarm::ConnectionDenied; -use std::fmt; /// The configurable memory usage based connection limits. #[derive(Debug)] -pub struct MemoryUsageBasedConnectionLimits { +pub(crate) struct MemoryUsageBasedConnectionLimits { max_process_memory_usage_bytes: Option, max_process_memory_usage_percentage: Option, system_physical_memory_bytes: usize, @@ -95,23 +94,3 @@ impl Default for MemoryUsageBasedConnectionLimits { Self::new() } } - -/// A connection limit has been exceeded. -#[derive(Debug, Clone, Copy)] -pub struct MemoryUsageLimitExceeded { - pub process_physical_memory_bytes: usize, - pub max_allowed_bytes: usize, -} - -impl std::error::Error for MemoryUsageLimitExceeded {} - -impl fmt::Display for MemoryUsageLimitExceeded { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "pending connections are dropped, process physical memory usage limit exceeded: process memory: {} bytes, max allowed: {} bytes", - self.process_physical_memory_bytes, - self.max_allowed_bytes, - ) - } -} diff --git a/misc/memory-connection-limits/src/error.rs b/misc/memory-connection-limits/src/error.rs new file mode 100644 index 00000000000..04f58431e1f --- /dev/null +++ b/misc/memory-connection-limits/src/error.rs @@ -0,0 +1,41 @@ +// Copyright 2023 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use std::fmt; + +/// A connection limit has been exceeded. +#[derive(Debug, Clone, Copy)] +pub struct MemoryUsageLimitExceeded { + pub process_physical_memory_bytes: usize, + pub max_allowed_bytes: usize, +} + +impl std::error::Error for MemoryUsageLimitExceeded {} + +impl fmt::Display for MemoryUsageLimitExceeded { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "pending connections are dropped, process physical memory usage limit exceeded: process memory: {} bytes, max allowed: {} bytes", + self.process_physical_memory_bytes, + self.max_allowed_bytes, + ) + } +} diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index d8d9a9df707..2c9cb5c0665 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -19,7 +19,10 @@ // DEALINGS IN THE SOFTWARE. mod connection_limits; -pub use connection_limits::{MemoryUsageBasedConnectionLimits, MemoryUsageLimitExceeded}; +use connection_limits::MemoryUsageBasedConnectionLimits; + +mod error; +pub use error::MemoryUsageLimitExceeded; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; @@ -57,7 +60,7 @@ use std::{ /// # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] /// struct MyBehaviour { /// identify: identify::Behaviour, -/// limits: connection_limits::Behaviour +/// limits: memory_connection_limits::Behaviour /// } /// ``` pub struct Behaviour { @@ -66,15 +69,22 @@ pub struct Behaviour { } impl Behaviour { - pub fn new(limits: MemoryUsageBasedConnectionLimits) -> Self { + /// Sets the process memory usage threshold in bytes, + /// all pending connections will be dropped when the threshold is exeeded + pub fn new_with_max_bytes(bytes: usize) -> Self { Self { - limits, + limits: MemoryUsageBasedConnectionLimits::default().with_max_bytes(bytes), mem_tracker: ProcessMemoryUsageTracker::new(), } } - pub fn update_limits(&mut self, limits: MemoryUsageBasedConnectionLimits) { - self.limits = limits; + /// Sets the process memory usage threshold in the percentage of the total physical memory, + /// all pending connections will be dropped when the threshold is exeeded + pub fn new_with_max_percentage(percentage: f64) -> Self { + Self { + limits: MemoryUsageBasedConnectionLimits::default().with_max_percentage(percentage), + mem_tracker: ProcessMemoryUsageTracker::new(), + } } /// Sets memory usage refresh interval, default is 1s. Use `None` to always refresh @@ -82,6 +92,16 @@ impl Behaviour { self.mem_tracker.refresh_interval = interval; self } + + /// Updates the process memory usage threshold in bytes, + pub fn update_max_bytes(&mut self, bytes: usize) { + self.limits = MemoryUsageBasedConnectionLimits::default().with_max_bytes(bytes); + } + + /// Updates the process memory usage threshold in the percentage of the total physical memory, + pub fn update_max_percentage(&mut self, percentage: f64) { + self.limits = MemoryUsageBasedConnectionLimits::default().with_max_percentage(percentage); + } } impl NetworkBehaviour for Behaviour { diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs index 91759043d9c..fb9ea238156 100644 --- a/misc/memory-connection-limits/tests/max_bytes.rs +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -34,20 +34,18 @@ fn max_bytes() { let max_allowed_bytes = connection_limit * 1024 * 1024; let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::new( - MemoryUsageBasedConnectionLimits::default().with_max_bytes(max_allowed_bytes), - ) - .with_memory_usage_refresh_interval(None), + connection_limits: Behaviour::new_with_max_bytes(max_allowed_bytes) + .with_memory_usage_refresh_interval(None), mem: Default::default(), }); // Adds current mem usage to the limit and update let current_mem = memory_stats::memory_stats().unwrap().physical_mem; let max_allowed_bytes_plus_base_usage = max_allowed_bytes + current_mem; - network.behaviour_mut().connection_limits.update_limits( - MemoryUsageBasedConnectionLimits::default() - .with_max_bytes(max_allowed_bytes_plus_base_usage), - ); + network + .behaviour_mut() + .connection_limits + .update_max_bytes(max_allowed_bytes_plus_base_usage); let addr: Multiaddr = "/memory/1234".parse().unwrap(); let target = PeerId::random(); diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs index fe5097d0df6..75adaee723c 100644 --- a/misc/memory-connection-limits/tests/max_percentage.rs +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -35,20 +35,18 @@ fn max_percentage() { let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::new( - MemoryUsageBasedConnectionLimits::default().with_max_percentage(0.1), - ) - .with_memory_usage_refresh_interval(None), + connection_limits: Behaviour::new_with_max_percentage(0.1) + .with_memory_usage_refresh_interval(None), mem: Default::default(), }); // Adds current mem usage to the limit and update let current_mem = memory_stats::memory_stats().unwrap().physical_mem; let max_allowed_bytes = current_mem + connection_limit * 1024 * 1024; - network.behaviour_mut().connection_limits.update_limits( - MemoryUsageBasedConnectionLimits::default() - .with_max_percentage(max_allowed_bytes as f64 / system_info.total_memory() as f64), - ); + network + .behaviour_mut() + .connection_limits + .update_max_percentage(max_allowed_bytes as f64 / system_info.total_memory() as f64); let addr: Multiaddr = "/memory/1234".parse().unwrap(); let target = PeerId::random(); From b8a9916e083d1266b5228b922e30e21788496103 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 3 Aug 2023 23:52:01 +0800 Subject: [PATCH 15/35] update MemoryUsageLimitExceeded fmt as suggested --- misc/memory-connection-limits/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/memory-connection-limits/src/error.rs b/misc/memory-connection-limits/src/error.rs index 04f58431e1f..e6f45485863 100644 --- a/misc/memory-connection-limits/src/error.rs +++ b/misc/memory-connection-limits/src/error.rs @@ -33,7 +33,7 @@ impl fmt::Display for MemoryUsageLimitExceeded { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "pending connections are dropped, process physical memory usage limit exceeded: process memory: {} bytes, max allowed: {} bytes", + "process physical memory usage limit exceeded: process memory: {} bytes, max allowed: {} bytes", self.process_physical_memory_bytes, self.max_allowed_bytes, ) From f43fed50a078e814a4ffa7d47a620c220fcbbe47 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 4 Aug 2023 00:03:52 +0800 Subject: [PATCH 16/35] fix clippy --- .../src/connection_limits.rs | 6 +++--- misc/memory-connection-limits/tests/util.rs | 15 ++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/misc/memory-connection-limits/src/connection_limits.rs b/misc/memory-connection-limits/src/connection_limits.rs index 4f452ae2daf..6dc52220843 100644 --- a/misc/memory-connection-limits/src/connection_limits.rs +++ b/misc/memory-connection-limits/src/connection_limits.rs @@ -30,7 +30,7 @@ pub(crate) struct MemoryUsageBasedConnectionLimits { } impl MemoryUsageBasedConnectionLimits { - pub fn new() -> Self { + pub(crate) fn new() -> Self { use sysinfo::{RefreshKind, SystemExt}; let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); @@ -44,14 +44,14 @@ impl MemoryUsageBasedConnectionLimits { /// Sets the process memory usage threshold in bytes, /// all pending connections will be dropped when the threshold is exeeded - pub fn with_max_bytes(mut self, bytes: usize) -> Self { + pub(crate) fn with_max_bytes(mut self, bytes: usize) -> Self { self.max_process_memory_usage_bytes = Some(bytes); self } /// Sets the process memory usage threshold in the percentage of the total physical memory, /// all pending connections will be dropped when the threshold is exeeded - pub fn with_max_percentage(mut self, percentage: f64) -> Self { + pub(crate) fn with_max_percentage(mut self, percentage: f64) -> Self { self.max_process_memory_usage_percentage = Some(percentage); self } diff --git a/misc/memory-connection-limits/tests/util.rs b/misc/memory-connection-limits/tests/util.rs index b03459636dd..f5f34f5ebcc 100644 --- a/misc/memory-connection-limits/tests/util.rs +++ b/misc/memory-connection-limits/tests/util.rs @@ -30,13 +30,13 @@ use void::Void; #[derive(libp2p_swarm_derive::NetworkBehaviour)] #[behaviour(prelude = "libp2p_swarm::derive_prelude")] -pub struct TestBehaviour { - pub connection_limits: libp2p_memory_connection_limits::Behaviour, - pub mem: ConsumeMemoryBehaviour, +pub(crate) struct TestBehaviour { + pub(crate) connection_limits: libp2p_memory_connection_limits::Behaviour, + pub(crate) mem: ConsumeMemoryBehaviour, } #[derive(Default)] -pub struct ConsumeMemoryBehaviour { +pub(crate) struct ConsumeMemoryBehaviour { map: Vec>, } @@ -95,11 +95,8 @@ impl NetworkBehaviour for ConsumeMemoryBehaviour { } fn on_swarm_event(&mut self, event: FromSwarm) { - match event { - FromSwarm::ConnectionClosed(ConnectionClosed { .. }) => { - self.map.pop(); - } - _ => {} + if let FromSwarm::ConnectionClosed(ConnectionClosed { .. }) = event { + self.map.pop(); } } From 9a66866b0110ca0cdc3eca1c043e006c2753a6be Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 4 Aug 2023 00:05:50 +0800 Subject: [PATCH 17/35] fix rustdoc --- misc/memory-connection-limits/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 2c9cb5c0665..61a0bdba318 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -37,7 +37,7 @@ use std::{ time::{Duration, Instant}, }; -/// A [`NetworkBehaviour`] that enforces a set of memory usage based [`ConnectionLimits`]. +/// A [`NetworkBehaviour`] that enforces a set of memory usage based limits. /// /// For these limits to take effect, this needs to be composed into the behaviour tree of your application. /// From 7990be6e739e85fbd0d6968f3cca8020adbe4dc2 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 4 Aug 2023 16:13:08 +0800 Subject: [PATCH 18/35] Update misc/memory-connection-limits/src/lib.rs Co-authored-by: Thomas Eizinger --- misc/memory-connection-limits/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 61a0bdba318..31043e11778 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -69,8 +69,9 @@ pub struct Behaviour { } impl Behaviour { - /// Sets the process memory usage threshold in bytes, - /// all pending connections will be dropped when the threshold is exeeded + /// Sets the process memory usage threshold in absolute bytes. + /// + /// New inbound and outbound connections will be denied when the threshold is reached. pub fn new_with_max_bytes(bytes: usize) -> Self { Self { limits: MemoryUsageBasedConnectionLimits::default().with_max_bytes(bytes), From 07e30bbf4ab88996afc6c071137c3592f4498599 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 4 Aug 2023 16:54:38 +0800 Subject: [PATCH 19/35] resolve comments --- misc/memory-connection-limits/Cargo.toml | 2 +- .../src/connection_limits.rs | 96 ----------- misc/memory-connection-limits/src/error.rs | 41 ----- misc/memory-connection-limits/src/lib.rs | 156 ++++++++---------- .../tests/max_bytes.rs | 5 +- .../tests/max_percentage.rs | 5 +- misc/memory-connection-limits/tests/util.rs | 45 +++-- 7 files changed, 105 insertions(+), 245 deletions(-) delete mode 100644 misc/memory-connection-limits/src/connection_limits.rs delete mode 100644 misc/memory-connection-limits/src/error.rs diff --git a/misc/memory-connection-limits/Cargo.toml b/misc/memory-connection-limits/Cargo.toml index 3efe3406f9b..6bfecfd2b0e 100644 --- a/misc/memory-connection-limits/Cargo.toml +++ b/misc/memory-connection-limits/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -memory-stats = "1" +memory-stats = { version = "1", features = ["always_use_statm"] } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true, features = ["peerid"] } diff --git a/misc/memory-connection-limits/src/connection_limits.rs b/misc/memory-connection-limits/src/connection_limits.rs deleted file mode 100644 index 6dc52220843..00000000000 --- a/misc/memory-connection-limits/src/connection_limits.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2023 Protocol Labs. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::*; -use libp2p_swarm::ConnectionDenied; - -/// The configurable memory usage based connection limits. -#[derive(Debug)] -pub(crate) struct MemoryUsageBasedConnectionLimits { - max_process_memory_usage_bytes: Option, - max_process_memory_usage_percentage: Option, - system_physical_memory_bytes: usize, -} - -impl MemoryUsageBasedConnectionLimits { - pub(crate) fn new() -> Self { - use sysinfo::{RefreshKind, SystemExt}; - - let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); - - MemoryUsageBasedConnectionLimits { - max_process_memory_usage_bytes: None, - max_process_memory_usage_percentage: None, - system_physical_memory_bytes: system_info.total_memory() as usize, - } - } - - /// Sets the process memory usage threshold in bytes, - /// all pending connections will be dropped when the threshold is exeeded - pub(crate) fn with_max_bytes(mut self, bytes: usize) -> Self { - self.max_process_memory_usage_bytes = Some(bytes); - self - } - - /// Sets the process memory usage threshold in the percentage of the total physical memory, - /// all pending connections will be dropped when the threshold is exeeded - pub(crate) fn with_max_percentage(mut self, percentage: f64) -> Self { - self.max_process_memory_usage_percentage = Some(percentage); - self - } - - pub(crate) fn check_limit( - &self, - mem_tracker: &mut ProcessMemoryUsageTracker, - ) -> Result<(), ConnectionDenied> { - if let Some(max_allowed_bytes) = self.max_allowed_bytes() { - mem_tracker.refresh_if_needed(); - if mem_tracker.physical_bytes > max_allowed_bytes { - return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { - process_physical_memory_bytes: mem_tracker.physical_bytes, - max_allowed_bytes, - })); - } - } - - Ok(()) - } - - fn max_allowed_bytes(&self) -> Option { - let max_process_memory_usage_percentage = self - .max_process_memory_usage_percentage - .map(|p| (self.system_physical_memory_bytes as f64 * p).round() as usize); - match ( - self.max_process_memory_usage_bytes, - max_process_memory_usage_percentage, - ) { - (None, None) => None, - (Some(a), Some(b)) => Some(a.min(b)), - (Some(a), None) => Some(a), - (None, Some(b)) => Some(b), - } - } -} - -impl Default for MemoryUsageBasedConnectionLimits { - fn default() -> Self { - Self::new() - } -} diff --git a/misc/memory-connection-limits/src/error.rs b/misc/memory-connection-limits/src/error.rs deleted file mode 100644 index e6f45485863..00000000000 --- a/misc/memory-connection-limits/src/error.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2023 Protocol Labs. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use std::fmt; - -/// A connection limit has been exceeded. -#[derive(Debug, Clone, Copy)] -pub struct MemoryUsageLimitExceeded { - pub process_physical_memory_bytes: usize, - pub max_allowed_bytes: usize, -} - -impl std::error::Error for MemoryUsageLimitExceeded {} - -impl fmt::Display for MemoryUsageLimitExceeded { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "process physical memory usage limit exceeded: process memory: {} bytes, max allowed: {} bytes", - self.process_physical_memory_bytes, - self.max_allowed_bytes, - ) - } -} diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 31043e11778..f4db643b4bf 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -18,12 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -mod connection_limits; -use connection_limits::MemoryUsageBasedConnectionLimits; - -mod error; -pub use error::MemoryUsageLimitExceeded; - use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ @@ -33,8 +27,8 @@ use libp2p_swarm::{ use void::Void; use std::{ + fmt, task::{Context, Poll}, - time::{Duration, Instant}, }; /// A [`NetworkBehaviour`] that enforces a set of memory usage based limits. @@ -64,44 +58,81 @@ use std::{ /// } /// ``` pub struct Behaviour { - limits: MemoryUsageBasedConnectionLimits, - mem_tracker: ProcessMemoryUsageTracker, + max_process_memory_usage_bytes: Option, + max_process_memory_usage_percentage: Option, + system_physical_memory_bytes: usize, } impl Behaviour { /// Sets the process memory usage threshold in absolute bytes. /// /// New inbound and outbound connections will be denied when the threshold is reached. - pub fn new_with_max_bytes(bytes: usize) -> Self { - Self { - limits: MemoryUsageBasedConnectionLimits::default().with_max_bytes(bytes), - mem_tracker: ProcessMemoryUsageTracker::new(), - } + pub fn with_max_bytes(bytes: usize) -> Self { + let mut b = Self::new(); + b.update_max_bytes(bytes); + b } /// Sets the process memory usage threshold in the percentage of the total physical memory, /// all pending connections will be dropped when the threshold is exeeded - pub fn new_with_max_percentage(percentage: f64) -> Self { - Self { - limits: MemoryUsageBasedConnectionLimits::default().with_max_percentage(percentage), - mem_tracker: ProcessMemoryUsageTracker::new(), - } - } - - /// Sets memory usage refresh interval, default is 1s. Use `None` to always refresh - pub fn with_memory_usage_refresh_interval(mut self, interval: Option) -> Self { - self.mem_tracker.refresh_interval = interval; - self + pub fn with_max_percentage(percentage: f64) -> Self { + let mut b = Self::new(); + b.update_max_percentage(percentage); + b } /// Updates the process memory usage threshold in bytes, pub fn update_max_bytes(&mut self, bytes: usize) { - self.limits = MemoryUsageBasedConnectionLimits::default().with_max_bytes(bytes); + self.max_process_memory_usage_bytes = Some(bytes); } /// Updates the process memory usage threshold in the percentage of the total physical memory, pub fn update_max_percentage(&mut self, percentage: f64) { - self.limits = MemoryUsageBasedConnectionLimits::default().with_max_percentage(percentage); + self.max_process_memory_usage_percentage = Some(percentage); + } + + fn new() -> Self { + use sysinfo::{RefreshKind, SystemExt}; + + let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); + + Self { + max_process_memory_usage_bytes: None, + max_process_memory_usage_percentage: None, + system_physical_memory_bytes: system_info.total_memory() as usize, + } + } + + fn check_limit(&self) -> Result<(), ConnectionDenied> { + if let Some(max_allowed_bytes) = self.max_allowed_bytes() { + if let Some(stats) = memory_stats::memory_stats() { + if stats.physical_mem > max_allowed_bytes { + return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { + process_physical_memory_bytes: stats.physical_mem, + max_allowed_bytes, + })); + } + } else { + log::warn!("Failed to retrive process memory stats"); + } + } + + Ok(()) + } + + fn max_allowed_bytes(&self) -> Option { + let max_process_memory_usage_percentage = self + .max_process_memory_usage_percentage + .map(|p| (self.system_physical_memory_bytes as f64 * p).round() as usize); + match ( + self.max_process_memory_usage_bytes, + max_process_memory_usage_percentage, + ) { + (None, None) => None, + (Some(a), Some(b)) => Some(a.min(b)), + (Some(a), None) => Some(a), + (None, Some(b)) => Some(b), + } } } @@ -115,7 +146,7 @@ impl NetworkBehaviour for Behaviour { _: &Multiaddr, _: &Multiaddr, ) -> Result<(), ConnectionDenied> { - self.limits.check_limit(&mut self.mem_tracker) + self.check_limit() } fn handle_pending_outbound_connection( @@ -125,7 +156,7 @@ impl NetworkBehaviour for Behaviour { _: &[Multiaddr], _: Endpoint, ) -> Result, ConnectionDenied> { - self.limits.check_limit(&mut self.mem_tracker)?; + self.check_limit()?; Ok(vec![]) } @@ -169,63 +200,22 @@ impl NetworkBehaviour for Behaviour { } } +/// A connection limit has been exceeded. #[derive(Debug, Clone, Copy)] -pub enum ConnectionKind { - PendingIncoming, - PendingOutgoing, - EstablishedIncoming, - EstablishedOutgoing, - EstablishedPerPeer, - EstablishedTotal, -} - -#[derive(Debug)] -struct ProcessMemoryUsageTracker { - refresh_interval: Option, - last_checked: Instant, - physical_bytes: usize, - virtual_bytes: usize, +pub struct MemoryUsageLimitExceeded { + pub process_physical_memory_bytes: usize, + pub max_allowed_bytes: usize, } -impl ProcessMemoryUsageTracker { - fn new() -> Self { - const DEFAULT_MEMORY_USAGE_REFRESH_INTERVAL: Duration = Duration::from_millis(1000); - - let stats = memory_stats::memory_stats(); - if stats.is_none() { - log::warn!("Failed to retrive process memory stats"); - } - Self { - refresh_interval: Some(DEFAULT_MEMORY_USAGE_REFRESH_INTERVAL), - last_checked: Instant::now(), - physical_bytes: stats.map(|s| s.physical_mem).unwrap_or_default(), - virtual_bytes: stats - .map(|s: memory_stats::MemoryStats| s.virtual_mem) - .unwrap_or_default(), - } - } - - fn need_refresh(&self) -> bool { - if let Some(refresh_interval) = self.refresh_interval { - self.last_checked + refresh_interval < Instant::now() - } else { - true - } - } +impl std::error::Error for MemoryUsageLimitExceeded {} - fn refresh(&mut self) { - if let Some(stats) = memory_stats::memory_stats() { - self.physical_bytes = stats.physical_mem; - self.virtual_bytes = stats.virtual_mem; - } else { - log::warn!("Failed to retrive process memory stats"); - } - self.last_checked = Instant::now(); - } - - fn refresh_if_needed(&mut self) { - if self.need_refresh() { - self.refresh(); - } +impl fmt::Display for MemoryUsageLimitExceeded { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "process physical memory usage limit exceeded: process memory: {} bytes, max allowed: {} bytes", + self.process_physical_memory_bytes, + self.max_allowed_bytes, + ) } } diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs index fb9ea238156..85d2fec5e82 100644 --- a/misc/memory-connection-limits/tests/max_bytes.rs +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -34,9 +34,8 @@ fn max_bytes() { let max_allowed_bytes = connection_limit * 1024 * 1024; let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::new_with_max_bytes(max_allowed_bytes) - .with_memory_usage_refresh_interval(None), - mem: Default::default(), + connection_limits: Behaviour::with_max_bytes(max_allowed_bytes), + mem_consumer: ConsumeMemoryBehaviour1MBPending0Established::default(), }); // Adds current mem usage to the limit and update diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs index 75adaee723c..200791b6cae 100644 --- a/misc/memory-connection-limits/tests/max_percentage.rs +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -35,9 +35,8 @@ fn max_percentage() { let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::new_with_max_percentage(0.1) - .with_memory_usage_refresh_interval(None), - mem: Default::default(), + connection_limits: Behaviour::with_max_percentage(0.1), + mem_consumer: ConsumeMemoryBehaviour1MBPending0Established::default(), }); // Adds current mem usage to the limit and update diff --git a/misc/memory-connection-limits/tests/util.rs b/misc/memory-connection-limits/tests/util.rs index f5f34f5ebcc..a2fd7c20fed 100644 --- a/misc/memory-connection-limits/tests/util.rs +++ b/misc/memory-connection-limits/tests/util.rs @@ -23,8 +23,8 @@ use std::task::{Context, Poll}; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ - dummy, ConnectionClosed, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, - PollParameters, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + dummy, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, PollParameters, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }; use void::Void; @@ -32,22 +32,35 @@ use void::Void; #[behaviour(prelude = "libp2p_swarm::derive_prelude")] pub(crate) struct TestBehaviour { pub(crate) connection_limits: libp2p_memory_connection_limits::Behaviour, - pub(crate) mem: ConsumeMemoryBehaviour, + pub(crate) mem_consumer: ConsumeMemoryBehaviour1MBPending0Established, } +pub(crate) type ConsumeMemoryBehaviour1MBPending0Established = + ConsumeMemoryBehaviour<{ 1024 * 1024 }, 0>; + #[derive(Default)] -pub(crate) struct ConsumeMemoryBehaviour { - map: Vec>, +pub(crate) struct ConsumeMemoryBehaviour { + mem_pending: Vec>, + mem_established: Vec>, } -impl ConsumeMemoryBehaviour { - fn handle_connection(&mut self) { +impl + ConsumeMemoryBehaviour +{ + fn handle_pending(&mut self) { // 1MB - self.map.push(vec![1; 1024 * 1024]); + self.mem_pending.push(vec![1; MEM_PENDING]); + } + + fn handle_established(&mut self) { + // 1MB + self.mem_established.push(vec![1; MEM_ESTABLISHED]); } } -impl NetworkBehaviour for ConsumeMemoryBehaviour { +impl NetworkBehaviour + for ConsumeMemoryBehaviour +{ type ConnectionHandler = dummy::ConnectionHandler; type ToSwarm = Void; @@ -57,7 +70,7 @@ impl NetworkBehaviour for ConsumeMemoryBehaviour { _: &Multiaddr, _: &Multiaddr, ) -> Result<(), ConnectionDenied> { - self.handle_connection(); + self.handle_pending(); Ok(()) } @@ -68,7 +81,7 @@ impl NetworkBehaviour for ConsumeMemoryBehaviour { _: &[Multiaddr], _: Endpoint, ) -> Result, ConnectionDenied> { - self.handle_connection(); + self.handle_pending(); Ok(vec![]) } @@ -79,7 +92,7 @@ impl NetworkBehaviour for ConsumeMemoryBehaviour { _: &Multiaddr, _: &Multiaddr, ) -> Result, ConnectionDenied> { - self.handle_connection(); + self.handle_established(); Ok(dummy::ConnectionHandler) } @@ -90,15 +103,11 @@ impl NetworkBehaviour for ConsumeMemoryBehaviour { _: &Multiaddr, _: Endpoint, ) -> Result, ConnectionDenied> { - self.handle_connection(); + self.handle_established(); Ok(dummy::ConnectionHandler) } - fn on_swarm_event(&mut self, event: FromSwarm) { - if let FromSwarm::ConnectionClosed(ConnectionClosed { .. }) = event { - self.map.pop(); - } - } + fn on_swarm_event(&mut self, _: FromSwarm) {} fn on_connection_handler_event( &mut self, From 6adf21fd23d3311b5d1ec9790e455dc07ea9ac45 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 4 Aug 2023 17:10:46 +0800 Subject: [PATCH 20/35] Make MemoryUsageLimitExceeded fields private --- misc/memory-connection-limits/src/lib.rs | 14 ++++++++++++-- misc/memory-connection-limits/tests/max_bytes.rs | 4 ++-- .../tests/max_percentage.rs | 4 ++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index f4db643b4bf..47cc60bdbed 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -203,8 +203,18 @@ impl NetworkBehaviour for Behaviour { /// A connection limit has been exceeded. #[derive(Debug, Clone, Copy)] pub struct MemoryUsageLimitExceeded { - pub process_physical_memory_bytes: usize, - pub max_allowed_bytes: usize, + process_physical_memory_bytes: usize, + max_allowed_bytes: usize, +} + +impl MemoryUsageLimitExceeded { + pub fn process_physical_memory_bytes(&self) -> usize { + self.process_physical_memory_bytes + } + + pub fn max_allowed_bytes(&self) -> usize { + self.max_allowed_bytes + } } impl std::error::Error for MemoryUsageLimitExceeded {} diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs index 85d2fec5e82..c9a66af16d5 100644 --- a/misc/memory-connection-limits/tests/max_bytes.rs +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -69,10 +69,10 @@ fn max_bytes() { .expect("connection denied because of limit"); assert_eq!( - exceeded.max_allowed_bytes, + exceeded.max_allowed_bytes(), max_allowed_bytes_plus_base_usage ); - assert!(exceeded.process_physical_memory_bytes >= exceeded.max_allowed_bytes); + assert!(exceeded.process_physical_memory_bytes() >= exceeded.max_allowed_bytes()); } e => panic!("Unexpected error: {e:?}"), } diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs index 200791b6cae..5304252b2c3 100644 --- a/misc/memory-connection-limits/tests/max_percentage.rs +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -69,8 +69,8 @@ fn max_percentage() { .downcast::() .expect("connection denied because of limit"); - assert_eq!(exceeded.max_allowed_bytes, max_allowed_bytes); - assert!(exceeded.process_physical_memory_bytes >= exceeded.max_allowed_bytes); + assert_eq!(exceeded.max_allowed_bytes(), max_allowed_bytes); + assert!(exceeded.process_physical_memory_bytes() >= exceeded.max_allowed_bytes()); } e => panic!("Unexpected error: {e:?}"), } From 803d8f5958de4753fe3385b9b8de087c9bcafe77 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 7 Aug 2023 20:04:17 +0800 Subject: [PATCH 21/35] Pre-calculate max_allowed_bytes --- misc/memory-connection-limits/src/lib.rs | 49 +++++++------------ .../tests/max_bytes.rs | 7 ++- .../tests/max_percentage.rs | 7 ++- 3 files changed, 25 insertions(+), 38 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 47cc60bdbed..716b5c66291 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -43,6 +43,8 @@ use std::{ /// /// If you employ multiple [`NetworkBehaviour`]s that manage connections, it may also be a different error. /// +/// Note: Note: `fn xx_max_bytes` and `fn xx_max_percentage` are mutually exclusive +/// /// # Example /// /// ```rust @@ -58,9 +60,7 @@ use std::{ /// } /// ``` pub struct Behaviour { - max_process_memory_usage_bytes: Option, - max_process_memory_usage_percentage: Option, - system_physical_memory_bytes: usize, + max_allowed_bytes: Option, } impl Behaviour { @@ -74,32 +74,36 @@ impl Behaviour { } /// Sets the process memory usage threshold in the percentage of the total physical memory, - /// all pending connections will be dropped when the threshold is exeeded + /// + /// New inbound and outbound connections will be denied when the threshold is reached. pub fn with_max_percentage(percentage: f64) -> Self { let mut b = Self::new(); b.update_max_percentage(percentage); b } - /// Updates the process memory usage threshold in bytes, + /// Updates the process memory usage threshold in bytes pub fn update_max_bytes(&mut self, bytes: usize) { - self.max_process_memory_usage_bytes = Some(bytes); + self.max_allowed_bytes = Some(bytes); } - /// Updates the process memory usage threshold in the percentage of the total physical memory, + /// Updates the process memory usage threshold in the percentage of the total physical memory pub fn update_max_percentage(&mut self, percentage: f64) { - self.max_process_memory_usage_percentage = Some(percentage); - } - - fn new() -> Self { use sysinfo::{RefreshKind, SystemExt}; - let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); + let system_memory_bytes = + sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()).total_memory(); + self.max_allowed_bytes = Some((system_memory_bytes as f64 * percentage).round() as usize); + } + + /// Gets the process memory usage threshold in bytes + pub fn max_allowed_bytes(&self) -> Option { + self.max_allowed_bytes + } + fn new() -> Self { Self { - max_process_memory_usage_bytes: None, - max_process_memory_usage_percentage: None, - system_physical_memory_bytes: system_info.total_memory() as usize, + max_allowed_bytes: None, } } @@ -119,21 +123,6 @@ impl Behaviour { Ok(()) } - - fn max_allowed_bytes(&self) -> Option { - let max_process_memory_usage_percentage = self - .max_process_memory_usage_percentage - .map(|p| (self.system_physical_memory_bytes as f64 * p).round() as usize); - match ( - self.max_process_memory_usage_bytes, - max_process_memory_usage_percentage, - ) { - (None, None) => None, - (Some(a), Some(b)) => Some(a.min(b)), - (Some(a), None) => Some(a), - (None, Some(b)) => Some(b), - } - } } impl NetworkBehaviour for Behaviour { diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs index c9a66af16d5..58f5b736799 100644 --- a/misc/memory-connection-limits/tests/max_bytes.rs +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -26,12 +26,11 @@ use util::*; use libp2p_swarm::{dial_opts::DialOpts, DialError, Swarm}; use libp2p_swarm_test::SwarmExt; -use rand::{rngs::OsRng, Rng}; #[test] fn max_bytes() { - let connection_limit = OsRng.gen_range(1..30); - let max_allowed_bytes = connection_limit * 1024 * 1024; + const CONNECTION_LIMIT: usize = 20; + let max_allowed_bytes = CONNECTION_LIMIT * 1024 * 1024; let mut network = Swarm::new_ephemeral(|_| TestBehaviour { connection_limits: Behaviour::with_max_bytes(max_allowed_bytes), @@ -49,7 +48,7 @@ fn max_bytes() { let addr: Multiaddr = "/memory/1234".parse().unwrap(); let target = PeerId::random(); - for _ in 0..connection_limit { + for _ in 0..CONNECTION_LIMIT { network .dial( DialOpts::peer_id(target) diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs index 5304252b2c3..55160462dc9 100644 --- a/misc/memory-connection-limits/tests/max_percentage.rs +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -27,11 +27,10 @@ use util::*; use libp2p_swarm::{dial_opts::DialOpts, DialError, Swarm}; use libp2p_swarm_test::SwarmExt; -use rand::{rngs::OsRng, Rng}; #[test] fn max_percentage() { - let connection_limit = OsRng.gen_range(1..30); + const CONNECTION_LIMIT: usize = 20; let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); let mut network = Swarm::new_ephemeral(|_| TestBehaviour { @@ -41,7 +40,7 @@ fn max_percentage() { // Adds current mem usage to the limit and update let current_mem = memory_stats::memory_stats().unwrap().physical_mem; - let max_allowed_bytes = current_mem + connection_limit * 1024 * 1024; + let max_allowed_bytes = current_mem + CONNECTION_LIMIT * 1024 * 1024; network .behaviour_mut() .connection_limits @@ -50,7 +49,7 @@ fn max_percentage() { let addr: Multiaddr = "/memory/1234".parse().unwrap(); let target = PeerId::random(); - for _ in 0..connection_limit { + for _ in 0..CONNECTION_LIMIT { network .dial( DialOpts::peer_id(target) From f60cfb0436f2e86ed29d9a380affa285635827fa Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 7 Aug 2023 20:17:56 +0800 Subject: [PATCH 22/35] refresh interval --- misc/memory-connection-limits/src/lib.rs | 44 +++++++++++++++---- .../tests/max_bytes.rs | 5 ++- .../tests/max_percentage.rs | 5 ++- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 716b5c66291..17b9dcd5f9d 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -29,6 +29,7 @@ use void::Void; use std::{ fmt, task::{Context, Poll}, + time::{Duration, Instant}, }; /// A [`NetworkBehaviour`] that enforces a set of memory usage based limits. @@ -61,6 +62,9 @@ use std::{ /// ``` pub struct Behaviour { max_allowed_bytes: Option, + process_physical_memory_bytes: usize, + last_refreshed: Instant, + refresh_interval: Duration, } impl Behaviour { @@ -82,6 +86,12 @@ impl Behaviour { b } + /// Sets a custom refresh interval of the process memory usage, the default interval is 100ms + pub fn with_refresh_interval(mut self, interval: Duration) -> Self { + self.refresh_interval = interval; + self + } + /// Updates the process memory usage threshold in bytes pub fn update_max_bytes(&mut self, bytes: usize) { self.max_allowed_bytes = Some(bytes); @@ -102,26 +112,42 @@ impl Behaviour { } fn new() -> Self { + const DEFAULT_REFRESH_INTERVAL: Duration = Duration::from_millis(100); + Self { max_allowed_bytes: None, + process_physical_memory_bytes: memory_stats::memory_stats() + .map(|s| s.physical_mem) + .unwrap_or_default(), + last_refreshed: Instant::now(), + refresh_interval: DEFAULT_REFRESH_INTERVAL, } } - fn check_limit(&self) -> Result<(), ConnectionDenied> { + fn check_limit(&mut self) -> Result<(), ConnectionDenied> { if let Some(max_allowed_bytes) = self.max_allowed_bytes() { + self.refresh_memory_stats_if_needed(); + if self.process_physical_memory_bytes > max_allowed_bytes { + return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { + process_physical_memory_bytes: self.process_physical_memory_bytes, + max_allowed_bytes, + })); + } + } + + Ok(()) + } + + fn refresh_memory_stats_if_needed(&mut self) { + let now = Instant::now(); + if self.last_refreshed + self.refresh_interval < now { + self.last_refreshed = now; if let Some(stats) = memory_stats::memory_stats() { - if stats.physical_mem > max_allowed_bytes { - return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { - process_physical_memory_bytes: stats.physical_mem, - max_allowed_bytes, - })); - } + self.process_physical_memory_bytes = stats.physical_mem; } else { log::warn!("Failed to retrive process memory stats"); } } - - Ok(()) } } diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs index 58f5b736799..eda99c3616b 100644 --- a/misc/memory-connection-limits/tests/max_bytes.rs +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -19,6 +19,8 @@ // DEALINGS IN THE SOFTWARE. mod util; +use std::time::Duration; + use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_memory_connection_limits::*; @@ -33,7 +35,8 @@ fn max_bytes() { let max_allowed_bytes = CONNECTION_LIMIT * 1024 * 1024; let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::with_max_bytes(max_allowed_bytes), + connection_limits: Behaviour::with_max_bytes(max_allowed_bytes) + .with_refresh_interval(Duration::from_millis(0)), mem_consumer: ConsumeMemoryBehaviour1MBPending0Established::default(), }); diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs index 55160462dc9..e4e118be151 100644 --- a/misc/memory-connection-limits/tests/max_percentage.rs +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -19,6 +19,8 @@ // DEALINGS IN THE SOFTWARE. mod util; +use std::time::Duration; + use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_memory_connection_limits::*; @@ -34,7 +36,8 @@ fn max_percentage() { let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::with_max_percentage(0.1), + connection_limits: Behaviour::with_max_percentage(0.1) + .with_refresh_interval(Duration::from_millis(0)), mem_consumer: ConsumeMemoryBehaviour1MBPending0Established::default(), }); From 259f038b1c4838f34ad965555bae8ad2c1fbb5de Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:34:07 +0200 Subject: [PATCH 23/35] Don't use `update` functions in tests --- misc/memory-connection-limits/tests/max_bytes.rs | 6 ++---- misc/memory-connection-limits/tests/max_percentage.rs | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs index eda99c3616b..b3011a62728 100644 --- a/misc/memory-connection-limits/tests/max_bytes.rs +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -43,10 +43,8 @@ fn max_bytes() { // Adds current mem usage to the limit and update let current_mem = memory_stats::memory_stats().unwrap().physical_mem; let max_allowed_bytes_plus_base_usage = max_allowed_bytes + current_mem; - network - .behaviour_mut() - .connection_limits - .update_max_bytes(max_allowed_bytes_plus_base_usage); + network.behaviour_mut().connection_limits = + Behaviour::with_max_bytes(max_allowed_bytes_plus_base_usage); let addr: Multiaddr = "/memory/1234".parse().unwrap(); let target = PeerId::random(); diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs index e4e118be151..2402a26c7f6 100644 --- a/misc/memory-connection-limits/tests/max_percentage.rs +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -44,10 +44,9 @@ fn max_percentage() { // Adds current mem usage to the limit and update let current_mem = memory_stats::memory_stats().unwrap().physical_mem; let max_allowed_bytes = current_mem + CONNECTION_LIMIT * 1024 * 1024; - network - .behaviour_mut() - .connection_limits - .update_max_percentage(max_allowed_bytes as f64 / system_info.total_memory() as f64); + network.behaviour_mut().connection_limits = Behaviour::with_max_percentage( + max_allowed_bytes as f64 / system_info.total_memory() as f64, + ); let addr: Multiaddr = "/memory/1234".parse().unwrap(); let target = PeerId::random(); From c5e103c9fa8ebfcbb14ff429c8559099d9ce2833 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:35:47 +0200 Subject: [PATCH 24/35] Remove inner `Option` --- misc/memory-connection-limits/src/lib.rs | 70 +++++++++--------------- 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 17b9dcd5f9d..8a5cde00cbf 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -61,7 +61,7 @@ use std::{ /// } /// ``` pub struct Behaviour { - max_allowed_bytes: Option, + max_allowed_bytes: usize, process_physical_memory_bytes: usize, last_refreshed: Instant, refresh_interval: Duration, @@ -71,19 +71,29 @@ impl Behaviour { /// Sets the process memory usage threshold in absolute bytes. /// /// New inbound and outbound connections will be denied when the threshold is reached. - pub fn with_max_bytes(bytes: usize) -> Self { - let mut b = Self::new(); - b.update_max_bytes(bytes); - b + pub fn with_max_bytes(max_allowed_bytes: usize) -> Self { + const DEFAULT_REFRESH_INTERVAL: Duration = Duration::from_millis(100); + + Self { + max_allowed_bytes, + process_physical_memory_bytes: memory_stats::memory_stats() + .map(|s| s.physical_mem) + .unwrap_or_default(), + last_refreshed: Instant::now(), + refresh_interval: DEFAULT_REFRESH_INTERVAL, + } } /// Sets the process memory usage threshold in the percentage of the total physical memory, /// /// New inbound and outbound connections will be denied when the threshold is reached. pub fn with_max_percentage(percentage: f64) -> Self { - let mut b = Self::new(); - b.update_max_percentage(percentage); - b + use sysinfo::{RefreshKind, SystemExt}; + + let system_memory_bytes = + sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()).total_memory(); + + Self::with_max_bytes((system_memory_bytes as f64 * percentage).round() as usize) } /// Sets a custom refresh interval of the process memory usage, the default interval is 100ms @@ -92,47 +102,19 @@ impl Behaviour { self } - /// Updates the process memory usage threshold in bytes - pub fn update_max_bytes(&mut self, bytes: usize) { - self.max_allowed_bytes = Some(bytes); - } - - /// Updates the process memory usage threshold in the percentage of the total physical memory - pub fn update_max_percentage(&mut self, percentage: f64) { - use sysinfo::{RefreshKind, SystemExt}; - - let system_memory_bytes = - sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()).total_memory(); - self.max_allowed_bytes = Some((system_memory_bytes as f64 * percentage).round() as usize); - } - /// Gets the process memory usage threshold in bytes - pub fn max_allowed_bytes(&self) -> Option { + pub fn max_allowed_bytes(&self) -> usize { self.max_allowed_bytes } - fn new() -> Self { - const DEFAULT_REFRESH_INTERVAL: Duration = Duration::from_millis(100); - - Self { - max_allowed_bytes: None, - process_physical_memory_bytes: memory_stats::memory_stats() - .map(|s| s.physical_mem) - .unwrap_or_default(), - last_refreshed: Instant::now(), - refresh_interval: DEFAULT_REFRESH_INTERVAL, - } - } - fn check_limit(&mut self) -> Result<(), ConnectionDenied> { - if let Some(max_allowed_bytes) = self.max_allowed_bytes() { - self.refresh_memory_stats_if_needed(); - if self.process_physical_memory_bytes > max_allowed_bytes { - return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { - process_physical_memory_bytes: self.process_physical_memory_bytes, - max_allowed_bytes, - })); - } + self.refresh_memory_stats_if_needed(); + + if self.process_physical_memory_bytes > self.max_allowed_bytes { + return Err(ConnectionDenied::new(MemoryUsageLimitExceeded { + process_physical_memory_bytes: self.process_physical_memory_bytes, + max_allowed_bytes: self.max_allowed_bytes, + })); } Ok(()) From 5c63b8718b4b428451b64038ccc017489f2634a6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:38:51 +0200 Subject: [PATCH 25/35] Don't make refresh interval configurable --- misc/memory-connection-limits/src/lib.rs | 6 ------ misc/memory-connection-limits/tests/max_bytes.rs | 5 +++-- misc/memory-connection-limits/tests/max_percentage.rs | 5 +++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 8a5cde00cbf..3e6e082de45 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -96,12 +96,6 @@ impl Behaviour { Self::with_max_bytes((system_memory_bytes as f64 * percentage).round() as usize) } - /// Sets a custom refresh interval of the process memory usage, the default interval is 100ms - pub fn with_refresh_interval(mut self, interval: Duration) -> Self { - self.refresh_interval = interval; - self - } - /// Gets the process memory usage threshold in bytes pub fn max_allowed_bytes(&self) -> usize { self.max_allowed_bytes diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs index b3011a62728..9943ec563fa 100644 --- a/misc/memory-connection-limits/tests/max_bytes.rs +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -35,8 +35,7 @@ fn max_bytes() { let max_allowed_bytes = CONNECTION_LIMIT * 1024 * 1024; let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::with_max_bytes(max_allowed_bytes) - .with_refresh_interval(Duration::from_millis(0)), + connection_limits: Behaviour::with_max_bytes(max_allowed_bytes), mem_consumer: ConsumeMemoryBehaviour1MBPending0Established::default(), }); @@ -59,6 +58,8 @@ fn max_bytes() { .expect("Unexpected connection limit."); } + std::thread::sleep(Duration::from_millis(100)); // Memory stats are only updated every 100ms internally, ensure they are up-to-date when we try to exceed it. + match network .dial(DialOpts::peer_id(target).addresses(vec![addr]).build()) .expect_err("Unexpected dialing success.") diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs index 2402a26c7f6..2b5d1943790 100644 --- a/misc/memory-connection-limits/tests/max_percentage.rs +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -36,8 +36,7 @@ fn max_percentage() { let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::with_max_percentage(0.1) - .with_refresh_interval(Duration::from_millis(0)), + connection_limits: Behaviour::with_max_percentage(0.1), mem_consumer: ConsumeMemoryBehaviour1MBPending0Established::default(), }); @@ -61,6 +60,8 @@ fn max_percentage() { .expect("Unexpected connection limit."); } + std::thread::sleep(Duration::from_millis(100)); // Memory stats are only updated every 100ms internally, ensure they are up-to-date when we try to exceed it. + match network .dial(DialOpts::peer_id(target).addresses(vec![addr]).build()) .expect_err("Unexpected dialing success.") From dee261c14b2b9aa613f959d24b0ed888990ad87c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:40:09 +0200 Subject: [PATCH 26/35] Update docs --- misc/memory-connection-limits/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 3e6e082de45..a328cee4ae5 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -44,7 +44,8 @@ use std::{ /// /// If you employ multiple [`NetworkBehaviour`]s that manage connections, it may also be a different error. /// -/// Note: Note: `fn xx_max_bytes` and `fn xx_max_percentage` are mutually exclusive +/// [Behaviour::with_max_bytes] and [Behaviour::with_max_percentage] are mutually exclusive. +/// If you need to employ both of them, compose two instances of [Behaviour] into your custom behaviour. /// /// # Example /// @@ -84,7 +85,7 @@ impl Behaviour { } } - /// Sets the process memory usage threshold in the percentage of the total physical memory, + /// Sets the process memory usage threshold in the percentage of the total physical memory. /// /// New inbound and outbound connections will be denied when the threshold is reached. pub fn with_max_percentage(percentage: f64) -> Self { @@ -96,7 +97,7 @@ impl Behaviour { Self::with_max_bytes((system_memory_bytes as f64 * percentage).round() as usize) } - /// Gets the process memory usage threshold in bytes + /// Gets the process memory usage threshold in bytes. pub fn max_allowed_bytes(&self) -> usize { self.max_allowed_bytes } From 1b7fb2404ea4ef747dcefb41096afde2cde99d5b Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:41:44 +0200 Subject: [PATCH 27/35] Remove refresh-interval field --- misc/memory-connection-limits/src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index a328cee4ae5..32958e86ddb 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -65,23 +65,21 @@ pub struct Behaviour { max_allowed_bytes: usize, process_physical_memory_bytes: usize, last_refreshed: Instant, - refresh_interval: Duration, } +const REFRESH_INTERVAL: Duration = Duration::from_millis(100); + impl Behaviour { /// Sets the process memory usage threshold in absolute bytes. /// /// New inbound and outbound connections will be denied when the threshold is reached. pub fn with_max_bytes(max_allowed_bytes: usize) -> Self { - const DEFAULT_REFRESH_INTERVAL: Duration = Duration::from_millis(100); - Self { max_allowed_bytes, process_physical_memory_bytes: memory_stats::memory_stats() .map(|s| s.physical_mem) .unwrap_or_default(), last_refreshed: Instant::now(), - refresh_interval: DEFAULT_REFRESH_INTERVAL, } } @@ -117,12 +115,12 @@ impl Behaviour { fn refresh_memory_stats_if_needed(&mut self) { let now = Instant::now(); - if self.last_refreshed + self.refresh_interval < now { + if self.last_refreshed + REFRESH_INTERVAL < now { self.last_refreshed = now; if let Some(stats) = memory_stats::memory_stats() { self.process_physical_memory_bytes = stats.physical_mem; } else { - log::warn!("Failed to retrive process memory stats"); + log::warn!("Failed to retrieve process memory stats"); } } } From 280d13a1357d687da47cfc5802a1400dac845bc0 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:43:55 +0200 Subject: [PATCH 28/35] Use early return to reduce indentation --- misc/memory-connection-limits/src/lib.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 32958e86ddb..91ef087f841 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -115,13 +115,17 @@ impl Behaviour { fn refresh_memory_stats_if_needed(&mut self) { let now = Instant::now(); - if self.last_refreshed + REFRESH_INTERVAL < now { - self.last_refreshed = now; - if let Some(stats) = memory_stats::memory_stats() { - self.process_physical_memory_bytes = stats.physical_mem; - } else { - log::warn!("Failed to retrieve process memory stats"); - } + + if self.last_refreshed + REFRESH_INTERVAL > now { + // Memory stats are reasonably recent, don't refresh. + return; + } + + self.last_refreshed = now; + if let Some(stats) = memory_stats::memory_stats() { + self.process_physical_memory_bytes = stats.physical_mem; + } else { + log::warn!("Failed to retrieve process memory stats"); } } } From aadad1699a91b40254d4d340e79d5ce76087aa3a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:44:10 +0200 Subject: [PATCH 29/35] Only set timestamp if we actually refreshed the data --- misc/memory-connection-limits/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 91ef087f841..25da189f08e 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -121,8 +121,8 @@ impl Behaviour { return; } - self.last_refreshed = now; if let Some(stats) = memory_stats::memory_stats() { + self.last_refreshed = now; self.process_physical_memory_bytes = stats.physical_mem; } else { log::warn!("Failed to retrieve process memory stats"); From 28758172cad217923b54be93634388ffbb56eb56 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:45:39 +0200 Subject: [PATCH 30/35] Use early return instead of else --- misc/memory-connection-limits/src/lib.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 25da189f08e..62d443906bb 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -121,12 +121,16 @@ impl Behaviour { return; } - if let Some(stats) = memory_stats::memory_stats() { - self.last_refreshed = now; - self.process_physical_memory_bytes = stats.physical_mem; - } else { - log::warn!("Failed to retrieve process memory stats"); - } + let stats = match memory_stats::memory_stats() { + Some(stats) => stats, + None => { + log::warn!("Failed to retrieve process memory stats"); + return; + } + }; + + self.last_refreshed = now; + self.process_physical_memory_bytes = stats.physical_mem; } } From 800d501d5ba80d5d437808dd052478faf88b12dd Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 7 Aug 2023 20:45:55 +0800 Subject: [PATCH 31/35] Make tests for stable --- misc/memory-connection-limits/src/lib.rs | 13 +++++++---- .../tests/max_bytes.rs | 23 ++++++++++++------- .../tests/max_percentage.rs | 19 ++++++++++----- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 17b9dcd5f9d..97ae4f1b5e9 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -64,7 +64,7 @@ pub struct Behaviour { max_allowed_bytes: Option, process_physical_memory_bytes: usize, last_refreshed: Instant, - refresh_interval: Duration, + refresh_interval: Option, } impl Behaviour { @@ -86,8 +86,8 @@ impl Behaviour { b } - /// Sets a custom refresh interval of the process memory usage, the default interval is 100ms - pub fn with_refresh_interval(mut self, interval: Duration) -> Self { + /// Sets a custom refresh interval of the process memory usage, the default interval is 100ms. Use `None` to always refresh. + pub fn with_refresh_interval(mut self, interval: Option) -> Self { self.refresh_interval = interval; self } @@ -120,7 +120,7 @@ impl Behaviour { .map(|s| s.physical_mem) .unwrap_or_default(), last_refreshed: Instant::now(), - refresh_interval: DEFAULT_REFRESH_INTERVAL, + refresh_interval: Some(DEFAULT_REFRESH_INTERVAL), } } @@ -140,7 +140,10 @@ impl Behaviour { fn refresh_memory_stats_if_needed(&mut self) { let now = Instant::now(); - if self.last_refreshed + self.refresh_interval < now { + if match self.refresh_interval { + Some(refresh_interval) => self.last_refreshed + refresh_interval < now, + None => true, + } { self.last_refreshed = now; if let Some(stats) = memory_stats::memory_stats() { self.process_physical_memory_bytes = stats.physical_mem; diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs index eda99c3616b..a22f5808173 100644 --- a/misc/memory-connection-limits/tests/max_bytes.rs +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -19,7 +19,6 @@ // DEALINGS IN THE SOFTWARE. mod util; -use std::time::Duration; use libp2p_core::Multiaddr; use libp2p_identity::PeerId; @@ -35,22 +34,30 @@ fn max_bytes() { let max_allowed_bytes = CONNECTION_LIMIT * 1024 * 1024; let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::with_max_bytes(max_allowed_bytes) - .with_refresh_interval(Duration::from_millis(0)), + connection_limits: Behaviour::with_max_bytes(max_allowed_bytes).with_refresh_interval(None), mem_consumer: ConsumeMemoryBehaviour1MBPending0Established::default(), }); + let addr: Multiaddr = "/memory/1234".parse().unwrap(); + let target = PeerId::random(); + + // Exercise `dial` function to get more stable memory stats later + network + .dial( + DialOpts::peer_id(target) + .addresses(vec![addr.clone()]) + .build(), + ) + .expect("Unexpected connection limit."); + // Adds current mem usage to the limit and update - let current_mem = memory_stats::memory_stats().unwrap().physical_mem; - let max_allowed_bytes_plus_base_usage = max_allowed_bytes + current_mem; + let max_allowed_bytes_plus_base_usage = + max_allowed_bytes + memory_stats::memory_stats().unwrap().physical_mem; network .behaviour_mut() .connection_limits .update_max_bytes(max_allowed_bytes_plus_base_usage); - let addr: Multiaddr = "/memory/1234".parse().unwrap(); - let target = PeerId::random(); - for _ in 0..CONNECTION_LIMIT { network .dial( diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs index e4e118be151..c4ff43d3ce0 100644 --- a/misc/memory-connection-limits/tests/max_percentage.rs +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -19,7 +19,6 @@ // DEALINGS IN THE SOFTWARE. mod util; -use std::time::Duration; use libp2p_core::Multiaddr; use libp2p_identity::PeerId; @@ -36,11 +35,22 @@ fn max_percentage() { let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); let mut network = Swarm::new_ephemeral(|_| TestBehaviour { - connection_limits: Behaviour::with_max_percentage(0.1) - .with_refresh_interval(Duration::from_millis(0)), + connection_limits: Behaviour::with_max_percentage(0.1).with_refresh_interval(None), mem_consumer: ConsumeMemoryBehaviour1MBPending0Established::default(), }); + let addr: Multiaddr = "/memory/1234".parse().unwrap(); + let target = PeerId::random(); + + // Exercise `dial` function to get more stable memory stats later + network + .dial( + DialOpts::peer_id(target) + .addresses(vec![addr.clone()]) + .build(), + ) + .expect("Unexpected connection limit."); + // Adds current mem usage to the limit and update let current_mem = memory_stats::memory_stats().unwrap().physical_mem; let max_allowed_bytes = current_mem + CONNECTION_LIMIT * 1024 * 1024; @@ -49,9 +59,6 @@ fn max_percentage() { .connection_limits .update_max_percentage(max_allowed_bytes as f64 / system_info.total_memory() as f64); - let addr: Multiaddr = "/memory/1234".parse().unwrap(); - let target = PeerId::random(); - for _ in 0..CONNECTION_LIMIT { network .dial( From 7331b8f2f524809e9c82d3963e988ecb1e16744a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:48:06 +0200 Subject: [PATCH 32/35] Add docs to constant --- misc/memory-connection-limits/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 62d443906bb..68f2edc255e 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -67,7 +67,10 @@ pub struct Behaviour { last_refreshed: Instant, } -const REFRESH_INTERVAL: Duration = Duration::from_millis(100); +/// The maximum duration for which the retrieved memory-stats of the process are allowed to be stale. +/// +/// Once exceeded, we will retrieve new stats. +const MAX_STALE_DURATION: Duration = Duration::from_millis(100); impl Behaviour { /// Sets the process memory usage threshold in absolute bytes. @@ -116,7 +119,7 @@ impl Behaviour { fn refresh_memory_stats_if_needed(&mut self) { let now = Instant::now(); - if self.last_refreshed + REFRESH_INTERVAL > now { + if self.last_refreshed + MAX_STALE_DURATION > now { // Memory stats are reasonably recent, don't refresh. return; } From a1d0b55a005cfdcc68a3c32ab1b2044482210473 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 14:49:25 +0200 Subject: [PATCH 33/35] Sort functions are per trait definition --- misc/memory-connection-limits/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 68f2edc255e..33e40b11843 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -150,6 +150,16 @@ impl NetworkBehaviour for Behaviour { self.check_limit() } + fn handle_established_inbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result, ConnectionDenied> { + Ok(dummy::ConnectionHandler) + } + fn handle_pending_outbound_connection( &mut self, _: ConnectionId, @@ -161,16 +171,6 @@ impl NetworkBehaviour for Behaviour { Ok(vec![]) } - fn handle_established_inbound_connection( - &mut self, - _: ConnectionId, - _: PeerId, - _: &Multiaddr, - _: &Multiaddr, - ) -> Result, ConnectionDenied> { - Ok(dummy::ConnectionHandler) - } - fn handle_established_outbound_connection( &mut self, _: ConnectionId, From b24fb86acfb6178d0b96a83c95c82eea7147f8f4 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 7 Aug 2023 21:38:13 +0800 Subject: [PATCH 34/35] Re-export from libp2p meta crate --- Cargo.lock | 1 + libp2p/CHANGELOG.md | 4 ++++ libp2p/Cargo.toml | 3 +++ libp2p/src/lib.rs | 5 +++++ 4 files changed, 13 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 38fdb37ddbb..2cf8332a7cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2537,6 +2537,7 @@ dependencies = [ "libp2p-identity", "libp2p-kad", "libp2p-mdns", + "libp2p-memory-connection-limits", "libp2p-metrics", "libp2p-noise", "libp2p-ping", diff --git a/libp2p/CHANGELOG.md b/libp2p/CHANGELOG.md index 4161ed054c5..03d05ed51f2 100644 --- a/libp2p/CHANGELOG.md +++ b/libp2p/CHANGELOG.md @@ -6,8 +6,12 @@ - Add `json` feature which exposes `request_response::json`. See [PR 4188]. +- Add `libp2p-memory-connection-limits` providing memory usage based connection limit configurations + See [PR 4281] + [PR 4188]: https://github.com/libp2p/rust-libp2p/pull/4188 [PR 4217]: https://github.com/libp2p/rust-libp2p/pull/4217 +[PR 4281]: https://github.com/libp2p/rust-libp2p/pull/4281 ## 0.52.1 diff --git a/libp2p/Cargo.toml b/libp2p/Cargo.toml index fd9290cf506..e057c9e8dd4 100644 --- a/libp2p/Cargo.toml +++ b/libp2p/Cargo.toml @@ -27,6 +27,7 @@ full = [ "kad", "macros", "mdns", + "memory-connection-limits", "metrics", "noise", "ping", @@ -65,6 +66,7 @@ json = ["libp2p-request-response?/json"] kad = ["dep:libp2p-kad", "libp2p-metrics?/kad"] macros = ["libp2p-swarm/macros"] mdns = ["dep:libp2p-mdns"] +memory-connection-limits = ["dep:libp2p-memory-connection-limits"] metrics = ["dep:libp2p-metrics"] noise = ["dep:libp2p-noise"] ping = ["dep:libp2p-ping", "libp2p-metrics?/ping"] @@ -124,6 +126,7 @@ pin-project = "1.0.0" libp2p-deflate = { workspace = true, optional = true } libp2p-dns = { workspace = true, optional = true } libp2p-mdns = { workspace = true, optional = true } +libp2p-memory-connection-limits = { workspace = true, optional = true } libp2p-tcp = { workspace = true, optional = true } libp2p-tls = { workspace = true, optional = true } libp2p-uds = { workspace = true, optional = true } diff --git a/libp2p/src/lib.rs b/libp2p/src/lib.rs index d22e4c25d30..9ec1fe48af4 100644 --- a/libp2p/src/lib.rs +++ b/libp2p/src/lib.rs @@ -78,6 +78,11 @@ pub use libp2p_kad as kad; #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))] #[doc(inline)] pub use libp2p_mdns as mdns; +#[cfg(feature = "memory-connection-limits")] +#[cfg(not(target_arch = "wasm32"))] +#[cfg_attr(docsrs, doc(cfg(feature = "memory-connection-limits")))] +#[doc(inline)] +pub use libp2p_memory_connection_limits as memory_connection_limits; #[cfg(feature = "metrics")] #[doc(inline)] pub use libp2p_metrics as metrics; From 9eeee38d073bbb9f09f64a35af21a1dbc2c35ac3 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 7 Aug 2023 16:06:57 +0200 Subject: [PATCH 35/35] Update libp2p/CHANGELOG.md --- libp2p/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libp2p/CHANGELOG.md b/libp2p/CHANGELOG.md index 03d05ed51f2..fc2b24bd6e6 100644 --- a/libp2p/CHANGELOG.md +++ b/libp2p/CHANGELOG.md @@ -6,8 +6,8 @@ - Add `json` feature which exposes `request_response::json`. See [PR 4188]. -- Add `libp2p-memory-connection-limits` providing memory usage based connection limit configurations - See [PR 4281] +- Add `libp2p-memory-connection-limits` providing memory usage based connection limit configurations. + See [PR 4281]. [PR 4188]: https://github.com/libp2p/rust-libp2p/pull/4188 [PR 4217]: https://github.com/libp2p/rust-libp2p/pull/4217