protocols/autonat: optionally use only global IPs (#2618)

Optionally only perform dial-backs on peers that are observed at a global ip-address.
This is relevant when multiple peers are in the same local network, in which case a peer could incorrectly assume themself to be public because a peer in the same local network was able to dial them. Thus servers should reject dial-back requests from clients with a non-global IP address, and at the same time clients should only pick connected peers as servers if they are global.
Behind a config flag (enabled by default) to also allow use-cases where AutoNAT is needed within a private network.
This commit is contained in:
Elena Frank
2022-05-22 22:04:05 +02:00
committed by GitHub
parent 48598fc1da
commit ef2afcd41e
6 changed files with 247 additions and 57 deletions

View File

@ -30,6 +30,7 @@ use futures_timer::Delay;
use instant::Instant;
use libp2p_core::{
connection::{ConnectionId, ListenerId},
multiaddr::Protocol,
ConnectedPoint, Endpoint, Multiaddr, PeerId,
};
use libp2p_request_response::{
@ -77,6 +78,11 @@ pub struct Config {
pub throttle_clients_peer_max: usize,
/// Period for throttling clients requests.
pub throttle_clients_period: Duration,
/// As a server reject probes for clients that are observed at a non-global ip address.
/// Correspondingly as a client only pick peers as server that are not observed at a
/// private ip address. Note that this does not apply for servers that are added via
/// [`Behaviour::add_server`].
pub only_global_ips: bool,
}
impl Default for Config {
@ -93,6 +99,7 @@ impl Default for Config {
throttle_clients_global_max: 30,
throttle_clients_peer_max: 3,
throttle_clients_period: Duration::from_secs(1),
only_global_ips: true,
}
}
}
@ -188,7 +195,8 @@ pub struct Behaviour {
ongoing_outbound: HashMap<RequestId, ProbeId>,
// Connected peers with the observed address of each connection.
// If the endpoint of a connection is relayed, the observed address is `None`.
// If the endpoint of a connection is relayed or not global (in case of Config::only_global_ips),
// the observed address is `None`.
connected: HashMap<PeerId, HashMap<ConnectionId, Option<Multiaddr>>>,
// Used servers in recent outbound probes that are throttled through Config::throttle_server_period.
@ -313,12 +321,14 @@ impl NetworkBehaviour for Behaviour {
other_established,
);
let connections = self.connected.entry(*peer).or_default();
let addr = if endpoint.is_relayed() {
None
} else {
Some(endpoint.get_remote_address().clone())
};
connections.insert(*conn, addr);
let addr = endpoint.get_remote_address();
let observed_addr =
if !endpoint.is_relayed() && (!self.config.only_global_ips || addr.is_global_ip()) {
Some(addr.clone())
} else {
None
};
connections.insert(*conn, observed_addr);
match endpoint {
ConnectedPoint::Dialer {
@ -386,12 +396,14 @@ impl NetworkBehaviour for Behaviour {
return;
}
let connections = self.connected.get_mut(peer).expect("Peer is connected.");
let addr = if new.is_relayed() {
None
} else {
Some(new.get_remote_address().clone())
};
connections.insert(*conn, addr);
let addr = new.get_remote_address();
let observed_addr =
if !new.is_relayed() && (!self.config.only_global_ips || addr.is_global_ip()) {
Some(addr.clone())
} else {
None
};
connections.insert(*conn, observed_addr);
}
fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) {
@ -512,3 +524,119 @@ trait HandleInnerEvent {
event: RequestResponseEvent<DialRequest, DialResponse>,
) -> (VecDeque<Event>, Option<Action>);
}
trait GlobalIp {
fn is_global_ip(&self) -> bool;
}
impl GlobalIp for Multiaddr {
fn is_global_ip(&self) -> bool {
match self.iter().next() {
Some(Protocol::Ip4(a)) => a.is_global_ip(),
Some(Protocol::Ip6(a)) => a.is_global_ip(),
_ => false,
}
}
}
impl GlobalIp for std::net::Ipv4Addr {
// NOTE: The below logic is copied from `std::net::Ipv4Addr::is_global`, which is at the time of
// writing behind the unstable `ip` feature.
// See https://github.com/rust-lang/rust/issues/27709 for more info.
fn is_global_ip(&self) -> bool {
// Check if this address is 192.0.0.9 or 192.0.0.10. These addresses are the only two
// globally routable addresses in the 192.0.0.0/24 range.
if u32::from_be_bytes(self.octets()) == 0xc0000009
|| u32::from_be_bytes(self.octets()) == 0xc000000a
{
return true;
}
// Copied from the unstable method `std::net::Ipv4Addr::is_shared`.
fn is_shared(addr: &std::net::Ipv4Addr) -> bool {
addr.octets()[0] == 100 && (addr.octets()[1] & 0b1100_0000 == 0b0100_0000)
}
// Copied from the unstable method `std::net::Ipv4Addr::is_reserved`.
//
// **Warning**: As IANA assigns new addresses, this logic will be
// updated. This may result in non-reserved addresses being
// treated as reserved in code that relies on an outdated version
// of this method.
fn is_reserved(addr: &std::net::Ipv4Addr) -> bool {
addr.octets()[0] & 240 == 240 && !addr.is_broadcast()
}
// Copied from the unstable method `std::net::Ipv4Addr::is_benchmarking`.
fn is_benchmarking(addr: &std::net::Ipv4Addr) -> bool {
addr.octets()[0] == 198 && (addr.octets()[1] & 0xfe) == 18
}
!self.is_private()
&& !self.is_loopback()
&& !self.is_link_local()
&& !self.is_broadcast()
&& !self.is_documentation()
&& !is_shared(self)
// addresses reserved for future protocols (`192.0.0.0/24`)
&& !(self.octets()[0] == 192 && self.octets()[1] == 0 && self.octets()[2] == 0)
&& !is_reserved(self)
&& !is_benchmarking(self)
// Make sure the address is not in 0.0.0.0/8
&& self.octets()[0] != 0
}
}
impl GlobalIp for std::net::Ipv6Addr {
// NOTE: The below logic is copied from `std::net::Ipv6Addr::is_global`, which is at the time of
// writing behind the unstable `ip` feature.
// See https://github.com/rust-lang/rust/issues/27709 for more info.
//
// Note that contrary to `Ipv4Addr::is_global_ip` this currently checks for global scope
// rather than global reachability.
fn is_global_ip(&self) -> bool {
// Copied from the unstable method `std::net::Ipv6Addr::is_unicast`.
fn is_unicast(addr: &std::net::Ipv6Addr) -> bool {
!addr.is_multicast()
}
// Copied from the unstable method `std::net::Ipv6Addr::is_unicast_link_local`.
fn is_unicast_link_local(addr: &std::net::Ipv6Addr) -> bool {
(addr.segments()[0] & 0xffc0) == 0xfe80
}
// Copied from the unstable method `std::net::Ipv6Addr::is_unique_local`.
fn is_unique_local(addr: &std::net::Ipv6Addr) -> bool {
(addr.segments()[0] & 0xfe00) == 0xfc00
}
// Copied from the unstable method `std::net::Ipv6Addr::is_documentation`.
fn is_documentation(addr: &std::net::Ipv6Addr) -> bool {
(addr.segments()[0] == 0x2001) && (addr.segments()[1] == 0xdb8)
}
// Copied from the unstable method `std::net::Ipv6Addr::is_unicast_global`.
fn is_unicast_global(addr: &std::net::Ipv6Addr) -> bool {
is_unicast(addr)
&& !addr.is_loopback()
&& !is_unicast_link_local(addr)
&& !is_unique_local(addr)
&& !addr.is_unspecified()
&& !is_documentation(addr)
}
// Variation of unstable method [`std::net::Ipv6Addr::multicast_scope`] that instead of the
// `Ipv6MulticastScope` just returns if the scope is global or not.
// Equivalent to `Ipv6Addr::multicast_scope(..).map(|scope| matches!(scope, Ipv6MulticastScope::Global))`.
fn is_multicast_scope_global(addr: &std::net::Ipv6Addr) -> Option<bool> {
match addr.segments()[0] & 0x000f {
14 => Some(true), // Global multicast scope.
1..=5 | 8 => Some(false), // Local multicast scope.
_ => None, // Unknown multicast scope.
}
}
match is_multicast_scope_global(self) {
Some(true) => true,
None => is_unicast_global(self),
_ => false,
}
}
}