//! `--libp2p-ignore`: address ranges neither published nor dialled, nor connected over //! (`Ignoring`, a behaviour that denies connections there). 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)] 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 IpRange { 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)) } /// 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) } 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 } }