From e3cdcaf543984892d7bfc8da66619d8d6636b40d Mon Sep 17 00:00:00 2001 From: Elena Frank Date: Mon, 23 Mar 2026 17:07:21 +0100 Subject: [PATCH 1/5] rendezvous: reduce API surface --- protocols/rendezvous/src/server.rs | 43 +++++++----------------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 572f82b057f..f72ae9592c3 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -279,9 +279,7 @@ fn handle_request( Some((event, Some(response))) } - Err(TtlOutOfRange::TooLong { .. }) | Err(TtlOutOfRange::TooShort { .. }) => { - let error = ErrorCode::InvalidTtl; - + Err(error) => { let response = Message::RegisterResponse(Err(error)); let event = Event::PeerNotRegistered { @@ -351,7 +349,7 @@ impl RegistrationId { #[derive(Debug, PartialEq)] struct ExpiredRegistration(Registration); -pub struct Registrations { +struct Registrations { registrations_for_peer: BiMap<(PeerId, Namespace), RegistrationId>, registrations: HashMap, cookies: HashMap>, @@ -360,14 +358,6 @@ pub struct Registrations { next_expiry: FuturesUnordered>, } -#[derive(Debug, thiserror::Error)] -pub enum TtlOutOfRange { - #[error("Requested TTL ({requested}s) is too long; max {bound}s")] - TooLong { bound: Ttl, requested: Ttl }, - #[error("Requested TTL ({requested}s) is too short; min {bound}s")] - TooShort { bound: Ttl, requested: Ttl }, -} - impl Default for Registrations { fn default() -> Self { Registrations::with_config(Config::default()) @@ -375,7 +365,7 @@ impl Default for Registrations { } impl Registrations { - pub fn with_config(config: Config) -> Self { + fn with_config(config: Config) -> Self { Self { registrations_for_peer: Default::default(), registrations: Default::default(), @@ -386,22 +376,10 @@ impl Registrations { } } - pub fn add( - &mut self, - new_registration: NewRegistration, - ) -> Result { + fn add(&mut self, new_registration: NewRegistration) -> Result { let ttl = new_registration.effective_ttl(); - if ttl > self.max_ttl { - return Err(TtlOutOfRange::TooLong { - bound: self.max_ttl, - requested: ttl, - }); - } - if ttl < self.min_ttl { - return Err(TtlOutOfRange::TooShort { - bound: self.min_ttl, - requested: ttl, - }); + if ttl > self.max_ttl || ttl < self.min_ttl { + return Err(ErrorCode::InvalidTtl); } let namespace = new_registration.namespace; @@ -436,7 +414,7 @@ impl Registrations { Ok(registration) } - pub fn remove(&mut self, namespace: Namespace, peer_id: PeerId) { + fn remove(&mut self, namespace: Namespace, peer_id: PeerId) { let reggo_to_remove = self .registrations_for_peer .remove_by_left(&(peer_id, namespace)); @@ -446,7 +424,7 @@ impl Registrations { } } - pub fn get( + fn get( &mut self, discover_namespace: Option, cookie: Option, @@ -536,9 +514,8 @@ impl Registrations { } } -#[derive(Debug, thiserror::Error, Eq, PartialEq)] -#[error("The provided cookie is not valid for a DISCOVER request for the given namespace")] -pub struct CookieNamespaceMismatch; +#[derive(Debug, Eq, PartialEq)] +struct CookieNamespaceMismatch; #[cfg(test)] mod tests { From fbe7034394fc48da4c45452078cdd0eab415c0b7 Mon Sep 17 00:00:00 2001 From: Elena Frank Date: Mon, 23 Mar 2026 17:10:40 +0100 Subject: [PATCH 2/5] rendezvous: limit # of registrations --- protocols/rendezvous/src/server.rs | 67 ++++++++++++++++++------------ 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index f72ae9592c3..fd65746608e 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -40,6 +40,11 @@ use crate::{ MAX_TTL, MIN_TTL, }; +/// Default maximum active registrations per peer. +pub const MAX_REGISTRATION_PEER: usize = 32; +/// Default maximum active registrations in total. +pub const MAX_REGISTRATIONS_TOTAL: usize = 10_000; + pub struct Behaviour { inner: libp2p_request_response::Behaviour, @@ -49,6 +54,8 @@ pub struct Behaviour { pub struct Config { min_ttl: Ttl, max_ttl: Ttl, + max_registrations_per_peer: usize, + max_registrations_total: usize, } impl Config { @@ -61,6 +68,16 @@ impl Config { self.max_ttl = max_ttl; self } + + pub fn with_max_registration_per_peer(mut self, max: usize) -> Self { + self.max_registrations_per_peer = max; + self + } + + pub fn with_max_registration_total(mut self, max: usize) -> Self { + self.max_registrations_total = max; + self + } } impl Default for Config { @@ -68,6 +85,8 @@ impl Default for Config { Self { min_ttl: MIN_TTL, max_ttl: MAX_TTL, + max_registrations_per_peer: MAX_REGISTRATION_PEER, + max_registrations_total: MAX_REGISTRATIONS_TOTAL, } } } @@ -350,11 +369,10 @@ impl RegistrationId { struct ExpiredRegistration(Registration); struct Registrations { + config: Config, registrations_for_peer: BiMap<(PeerId, Namespace), RegistrationId>, registrations: HashMap, cookies: HashMap>, - min_ttl: Ttl, - max_ttl: Ttl, next_expiry: FuturesUnordered>, } @@ -369,8 +387,7 @@ impl Registrations { Self { registrations_for_peer: Default::default(), registrations: Default::default(), - min_ttl: config.min_ttl, - max_ttl: config.max_ttl, + config, cookies: Default::default(), next_expiry: FuturesUnordered::from_iter(vec![futures::future::pending().boxed()]), } @@ -378,20 +395,26 @@ impl Registrations { fn add(&mut self, new_registration: NewRegistration) -> Result { let ttl = new_registration.effective_ttl(); - if ttl > self.max_ttl || ttl < self.min_ttl { + if ttl > self.config.max_ttl || ttl < self.config.min_ttl { return Err(ErrorCode::InvalidTtl); } - let namespace = new_registration.namespace; - let registration_id = RegistrationId::new(); + let peer = new_registration.record.peer_id(); - if let Some(old_registration) = self + if self .registrations_for_peer - .get_by_left(&(new_registration.record.peer_id(), namespace.clone())) + .left_values() + .filter(|(p, _)| p == &peer) + .count() + >= self.config.max_registrations_per_peer + || self.registrations_for_peer.len() > self.config.max_registrations_total { - self.registrations.remove(old_registration); + return Err(ErrorCode::Unavailable); } + let namespace = new_registration.namespace; + let registration_id = RegistrationId::new(); + self.registrations_for_peer.insert( (new_registration.record.peer_id(), namespace.clone()), registration_id, @@ -623,10 +646,8 @@ mod tests { #[tokio::test] async fn given_two_registration_ttls_one_expires_one_lives() { - let mut registrations = Registrations::with_config(Config { - min_ttl: 0, - max_ttl: 4, - }); + let mut registrations = + Registrations::with_config(Config::default().with_min_ttl(0).with_max_ttl(4)); let start_time = SystemTime::now(); @@ -659,10 +680,8 @@ mod tests { #[tokio::test] async fn given_peer_unregisters_before_expiry_do_not_emit_registration_expired() { - let mut registrations = Registrations::with_config(Config { - min_ttl: 1, - max_ttl: 10, - }); + let mut registrations = + Registrations::with_config(Config::default().with_min_ttl(1).with_max_ttl(10)); let dummy_registration = new_dummy_registration_with_ttl("foo", 2); let namespace = dummy_registration.namespace.clone(); let peer_id = dummy_registration.record.peer_id(); @@ -681,10 +700,8 @@ mod tests { #[tokio::test] async fn given_all_registrations_expired_then_successfully_handle_new_registration_and_expiry() { - let mut registrations = Registrations::with_config(Config { - min_ttl: 0, - max_ttl: 10, - }); + let mut registrations = + Registrations::with_config(Config::default().with_min_ttl(0).with_max_ttl(10)); let dummy_registration = new_dummy_registration_with_ttl("foo", 1); registrations.add(dummy_registration.clone()).unwrap(); @@ -698,10 +715,8 @@ mod tests { #[tokio::test] async fn cookies_are_cleaned_up_if_registrations_expire() { - let mut registrations = Registrations::with_config(Config { - min_ttl: 1, - max_ttl: 10, - }); + let mut registrations = + Registrations::with_config(Config::default().with_min_ttl(1).with_max_ttl(10)); registrations .add(new_dummy_registration_with_ttl("foo", 2)) From 752db87016cdd2d1b20b916ff520f867282d3edd Mon Sep 17 00:00:00 2001 From: Elena Frank Date: Mon, 23 Mar 2026 17:12:31 +0100 Subject: [PATCH 3/5] Update CHANGELOG.md --- Cargo.lock | 1 + protocols/rendezvous/CHANGELOG.md | 11 +++++++++++ protocols/rendezvous/Cargo.toml | 1 + protocols/rendezvous/src/server.rs | 14 ++++++++++++-- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e15ca33f681..12d430355d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3025,6 +3025,7 @@ dependencies = [ "bimap", "futures", "futures-timer", + "hashlink", "libp2p-core", "libp2p-identity", "libp2p-request-response", diff --git a/protocols/rendezvous/CHANGELOG.md b/protocols/rendezvous/CHANGELOG.md index 77727945347..90bf64a26df 100644 --- a/protocols/rendezvous/CHANGELOG.md +++ b/protocols/rendezvous/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.18.0 + +- Port [8fde2dc](https://github.com/libp2p/rust-libp2p/commit/8fde2dc0fae8b433f97c6cdf9ee24f59d51a359c) and make unrequired pub functions private + See [PR XXXX](https://github.com/libp2p/rust-libp2p/pull/XXXX) + +## 0.17.1 + +- Add limits for the per-peer and total number of store registrations, and the stored client cookies. + See [GHSA-cqfx-gf56-8x59](https://github.com/libp2p/rust-libp2p/security/advisories/GHSA-cqfx-gf56-8x59) and [GHSA-v5hw-cv9c-rpg7](https://github.com/libp2p/rust-libp2p/security/advisories/GHSA-v5hw-cv9c-rpg7) + + ## 0.17.0 - Emit `ToSwarm::NewExternalAddrOfPeer` for newly discovered peers. diff --git a/protocols/rendezvous/Cargo.toml b/protocols/rendezvous/Cargo.toml index d80db65a27c..f7e231bdd36 100644 --- a/protocols/rendezvous/Cargo.toml +++ b/protocols/rendezvous/Cargo.toml @@ -16,6 +16,7 @@ async-trait = "0.1" bimap = "0.6.3" futures = { workspace = true, features = ["std"] } futures-timer = "3.0.3" +hashlink = { workspace = true } web-time = { workspace = true } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index fd65746608e..6403fbbecd0 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -27,6 +27,7 @@ use std::{ use bimap::BiMap; use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt}; +use hashlink::LruCache; use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_request_response::ProtocolSupport; @@ -44,6 +45,8 @@ use crate::{ pub const MAX_REGISTRATION_PEER: usize = 32; /// Default maximum active registrations in total. pub const MAX_REGISTRATIONS_TOTAL: usize = 10_000; +/// Default size of the cache that stores client cookies. +pub const COOKIES_CACHE_SIZE: usize = 10_000; pub struct Behaviour { inner: libp2p_request_response::Behaviour, @@ -56,6 +59,7 @@ pub struct Config { max_ttl: Ttl, max_registrations_per_peer: usize, max_registrations_total: usize, + max_cookies: usize, } impl Config { @@ -78,6 +82,11 @@ impl Config { self.max_registrations_total = max; self } + + pub fn with_max_stored_cookies(mut self, max: usize) -> Self { + self.max_cookies = max; + self + } } impl Default for Config { @@ -87,6 +96,7 @@ impl Default for Config { max_ttl: MAX_TTL, max_registrations_per_peer: MAX_REGISTRATION_PEER, max_registrations_total: MAX_REGISTRATIONS_TOTAL, + max_cookies: COOKIES_CACHE_SIZE, } } } @@ -372,7 +382,7 @@ struct Registrations { config: Config, registrations_for_peer: BiMap<(PeerId, Namespace), RegistrationId>, registrations: HashMap, - cookies: HashMap>, + cookies: LruCache>, next_expiry: FuturesUnordered>, } @@ -387,8 +397,8 @@ impl Registrations { Self { registrations_for_peer: Default::default(), registrations: Default::default(), + cookies: LruCache::new(config.max_cookies), config, - cookies: Default::default(), next_expiry: FuturesUnordered::from_iter(vec![futures::future::pending().boxed()]), } } From 9e0998fe4bc23379828382298477fb6821426a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Thu, 2 Apr 2026 20:58:37 +0100 Subject: [PATCH 4/5] Update protocols/rendezvous/CHANGELOG.md Co-authored-by: Elena Frank --- protocols/rendezvous/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/rendezvous/CHANGELOG.md b/protocols/rendezvous/CHANGELOG.md index 1b8662e78d2..9d026042151 100644 --- a/protocols/rendezvous/CHANGELOG.md +++ b/protocols/rendezvous/CHANGELOG.md @@ -1,7 +1,7 @@ ## 0.18.0 - Port [8fde2dc](https://github.com/libp2p/rust-libp2p/commit/8fde2dc0fae8b433f97c6cdf9ee24f59d51a359c) and make unrequired pub functions private - See [PR XXXX](https://github.com/libp2p/rust-libp2p/pull/XXXX) + See [PR 6364](https://github.com/libp2p/rust-libp2p/pull/6364). - refactor: `Codec` no longer requires `#[async_trait]` See [PR 6292](https://github.com/libp2p/rust-libp2p/pull/6292) From a9e6f8816abd4d62a7a9d07208c2843a23bbb144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Thu, 2 Apr 2026 21:05:48 +0100 Subject: [PATCH 5/5] cargo fmt --- protocols/rendezvous/src/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index cfd4b5ff99f..e5a72c2b04b 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -26,9 +26,9 @@ use std::{ }; use bimap::BiMap; -use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt}; +use futures::{FutureExt, StreamExt, future::BoxFuture, stream::FuturesUnordered}; use hashlink::LruCache; -use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; +use libp2p_core::{Endpoint, Multiaddr, transport::PortUse}; use libp2p_identity::PeerId; use libp2p_request_response::ProtocolSupport; use libp2p_swarm::{