From fd2cbb98bade252bab9070deb37d8b240a62d3e1 Mon Sep 17 00:00:00 2001 From: Darius Date: Tue, 26 Aug 2025 05:50:03 -0500 Subject: [PATCH 1/7] feat: allow enabling and disabling of relay STOP advertisement --- protocols/relay/src/behaviour.rs | 104 ++++++++++++++++++++++- protocols/relay/src/behaviour/handler.rs | 31 +++++-- 2 files changed, 126 insertions(+), 9 deletions(-) diff --git a/protocols/relay/src/behaviour.rs b/protocols/relay/src/behaviour.rs index fe6b08c876a..e3f33019e55 100644 --- a/protocols/relay/src/behaviour.rs +++ b/protocols/relay/src/behaviour.rs @@ -26,7 +26,7 @@ use std::{ collections::{hash_map, HashMap, HashSet, VecDeque}, num::NonZeroU32, ops::Add, - task::{Context, Poll}, + task::{Context, Poll, Waker}, time::Duration, }; @@ -260,6 +260,21 @@ pub struct Behaviour { queued_actions: VecDeque>>, external_addresses: ExternalAddresses, + + status: Status, + + auto_status_change: bool, + + waker: Option, +} + +#[derive(PartialEq, Copy, Clone, Debug)] +pub enum Status { + /// Enables advertisement of the STOP protocol + Enable, + + /// Disables advertisement of the STOP protocol + Disable, } impl Behaviour { @@ -271,6 +286,78 @@ impl Behaviour { circuits: Default::default(), queued_actions: Default::default(), external_addresses: Default::default(), + status: Status::Enable, + auto_status_change: false, + waker: None, + } + } + + pub fn set_status(&mut self, status: Option) { + match status { + Some(status) => { + self.status = status; + self.auto_status_change = false; + self.reconfigure_relay_status(); + } + None => { + self.auto_status_change = true; + self.determine_relay_status_from_external_address(); + } + } + if let Some(waker) = self.waker.take() { + waker.wake(); + } + } + + fn reconfigure_relay_status(&mut self) { + if self.reservations.is_empty() { + return; + } + + for (peer_id, connections) in self.reservations.iter() { + self.queued_actions + .extend(connections.iter().map(|id| ToSwarm::NotifyHandler { + peer_id: *peer_id, + handler: NotifyHandler::One(*id), + event: Either::Left(handler::In::SetStatus { + status: self.status, + }), + })); + } + } + + fn determine_relay_status_from_external_address(&mut self) { + let old = self.status; + + self.status = match (self.external_addresses.as_slice(), self.status) { + ([], Status::Enable) => { + tracing::debug!("disabling protocol advertisment because we no longer have any confirmed external addresses"); + Status::Disable + } + ([], Status::Disable) => { + // Previously disabled because of no external addresses. + Status::Disable + } + (confirmed_external_addresses, Status::Disable) => { + debug_assert!( + !confirmed_external_addresses.is_empty(), + "Previous match arm handled empty list" + ); + tracing::debug!("advertising protcol because we are now externally reachable"); + Status::Enable + } + (confirmed_external_addresses, Status::Enable) => { + debug_assert!( + !confirmed_external_addresses.is_empty(), + "Previous match arm handled empty list" + ); + + Status::Enable + } + }; + + if self.status != old { + self.reconfigure_relay_status(); } } @@ -337,6 +424,7 @@ impl NetworkBehaviour for Behaviour { local_addr: local_addr.clone(), send_back_addr: remote_addr.clone(), }, + self.status, ))) } @@ -364,11 +452,16 @@ impl NetworkBehaviour for Behaviour { role_override, port_use, }, + self.status, ))) } fn on_swarm_event(&mut self, event: FromSwarm) { - self.external_addresses.on_swarm_event(&event); + let changed = self.external_addresses.on_swarm_event(&event); + + if self.auto_status_change && changed { + self.determine_relay_status_from_external_address(); + } if let FromSwarm::ConnectionClosed(connection_closed) = event { self.on_connection_closed(connection_closed) @@ -718,11 +811,16 @@ impl NetworkBehaviour for Behaviour { } #[tracing::instrument(level = "trace", name = "NetworkBehaviour::poll", skip(self))] - fn poll(&mut self, _: &mut Context<'_>) -> Poll>> { + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll>> { if let Some(to_swarm) = self.queued_actions.pop_front() { return Poll::Ready(to_swarm); } + self.waker = Some(cx.waker().clone()); + Poll::Pending } } diff --git a/protocols/relay/src/behaviour/handler.rs b/protocols/relay/src/behaviour/handler.rs index af130c35516..2560900b0bc 100644 --- a/protocols/relay/src/behaviour/handler.rs +++ b/protocols/relay/src/behaviour/handler.rs @@ -33,7 +33,10 @@ use futures::{ stream::{FuturesUnordered, StreamExt}, }; use futures_timer::Delay; -use libp2p_core::{upgrade::ReadyUpgrade, ConnectedPoint, Multiaddr}; +use libp2p_core::{ + upgrade::{DeniedUpgrade, ReadyUpgrade}, + ConnectedPoint, Multiaddr, +}; use libp2p_identity::PeerId; use libp2p_swarm::{ handler::{ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound}, @@ -43,7 +46,7 @@ use libp2p_swarm::{ use web_time::Instant; use crate::{ - behaviour::CircuitId, + behaviour::{self, CircuitId}, copy_future::CopyFuture, proto, protocol::{inbound_hop, outbound_stop}, @@ -87,6 +90,9 @@ pub enum In { dst_stream: Stream, dst_pending_data: Bytes, }, + SetStatus { + status: behaviour::Status, + }, } impl fmt::Debug for In { @@ -137,6 +143,10 @@ impl fmt::Debug for In { .field("circuit_id", circuit_id) .field("dst_peer_id", dst_peer_id) .finish(), + In::SetStatus { status } => f + .debug_struct("In::SetStatus") + .field("status", status) + .finish(), } } } @@ -385,10 +395,12 @@ pub struct Handler { CircuitId, Result, >, + + status: behaviour::Status, } impl Handler { - pub fn new(config: Config, endpoint: ConnectedPoint) -> Handler { + pub fn new(config: Config, endpoint: ConnectedPoint, status: behaviour::Status) -> Handler { Handler { inbound_workers: futures_bounded::FuturesSet::new( STREAM_TIMEOUT, @@ -409,6 +421,7 @@ impl Handler { active_reservation: Default::default(), pending_connect_requests: Default::default(), active_connect_requests: Default::default(), + status, } } @@ -496,13 +509,18 @@ type Futures = FuturesUnordered>; impl ConnectionHandler for Handler { type FromBehaviour = In; type ToBehaviour = Event; - type InboundProtocol = ReadyUpgrade; + type InboundProtocol = Either, DeniedUpgrade>; type InboundOpenInfo = (); type OutboundProtocol = ReadyUpgrade; type OutboundOpenInfo = (); fn listen_protocol(&self) -> SubstreamProtocol { - SubstreamProtocol::new(ReadyUpgrade::new(HOP_PROTOCOL_NAME), ()) + match self.status { + behaviour::Status::Enable => { + SubstreamProtocol::new(Either::Left(ReadyUpgrade::new(HOP_PROTOCOL_NAME)), ()) + } + behaviour::Status::Disable => SubstreamProtocol::new(Either::Right(DeniedUpgrade), ()), + } } fn on_behaviour_event(&mut self, event: Self::FromBehaviour) { @@ -594,6 +612,7 @@ impl ConnectionHandler for Handler { .boxed(), ); } + In::SetStatus { status } => self.status = status, } } @@ -890,7 +909,7 @@ impl ConnectionHandler for Handler { ) { match event { ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { - protocol: stream, + protocol: futures::future::Either::Left(stream), .. }) => { self.on_fully_negotiated_inbound(stream); From 714c97b21669b3089108c33593997b86fac44c57 Mon Sep 17 00:00:00 2001 From: Darius Date: Tue, 26 Aug 2025 07:39:19 -0500 Subject: [PATCH 2/7] chore: export Status --- protocols/relay/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocols/relay/src/lib.rs b/protocols/relay/src/lib.rs index 515fb40ef4b..13bcee45204 100644 --- a/protocols/relay/src/lib.rs +++ b/protocols/relay/src/lib.rs @@ -39,7 +39,9 @@ mod proto { }; } -pub use behaviour::{rate_limiter::RateLimiter, Behaviour, CircuitId, Config, Event, StatusCode}; +pub use behaviour::{ + rate_limiter::RateLimiter, Behaviour, CircuitId, Config, Event, Status, StatusCode, +}; pub use protocol::{HOP_PROTOCOL_NAME, STOP_PROTOCOL_NAME}; /// Types related to the relay protocol inbound. From 2249d1c9d09db456b8c39a6f97e339b3d7ab943c Mon Sep 17 00:00:00 2001 From: Darius Date: Tue, 26 Aug 2025 17:33:16 -0500 Subject: [PATCH 3/7] chore: have relay disabled by default --- protocols/relay/src/behaviour.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/relay/src/behaviour.rs b/protocols/relay/src/behaviour.rs index e3f33019e55..209ce22d07d 100644 --- a/protocols/relay/src/behaviour.rs +++ b/protocols/relay/src/behaviour.rs @@ -286,8 +286,8 @@ impl Behaviour { circuits: Default::default(), queued_actions: Default::default(), external_addresses: Default::default(), - status: Status::Enable, - auto_status_change: false, + status: Status::Disable, + auto_status_change: true, waker: None, } } From 5565f8b70c9f43fe181dc1e67e9e5cbd0d8b7bce Mon Sep 17 00:00:00 2001 From: Darius Date: Tue, 26 Aug 2025 18:14:34 -0500 Subject: [PATCH 4/7] chore: update Cargo.toml and add changelog entry --- Cargo.lock | 2 +- Cargo.toml | 2 +- protocols/relay/CHANGELOG.md | 7 +++++++ protocols/relay/Cargo.toml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b088d0da44f..2d254c1a07c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2959,7 +2959,7 @@ dependencies = [ [[package]] name = "libp2p-relay" -version = "0.21.0" +version = "0.21.1" dependencies = [ "asynchronous-codec", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 527d20c27e4..6d3a460cc58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ libp2p-ping = { version = "0.47.0", path = "protocols/ping" } libp2p-plaintext = { version = "0.43.0", path = "transports/plaintext" } libp2p-pnet = { version = "0.26.0", path = "transports/pnet" } libp2p-quic = { version = "0.13.0", path = "transports/quic" } -libp2p-relay = { version = "0.21.0", path = "protocols/relay" } +libp2p-relay = { version = "0.21.1", path = "protocols/relay" } libp2p-rendezvous = { version = "0.17.0", path = "protocols/rendezvous" } libp2p-request-response = { version = "0.29.0", path = "protocols/request-response" } libp2p-server = { version = "0.12.7", path = "misc/server" } diff --git a/protocols/relay/CHANGELOG.md b/protocols/relay/CHANGELOG.md index 0f17112a76e..a7cd50d54d1 100644 --- a/protocols/relay/CHANGELOG.md +++ b/protocols/relay/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.21.1 + +- Automatically configure HOP protocol advertisement based on external addresses, with the ability to override this + functionality using `Behaviour::set_status` to explicitly set `Status::{Enable,Disable}` to enable or disable + protocol advertisement. + See [PR 6154](https://github.com/libp2p/rust-libp2p/pull/6154). + ## 0.21.0 diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 6124744cb0d..3871abbcf8a 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-relay" edition.workspace = true rust-version = { workspace = true } description = "Communications relaying for libp2p" -version = "0.21.0" +version = "0.21.1" authors = ["Parity Technologies ", "Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" From e5eded12a509087583eb6541fc7f328979447f54 Mon Sep 17 00:00:00 2001 From: Darius Clark Date: Mon, 29 Sep 2025 13:10:31 -0500 Subject: [PATCH 5/7] chore: apply suggestions Co-authored-by: Elena Frank --- protocols/relay/src/behaviour.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/protocols/relay/src/behaviour.rs b/protocols/relay/src/behaviour.rs index 209ce22d07d..05ad36ae8fd 100644 --- a/protocols/relay/src/behaviour.rs +++ b/protocols/relay/src/behaviour.rs @@ -270,10 +270,10 @@ pub struct Behaviour { #[derive(PartialEq, Copy, Clone, Debug)] pub enum Status { - /// Enables advertisement of the STOP protocol + /// Enables advertisement of the HOP protocol Enable, - /// Disables advertisement of the STOP protocol + /// Disables advertisement of the HOP protocol Disable, } @@ -295,9 +295,11 @@ impl Behaviour { pub fn set_status(&mut self, status: Option) { match status { Some(status) => { - self.status = status; self.auto_status_change = false; - self.reconfigure_relay_status(); + if (self.status != status) { + self.status = status; + self.reconfigure_relay_status(); + } } None => { self.auto_status_change = true; From 5eb01ccc408bd02c50476fa3042c643676fa7620 Mon Sep 17 00:00:00 2001 From: Darius Date: Wed, 1 Oct 2025 08:38:31 -0500 Subject: [PATCH 6/7] chore: track connections --- protocols/relay/src/behaviour.rs | 40 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/protocols/relay/src/behaviour.rs b/protocols/relay/src/behaviour.rs index 05ad36ae8fd..9ed4d557c5e 100644 --- a/protocols/relay/src/behaviour.rs +++ b/protocols/relay/src/behaviour.rs @@ -35,6 +35,7 @@ use libp2p_core::{multiaddr::Protocol, transport::PortUse, ConnectedPoint, Endpo use libp2p_identity::PeerId; use libp2p_swarm::{ behaviour::{ConnectionClosed, FromSwarm}, + derive_prelude::ConnectionEstablished, dummy, ConnectionDenied, ConnectionId, ExternalAddresses, NetworkBehaviour, NotifyHandler, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; @@ -253,6 +254,7 @@ pub struct Behaviour { local_peer_id: PeerId, + connections: HashMap>, reservations: HashMap>, circuits: CircuitsTracker, @@ -282,6 +284,7 @@ impl Behaviour { Self { config, local_peer_id, + connections: Default::default(), reservations: Default::default(), circuits: Default::default(), queued_actions: Default::default(), @@ -296,7 +299,7 @@ impl Behaviour { match status { Some(status) => { self.auto_status_change = false; - if (self.status != status) { + if (self.status != status) { self.status = status; self.reconfigure_relay_status(); } @@ -312,11 +315,11 @@ impl Behaviour { } fn reconfigure_relay_status(&mut self) { - if self.reservations.is_empty() { + if self.connections.is_empty() { return; } - for (peer_id, connections) in self.reservations.iter() { + for (peer_id, connections) in self.connections.iter() { self.queued_actions .extend(connections.iter().map(|id| ToSwarm::NotifyHandler { peer_id: *peer_id, @@ -363,6 +366,20 @@ impl Behaviour { } } + fn on_connection_established( + &mut self, + ConnectionEstablished { + peer_id, + connection_id, + .. + }: ConnectionEstablished, + ) { + self.connections + .entry(peer_id) + .or_default() + .insert(connection_id); + } + fn on_connection_closed( &mut self, ConnectionClosed { @@ -383,6 +400,13 @@ impl Behaviour { } } + if let hash_map::Entry::Occupied(mut peer) = self.connections.entry(peer_id) { + peer.get_mut().remove(&connection_id); + if peer.get().is_empty() { + peer.remove(); + } + } + for circuit in self .circuits .remove_by_connection(peer_id, connection_id) @@ -465,8 +489,14 @@ impl NetworkBehaviour for Behaviour { self.determine_relay_status_from_external_address(); } - if let FromSwarm::ConnectionClosed(connection_closed) = event { - self.on_connection_closed(connection_closed) + match event { + FromSwarm::ConnectionEstablished(connection_established) => { + self.on_connection_established(connection_established) + } + FromSwarm::ConnectionClosed(connection_closed) => { + self.on_connection_closed(connection_closed) + } + _ => {} } } From 2d38e9cedc788d5384bc02e9939eb19eaa368691 Mon Sep 17 00:00:00 2001 From: Darius Date: Thu, 2 Oct 2025 06:02:25 -0500 Subject: [PATCH 7/7] chore: remove unnecessary parentheses --- protocols/relay/src/behaviour.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/relay/src/behaviour.rs b/protocols/relay/src/behaviour.rs index 9ed4d557c5e..998d580dc4f 100644 --- a/protocols/relay/src/behaviour.rs +++ b/protocols/relay/src/behaviour.rs @@ -299,7 +299,7 @@ impl Behaviour { match status { Some(status) => { self.auto_status_change = false; - if (self.status != status) { + if self.status != status { self.status = status; self.reconfigure_relay_status(); }