Skip to content

Commit ef2afcd

Browse files
authored
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.
1 parent 48598fc commit ef2afcd

File tree

6 files changed

+247
-57
lines changed

6 files changed

+247
-57
lines changed

protocols/autonat/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
- Update to `libp2p-request-response` `v0.18.0`.
88

9+
- Add `Config::only_global_ips` to skip peers that are observed at a private IP-address
10+
(see [PR 2618]).
11+
12+
[PR 2618]: https://github.com/libp2p/rust-libp2p/pull/2618
13+
914
# 0.3.0
1015

1116
- Update to `libp2p-swarm` `v0.35.0`.

protocols/autonat/src/behaviour.rs

Lines changed: 141 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use futures_timer::Delay;
3030
use instant::Instant;
3131
use libp2p_core::{
3232
connection::{ConnectionId, ListenerId},
33+
multiaddr::Protocol,
3334
ConnectedPoint, Endpoint, Multiaddr, PeerId,
3435
};
3536
use libp2p_request_response::{
@@ -77,6 +78,11 @@ pub struct Config {
7778
pub throttle_clients_peer_max: usize,
7879
/// Period for throttling clients requests.
7980
pub throttle_clients_period: Duration,
81+
/// As a server reject probes for clients that are observed at a non-global ip address.
82+
/// Correspondingly as a client only pick peers as server that are not observed at a
83+
/// private ip address. Note that this does not apply for servers that are added via
84+
/// [`Behaviour::add_server`].
85+
pub only_global_ips: bool,
8086
}
8187

8288
impl Default for Config {
@@ -93,6 +99,7 @@ impl Default for Config {
9399
throttle_clients_global_max: 30,
94100
throttle_clients_peer_max: 3,
95101
throttle_clients_period: Duration::from_secs(1),
102+
only_global_ips: true,
96103
}
97104
}
98105
}
@@ -188,7 +195,8 @@ pub struct Behaviour {
188195
ongoing_outbound: HashMap<RequestId, ProbeId>,
189196

190197
// Connected peers with the observed address of each connection.
191-
// If the endpoint of a connection is relayed, the observed address is `None`.
198+
// If the endpoint of a connection is relayed or not global (in case of Config::only_global_ips),
199+
// the observed address is `None`.
192200
connected: HashMap<PeerId, HashMap<ConnectionId, Option<Multiaddr>>>,
193201

194202
// Used servers in recent outbound probes that are throttled through Config::throttle_server_period.
@@ -313,12 +321,14 @@ impl NetworkBehaviour for Behaviour {
313321
other_established,
314322
);
315323
let connections = self.connected.entry(*peer).or_default();
316-
let addr = if endpoint.is_relayed() {
317-
None
318-
} else {
319-
Some(endpoint.get_remote_address().clone())
320-
};
321-
connections.insert(*conn, addr);
324+
let addr = endpoint.get_remote_address();
325+
let observed_addr =
326+
if !endpoint.is_relayed() && (!self.config.only_global_ips || addr.is_global_ip()) {
327+
Some(addr.clone())
328+
} else {
329+
None
330+
};
331+
connections.insert(*conn, observed_addr);
322332

323333
match endpoint {
324334
ConnectedPoint::Dialer {
@@ -386,12 +396,14 @@ impl NetworkBehaviour for Behaviour {
386396
return;
387397
}
388398
let connections = self.connected.get_mut(peer).expect("Peer is connected.");
389-
let addr = if new.is_relayed() {
390-
None
391-
} else {
392-
Some(new.get_remote_address().clone())
393-
};
394-
connections.insert(*conn, addr);
399+
let addr = new.get_remote_address();
400+
let observed_addr =
401+
if !new.is_relayed() && (!self.config.only_global_ips || addr.is_global_ip()) {
402+
Some(addr.clone())
403+
} else {
404+
None
405+
};
406+
connections.insert(*conn, observed_addr);
395407
}
396408

397409
fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) {
@@ -512,3 +524,119 @@ trait HandleInnerEvent {
512524
event: RequestResponseEvent<DialRequest, DialResponse>,
513525
) -> (VecDeque<Event>, Option<Action>);
514526
}
527+
528+
trait GlobalIp {
529+
fn is_global_ip(&self) -> bool;
530+
}
531+
532+
impl GlobalIp for Multiaddr {
533+
fn is_global_ip(&self) -> bool {
534+
match self.iter().next() {
535+
Some(Protocol::Ip4(a)) => a.is_global_ip(),
536+
Some(Protocol::Ip6(a)) => a.is_global_ip(),
537+
_ => false,
538+
}
539+
}
540+
}
541+
542+
impl GlobalIp for std::net::Ipv4Addr {
543+
// NOTE: The below logic is copied from `std::net::Ipv4Addr::is_global`, which is at the time of
544+
// writing behind the unstable `ip` feature.
545+
// See https://github.com/rust-lang/rust/issues/27709 for more info.
546+
fn is_global_ip(&self) -> bool {
547+
// Check if this address is 192.0.0.9 or 192.0.0.10. These addresses are the only two
548+
// globally routable addresses in the 192.0.0.0/24 range.
549+
if u32::from_be_bytes(self.octets()) == 0xc0000009
550+
|| u32::from_be_bytes(self.octets()) == 0xc000000a
551+
{
552+
return true;
553+
}
554+
555+
// Copied from the unstable method `std::net::Ipv4Addr::is_shared`.
556+
fn is_shared(addr: &std::net::Ipv4Addr) -> bool {
557+
addr.octets()[0] == 100 && (addr.octets()[1] & 0b1100_0000 == 0b0100_0000)
558+
}
559+
560+
// Copied from the unstable method `std::net::Ipv4Addr::is_reserved`.
561+
//
562+
// **Warning**: As IANA assigns new addresses, this logic will be
563+
// updated. This may result in non-reserved addresses being
564+
// treated as reserved in code that relies on an outdated version
565+
// of this method.
566+
fn is_reserved(addr: &std::net::Ipv4Addr) -> bool {
567+
addr.octets()[0] & 240 == 240 && !addr.is_broadcast()
568+
}
569+
570+
// Copied from the unstable method `std::net::Ipv4Addr::is_benchmarking`.
571+
fn is_benchmarking(addr: &std::net::Ipv4Addr) -> bool {
572+
addr.octets()[0] == 198 && (addr.octets()[1] & 0xfe) == 18
573+
}
574+
575+
!self.is_private()
576+
&& !self.is_loopback()
577+
&& !self.is_link_local()
578+
&& !self.is_broadcast()
579+
&& !self.is_documentation()
580+
&& !is_shared(self)
581+
// addresses reserved for future protocols (`192.0.0.0/24`)
582+
&& !(self.octets()[0] == 192 && self.octets()[1] == 0 && self.octets()[2] == 0)
583+
&& !is_reserved(self)
584+
&& !is_benchmarking(self)
585+
// Make sure the address is not in 0.0.0.0/8
586+
&& self.octets()[0] != 0
587+
}
588+
}
589+
590+
impl GlobalIp for std::net::Ipv6Addr {
591+
// NOTE: The below logic is copied from `std::net::Ipv6Addr::is_global`, which is at the time of
592+
// writing behind the unstable `ip` feature.
593+
// See https://github.com/rust-lang/rust/issues/27709 for more info.
594+
//
595+
// Note that contrary to `Ipv4Addr::is_global_ip` this currently checks for global scope
596+
// rather than global reachability.
597+
fn is_global_ip(&self) -> bool {
598+
// Copied from the unstable method `std::net::Ipv6Addr::is_unicast`.
599+
fn is_unicast(addr: &std::net::Ipv6Addr) -> bool {
600+
!addr.is_multicast()
601+
}
602+
// Copied from the unstable method `std::net::Ipv6Addr::is_unicast_link_local`.
603+
fn is_unicast_link_local(addr: &std::net::Ipv6Addr) -> bool {
604+
(addr.segments()[0] & 0xffc0) == 0xfe80
605+
}
606+
// Copied from the unstable method `std::net::Ipv6Addr::is_unique_local`.
607+
fn is_unique_local(addr: &std::net::Ipv6Addr) -> bool {
608+
(addr.segments()[0] & 0xfe00) == 0xfc00
609+
}
610+
// Copied from the unstable method `std::net::Ipv6Addr::is_documentation`.
611+
fn is_documentation(addr: &std::net::Ipv6Addr) -> bool {
612+
(addr.segments()[0] == 0x2001) && (addr.segments()[1] == 0xdb8)
613+
}
614+
615+
// Copied from the unstable method `std::net::Ipv6Addr::is_unicast_global`.
616+
fn is_unicast_global(addr: &std::net::Ipv6Addr) -> bool {
617+
is_unicast(addr)
618+
&& !addr.is_loopback()
619+
&& !is_unicast_link_local(addr)
620+
&& !is_unique_local(addr)
621+
&& !addr.is_unspecified()
622+
&& !is_documentation(addr)
623+
}
624+
625+
// Variation of unstable method [`std::net::Ipv6Addr::multicast_scope`] that instead of the
626+
// `Ipv6MulticastScope` just returns if the scope is global or not.
627+
// Equivalent to `Ipv6Addr::multicast_scope(..).map(|scope| matches!(scope, Ipv6MulticastScope::Global))`.
628+
fn is_multicast_scope_global(addr: &std::net::Ipv6Addr) -> Option<bool> {
629+
match addr.segments()[0] & 0x000f {
630+
14 => Some(true), // Global multicast scope.
631+
1..=5 | 8 => Some(false), // Local multicast scope.
632+
_ => None, // Unknown multicast scope.
633+
}
634+
}
635+
636+
match is_multicast_scope_global(self) {
637+
Some(true) => true,
638+
None => is_unicast_global(self),
639+
_ => false,
640+
}
641+
}
642+
}

protocols/autonat/src/behaviour/as_client.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,12 @@ impl<'a> AsClient<'a> {
262262
let mut servers: Vec<&PeerId> = self.servers.iter().collect();
263263

264264
if self.config.use_connected {
265-
servers.extend(self.connected.iter().map(|(id, _)| id));
265+
servers.extend(self.connected.iter().filter_map(|(id, addrs)| {
266+
// Filter servers for which no qualified address is known.
267+
// This is the case if the connection is relayed or the address is
268+
// not global (in case of Config::only_global_ips).
269+
addrs.values().any(|a| a.is_some()).then(|| id)
270+
}));
266271
}
267272

268273
servers.retain(|s| !self.throttled_servers.iter().any(|(id, _)| s == &id));

protocols/autonat/src/behaviour/as_server.rs

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -295,16 +295,16 @@ impl<'a> AsServer<'a> {
295295
.values()
296296
.find_map(|a| a.as_ref())
297297
.ok_or_else(|| {
298-
let status_text = "no dial-request over relayed connections".to_string();
299-
(status_text, ResponseError::DialError)
298+
let status_text = "refusing to dial peer with blocked observed address".to_string();
299+
(status_text, ResponseError::DialRefused)
300300
})?;
301301

302302
let mut addrs = Self::filter_valid_addrs(sender, request.addresses, observed_addr);
303303
addrs.truncate(self.config.max_peer_addresses);
304304

305305
if addrs.is_empty() {
306306
let status_text = "no dialable addresses".to_string();
307-
return Err((status_text, ResponseError::DialError));
307+
return Err((status_text, ResponseError::DialRefused));
308308
}
309309

310310
Ok(addrs)
@@ -316,10 +316,6 @@ impl<'a> AsServer<'a> {
316316
demanded: Vec<Multiaddr>,
317317
observed_remote_at: &Multiaddr,
318318
) -> Vec<Multiaddr> {
319-
// Skip if the observed address is a relay address.
320-
if observed_remote_at.iter().any(|p| p == Protocol::P2pCircuit) {
321-
return Vec::new();
322-
}
323319
let observed_ip = match observed_remote_at
324320
.into_iter()
325321
.find(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_)))
@@ -417,23 +413,4 @@ mod test {
417413
.with(Protocol::P2p(peer_id.into()));
418414
assert_eq!(filtered, vec![expected_1, expected_2]);
419415
}
420-
421-
#[test]
422-
fn skip_relayed_addr() {
423-
let peer_id = PeerId::random();
424-
let observed_ip = random_ip();
425-
// Observed address is relayed.
426-
let observed_addr = Multiaddr::empty()
427-
.with(observed_ip.clone())
428-
.with(random_port())
429-
.with(Protocol::P2p(PeerId::random().into()))
430-
.with(Protocol::P2pCircuit)
431-
.with(Protocol::P2p(peer_id.into()));
432-
let demanded = Multiaddr::empty()
433-
.with(random_ip())
434-
.with(random_port())
435-
.with(Protocol::P2p(peer_id.into()));
436-
let filtered = AsServer::filter_valid_addrs(peer_id, vec![demanded], &observed_addr);
437-
assert!(filtered.is_empty());
438-
}
439416
}

0 commit comments

Comments
 (0)