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

feat(nat): add binary to probe for NAT status #1678

Merged
merged 10 commits into from
May 15, 2024
47 changes: 47 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"sn_faucet",
"sn_logging",
"sn_metrics",
"sn_nat_detection",
"sn_networking",
"sn_node",
"node-launchpad",
Expand Down
28 changes: 28 additions & 0 deletions sn_nat_detection/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
authors = ["MaidSafe Developers <dev@maidsafe.net>"]
description = "Safe Network AutoNAT probing"
edition = "2021"
homepage = "https://maidsafe.net"
license = "GPL-3.0"
name = "sn_nat_detection"
readme = "README.md"
repository = "https://github.com/maidsafe/safe_network"
version = "0.1.0"

[[bin]]
name = "detect-nat"
path = "src/main.rs"

[dependencies]
clap = { version = "4.5.4", features = ["derive"] }
clap-verbosity-flag = "2.2.0"
futures = "~0.3.13"
libp2p = { version = "0.53", features = ["tokio", "tcp", "noise", "yamux", "autonat", "identify", "macros", "upnp"] }
sn_networking = { path = "../sn_networking", version = "0.15.2" }
tokio = { version = "1.32.0", features = ["full"] }
tracing = { version = "~0.1.26" }
tracing-log = "0.2.0"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[lints]
workspace = true
75 changes: 75 additions & 0 deletions sn_nat_detection/src/behaviour/autonat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use libp2p::autonat;
use tracing::{debug, info, warn};

use crate::App;

impl App {
pub(crate) fn on_event_autonat(&mut self, event: autonat::Event) {
match event {
autonat::Event::InboundProbe(event) => match event {
autonat::InboundProbeEvent::Request {
probe_id,
peer: peer_id,
addresses,
} => {
info!(?probe_id, %peer_id, ?addresses, "Received a request to probe peer")
}
autonat::InboundProbeEvent::Response {
probe_id,
peer: peer_id,
address,
} => {
debug!(?probe_id, %peer_id, ?address, "Successfully probed a peer");
}
autonat::InboundProbeEvent::Error {
probe_id,
peer: peer_id,
error,
} => {
warn!(?probe_id, %peer_id, ?error, "Probing a peer failed")
}
},
autonat::Event::OutboundProbe(event) => match event {
autonat::OutboundProbeEvent::Request {
probe_id,
peer: peer_id,
} => {
debug!(?probe_id, %peer_id, "Asking remote to probe us")
}
autonat::OutboundProbeEvent::Response {
probe_id,
peer: peer_id,
address,
} => {
info!(?probe_id, %peer_id, ?address, "Remote successfully probed (reached) us")
}
autonat::OutboundProbeEvent::Error {
probe_id,
peer: peer_id,
error,
} => {
// Ignore the `NoServer` error if we're a server ourselves.
if self.client_state.is_none()
&& !matches!(error, autonat::OutboundProbeError::NoServer)
{
warn!(
?probe_id,
?peer_id,
?error,
"A request for probing us has failed"
)
}
}
},
autonat::Event::StatusChanged { old, new } => {
info!(
?new,
?old,
confidence = self.swarm.behaviour().autonat.confidence(),
"AutoNAT status changed"
);
self.check_state();
}
}
}
}
57 changes: 57 additions & 0 deletions sn_nat_detection/src/behaviour/identify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use libp2p::{autonat, identify};
use sn_networking::multiaddr_is_global;
use tracing::{debug, info, warn};

use crate::{behaviour::PROTOCOL_VERSION, App};

