diff options
| author | tv <tv@krebsco.de> | 2026-10-31 20:51:53 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-31 20:51:53 +0100 |
| commit | 491b24d27c2e70d2197c5cd16397967232f087fe (patch) | |
| tree | 0dd860c27c83bfd2e8de6a07d4b7abdd2382a385 /src/transport/libp2p | |
| parent | 468112d27b272214e0dbec08c662c39826eb684a (diff) | |
libp2p: identify replaces addresses
Diffstat (limited to 'src/transport/libp2p')
| -rw-r--r-- | src/transport/libp2p/book.rs | 40 | ||||
| -rw-r--r-- | src/transport/libp2p/driver.rs | 8 | ||||
| -rw-r--r-- | src/transport/libp2p/mod.rs | 4 | ||||
| -rw-r--r-- | src/transport/libp2p/tests.rs | 29 |
4 files changed, 75 insertions, 6 deletions
diff --git a/src/transport/libp2p/book.rs b/src/transport/libp2p/book.rs index 6706512..ceb9d27 100644 --- a/src/transport/libp2p/book.rs +++ b/src/transport/libp2p/book.rs @@ -36,6 +36,11 @@ pub(super) struct NamedRelay { #[derive(Default)] pub(super) struct Book { pub(super) addresses: HashMap<PeerId, Vec<Multiaddr>>, + /// Where connected peers say they are, by their last identify (`identified`): while + /// connected, the only direct addresses of theirs kept. Forgotten when the last + /// connection to the peer closes, as a peer restarted elsewhere is found by what + /// the others say. + pub(super) identified: HashMap<PeerId, Vec<Multiaddr>>, pub(super) listening: Vec<Multiaddr>, /// Where the router forwards to our listen addresses (UPnP, `Libp2pConfig::upnp`). pub(super) mapped: Vec<Multiaddr>, @@ -205,6 +210,7 @@ impl Book { .collect(); for peer in &stale { self.addresses.remove(peer); + self.identified.remove(peer); self.relays.remove(peer); self.public_relays.remove(peer); self.continuum.remove(peer); @@ -439,18 +445,50 @@ impl Book { .all(|address| known.contains(&without_peer(address.clone()))) } - /// `peer` is at `address` (without its `/p2p` part): kept first. + /// `peer` is at `address` (without its `/p2p` part): kept first. Not a direct + /// address the peer's identify left out (`identified`): one it no longer has, or + /// one it doesn't publish (`Libp2pConfig::ignore`). pub(super) fn remember(&mut self, peer: PeerId, address: Multiaddr) { let address = without_peer(address); if !self.addresses.contains_key(&peer) && self.addresses.len() >= MAX_PEERS { return; } + if self + .identified + .get(&peer) + .is_some_and(|said| is_ip(&address) && !said.contains(&address)) + { + return; + } self.touch(peer); let addresses = self.addresses.entry(peer).or_default(); addresses.retain(|known| *known != address); addresses.insert(0, address); addresses.truncate(ADDRESSES_PER_PEER); } + + /// `peer`, connected, identified itself at `listen` (`published`): its direct + /// addresses are those from now on, while connected (`remember`); those through + /// relays and by name stay. + pub(super) fn identified(&mut self, peer: PeerId, listen: Vec<Multiaddr>) { + let listen: Vec<Multiaddr> = listen.into_iter().map(without_peer).collect(); + if let Some(known) = self.addresses.get_mut(&peer) { + known.retain(|address| !is_ip(address) || listen.contains(address)); + } + self.identified.insert(peer, listen.clone()); + // Last first: `remember` puts each in front. + for address in listen.into_iter().rev() { + self.remember(peer, address); + } + } +} + +/// Whether `address` is a direct one at an IP address (not through a relay, nor a name). +pub(super) fn is_ip(address: &Multiaddr) -> bool { + matches!( + address.iter().next(), + Some(Protocol::Ip4(_) | Protocol::Ip6(_)) + ) && !is_relayed(address) } /// Whether `address` goes through a relay. diff --git a/src/transport/libp2p/driver.rs b/src/transport/libp2p/driver.rs index 37eae35..a450cb0 100644 --- a/src/transport/libp2p/driver.rs +++ b/src/transport/libp2p/driver.rs @@ -218,6 +218,9 @@ pub(super) async fn run( let mut book = lock(&book); book.connections.remove(&connection_id); book.rtts.remove(&connection_id); + if book.connection(&peer_id).is_none() { + book.identified.remove(&peer_id); + } } let cause = match cause { _ if closing_duplicates.remove(&connection_id) => "a duplicate".to_owned(), @@ -462,10 +465,7 @@ pub(super) async fn run( "Peer identified" ); } - // Last first: `remember` puts each in front. - for address in listen.into_iter().rev() { - book.remember(peer_id, address); - } + book.identified(peer_id, listen); if relaying.public && info.protocols.contains(&::libp2p::relay::HOP_PROTOCOL_NAME) && (book.public_relays.len() < MAX_PEERS diff --git a/src/transport/libp2p/mod.rs b/src/transport/libp2p/mod.rs index 06e9e15..b2a3da7 100644 --- a/src/transport/libp2p/mod.rs +++ b/src/transport/libp2p/mod.rs @@ -11,7 +11,9 @@ //! (`swarm_config`): opening a stream costs no round trip. //! //! Connected peers tell each other where they listen (libp2p's identify protocol), so a -//! node learns the addresses of peers that dialled it. A node publishes where it can be +//! node learns the addresses of peers that dialled it. While connected, a peer's +//! identify is all a node goes by for its direct addresses (`Book::identified`): those +//! it no longer has, or no longer publishes, are forgotten, whoever still names them. A node publishes where it can be //! reached (`local_addresses`): the addresses it announces (`--libp2p-announce`, e.g. a //! public address forwarded to it), or else those it listens on, loopback left out. //! diff --git a/src/transport/libp2p/tests.rs b/src/transport/libp2p/tests.rs index f02668a..12e4fce 100644 --- a/src/transport/libp2p/tests.rs +++ b/src/transport/libp2p/tests.rs @@ -572,6 +572,35 @@ fn known_addresses() { assert!(!book.knows(us, &["/ip6/fd00::3/tcp/9661".parse().unwrap()])); } +/// A connected peer's identify says where it is: older direct addresses go, and others +/// aren't taken while it stays connected; those through relays and by name stay. +#[test] +fn identify_replaces_direct_addresses() { + let mut book = Book::default(); + let (relay, them) = (peer(), peer()); + let old: Multiaddr = "/ip4/10.244.0.7/tcp/9661".parse().unwrap(); + let lan: Multiaddr = "/ip4/192.168.1.7/tcp/9661".parse().unwrap(); + let named: Multiaddr = "/dns4/node.example/tcp/9661".parse().unwrap(); + let relayed: Multiaddr = format!("/ip4/192.0.2.1/tcp/9661/p2p/{relay}/p2p-circuit") + .parse() + .unwrap(); + book.remember(them, old.clone()); + book.remember(them, named.clone()); + book.remember(them, relayed.clone()); + + book.identified(them, vec![lan.clone().with_p2p(them).unwrap()]); + assert!(book.knows(them, &[lan.clone(), named.clone(), relayed.clone()])); + assert!(!book.knows(them, &[old.clone()])); + // The member list, say, still has the old one. + book.remember(them, old.clone()); + assert!(!book.knows(them, &[old.clone()])); + + // Disconnected (`driver`): taken again, it may have moved there. + book.identified.remove(&them); + book.remember(them, old.clone()); + assert!(book.knows(them, &[old])); +} + /// Peers nobody mentions any more are forgotten, but connected ones. #[test] fn stale_peers_are_forgotten() { |
