//! `--libp2p-ignore`: address ranges neither published nor dialled, nor connected over //! (`Ignoring`, a behaviour that denies connections there), nor told peers by identify //! (`Identifying`). use std::fmt; use super::*; /// An address range (`10.244.0.0/16`, `42::/16`; a bare address is a range of one), for /// `Libp2pConfig::ignore`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct IpRange { pub(super) network: std::net::IpAddr, pub(super) prefix: u8, } impl FromStr for IpRange { type Err = anyhow::Error; fn from_str(text: &str) -> Result { let (address, prefix) = match text.split_once('/') { Some((address, prefix)) => (address, Some(prefix)), None => (text, None), }; let network: std::net::IpAddr = address .parse() .with_context(|| format!("{text:?} is no address range (e.g. 10.244.0.0/16)"))?; let bits = if network.is_ipv4() { 32 } else { 128 }; let prefix = match prefix { Some(prefix) => prefix .parse::() .ok() .filter(|prefix| *prefix <= bits) .with_context(|| format!("{text:?}: the prefix is 0 to {bits}"))?, None => bits, }; Ok(Self { network, prefix }) } } impl fmt::Display for IpRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}/{}", self.network, self.prefix) } } impl IpRange { /// The range of the `prefix` bits of `ip` (the rest zero). pub(super) fn masked(ip: std::net::IpAddr, prefix: u8) -> Self { use std::net::IpAddr; let network = match ip { IpAddr::V4(ip) => { let mask = u32::MAX.checked_shl(32 - u32::from(prefix)).unwrap_or(0); IpAddr::V4((u32::from(ip) & mask).into()) } IpAddr::V6(ip) => { let mask = u128::MAX.checked_shl(128 - u32::from(prefix)).unwrap_or(0); IpAddr::V6((u128::from(ip) & mask).into()) } }; Self { network, prefix } } /// Whether every address of `other` is in this range too. pub(super) fn covers(&self, other: &IpRange) -> bool { self.network.is_ipv4() == other.network.is_ipv4() && self.prefix <= other.prefix && self.contains(other.network) } pub(super) fn contains(&self, ip: std::net::IpAddr) -> bool { use std::net::IpAddr; match (self.network, ip) { (IpAddr::V4(network), IpAddr::V4(ip)) => { let mask = u32::MAX .checked_shl(32 - u32::from(self.prefix)) .unwrap_or(0); u32::from(network) & mask == u32::from(ip) & mask } (IpAddr::V6(network), IpAddr::V6(ip)) => { let mask = u128::MAX .checked_shl(128 - u32::from(self.prefix)) .unwrap_or(0); u128::from(network) & mask == u128::from(ip) & mask } _ => false, } } } /// Whether `address` is in one of `ranges`: a direct address there (one through a relay /// is judged by the connection to the relay). pub(super) fn ignored(ranges: &[IpRange], address: &Multiaddr) -> bool { if ranges.is_empty() || is_relayed(address) { return false; } let ip = match address.iter().next() { Some(Protocol::Ip4(ip)) => std::net::IpAddr::V4(ip), Some(Protocol::Ip6(ip)) => std::net::IpAddr::V6(ip), _ => return false, }; ranges.iter().any(|range| range.contains(ip)) } /// Whether `address` can only be reached over a range of `ranges`: a direct address /// there, or one through a relay whose own address is there (dialling the relay is /// refused, `Ignoring`). For what's kept and dialled; connections are judged by /// `ignored`. pub(super) fn left_out(ranges: &[IpRange], address: &Multiaddr) -> bool { if !is_relayed(address) { return ignored(ranges, address); } let relay: Multiaddr = address .iter() .take_while(|protocol| !matches!(protocol, Protocol::P2pCircuit)) .collect(); ignored(ranges, &without_peer(relay)) } /// Keeps connections off the address ranges `libp2p-ignore` names, whoever dialled: /// hole punching and identify bring addresses there too, and a peer may dial us there. pub(super) struct Ignoring { pub(super) ranges: Vec, } #[derive(Debug, thiserror::Error)] #[error("{0} is in a range left out (libp2p-ignore)")] pub(super) struct IgnoredAddress(Multiaddr); impl ::libp2p::swarm::NetworkBehaviour for Ignoring { type ConnectionHandler = ::libp2p::swarm::dummy::ConnectionHandler; type ToSwarm = std::convert::Infallible; fn handle_pending_inbound_connection( &mut self, _connection_id: ConnectionId, _local_addr: &Multiaddr, remote_addr: &Multiaddr, ) -> Result<(), ::libp2p::swarm::ConnectionDenied> { if ignored(&self.ranges, remote_addr) { return Err(::libp2p::swarm::ConnectionDenied::new(IgnoredAddress( remote_addr.clone(), ))); } Ok(()) } fn handle_established_inbound_connection( &mut self, _connection_id: ConnectionId, _peer: PeerId, _local_addr: &Multiaddr, _remote_addr: &Multiaddr, ) -> Result<::libp2p::swarm::THandler, ::libp2p::swarm::ConnectionDenied> { Ok(::libp2p::swarm::dummy::ConnectionHandler) } /// Dials libp2p makes itself (hole punching, AutoNAT, the DHT) to addresses all in /// ranges left out are refused before they go out; ours never go there /// (`Book::usable`). A dial with an address elsewhere too goes, and a connection it /// makes in a range left out is refused once established (below). fn handle_pending_outbound_connection( &mut self, _connection_id: ConnectionId, _maybe_peer: Option, addresses: &[Multiaddr], _effective_role: ::libp2p::core::Endpoint, ) -> Result, ::libp2p::swarm::ConnectionDenied> { if let Some(first) = addresses.first() && addresses .iter() .all(|address| ignored(&self.ranges, address)) { return Err(::libp2p::swarm::ConnectionDenied::new(IgnoredAddress( first.clone(), ))); } Ok(Vec::new()) } fn handle_established_outbound_connection( &mut self, _connection_id: ConnectionId, _peer: PeerId, addr: &Multiaddr, _role_override: ::libp2p::core::Endpoint, _port_use: ::libp2p::core::transport::PortUse, ) -> Result<::libp2p::swarm::THandler, ::libp2p::swarm::ConnectionDenied> { if ignored(&self.ranges, addr) { return Err(::libp2p::swarm::ConnectionDenied::new(IgnoredAddress( addr.clone(), ))); } Ok(::libp2p::swarm::dummy::ConnectionHandler) } fn on_swarm_event(&mut self, _event: ::libp2p::swarm::FromSwarm) {} fn on_connection_handler_event( &mut self, _peer_id: PeerId, _connection_id: ConnectionId, event: ::libp2p::swarm::THandlerOutEvent, ) { match event {} } fn poll( &mut self, _cx: &mut std::task::Context<'_>, ) -> std::task::Poll< ::libp2p::swarm::ToSwarm>, > { std::task::Poll::Pending } } /// libp2p's identify, not told our listen addresses, nor external ones (found by UPnP, /// AutoNAT, a relay), in the ranges `libp2p-ignore` names: peers would remember them, /// show them, and dial them in vain. Announced addresses (`--libp2p-announce`) are told /// whatever range they are in. pub(super) struct Identifying { pub(super) inner: ::libp2p::identify::Behaviour, pub(super) ranges: Vec, pub(super) announced: Vec, } impl Identifying { /// An external address identify isn't told: in an ignored range, and not announced. fn left_out(&self, address: &Multiaddr) -> bool { ignored(&self.ranges, address) && !self.announced.contains(address) } } impl ::libp2p::swarm::NetworkBehaviour for Identifying { type ConnectionHandler = <::libp2p::identify::Behaviour as ::libp2p::swarm::NetworkBehaviour>::ConnectionHandler; type ToSwarm = ::libp2p::identify::Event; fn handle_pending_inbound_connection( &mut self, connection_id: ConnectionId, local_addr: &Multiaddr, remote_addr: &Multiaddr, ) -> Result<(), ::libp2p::swarm::ConnectionDenied> { self.inner .handle_pending_inbound_connection(connection_id, local_addr, remote_addr) } fn handle_established_inbound_connection( &mut self, connection_id: ConnectionId, peer: PeerId, local_addr: &Multiaddr, remote_addr: &Multiaddr, ) -> Result<::libp2p::swarm::THandler, ::libp2p::swarm::ConnectionDenied> { self.inner.handle_established_inbound_connection( connection_id, peer, local_addr, remote_addr, ) } fn handle_pending_outbound_connection( &mut self, connection_id: ConnectionId, maybe_peer: Option, addresses: &[Multiaddr], effective_role: ::libp2p::core::Endpoint, ) -> Result, ::libp2p::swarm::ConnectionDenied> { self.inner.handle_pending_outbound_connection( connection_id, maybe_peer, addresses, effective_role, ) } fn handle_established_outbound_connection( &mut self, connection_id: ConnectionId, peer: PeerId, addr: &Multiaddr, role_override: ::libp2p::core::Endpoint, port_use: ::libp2p::core::transport::PortUse, ) -> Result<::libp2p::swarm::THandler, ::libp2p::swarm::ConnectionDenied> { self.inner.handle_established_outbound_connection( connection_id, peer, addr, role_override, port_use, ) } fn on_swarm_event(&mut self, event: ::libp2p::swarm::FromSwarm) { use ::libp2p::swarm::FromSwarm; match event { FromSwarm::NewListenAddr(new) if ignored(&self.ranges, new.addr) => {} FromSwarm::ExpiredListenAddr(expired) if ignored(&self.ranges, expired.addr) => {} FromSwarm::ExternalAddrConfirmed(confirmed) if self.left_out(confirmed.addr) => {} FromSwarm::ExternalAddrExpired(expired) if self.left_out(expired.addr) => {} _ => self.inner.on_swarm_event(event), } } fn on_connection_handler_event( &mut self, peer_id: PeerId, connection_id: ConnectionId, event: ::libp2p::swarm::THandlerOutEvent, ) { self.inner .on_connection_handler_event(peer_id, connection_id, event) } fn poll( &mut self, cx: &mut std::task::Context<'_>, ) -> std::task::Poll< ::libp2p::swarm::ToSwarm>, > { self.inner.poll(cx) } }