Skip to content

Commit

Permalink
protocols/: Add basic AutoNAT implementation (libp2p#2262)
Browse files Browse the repository at this point in the history
This commit adds a behaviour protocol that implements the AutoNAT specification.
It enables users to detect whether they are behind a NAT. The Autonat Protocol
implements a Codec for the Request-Response protocol, and wraps it in a new
Network Behaviour with some additional functionality.

Co-authored-by: David Craven <david@craven.ch>
Co-authored-by: Max Inden <mail@max-inden.de>
  • Loading branch information
3 people committed Jan 14, 2022
0 parents commit ae96f4b
Show file tree
Hide file tree
Showing 12 changed files with 2,961 additions and 0 deletions.
36 changes: 36 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "libp2p-autonat"
edition = "2021"
rust-version = "1.56.1"
version = "0.20.0"
authors = ["David Craven <david@craven.ch>", "Elena Frank <elena.frank@protonmail.com>"]
license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p"
keywords = ["peer-to-peer", "libp2p", "networking"]
categories = ["network-programming", "asynchronous"]

[build-dependencies]
prost-build = "0.6"

[dependencies]
async-trait = "0.1"
futures = "0.3"
futures-timer = "3.0"
instant = "0.1"
libp2p-core = { version = "0.31.0", path = "../../core", default-features = false }
libp2p-swarm = { version = "0.33.0", path = "../../swarm" }
libp2p-request-response = { version = "0.15.0", path = "../request-response" }
log = "0.4"
rand = "0.8"
prost = "0.8"

[dev-dependencies]
async-std = { version = "1.10", features = ["attributes"] }
env_logger = "0.9"
structopt = "0.3"


[dev-dependencies.libp2p]
path = "../../"
default-features = false
features = ["autonat", "dns-async-std", "identify", "mplex", "noise", "tcp-async-io", "websocket", "yamux"]
23 changes: 23 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2021 Protocol Labs.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

fn main() {
prost_build::compile_protos(&["src/structs.proto"], &["src"]).unwrap();
}
135 changes: 135 additions & 0 deletions examples/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2021 Protocol Labs.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//! Basic example that combines the AutoNAT and identify protocols.
//!
//! The identify protocol informs the local peer of its external addresses, that are then send in AutoNAT dial-back
//! requests to the server.
//!
//! To run this example, follow the instructions in `examples/server` to start a server, then run in a new terminal:
//! ```sh
//! cargo run --example client -- --server-address <server-addr> --server-peer-id <server-peer-id> --listen_port <port>
//! ```
//! The `listen_port` parameter is optional and allows to set a fixed port at which the local client should listen.

use futures::prelude::*;
use libp2p::autonat;
use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent};
use libp2p::multiaddr::Protocol;
use libp2p::swarm::{Swarm, SwarmEvent};
use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId};
use std::error::Error;
use std::net::Ipv4Addr;
use std::time::Duration;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
#[structopt(name = "libp2p autonat")]
struct Opt {
#[structopt(long)]
listen_port: Option<u16>,

#[structopt(long)]
server_address: Multiaddr,

#[structopt(long)]
server_peer_id: PeerId,
}

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();

let opt = Opt::from_args();

let local_key = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());
println!("Local peer id: {:?}", local_peer_id);

let transport = libp2p::development_transport(local_key.clone()).await?;

let behaviour = Behaviour::new(local_key.public());

let mut swarm = Swarm::new(transport, behaviour, local_peer_id);
swarm.listen_on(
Multiaddr::empty()
.with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED))
.with(Protocol::Tcp(opt.listen_port.unwrap_or(0))),
)?;

swarm
.behaviour_mut()
.auto_nat
.add_server(opt.server_peer_id, Some(opt.server_address));

loop {
match swarm.select_next_some().await {
SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {:?}", address),
SwarmEvent::Behaviour(event) => println!("{:?}", event),
e => println!("{:?}", e),
}
}
}

#[derive(NetworkBehaviour)]
#[behaviour(out_event = "Event")]
struct Behaviour {
identify: Identify,
auto_nat: autonat::Behaviour,
}

impl Behaviour {
fn new(local_public_key: identity::PublicKey) -> Self {
Self {
identify: Identify::new(IdentifyConfig::new(
"/ipfs/0.1.0".into(),
local_public_key.clone(),
)),
auto_nat: autonat::Behaviour::new(
local_public_key.to_peer_id(),
autonat::Config {
retry_interval: Duration::from_secs(10),
refresh_interval: Duration::from_secs(30),
boot_delay: Duration::from_secs(5),
throttle_server_period: Duration::ZERO,
..Default::default()
},
),
}
}
}

#[derive(Debug)]
enum Event {
AutoNat(autonat::Event),
Identify(IdentifyEvent),
}

impl From<IdentifyEvent> for Event {
fn from(v: IdentifyEvent) -> Self {
Self::Identify(v)
}
}

impl From<autonat::Event> for Event {
fn from(v: autonat::Event) -> Self {
Self::AutoNat(v)
}
}
114 changes: 114 additions & 0 deletions examples/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2021 Protocol Labs.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//! Basic example for a AutoNAT server that supports the /libp2p/autonat/1.0.0 and "/ipfs/0.1.0" protocols.
//!
//! To start the server run:
//! ```sh
//! cargo run --example server -- --listen_port <port>
//! ```
//! The `listen_port` parameter is optional and allows to set a fixed port at which the local peer should listen.

use futures::prelude::*;
use libp2p::autonat;
use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent};
use libp2p::multiaddr::Protocol;
use libp2p::swarm::{Swarm, SwarmEvent};
use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId};
use std::error::Error;
use std::net::Ipv4Addr;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
#[structopt(name = "libp2p autonat")]
struct Opt {
#[structopt(long)]
listen_port: Option<u16>,
}

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();

let opt = Opt::from_args();

let local_key = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());
println!("Local peer id: {:?}", local_peer_id);

let transport = libp2p::development_transport(local_key.clone()).await?;

let behaviour = Behaviour::new(local_key.public());

let mut swarm = Swarm::new(transport, behaviour, local_peer_id);
swarm.listen_on(
Multiaddr::empty()
.with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED))
.with(Protocol::Tcp(opt.listen_port.unwrap_or(0))),
)?;

loop {
match swarm.select_next_some().await {
SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {:?}", address),
SwarmEvent::Behaviour(event) => println!("{:?}", event),
e => println!("{:?}", e),
}
}
}

#[derive(NetworkBehaviour)]
#[behaviour(out_event = "Event")]
struct Behaviour {
identify: Identify,
auto_nat: autonat::Behaviour,
}

impl Behaviour {
fn new(local_public_key: identity::PublicKey) -> Self {
Self {
identify: Identify::new(IdentifyConfig::new(
"/ipfs/0.1.0".into(),
local_public_key.clone(),
)),
auto_nat: autonat::Behaviour::new(
local_public_key.to_peer_id(),
autonat::Config::default(),
),
}
}
}

#[derive(Debug)]
enum Event {
AutoNat(autonat::Event),
Identify(IdentifyEvent),
}

impl From<IdentifyEvent> for Event {
fn from(v: IdentifyEvent) -> Self {
Self::Identify(v)
}
}

impl From<autonat::Event> for Event {
fn from(v: autonat::Event) -> Self {
Self::AutoNat(v)
}
}
Loading

0 comments on commit ae96f4b

Please sign in to comment.