From fb20fcc6d1036fe6de86f2b4388fb55951d9c6ac Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 31 Oct 2026 20:53:22 +0100 Subject: libp2p: add prefer option --- src/transport/libp2p/book.rs | 66 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) (limited to 'src/transport/libp2p/book.rs') diff --git a/src/transport/libp2p/book.rs b/src/transport/libp2p/book.rs index ceb9d27..b495719 100644 --- a/src/transport/libp2p/book.rs +++ b/src/transport/libp2p/book.rs @@ -48,6 +48,8 @@ pub(super) struct Book { 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, /// `Libp2pConfig::relay`: this node relays, and says so. pub(super) serving: bool, /// Relays named to pick (configured, listed), by peer: picked first. @@ -265,6 +267,7 @@ impl Book { } } 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)); first @@ -469,9 +472,11 @@ impl Book { /// `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. + /// 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 listen: Vec = listen.into_iter().map(without_peer).collect(); + 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)); } @@ -483,6 +488,63 @@ impl Book { } } +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. +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. +fn is_public(ip: std::net::IpAddr) -> bool { + use std::net::IpAddr; + match ip { + IpAddr::V4(ip) => { + // 100.64.0.0/10: carrier-grade NAT. + let shared = ip.octets()[0] == 100 && (ip.octets()[1] & 0xc0) == 64; + !(ip.is_private() + || ip.is_loopback() + || ip.is_link_local() + || ip.is_unspecified() + || shared) + } + IpAddr::V6(ip) => { + !(ip.is_loopback() + || ip.is_unspecified() + || ip.is_unique_local() + || ip.is_unicast_link_local()) + } + } +} + /// 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!( -- cgit v1.3.1