aboutsummaryrefslogtreecommitdiffstats
path: root/src/transport/libp2p/book.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/transport/libp2p/book.rs')
-rw-r--r--src/transport/libp2p/book.rs779
1 files changed, 779 insertions, 0 deletions
diff --git a/src/transport/libp2p/book.rs b/src/transport/libp2p/book.rs
new file mode 100644
index 0000000..0c61714
--- /dev/null
+++ b/src/transport/libp2p/book.rs
@@ -0,0 +1,779 @@
+//! The book: who is where (peers' addresses, connections and their round trips), the
+//! relays and their slots, hole punching and upgrades, and the counts the figures show.
+
+use super::*;
+
+/// Who named a relay to pick, in the order they are picked.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub(super) enum Named {
+ /// `Libp2pConfig::relays`.
+ Configured,
+ /// The relays listed in the document (`Transport::learn_relays`).
+ Listed,
+}
+
+impl Named {
+ pub(super) fn as_str(self) -> &'static str {
+ match self {
+ Self::Configured => "configured",
+ Self::Listed => "listed",
+ }
+ }
+}
+
+/// A relay named to pick.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(super) struct NamedRelay {
+ /// Where, with the relay's peer ID.
+ pub(super) at: Multiaddr,
+ /// As named: without the peer ID, it is found out again when the relay doesn't
+ /// grant a slot (it may have a new one).
+ pub(super) given: Multiaddr,
+ pub(super) named: Named,
+}
+
+/// Where peers are, and where we listen.
+#[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>>,
+ /// Addresses of directly connected peers dialled for a better path (`explore`):
+ /// not again while connected, unless our own interfaces change.
+ pub(super) explored: HashMap<PeerId, std::collections::HashSet<Multiaddr>>,
+ /// Addresses where a dial for a peer reached another (`wrongPeerId`), say a NATed
+ /// peer's LAN address in a range our LAN uses too: not that peer's, whatever
+ /// identify or the member list say, so not kept, dialled or explored again. As long
+ /// as the peer is known (`prune`), `WRONG_PER_PEER` each.
+ pub(super) wrong: HashMap<PeerId, std::collections::HashSet<Multiaddr>>,
+ pub(super) listening: Vec<Multiaddr>,
+ /// Where the router forwards to our listen addresses (UPnP, `Libp2pConfig::upnp`).
+ pub(super) mapped: Vec<Multiaddr>,
+ /// `Libp2pConfig::announce`.
+ pub(super) announced: Vec<Multiaddr>,
+ /// `Libp2pConfig::ignore`: not published (but announced), not dialled.
+ pub(super) ignored: Vec<IpRange>,
+ /// `Libp2pConfig::prefer`: addresses there first (`preference`).
+ pub(super) preferred: Vec<IpRange>,
+ /// Which address ranges have a route from here (`routes`): those without are left
+ /// out, as if ignored.
+ pub(super) routes: Routes,
+ /// `Libp2pConfig::relay`: this node relays, and says so.
+ pub(super) serving: bool,
+ /// Relays named to pick (configured, listed), by peer: picked first.
+ pub(super) named_relays: HashMap<PeerId, NamedRelay>,
+ /// Relays named without their peer ID, being found out (`Command::FindRelay`).
+ pub(super) finding: HashMap<Multiaddr, Named>,
+ /// The relays listed in the document, as last learned (`learn_listed`).
+ pub(super) listed: Vec<Multiaddr>,
+ /// Members that say they relay, and where (with the relay's peer ID).
+ pub(super) relays: HashMap<PeerId, Multiaddr>,
+ /// With `Libp2pConfig::public`: other peers that relay (identify says so), and
+ /// where.
+ pub(super) public_relays: HashMap<PeerId, Multiaddr>,
+ /// Relays not to be picked, until when (`RELAY_PAUSE`, `RELAY_RETRY`).
+ pub(super) failed_relays: HashMap<PeerId, tokio::time::Instant>,
+ /// Relays that granted us a slot we hold.
+ pub(super) slots: std::collections::HashSet<PeerId>,
+ /// Relays we hold a slot on whose duplicate connections we closed, and when
+ /// (`duplicates`): a slot lost then moves to the connection kept, it isn't lost.
+ pub(super) rehoming: HashMap<PeerId, tokio::time::Instant>,
+ /// Peers holding a slot on this node, as a relay.
+ pub(super) slots_here: std::collections::HashSet<PeerId>,
+ /// Peers connected only through a relay, dialled directly (`upgrade`): when to again
+ /// at the soonest, and how long to wait after that.
+ pub(super) upgrades: HashMap<PeerId, (tokio::time::Instant, Duration)>,
+ /// The shortest round trip measured on each open connection (libp2p's ping), since
+ /// the last connection to the same peer opened (they are compared then).
+ pub(super) rtts: HashMap<ConnectionId, Duration>,
+ /// Peers whose hole punching failed: how often in a row, and when to try again
+ /// (`PUNCH_RETRIES`).
+ pub(super) punching: HashMap<PeerId, (usize, tokio::time::Instant)>,
+ /// Open connections: to whom, whether through a relay, and the remote address.
+ pub(super) connections: HashMap<ConnectionId, (PeerId, bool, Multiaddr)>,
+ /// What AutoNAT says, and the address it confirmed while public.
+ pub(super) reachability: Reachability,
+ pub(super) public_address: Option<Multiaddr>,
+ /// Since start (`stats`).
+ pub(super) counts: Counts,
+ /// When each peer was last mentioned (`STALE_AFTER`).
+ pub(super) seen: HashMap<PeerId, tokio::time::Instant>,
+ /// Peers found on the LAN (mDNS) not known yet to speak continuum.
+ pub(super) lan: std::collections::HashSet<PeerId>,
+ /// Peers that speak continuum (identify says so).
+ pub(super) continuum: std::collections::HashSet<PeerId>,
+ /// Peers identify says don't speak continuum, and whether they relay: not sent to
+ /// (`send_encoded`), so that a seed that's no node says so.
+ pub(super) not_nodes: HashMap<PeerId, bool>,
+ /// Peers found on the LAN that speak continuum, with their address: seeds not
+ /// handed out yet (`discovered_seeds`).
+ pub(super) discovered: Vec<Multiaddr>,
+ /// Told when a seed is found (`Transport::on_seeds_found`).
+ pub(super) seeds_found: Option<Arc<tokio::sync::Notify>>,
+ /// Told when others may reach this node where they couldn't before
+ /// (`Transport::on_reachable`).
+ pub(super) reachable: Option<Arc<tokio::sync::Notify>>,
+}
+
+/// What `Libp2pStats` counts since start.
+#[derive(Default)]
+pub(super) struct Counts {
+ pub(super) slots_granted: u64,
+ pub(super) slots_lost: u64,
+ pub(super) slots_not_granted: u64,
+ pub(super) hole_punches: HolePunches,
+ pub(super) dial_failures: DialFailures,
+}
+
+impl Book {
+ /// How we are connected to `peer`: directly if any connection is direct.
+ pub(super) fn connection(&self, peer: &PeerId) -> Option<Connection> {
+ book_connection(&self.connections, peer)
+ }
+
+ /// The connections to `peer` to close, of several: all but the one to keep
+ /// (`ranked`).
+ ///
+ /// Between a relay and a node with a slot on it too: messages go over any of the
+ /// connections (libp2p-stream picks one at random for each), so a slower one kept
+ /// would carry half of them. A slot lives on one connection, which libp2p doesn't
+ /// tell; if that one closes, the slot ends, and the node reserves it again at once,
+ /// over the connection kept (`Book::rehoming`, not counted as lost); circuits
+ /// through it end, and are made again with the next message.
+ pub(super) fn duplicates(&self, peer: &PeerId) -> Vec<ConnectionId> {
+ self.ranked(peer).into_iter().skip(1).collect()
+ }
+
+ /// The connections to `peer`, the one to keep first: direct ones before relayed
+ /// ones, measured ones by their round trip (`rtts`, the shortest since the last of
+ /// them opened: over the LAN before a VPN's hub, whichever opened first), then the
+ /// oldest.
+ pub(super) fn ranked(&self, peer: &PeerId) -> Vec<ConnectionId> {
+ let mut connections: Vec<(bool, Duration, ConnectionId)> = self
+ .connections
+ .iter()
+ .filter(|(_, (connected, _, _))| connected == peer)
+ .map(|(id, (_, relayed, _))| {
+ let rtt = self.rtts.get(id).copied().unwrap_or(Duration::MAX);
+ (*relayed, rtt, *id)
+ })
+ .collect();
+ connections.sort();
+ connections.into_iter().map(|(_, _, id)| id).collect()
+ }
+
+ /// The remote address of the connection to `peer` that is kept (`ranked`).
+ pub(super) fn location(&self, peer: &PeerId) -> Option<&Multiaddr> {
+ let kept = *self.ranked(peer).first()?;
+ self.connections.get(&kept).map(|(_, _, address)| address)
+ }
+
+ pub(super) fn stats(&self) -> Libp2pStats {
+ let mut connections = Connections::default();
+ for (_, relayed, _) in self.connections.values() {
+ if *relayed {
+ connections.relayed += 1;
+ } else {
+ connections.direct += 1;
+ }
+ }
+ Libp2pStats {
+ reachability: self.reachability,
+ public_address: self.public_address.as_ref().map(Multiaddr::to_string),
+ connections,
+ relay_slots: RelaySlots {
+ held: self.slots.len(),
+ paused: self
+ .failed_relays
+ .values()
+ .filter(|until| tokio::time::Instant::now() < **until)
+ .count(),
+ granted: self.counts.slots_granted,
+ lost: self.counts.slots_lost,
+ not_granted: self.counts.slots_not_granted,
+ },
+ hole_punches: self.counts.hole_punches,
+ dial_failures: self.counts.dial_failures,
+ }
+ }
+
+ /// `peer` was mentioned just now.
+ pub(super) fn touch(&mut self, peer: PeerId) {
+ self.seen.insert(peer, tokio::time::Instant::now());
+ }
+
+ /// Forgets the peers not mentioned within `STALE_AFTER` before `now`, but those
+ /// `connected`. Returns how many.
+ pub(super) fn prune(
+ &mut self,
+ now: tokio::time::Instant,
+ connected: impl Fn(&PeerId) -> bool,
+ ) -> usize {
+ let stale: Vec<PeerId> = self
+ .addresses
+ .keys()
+ .chain(self.relays.keys())
+ .chain(self.public_relays.keys())
+ .chain(self.continuum.iter())
+ .chain(self.lan.iter())
+ .filter(|peer| {
+ !connected(peer)
+ && self
+ .seen
+ .get(peer)
+ .is_none_or(|at| now.saturating_duration_since(*at) >= STALE_AFTER)
+ })
+ .copied()
+ .collect::<std::collections::HashSet<_>>()
+ .into_iter()
+ .collect();
+ for peer in &stale {
+ self.addresses.remove(peer);
+ self.identified.remove(peer);
+ self.explored.remove(peer);
+ self.wrong.remove(peer);
+ self.relays.remove(peer);
+ self.public_relays.remove(peer);
+ self.continuum.remove(peer);
+ self.not_nodes.remove(peer);
+ self.lan.remove(peer);
+ self.failed_relays.remove(peer);
+ self.upgrades.remove(peer);
+ self.punching.remove(peer);
+ self.seen.remove(peer);
+ }
+ stale.len()
+ }
+
+ /// `peer`, found on the LAN, speaks continuum: a seed, at the address we know first.
+ pub(super) fn discover(&mut self, peer: PeerId) {
+ let Some(address) = self.addresses.get(&peer).and_then(|known| known.first()) else {
+ return;
+ };
+ if let Ok(address) = address.clone().with_p2p(peer)
+ && !self.discovered.contains(&address)
+ && self.discovered.len() < MAX_PEERS
+ {
+ self.discovered.push(address);
+ if let Some(found) = &self.seeds_found {
+ found.notify_one();
+ }
+ }
+ }
+
+ /// Where others could dial us directly: the announced addresses, or else those we
+ /// listen on (`published`), where the router forwards to them (`mapped`), and where
+ /// AutoNAT found others can dial us (`public_address`: a port forwarded by hand, say),
+ /// without the peer ID.
+ pub(super) fn direct(&self) -> Vec<Multiaddr> {
+ if !self.announced.is_empty() {
+ return self.announced.clone();
+ }
+ let direct: Vec<Multiaddr> = self
+ .listening
+ .iter()
+ .filter(|address| !is_relayed(address))
+ .cloned()
+ .collect();
+ let mut direct = published(&direct);
+ let confirmed = self.public_address.iter().filter_map(|address| {
+ let address = without_peer(address.clone());
+ (!address.is_empty() && !is_relayed(&address)).then_some(address)
+ });
+ // First: only so many are published (`PUBLISHED_DIRECT`), and a node listening
+ // everywhere has many.
+ let mut first: Vec<Multiaddr> = Vec::new();
+ for address in self.mapped.iter().cloned().chain(confirmed) {
+ if !first.contains(&address) {
+ first.push(address);
+ }
+ }
+ direct.retain(|address| !first.contains(address));
+ direct.sort_by_key(|address| self.preferred_range(address));
+ first.extend(direct);
+ first.retain(|address| {
+ !ignored(&self.ignored, address)
+ && ip_of(address).is_none_or(|ip| self.routes.publishable(ip))
+ });
+ first
+ }
+}
+
+/// Whether `address` only reaches this host (or this link): no use to other hosts, and
+/// dialled by one, it reaches that host itself.
+pub(super) fn is_local(address: &Multiaddr) -> bool {
+ match address.iter().next() {
+ Some(Protocol::Ip4(ip)) => ip.is_loopback() || ip.is_link_local() || ip.is_unspecified(),
+ Some(Protocol::Ip6(ip)) => {
+ ip.is_loopback() || ip.is_unicast_link_local() || ip.is_unspecified()
+ }
+ _ => false,
+ }
+}
+
+/// The addresses of `all` worth publishing: all but local ones, unless they're all
+/// local (a test on one host).
+pub(super) fn published(all: &[Multiaddr]) -> Vec<Multiaddr> {
+ let reachable: Vec<Multiaddr> = all
+ .iter()
+ .filter(|address| !is_local(address))
+ .cloned()
+ .collect();
+ if reachable.is_empty() {
+ all.to_vec()
+ } else {
+ reachable
+ }
+}
+
+impl Book {
+ /// `named` names the relay at `given`; returns it if its peer is to be found out.
+ /// `local`: this node, never its own relay.
+ pub(super) fn name_relay(
+ &mut self,
+ given: Multiaddr,
+ named: Named,
+ local: PeerId,
+ ) -> Option<Multiaddr> {
+ match split_peer(&given) {
+ Some(peer) if peer == local => None,
+ Some(peer) => {
+ // Configured wins over listed.
+ if self
+ .named_relays
+ .get(&peer)
+ .is_none_or(|known| known.named == Named::Listed || named == Named::Configured)
+ {
+ self.named_relays.insert(
+ peer,
+ NamedRelay {
+ at: given.clone(),
+ given,
+ named,
+ },
+ );
+ }
+ None
+ }
+ None => {
+ let known = self.finding.contains_key(&given)
+ || self.named_relays.values().any(|relay| relay.given == given);
+ if known {
+ return None;
+ }
+ self.finding.insert(given.clone(), named);
+ Some(given)
+ }
+ }
+ }
+
+ /// The relay named at `given` (without a peer ID) is `peer`: unless it is no longer
+ /// named (or is this node). Returns whether that's news.
+ pub(super) fn relay_found(&mut self, given: Multiaddr, peer: PeerId, local: PeerId) -> bool {
+ let Some(named) = self.finding.remove(&given) else {
+ return false;
+ };
+ let Ok(at) = given.clone().with_p2p(peer) else {
+ return false;
+ };
+ if peer == local
+ || (named == Named::Listed
+ && self
+ .named_relays
+ .get(&peer)
+ .is_some_and(|known| known.named == Named::Configured))
+ {
+ return false;
+ }
+ self.named_relays
+ .insert(peer, NamedRelay { at, given, named });
+ true
+ }
+
+ /// `relay` didn't grant a slot: if it was named without its peer ID, its address is
+ /// to be found out again, returned.
+ pub(super) fn find_again(&mut self, relay: PeerId) -> Option<Multiaddr> {
+ if self
+ .named_relays
+ .get(&relay)
+ .is_none_or(|known| split_peer(&known.given).is_some())
+ {
+ return None;
+ }
+ let known = self.named_relays.remove(&relay)?;
+ self.finding.insert(known.given.clone(), known.named);
+ Some(known.given)
+ }
+
+ /// The relays listed in the document are `listed` now: returns those to find out,
+ /// None if nothing changed.
+ pub(super) fn learn_listed(
+ &mut self,
+ listed: Vec<Multiaddr>,
+ local: PeerId,
+ ) -> Option<Vec<Multiaddr>> {
+ if listed == self.listed {
+ return None;
+ }
+ self.named_relays
+ .retain(|_, relay| relay.named != Named::Listed || listed.contains(&relay.given));
+ self.finding
+ .retain(|given, named| *named != Named::Listed || listed.contains(given));
+ self.listed = listed.clone();
+ Some(
+ listed
+ .into_iter()
+ .filter_map(|given| self.name_relay(given, Named::Listed, local))
+ .collect(),
+ )
+ }
+
+ /// The named relays `named` names, by peer, where.
+ pub(super) fn named(&self, named: Named) -> HashMap<PeerId, Multiaddr> {
+ self.named_relays
+ .iter()
+ .filter(|(_, relay)| relay.named == named)
+ .map(|(peer, relay)| (*peer, relay.at.clone()))
+ .collect()
+ }
+
+ /// Where `peer` may be reached through the relays we know (named, then members that
+ /// relay): for a peer known by its ID only (a seed `/p2p/<peer ID>`), which may hold
+ /// a slot on one of them. `local`: this node, never its own relay.
+ pub(super) fn through_relays(&mut self, peer: PeerId, local: PeerId) -> Vec<Multiaddr> {
+ let mut relays: Vec<(PeerId, Multiaddr)> = self
+ .named_relays
+ .iter()
+ .map(|(relay, named)| (*relay, named.at.clone()))
+ .collect();
+ relays.sort_by_key(|(_, address)| address.to_string());
+ let mut members: Vec<(PeerId, Multiaddr)> = self
+ .relays
+ .iter()
+ .filter(|(relay, _)| !self.named_relays.contains_key(relay))
+ .map(|(relay, address)| (*relay, address.clone()))
+ .collect();
+ members.sort_by_key(|(_, address)| address.to_string());
+ relays
+ .into_iter()
+ .chain(members)
+ .filter(|(relay, address)| *relay != local && *relay != peer && !self.left_out(address))
+ .map(|(_, address)| address.with(Protocol::P2pCircuit).with(Protocol::P2p(peer)))
+ .take(ADDRESSES_PER_PEER)
+ .collect()
+ }
+
+ /// Whether all of `addresses` are known of `peer` (`remember`).
+ pub(super) fn knows(&self, peer: PeerId, addresses: &[Multiaddr]) -> bool {
+ let Some(known) = self.addresses.get(&peer) else {
+ return false;
+ };
+ addresses
+ .iter()
+ .all(|address| known.contains(&without_peer(address.clone())))
+ }
+
+ /// `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`). Nor one in a range this node
+ /// leaves out, directly or through a relay there (`left_out`): never dialled, it
+ /// would only take a place, and be passed on.
+ /// A dial for `peer` at `address` reached another peer: `address` isn't `peer`'s.
+ /// Forgotten, and not taken again (`wrong`).
+ pub(super) fn not_theirs(&mut self, peer: PeerId, address: Multiaddr) {
+ let address = without_peer(address);
+ if let Some(known) = self.addresses.get_mut(&peer) {
+ known.retain(|known| *known != address);
+ }
+ let wrong = self.wrong.entry(peer).or_default();
+ if wrong.len() < WRONG_PER_PEER {
+ wrong.insert(address);
+ }
+ }
+
+ 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.left_out(&address) {
+ return;
+ }
+ if self
+ .wrong
+ .get(&peer)
+ .is_some_and(|wrong| wrong.contains(&address))
+ {
+ 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);
+ }
+
+ /// Whether `address` can only be reached over a range left out: ignored
+ /// (`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 {
+ 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<Multiaddr> {
+ let Some(known) = self.addresses.get(peer) else {
+ return Vec::new();
+ };
+ 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
+ /// that lost theirs go.
+ pub(super) fn recheck_routes(&mut self) {
+ let lost = self.routes.recheck();
+ if lost.is_empty() {
+ return;
+ }
+ for known in self.addresses.values_mut() {
+ known.retain(|address| {
+ ip_of(address).is_none_or(|ip| !lost.iter().any(|range| range.contains(ip)))
+ });
+ }
+ }
+
+ /// The addresses `explore` dials `peer` at, noted as dialled: a node of the swarm
+ /// connected directly, at addresses of its not dialled so yet, and with `again`
+ /// not only those at an IP it isn't connected at (an inbound connection's remote
+ /// port isn't where the peer listens).
+ pub(super) fn to_explore(&mut self, peer: PeerId, again: bool) -> Vec<Multiaddr> {
+ if !self.continuum.contains(&peer)
+ || book_connection(&self.connections, &peer) != Some(Connection::Direct)
+ {
+ return Vec::new();
+ }
+ let used: Vec<Option<std::net::IpAddr>> = self
+ .connections
+ .values()
+ .filter(|(connected, relayed, _)| *connected == peer && !relayed)
+ .map(|(_, _, address)| ip_of(address))
+ .collect();
+ let tried = self.explored.entry(peer).or_default();
+ let candidates: Vec<Multiaddr> = self
+ .addresses
+ .get(&peer)
+ .into_iter()
+ .flatten()
+ .filter(|address| is_ip(address) && !left_out(&self.ignored, address))
+ .filter(|address| again || !used.contains(&ip_of(address)))
+ .filter(|address| !tried.contains(*address))
+ .cloned()
+ .collect();
+ let candidates: Vec<Multiaddr> = candidates
+ .into_iter()
+ .filter(|address| !self.left_out(address))
+ .take(EXPLORED_AT_ONCE)
+ .collect();
+ self.explored
+ .entry(peer)
+ .or_default()
+ .extend(candidates.iter().cloned());
+ candidates
+ }
+
+ /// `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. Kept in order of `preference`: of a peer with many,
+ /// the ones that matter stay (`ADDRESSES_PER_PEER`).
+ pub(super) fn identified(&mut self, peer: PeerId, listen: Vec<Multiaddr>) {
+ let mut listen: Vec<Multiaddr> = listen.into_iter().map(without_peer).collect();
+ listen.sort_by_key(|address| self.preference(address));
+ 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);
+ }
+ }
+}
+
+impl Book {
+ /// Where a peer's `address` comes, first first: in a range `libp2p-prefer` names
+ /// (by the range's place), then public, then through a relay or by name, then a
+ /// private one (another LAN, a container bridge).
+ fn preference(&self, address: &Multiaddr) -> (u8, usize) {
+ let range = self.preferred_range(address);
+ if range != usize::MAX {
+ return (0, range);
+ }
+ match ip_of(address).filter(|_| is_ip(address)) {
+ Some(ip) if is_public(ip) => (1, 0),
+ Some(_) => (3, 0),
+ None => (2, 0),
+ }
+ }
+
+ /// The place of the first range of `libp2p-prefer` that has `address`; for an
+ /// address none has, `usize::MAX`: after them.
+ fn preferred_range(&self, address: &Multiaddr) -> usize {
+ ip_of(address)
+ .filter(|_| is_ip(address))
+ .and_then(|ip| self.preferred.iter().position(|range| range.contains(ip)))
+ .unwrap_or(usize::MAX)
+ }
+}
+
+/// The IP address `address` starts with, if any.
+pub(super) fn ip_of(address: &Multiaddr) -> Option<std::net::IpAddr> {
+ match address.iter().next() {
+ Some(Protocol::Ip4(ip)) => Some(ip.into()),
+ Some(Protocol::Ip6(ip)) => Some(ip.into()),
+ _ => None,
+ }
+}
+
+/// Whether `ip` can be reached from the internet, as far as its range tells. For IPv6,
+/// only global unicast (`2000::/3`) is: other ranges in use are an overlay's
+/// (Mycelium's `400::/7`), a translator's (`64:ff9b::/96`) or local, and, ranked as
+/// public, would be preferred over the LAN (`preference`).
+pub(super) fn is_public(ip: std::net::IpAddr) -> bool {
+ use std::net::IpAddr;
+ match ip {
+ IpAddr::V4(ip) => {
+ let [a, b, c, _] = ip.octets();
+ // 100.64.0.0/10: carrier-grade NAT.
+ let shared = a == 100 && (b & 0xc0) == 64;
+ // 198.18.0.0/15: benchmarking; 240.0.0.0/4: reserved (and broadcast).
+ let benchmarking = a == 198 && (b & 0xfe) == 18;
+ let reserved = a >= 240;
+ // 192.0.0.0/24: protocol assignments.
+ let protocol = a == 192 && b == 0 && c == 0;
+ !(ip.is_private()
+ || ip.is_loopback()
+ || ip.is_link_local()
+ || ip.is_unspecified()
+ || ip.is_documentation()
+ || ip.is_multicast()
+ || shared
+ || benchmarking
+ || reserved
+ || protocol)
+ }
+ IpAddr::V6(ip) => {
+ let [first, second, ..] = ip.segments();
+ // 2001:db8::/32: documentation.
+ let documentation = first == 0x2001 && second == 0x0db8;
+ (first & 0xe000) == 0x2000 && !documentation
+ }
+ }
+}
+
+/// 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.
+pub(super) fn is_relayed(address: &Multiaddr) -> bool {
+ address
+ .iter()
+ .any(|protocol| matches!(protocol, Protocol::P2pCircuit))
+}
+
+/// The relay `address` goes through, if it goes through one: the peer before
+/// `/p2p-circuit`.
+pub(super) fn relay_of(address: &Multiaddr) -> Option<PeerId> {
+ let mut relay = None;
+ for protocol in address.iter() {
+ match protocol {
+ Protocol::P2p(peer) => relay = Some(peer),
+ Protocol::P2pCircuit => return relay,
+ _ => {}
+ }
+ }
+ None
+}
+
+/// How `peer` is connected, of `connections` (`Book::connections`): directly if one is
+/// direct.
+pub(super) fn book_connection(
+ connections: &HashMap<ConnectionId, (PeerId, bool, Multiaddr)>,
+ peer: &PeerId,
+) -> Option<Connection> {
+ let mut found = None;
+ for (connected, relayed, _) in connections.values() {
+ if connected == peer {
+ if !relayed {
+ return Some(Connection::Direct);
+ }
+ found = Some(Connection::Relayed);
+ }
+ }
+ found
+}
+
+/// 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::<Multiaddr>()
+ } else {
+ address.clone()
+ };
+ ip_of(&at).is_some_and(|ip| !routes.routable(ip))
+}
+
+pub(super) fn dialable(known: &[Multiaddr], local: PeerId) -> Vec<Multiaddr> {
+ known
+ .iter()
+ .filter(|address| relay_of(address) != Some(local))
+ .cloned()
+ .collect()
+}
+
+/// `address` without the peer ID (`/p2p` part) it ends in, if it ends in one.
+pub(super) fn without_peer(mut address: Multiaddr) -> Multiaddr {
+ if let Some(Protocol::P2p(_)) = address.iter().last() {
+ address.pop();
+ }
+ address
+}
+
+/// `address` with the peer ID it ends in, if it ends in one.
+pub(super) fn split_peer(address: &Multiaddr) -> Option<PeerId> {
+ match address.iter().last() {
+ Some(Protocol::P2p(peer)) => Some(peer),
+ _ => None,
+ }
+}
+
+pub(super) fn lock(book: &Mutex<Book>) -> MutexGuard<'_, Book> {
+ book.lock_or_recover()
+}