Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

protocols/autonat: optionally use only global IPs #2618

Merged
merged 21 commits into from May 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3a214d7
protocols/autonat: add only_global_ips config
elenaf9 Apr 17, 2022
dedb309
protocols/autonat: fix tests and fmt
elenaf9 Apr 17, 2022
3df7e84
Merge branch 'master' into autonat/config-global-ips
elenaf9 Apr 26, 2022
a9e64da
Merge branch 'master' of github.com:libp2p/rust-libp2p into autonat/c…
elenaf9 May 4, 2022
099b258
autonat: literal copy of fns from std::net
elenaf9 May 4, 2022
f2a51f3
Merge branch 'master' of github.com:libp2p/rust-libp2p into autonat/c…
elenaf9 May 8, 2022
9c2d901
autonat: literal copy of fns from std Ipv6Addr
elenaf9 May 8, 2022
b0f9713
autonat: return DialRefused on relayed/ private ip
elenaf9 May 8, 2022
99fec62
autonat: test only_global_ips config
elenaf9 May 8, 2022
d3d9015
autonat: remove unneeded explicit `drop`
elenaf9 May 8, 2022
05d5f2d
autonat: remove outdated comment
elenaf9 May 8, 2022
5b3b909
autonat: add changelog entry
elenaf9 May 8, 2022
f7d47da
autonat: fix changelog entry
elenaf9 May 10, 2022
b5a2c46
Merge branch 'master' into autonat/config-global-ips
elenaf9 May 10, 2022
2954c1a
autonat: remove redundant check
elenaf9 May 10, 2022
7035422
autonat: return DialRefused if no dialable addrs
elenaf9 May 10, 2022
f027e09
Merge branch 'master' into autonat/config-global-ips
elenaf9 May 11, 2022
202e0ad
autonat: fix typo
elenaf9 May 19, 2022
1d6dbeb
Merge branch 'master' into autonat/config-global-ips
elenaf9 May 19, 2022
36665ee
Merge branch 'master' into autonat/config-global-ips
elenaf9 May 22, 2022
54c9681
protocols/autonat: fix typo in changelog
elenaf9 May 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions protocols/autonat/CHANGELOG.md
Expand Up @@ -6,6 +6,11 @@

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

- Add `Config::only_global_ips` to skip peers that are observed at a private IP-address
(see [PR 2618]).

[PR 2618]: https://github.com/libp2p/rust-libp2p/pull/2618

# 0.3.0

- Update to `libp2p-swarm` `v0.35.0`.
Expand Down
154 changes: 141 additions & 13 deletions protocols/autonat/src/behaviour.rs
Expand Up @@ -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::{
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
}
}
}
7 changes: 6 additions & 1 deletion protocols/autonat/src/behaviour/as_client.rs
Expand Up @@ -262,7 +262,12 @@ impl<'a> AsClient<'a> {
let mut servers: Vec<&PeerId> = self.servers.iter().collect();

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

servers.retain(|s| !self.throttled_servers.iter().any(|(id, _)| s == &id));
Expand Down
29 changes: 3 additions & 26 deletions protocols/autonat/src/behaviour/as_server.rs
Expand Up @@ -295,16 +295,16 @@ impl<'a> AsServer<'a> {
.values()
.find_map(|a| a.as_ref())
.ok_or_else(|| {
let status_text = "no dial-request over relayed connections".to_string();
(status_text, ResponseError::DialError)
let status_text = "refusing to dial peer with blocked observed address".to_string();
(status_text, ResponseError::DialRefused)
Copy link
Contributor Author

@elenaf9 elenaf9 May 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the go implementation returns a ResponseError::DialError here (but with the same status text). I decided to change it here because imo a DialRefused makes more sense. A client should not flip their status to private or reduce the confidence in their public status just because they accidentally picked a server that rejects them because of the observed address (though if the client also enabled only_global_ips this should not happen in practice).
Edit: should it be part of the spec to list the cases in which a server returns DialError?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: should it be part of the spec to list the cases in which a server returns DialError?

Yes. I think this is worth consolidating on. Mind driving it on libp2p/specs?

Copy link
Contributor Author

@elenaf9 elenaf9 May 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also added 7035422, which does the same change for the case that filter_valid_addrs filters out all addresses send from the client.

})?;

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

if addrs.is_empty() {
let status_text = "no dialable addresses".to_string();
return Err((status_text, ResponseError::DialError));
return Err((status_text, ResponseError::DialRefused));
}

Ok(addrs)
Expand All @@ -316,10 +316,6 @@ impl<'a> AsServer<'a> {
demanded: Vec<Multiaddr>,
observed_remote_at: &Multiaddr,
) -> Vec<Multiaddr> {
// Skip if the observed address is a relay address.
if observed_remote_at.iter().any(|p| p == Protocol::P2pCircuit) {
return Vec::new();
}
let observed_ip = match observed_remote_at
.into_iter()
.find(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_)))
Expand Down Expand Up @@ -417,23 +413,4 @@ mod test {
.with(Protocol::P2p(peer_id.into()));
assert_eq!(filtered, vec![expected_1, expected_2]);
}

#[test]
fn skip_relayed_addr() {
let peer_id = PeerId::random();
let observed_ip = random_ip();
// Observed address is relayed.
let observed_addr = Multiaddr::empty()
.with(observed_ip.clone())
.with(random_port())
.with(Protocol::P2p(PeerId::random().into()))
.with(Protocol::P2pCircuit)
.with(Protocol::P2p(peer_id.into()));
let demanded = Multiaddr::empty()
.with(random_ip())
.with(random_port())
.with(Protocol::P2p(peer_id.into()));
let filtered = AsServer::filter_valid_addrs(peer_id, vec![demanded], &observed_addr);
assert!(filtered.is_empty());
}
}