aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/transport/libp2p/book.rs40
-rw-r--r--src/transport/libp2p/driver.rs8
-rw-r--r--src/transport/libp2p/mod.rs4
-rw-r--r--src/transport/libp2p/tests.rs29
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() {