//! Addresses without a route from here, left out as if `--libp2p-ignore` named them: a //! node without IPv6 to the internet doesn't keep, show or dial peers' public IPv6 //! addresses; one without a route to another LAN, that LAN's. And with //! `--libp2p-internet-hosts`, whether there's a route to the internet at all, per //! family: a node without one doesn't publish its own public addresses of that family. //! //! Whether there is a route is asked of the operating system: a UDP socket "connected" //! to the address (no packet is sent) fails where the routing table has none. Asked //! per range (`ROUTE_PREFIX_V4`, `ROUTE_PREFIX_V6`) of the addresses heard of, and //! asked again every `ROUTES_RECHECK`: a range gets a route (IPv6 comes up), and its //! addresses are taken again as they're heard of next (the member list, identify); //! one loses its route, and the addresses there are dropped. use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; use super::*; /// The ranges routes are asked for: an IPv4 /24 or an IPv6 /64, as far as a route /// usually differs within them. const ROUTE_PREFIX_V4: u8 = 24; const ROUTE_PREFIX_V6: u8 = 64; /// How long an answer is taken for one. const ROUTES_RECHECK: Duration = Duration::from_secs(30); /// Ranges remembered at most. const MAX_RANGES: usize = 1024; /// How long resolving `--libp2p-internet-hosts` may take, each time. const RESOLVE_WITHIN: Duration = Duration::from_secs(5); /// Whether the routing table has a route to `ip`. pub(super) fn has_route(ip: IpAddr) -> bool { let any: SocketAddr = match ip { IpAddr::V4(_) => (Ipv4Addr::UNSPECIFIED, 0).into(), IpAddr::V6(_) => (Ipv6Addr::UNSPECIFIED, 0).into(), }; UdpSocket::bind(any) .and_then(|socket| socket.connect((ip, 9))) .is_ok() } /// The answers, by range, and the question asked (`has_route`, or a stand-in in tests). pub(super) struct Routes { known: HashMap, /// Whether there is a route to the internet, per family (IPv4, IPv6), as last /// asked (`internet`): own public addresses are published only then. True where /// nobody asks (no `--libp2p-internet-hosts` of that family); false until the first /// answer where someone does (resolving takes up to `RESOLVE_WITHIN`), so the first /// member list entry doesn't list addresses nobody can reach. internet: [bool; 2], /// `--libp2p-internet-hosts`: names or addresses on the internet, whose routes /// tell whether there is one to the internet at all. pub(super) internet_hosts: Vec, probe: fn(IpAddr) -> bool, } impl Default for Routes { fn default() -> Self { // Tests don't depend on the host's routes; those that care set `probe`. #[cfg(test)] let probe: fn(IpAddr) -> bool = |_| true; #[cfg(not(test))] let probe: fn(IpAddr) -> bool = has_route; Self::with_probe(probe) } } /// The range of `ip` routes are asked for. fn range_of(ip: IpAddr) -> IpRange { let prefix = if ip.is_ipv4() { ROUTE_PREFIX_V4 } else { ROUTE_PREFIX_V6 }; IpRange::masked(ip, prefix) } impl Routes { /// With `--libp2p-internet-hosts` `internet_hosts`. pub(super) fn asking(internet_hosts: Vec) -> Self { let unknown = !internet_hosts.is_empty(); Self { internet: [!unknown, !unknown], internet_hosts, ..Self::default() } } pub(super) fn with_probe(probe: fn(IpAddr) -> bool) -> Self { Self { known: HashMap::new(), internet: [true, true], internet_hosts: Vec::new(), probe, } } /// Whether `ip` has a route from here: asked if not known lately, told when the /// answer is news (debug). pub(super) fn routable(&mut self, ip: IpAddr) -> bool { let range = range_of(ip); let now = tokio::time::Instant::now(); if let Some((routable, at)) = self.known.get(&range) && now.saturating_duration_since(*at) < ROUTES_RECHECK { return *routable; } let routable = (self.probe)(ip); let before = self.known.get(&range).map(|(routable, _)| *routable); if before.is_none() && self.known.len() >= MAX_RANGES { self.known.clear(); } self.known.insert(range, (routable, now)); announce(range, before, routable); routable } /// Asks again for the ranges known; returns those that lost their route, whose /// addresses are to go. pub(super) fn recheck(&mut self) -> Vec { let now = tokio::time::Instant::now(); let mut lost = Vec::new(); for (range, (routable, at)) in &mut self.known { let now_routable = (self.probe)(range.network); if *routable != now_routable { announce(*range, Some(*routable), now_routable); if !now_routable { lost.push(*range); } } *routable = now_routable; *at = now; } lost } /// Whether there's a route to the internet, per family, by the addresses /// `--libp2p-internet-hosts` resolved to (`resolve_internet_hosts`): one of them /// with a route is enough. A family none of them has isn't judged (taken as having /// one). pub(super) fn internet(&mut self, hosts: &[IpAddr]) { for (family, name) in ["IPv4", "IPv6"].into_iter().enumerate() { let of_family: Vec = hosts .iter() .copied() .filter(|ip| usize::from(ip.is_ipv6()) == family) .collect(); let routable = of_family.is_empty() || of_family.iter().any(|ip| (self.probe)(*ip)); if routable != self.internet[family] { if routable { debug!( family = name, "A route to the internet again: own public addresses published again" ); } else { debug!( family = name, "No route to the internet: own public addresses not published" ); } } self.internet[family] = routable; } } /// Whether an own address `ip` is worth publishing: not a public one of a family /// with no route to the internet. pub(super) fn publishable(&self, ip: IpAddr) -> bool { !is_public(ip) || self.internet[usize::from(ip.is_ipv6())] } } fn announce(range: IpRange, before: Option, routable: bool) { match (before, routable) { (None | Some(true), false) => { debug!(range = %range, "No route to an address range: its addresses left out until there is"); } (Some(false), true) => { debug!(range = %range, "A route to an address range again: its addresses taken again"); } _ => {} } } /// The addresses `hosts` (names or addresses) resolve to, within `RESOLVE_WITHIN`; a /// name that doesn't resolve is left out (told at debug level). pub(super) async fn resolve_internet_hosts(hosts: &[String]) -> Vec { let mut addresses = Vec::new(); for host in hosts { if let Ok(ip) = host.parse::() { addresses.push(ip); continue; } match tokio::time::timeout(RESOLVE_WITHIN, tokio::net::lookup_host((host.as_str(), 0))) .await { Ok(Ok(found)) => addresses.extend(found.map(|address| address.ip())), Ok(Err(error)) => { debug!(host = %host, error = %error, "An internet host didn't resolve") } Err(_) => debug!(host = %host, "An internet host didn't resolve in time"), } } addresses }