diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 27c68fc2c88..e2ce70ee7be 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -30,6 +30,7 @@ prost-build = "0.7" [dev-dependencies] env_logger = "0.8.3" +structopt = "0.3.21" libp2p = { path = "../.." } libp2p-kad = { path = "../kad" } libp2p-ping = { path = "../ping" } diff --git a/protocols/relay/examples/Dockerfile b/protocols/relay/examples/Dockerfile new file mode 100644 index 00000000000..cd511a879ce --- /dev/null +++ b/protocols/relay/examples/Dockerfile @@ -0,0 +1,19 @@ +# 1: Build the exe +FROM rust:1 as builder +LABEL Name=libp2p-relay Version=0.0.1 +# 1a: Prepare for static linking +RUN apt-get update && \ + apt-get dist-upgrade -y && \ + apt-get install -y musl-tools && \ + rustup target add x86_64-unknown-linux-musl +# 1b: Download and compile Rust dependencies (and store as a separate Docker layer) +WORKDIR /usr/src/libp2p + +COPY . . +RUN cargo build --target x86_64-unknown-linux-musl --example=relay --package=libp2p-relay + +# 2: Copy the exe and extra files ("static") to an empty Docker image +FROM debian:buster-slim + +COPY --from=builder /usr/src/libp2p/target/x86_64-unknown-linux-musl/debug/examples/relay . +USER 1000 \ No newline at end of file diff --git a/protocols/relay/examples/docker-compose.yml b/protocols/relay/examples/docker-compose.yml new file mode 100644 index 00000000000..5ae38393465 --- /dev/null +++ b/protocols/relay/examples/docker-compose.yml @@ -0,0 +1,63 @@ +# Run `docker-compose up` to start the setup. + +version: '2.1' +services: + relay: + image: libp2p-relay + command: + - "./relay" + - "--mode=relay" + - "--secret-key-seed=1" + - "--address=/ip6/::/tcp/4444" + build: + context: ../../../. + dockerfile: ./protocols/relay/examples/Dockerfile + networks: + - network-a + - network-b + + client-listen: + image: libp2p-relay + command: + - "./relay" + - "--mode=client-listen" + - "--secret-key-seed=2" + - "--address=/dns6/relay/tcp/4444/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X/p2p-circuit" + build: + context: ../../../. + dockerfile: ./protocols/relay/examples/Dockerfile + depends_on: + - "relay" + networks: + - network-a + + client-dial: + image: libp2p-relay + command: + - "./relay" + - "--mode=client-dial" + - "--secret-key-seed=3" + - "--address=/dns6/relay/tcp/4444/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X/p2p-circuit/p2p/12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3" + build: + context: ../../../. + dockerfile: ./protocols/relay/examples/Dockerfile + depends_on: + - "client-listen" + networks: + - network-b + +networks: + network-a: + driver: bridge + enable_ipv6: true + ipam: + driver: default + config: + - subnet: 2001:3984:3989::/64 + network-b: + driver: bridge + enable_ipv6: true + ipam: + driver: default + config: + - subnet: 2001:3984:3988::/64 diff --git a/protocols/relay/examples/relay.rs b/protocols/relay/examples/relay.rs index b79c3e476f9..715ed1a5a32 100644 --- a/protocols/relay/examples/relay.rs +++ b/protocols/relay/examples/relay.rs @@ -24,63 +24,78 @@ //! relay client listening via the relay server and (3) a dialing relay client //! dialing the listening relay client via the relay server. //! -//! 1. To start the relay server, run `cargo run --example relay -- relay` which will print -//! something along the lines of: +//! 1. To start the relay server, run `cargo run --example=relay --package=libp2p-relay --mode relay --secret-key-seed 1 --address /ip4//tcp/`. +//! The `-secret-key-seed` helps create a static peer id using the given number argument as a seed. +//! The mode specifies whether the node should run as a relay server, a listening client or a dialing client. +//! The address specifies a static address. Usually it will be some loop back address such as `/ip4/0.0.0.0/tcp/4444`. +//! Example: +//! `cargo run --example=relay --package=libp2p-relay -- --mode relay --secret-key-seed 1 --address /ip4/0.0.0.0/tcp/4444` +//! `cargo run --example=relay --package=libp2p-relay -- --mode relay --secret-key-seed 1 --address /ip6/::/tcp/4444` //! -//! ``` -//! Local peer id: PeerId("12D3KooWAP5X5k9DS94n7AsiUAsaiso59Kioh14j2c13fCiudjdZ") -//! # ^-- -//! Listening on "/ip6/::1/tcp/36537" -//! # ^-- -//! ``` -//! -//! 2. To start the listening relay client run `cargo run --example relay -- client-listen +//! 2. To start the listening relay client run `cargo run --example=relay --package=libp2p-relay -- --mode client-listen --secret-key-seed 2 --address //! /p2p//p2p-circuit` in a second terminal where: //! -//! - ``: one of the listening addresses of the relay server -//! - ``: the peer id of the relay server +//! - `` is replaced by one of the listening addresses of the relay server. +//! - `` is replaced by the peer id of the relay server. +//! +//! Example: +//! `cargo run --example=relay --package=libp2p-relay -- --mode client-listen --secret-key-seed 2 --address /ip4/127.0.0.1/tcp/4444/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X/p2p-circuit` +//! `cargo run --example=relay --package=libp2p-relay -- --mode client-listen --secret-key-seed 2 --address /ip6/::1/tcp/4444/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X/p2p-circuit` //! -//! 3. To start the dialing relay client run `cargo run --example relay -- client-dial -//! /p2p//p2p-circuit/p2p/` -//! in a third terminal where: +//! 3. To start the dialing relay client run `cargo run --example=relay --package=libp2p-relay -- --mode client-dial --secret-key-seed 3 --address +//! /p2p//p2p-circuit/p2p/` in +//! a third terminal where: //! -//! - ``: one of the listening addresses of the relay server -//! - ``: the peer id of the relay server -//! - ``: the peer id of the listening relay client +//! - `` is replaced by one of the listening addresses of the relay server. +//! - `` is replaced by the peer id of the relay server. +//! - `` is replaced by the peer id of the listening relay client. +//! Example: +//! `cargo run --example=relay --package=libp2p-relay -- --mode client-dial --secret-key-seed 3 --address /ip4/127.0.0.1/tcp/4444/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X/p2p-circuit/p2p/12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3` +//! `cargo run --example=relay --package=libp2p-relay -- --mode client-dial --secret-key-seed 3 --address /ip6/::1/tcp/4444/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X/p2p-circuit/p2p/12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3` //! -//! In the third terminal you will see the dialing relay client to receive pings from both the relay -//! server AND from the listening relay client relayed via the relay server. +//! In the third terminal you will see the dialing relay client to receive pings +//! from both the relay server AND from the listening relay client relayed via +//! the relay server. use futures::executor::block_on; use futures::stream::StreamExt; -use libp2p::core::upgrade; +use libp2p::dns::DnsConfig; use libp2p::ping::{Ping, PingConfig, PingEvent}; use libp2p::plaintext; use libp2p::relay::{Relay, RelayConfig}; use libp2p::swarm::SwarmEvent; use libp2p::tcp::TcpConfig; use libp2p::Transport; -use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId, Swarm}; +use libp2p::{core::upgrade, identity::ed25519}; +use libp2p::{identity, NetworkBehaviour, PeerId, Swarm}; use std::error::Error; use std::task::{Context, Poll}; use std::time::Duration; +use std::{fmt, str::FromStr}; +use structopt::StructOpt; + +// Listen on all interfaces and whatever port the OS assigns +const DEFAULT_RELAY_ADDRESS: &str = "/ip4/0.0.0.0/tcp/0"; fn main() -> Result<(), Box> { env_logger::init(); - // Create a random PeerId - let local_key = identity::Keypair::generate_ed25519(); + let opt = Opt::from_args(); + println!("opt: {:?}", opt); + + // Create a static known PeerId based on given secret + let local_key: identity::Keypair = generate_ed25519(opt.secret_key_seed); let local_peer_id = PeerId::from(local_key.public()); println!("Local peer id: {:?}", local_peer_id); - let tcp_transport = TcpConfig::new(); + let transport = block_on(DnsConfig::system(TcpConfig::new()))?; let relay_config = RelayConfig { connection_idle_timeout: Duration::from_secs(10 * 60), ..Default::default() }; let (relay_wrapped_transport, relay_behaviour) = - libp2p_relay::new_transport_and_behaviour(relay_config, tcp_transport); + libp2p_relay::new_transport_and_behaviour(relay_config, transport); let behaviour = Behaviour { relay: relay_behaviour, @@ -103,40 +118,33 @@ fn main() -> Result<(), Box> { let mut swarm = Swarm::new(transport, behaviour, local_peer_id); - match std::env::args() - .nth(1) - .expect("Please provide either of relay, client-listen or client-dial.") - .as_str() - { - "relay" => { - // Listen on all interfaces and whatever port the OS assigns - swarm.listen_on("/ip6/::/tcp/0".parse()?)?; + match opt.mode { + Mode::Relay => { + let address = get_relay_address(&opt); + swarm.listen_on(address.parse()?)?; + println!("starting listening as relay on {}", &address); } - "client-listen" => { - let addr: Multiaddr = std::env::args() - .nth(2) - .expect("Please provide relayed listen address.") - .parse()?; - swarm.listen_on(addr)?; + Mode::ClientListen => { + let relay_address = get_relay_peer_address(&opt); + swarm.listen_on(relay_address.parse()?)?; + println!("starting client listener via relay on {}", &relay_address); } - "client-dial" => { - let addr: Multiaddr = std::env::args() - .nth(2) - .expect("Please provide relayed dial address.") - .parse()?; - swarm.dial_addr(addr)?; + Mode::ClientDial => { + let client_listen_address = get_client_listen_address(&opt); + swarm.dial_addr(client_listen_address.parse()?)?; + println!("starting as client dialer on {}", client_listen_address); } - s => panic!("Unexpected argument {:?}", s), } block_on(futures::future::poll_fn(move |cx: &mut Context<'_>| { loop { match swarm.poll_next_unpin(cx) { - Poll::Ready(Some(event)) => { - if let SwarmEvent::NewListenAddr(addr) = event { - println!("Listening on {:?}", addr); + Poll::Ready(Some(event)) => match event { + SwarmEvent::NewListenAddr(addr) => { + print_listener_peer(&addr, &opt.mode, local_peer_id) } - } + _ => println!("{:?}", event), + }, Poll::Ready(None) => return Poll::Ready(Ok(())), Poll::Pending => break, } @@ -145,6 +153,62 @@ fn main() -> Result<(), Box> { })) } +fn print_listener_peer(addr: &libp2p::Multiaddr, mode: &Mode, local_peer_id: PeerId) -> () { + match mode { + Mode::Relay => { + println!( + "Peer that act as Relay can access on: `{}/p2p/{}/p2p-circuit`", + addr, local_peer_id + ); + } + Mode::ClientListen => { + println!( + "Peer that act as Client Listen can access on: `/p2p/{}/{}`", + addr, local_peer_id + ); + } + Mode::ClientDial => { + println!("Peer that act as Client Dial Listening on {:?}", addr); + } + } +} + +fn generate_ed25519(secret_key_seed: u8) -> identity::Keypair { + let mut bytes = [0u8; 32]; + bytes[0] = secret_key_seed; + + let secret_key = ed25519::SecretKey::from_bytes(&mut bytes) + .expect("this returns `Err` only if the length is wrong; the length is correct; qed"); + identity::Keypair::Ed25519(secret_key.into()) +} + +/// Get the address for relay mode +fn get_relay_address(opt: &Opt) -> String { + match &opt.address { + Some(address) => address.clone(), + None => { + println!("--address argument was not provided, will use the default listening relay address: {}",DEFAULT_RELAY_ADDRESS); + DEFAULT_RELAY_ADDRESS.to_string() + } + } +} + +/// Get the address for client_listen mode +fn get_relay_peer_address(opt: &Opt) -> String { + match &opt.address { + Some(address) => address.clone(), + None => panic!("Please provide relayed listen address such as: /p2p//p2p-circuit"), + } +} + +/// Get the address for client-dial mode +fn get_client_listen_address(opt: &Opt) -> String { + match &opt.address { + Some(address) => address.clone(), + None => panic!("Please provide client listen address such as: /p2p//p2p-circuit/p2p/") + } +} + #[derive(NetworkBehaviour)] #[behaviour(out_event = "Event", event_process = false)] struct Behaviour { @@ -169,3 +233,47 @@ impl From<()> for Event { Event::Relay(()) } } + +#[derive(Debug, StructOpt)] +enum Mode { + Relay, + ClientListen, + ClientDial, +} + +impl FromStr for Mode { + type Err = ModeError; + fn from_str(mode: &str) -> Result { + match mode { + "relay" => Ok(Mode::Relay), + "client-listen" => Ok(Mode::ClientListen), + "client-dial" => Ok(Mode::ClientDial), + _ => Err(ModeError {}), + } + } +} + +#[derive(Debug)] +struct ModeError {} +impl Error for ModeError {} +impl fmt::Display for ModeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Could not parse a mode") + } +} + +#[derive(Debug, StructOpt)] +#[structopt(name = "libp2p relay")] +struct Opt { + /// The mode (relay, client-listen, client-dial) + #[structopt(long)] + mode: Mode, + + /// Fixed value to generate deterministic peer id + #[structopt(long)] + secret_key_seed: u8, + + /// The listening address + #[structopt(long)] + address: Option, +}