//! 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>, /// 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>, /// Addresses of directly connected peers dialled for a better path (`explore`): /// not again while connected, unless our own interfaces change. pub(super) explored: HashMap>, /// 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>, pub(super) listening: Vec, /// Where the router forwards to our listen addresses (UPnP, `Libp2pConfig::upnp`). pub(super) mapped: Vec, /// `Libp2pConfig::announce`. pub(super) announced: Vec, /// `Libp2pConfig::ignore`: not published (but announced), not dialled. pub(super) ignored: Vec, /// `Libp2pConfig::prefer`: addresses there first (`preference`). pub(super) preferred: Vec, /// 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, /// Relays named without their peer ID, being found out (`Command::FindRelay`). pub(super) finding: HashMap, /// The relays listed in the document, as last learned (`learn_listed`). pub(super) listed: Vec, /// Members that say they relay, and where (with the relay's peer ID). pub(super) relays: HashMap, /// With `Libp2pConfig::public`: other peers that relay (identify says so), and /// where. pub(super) public_relays: HashMap, /// Relays not to be picked, until when (`RELAY_PAUSE`, `RELAY_RETRY`). pub(super) failed_relays: HashMap, /// Relays that granted us a slot we hold. pub(super) slots: std::collections::HashSet, /// 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, /// Peers holding a slot on this node, as a relay. pub(super) slots_here: std::collections::HashSet, /// 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, /// 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, /// Peers whose hole punching failed: how often in a row, and when to try again /// (`PUNCH_RETRIES`). pub(super) punching: HashMap, /// Open connections: to whom, whether through a relay, and the remote address. pub(super) connections: HashMap, /// What AutoNAT says, and the address it confirmed while public. pub(super) reachability: Reachability, pub(super) public_address: Option, /// Since start (`stats`). pub(super) counts: Counts, /// When each peer was last mentioned (`STALE_AFTER`). pub(super) seen: HashMap, /// Peers found on the LAN (mDNS) not known yet to speak continuum. pub(super) lan: std::collections::HashSet, /// Peers that speak continuum (identify says so). pub(super) continuum: std::collections::HashSet, /// 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, /// Peers found on the LAN that speak continuum, with their address: seeds not /// handed out yet (`discovered_seeds`). pub(super) discovered: Vec, /// Told when a seed is found (`Transport::on_seeds_found`). pub(super) seeds_found: Option>, /// Told when others may reach this node where they couldn't before /// (`Transport::on_reachable`). pub(super) reachable: Option>, } /// 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 { 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 { 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 { 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 = 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::>() .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 { if !self.announced.is_empty() { return self.announced.clone(); } let direct: Vec = 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 = 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 { let reachable: Vec = 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 { 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 { 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, local: PeerId, ) -> Option> { 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 { 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/`), 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 { 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 { 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 { if !self.continuum.contains(&peer) || book_connection(&self.connections, &peer) != Some(Connection::Direct) { return Vec::new(); } let used: Vec> = 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 = 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 = 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) { let mut listen: Vec = 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 { 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 { 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, peer: &PeerId, ) -> Option { 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::() } else { address.clone() }; ip_of(&at).is_some_and(|ip| !routes.routable(ip)) } pub(super) fn dialable(known: &[Multiaddr], local: PeerId) -> Vec { 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 { match address.iter().last() { Some(Protocol::P2p(peer)) => Some(peer), _ => None, } } pub(super) fn lock(book: &Mutex) -> MutexGuard<'_, Book> { book.lock_or_recover() }