From 7a6eda9ca3abcb64bd41a6b85ea56a5b3f8b0b51 Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 1 Nov 2026 13:30:12 +0100 Subject: libp2p: one Book::usable for all dials --- src/transport/libp2p/book.rs | 40 +++++++++++++++++++++++++++++----------- src/transport/libp2p/ignore.rs | 23 +++++++++++++++++++++++ src/transport/libp2p/mod.rs | 12 ++++-------- src/transport/libp2p/relays.rs | 17 +++++++---------- 4 files changed, 63 insertions(+), 29 deletions(-) diff --git a/src/transport/libp2p/book.rs b/src/transport/libp2p/book.rs index 087cd17..c5a73c4 100644 --- a/src/transport/libp2p/book.rs +++ b/src/transport/libp2p/book.rs @@ -493,18 +493,20 @@ impl Book { /// (`left_out`), or without a route from here (`routes`), directly or through a /// relay there. pub(super) fn left_out(&mut self, address: &Multiaddr) -> bool { - if left_out(&self.ignored, address) { - return true; - } - let at = if is_relayed(address) { - address - .iter() - .take_while(|protocol| !matches!(protocol, Protocol::P2pCircuit)) - .collect::() - } else { - address.clone() + excluded(&self.ignored, &mut self.routes, address) + } + + /// Where `peer` can be dialled: its known addresses, but those through this node, + /// and those left out (`left_out`). What every dial of ours goes to (connecting, + /// upgrading a relayed connection, retrying a hole punch), so none dials into a + /// range left out. + pub(super) fn usable(&mut self, peer: &PeerId, local: PeerId) -> Vec { + let Some(known) = self.addresses.get(peer) else { + return Vec::new(); }; - ip_of(&at).is_some_and(|ip| !self.routes.routable(ip)) + let mut usable = dialable(known, local); + usable.retain(|address| !excluded(&self.ignored, &mut self.routes, address)); + usable } /// Asks again which ranges have a route (`Routes::recheck`); the addresses in those @@ -698,6 +700,22 @@ pub(super) fn book_connection( /// The addresses of `known` this node can dial: not those through itself as the relay /// (a relay reaches the nodes it relays for only while they are connected to it). +/// `Book::left_out`, by the fields it needs, for where the book is borrowed in parts. +pub(super) fn excluded(ignored: &[IpRange], routes: &mut Routes, address: &Multiaddr) -> bool { + if left_out(ignored, address) { + return true; + } + let at = if is_relayed(address) { + address + .iter() + .take_while(|protocol| !matches!(protocol, Protocol::P2pCircuit)) + .collect::() + } else { + address.clone() + }; + ip_of(&at).is_some_and(|ip| !routes.routable(ip)) +} + pub(super) fn dialable(known: &[Multiaddr], local: PeerId) -> Vec { known .iter() diff --git a/src/transport/libp2p/ignore.rs b/src/transport/libp2p/ignore.rs index 2fcbd6f..6062839 100644 --- a/src/transport/libp2p/ignore.rs +++ b/src/transport/libp2p/ignore.rs @@ -155,6 +155,29 @@ impl ::libp2p::swarm::NetworkBehaviour for Ignoring { Ok(::libp2p::swarm::dummy::ConnectionHandler) } + /// Dials libp2p makes itself (hole punching, AutoNAT, the DHT) to addresses all in + /// ranges left out are refused before they go out; ours never go there + /// (`Book::usable`). A dial with an address elsewhere too goes, and a connection it + /// makes in a range left out is refused once established (below). + fn handle_pending_outbound_connection( + &mut self, + _connection_id: ConnectionId, + _maybe_peer: Option, + addresses: &[Multiaddr], + _effective_role: ::libp2p::core::Endpoint, + ) -> Result, ::libp2p::swarm::ConnectionDenied> { + if let Some(first) = addresses.first() + && addresses + .iter() + .all(|address| ignored(&self.ranges, address)) + { + return Err(::libp2p::swarm::ConnectionDenied::new(IgnoredAddress( + first.clone(), + ))); + } + Ok(Vec::new()) + } + fn handle_established_outbound_connection( &mut self, _connection_id: ConnectionId, diff --git a/src/transport/libp2p/mod.rs b/src/transport/libp2p/mod.rs index 6c57867..6fea05a 100644 --- a/src/transport/libp2p/mod.rs +++ b/src/transport/libp2p/mod.rs @@ -49,8 +49,9 @@ //! it can be dialled, it lets the relays it picked go. //! //! With `--libp2p-ignore`, address ranges are left out (VPNs): not published, not told -//! peers by identify (`Identifying`), not offered for hole punching, not dialled, and -//! connections there refused (`Ignoring`). Ranges this node has no route to are left +//! peers by identify (`Identifying`), not offered for hole punching, not dialled by us +//! (`Book::usable`) nor by libp2p unless with an address elsewhere too, and connections +//! there refused (`Ignoring`). Ranges this node has no route to are left //! out alike, as long as it has none (`routes`): without IPv6 to the internet, peers' //! public IPv6 addresses; and with `--libp2p-internet-hosts`, its own aren't published //! then. With `--libp2p-prefer`, @@ -302,12 +303,7 @@ impl Libp2pTransport { async fn connect(&self, peer: PeerId) -> Result<()> { let addresses = { let mut book = lock(&self.book); - let mut known = book - .addresses - .get(&peer) - .map(|known| dialable(known, self.peer_id)) - .unwrap_or_default(); - known.retain(|address| !book.left_out(address)); + let known = book.usable(&peer, self.peer_id); if known.is_empty() { book.through_relays(peer, self.peer_id) } else { diff --git a/src/transport/libp2p/relays.rs b/src/transport/libp2p/relays.rs index 81a97fe..1a57635 100644 --- a/src/transport/libp2p/relays.rs +++ b/src/transport/libp2p/relays.rs @@ -137,18 +137,12 @@ pub(super) fn upgrade( local: PeerId, ) { let (direct, now, wait) = { - let book = lock(book); + let mut book = lock(book); if book.connection(&peer) != Some(Connection::Relayed) { return; } - let direct: Vec = book - .addresses - .get(&peer) - .map(|known| dialable(known, local)) - .unwrap_or_default() - .into_iter() - .filter(|address| !is_relayed(address) && !ignored(&book.ignored, address)) - .collect(); + let mut direct = book.usable(&peer, local); + direct.retain(|address| !is_relayed(address)); if direct.is_empty() { return; } @@ -227,13 +221,16 @@ pub(super) fn retry_punches(swarm: &mut Swarm, book: &Mutex, lo Some(Connection::Relayed) if now < *next => return true, Some(Connection::Relayed) => {} } + // As `Book::usable`, by parts: `punching` is borrowed. let through: Vec = book .addresses .get(peer) .map(|known| dialable(known, local)) .unwrap_or_default() .into_iter() - .filter(is_relayed) + .filter(|address| { + is_relayed(address) && !excluded(&book.ignored, &mut book.routes, address) + }) .collect(); // Until its punch fails again (or the peer tries from its side). *next = now + PUNCH_RETRIES[(*failed).min(PUNCH_RETRIES.len() - 1)]; -- cgit v1.3.1