impl App {
pub(crate) fn on_event_identify(&mut self, event: identify::Event) {
match event {
identify::Event::Received { peer_id, info } => {
debug!(
%peer_id,
protocols=?info.protocols,
observed_address=%info.observed_addr,
protocol_version=%info.protocol_version,
"Received peer info"
);

// Disconnect if peer has incompatible protocol version.
if info.protocol_version != PROTOCOL_VERSION {
warn!(%peer_id, "Incompatible protocol version. Disconnecting from peer.");
let _ = self.swarm.disconnect_peer_id(peer_id);
return;
}

// Disconnect if peer has no AutoNAT support.
if !info
.protocols
.iter()
.any(|p| *p == autonat::DEFAULT_PROTOCOL_NAME)
{
warn!(%peer_id, "Peer does not support AutoNAT. Disconnecting from peer.");
let _ = self.swarm.disconnect_peer_id(peer_id);
#[allow(clippy::needless_return)]
return;
b-zee marked this conversation as resolved.
Show resolved Hide resolved
}

info!(%peer_id, "Received peer info: confirmed it supports AutoNAT");

// If we're a client and the peer has (a) global listen address(es),
// add it as an AutoNAT server.
if self.client_state.is_some() {
for addr in info.listen_addrs.into_iter().filter(multiaddr_is_global) {
self.swarm
.behaviour_mut()
.autonat
.add_server(peer_id, Some(addr));
}
}
self.check_state();
}
identify::Event::Sent { .. } => { /* ignore */ }
identify::Event::Pushed { .. } => { /* ignore */ }
identify::Event::Error { .. } => { /* ignore */ }
}
}
}
71 changes: 71 additions & 0 deletions sn_nat_detection/src/behaviour/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use libp2p::identity;
use libp2p::swarm::behaviour::toggle::Toggle;
use libp2p::swarm::NetworkBehaviour;
use std::time::Duration;

use crate::CONFIDENCE_MAX;

mod autonat;
mod identify;
mod upnp;

pub(crate) const PROTOCOL_VERSION: &str = "/sn_nat_detection/0.1.0";

#[derive(NetworkBehaviour)]
pub(crate) struct Behaviour {
pub autonat: libp2p::autonat::Behaviour,
pub identify: libp2p::identify::Behaviour,
pub upnp: Toggle<libp2p::upnp::tokio::Behaviour>,
}

impl Behaviour {
pub(crate) fn new(
local_public_key: identity::PublicKey,
client_mode: bool,
upnp: bool,
) -> Self {
let far_future = Duration::MAX / 10; // `MAX` on itself causes overflows. This is a workaround.
Self {
autonat: libp2p::autonat::Behaviour::new(
local_public_key.to_peer_id(),
if client_mode {
libp2p::autonat::Config {
// Use dialed peers for probing.
use_connected: true,
// Start probing a few seconds after swarm init. This gives us time to connect to the dialed server.
// With UPnP enabled, give it a bit more time to possibly open up the port.
boot_delay: if upnp {
Duration::from_secs(7)
} else {
Duration::from_secs(3)
},
// Reuse probe server immediately even if it's the only one.
throttle_server_period: Duration::ZERO,
retry_interval: Duration::from_secs(10),
// We do not want to refresh.
refresh_interval: far_future,
confidence_max: CONFIDENCE_MAX,
..Default::default()
}
} else {
libp2p::autonat::Config {
// Do not ask for dial-backs, essentially putting us in server mode.
use_connected: false,
// Never start probing, as we are a server.
boot_delay: far_future,
..Default::default()
}
},
),
identify: libp2p::identify::Behaviour::new(
libp2p::identify::Config::new(
PROTOCOL_VERSION.to_string(),
local_public_key.clone(),
)
// Exchange information every 5 minutes.
.with_interval(Duration::from_secs(5 * 60)),
),
upnp: upnp.then(libp2p::upnp::tokio::Behaviour::default).into(),
}
}
}
24 changes: 24 additions & 0 deletions sn_nat_detection/src/behaviour/upnp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use libp2p::upnp;
use tracing::{debug, info};
use tracing_log::log::error;

use crate::App;

impl App {
pub(crate) fn on_event_upnp(&mut self, event: upnp::Event) {
match event {
upnp::Event::NewExternalAddr(addr) => {
info!(%addr, "UPnP: New external address detected");
}
upnp::Event::ExpiredExternalAddr(addr) => {
debug!(%addr, "UPnP: External address expired");
}
upnp::Event::GatewayNotFound => {
error!("UPnP: No gateway not found");
}
upnp::Event::NonRoutableGateway => {
error!("UPnP: Gateway is not routable");
}
}
}
}
Loading
Loading