dcutr_example/
main.rs

1// Copyright 2021 Protocol Labs.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21#![doc = include_str!("../README.md")]
22
23use std::{error::Error, str::FromStr};
24
25use clap::Parser;
26use futures::{executor::block_on, future::FutureExt, stream::StreamExt};
27use libp2p::{
28    core::multiaddr::{Multiaddr, Protocol},
29    dcutr, identify, identity, noise, ping, relay,
30    swarm::{NetworkBehaviour, SwarmEvent},
31    tcp, yamux, PeerId,
32};
33use tracing_subscriber::EnvFilter;
34
35#[derive(Debug, Parser)]
36#[command(name = "libp2p DCUtR client")]
37struct Opts {
38    /// The mode (client-listen, client-dial).
39    #[arg(long)]
40    mode: Mode,
41
42    /// Fixed value to generate deterministic peer id.
43    #[arg(long)]
44    secret_key_seed: u8,
45
46    /// The listening address
47    #[arg(long)]
48    relay_address: Multiaddr,
49
50    /// Peer ID of the remote peer to hole punch to.
51    #[arg(long)]
52    remote_peer_id: Option<PeerId>,
53}
54
55#[derive(Clone, Debug, PartialEq, Parser)]
56enum Mode {
57    Dial,
58    Listen,
59}
60
61impl FromStr for Mode {
62    type Err = String;
63    fn from_str(mode: &str) -> Result<Self, Self::Err> {
64        match mode {
65            "dial" => Ok(Mode::Dial),
66            "listen" => Ok(Mode::Listen),
67            _ => Err("Expected either 'dial' or 'listen'".to_string()),
68        }
69    }
70}
71
72#[tokio::main]
73async fn main() -> Result<(), Box<dyn Error>> {
74    let _ = tracing_subscriber::fmt()
75        .with_env_filter(EnvFilter::from_default_env())
76        .try_init();
77
78    let opts = Opts::parse();
79
80    #[derive(NetworkBehaviour)]
81    struct Behaviour {
82        relay_client: relay::client::Behaviour,
83        ping: ping::Behaviour,
84        identify: identify::Behaviour,
85        dcutr: dcutr::Behaviour,
86    }
87
88    let mut swarm =
89        libp2p::SwarmBuilder::with_existing_identity(generate_ed25519(opts.secret_key_seed))
90            .with_tokio()
91            .with_tcp(
92                tcp::Config::default().nodelay(true),
93                noise::Config::new,
94                yamux::Config::default,
95            )?
96            .with_quic()
97            .with_dns()?
98            .with_relay_client(noise::Config::new, yamux::Config::default)?
99            .with_behaviour(|keypair, relay_behaviour| Behaviour {
100                relay_client: relay_behaviour,
101                ping: ping::Behaviour::new(ping::Config::new()),
102                identify: identify::Behaviour::new(identify::Config::new(
103                    "/TODO/0.0.1".to_string(),
104                    keypair.public(),
105                )),
106                dcutr: dcutr::Behaviour::new(keypair.public().to_peer_id()),
107            })?
108            .build();
109
110    swarm
111        .listen_on("/ip4/0.0.0.0/udp/0/quic-v1".parse().unwrap())
112        .unwrap();
113    swarm
114        .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap())
115        .unwrap();
116
117    // Wait to listen on all interfaces.
118    block_on(async {
119        let mut delay = futures_timer::Delay::new(std::time::Duration::from_secs(1)).fuse();
120        loop {
121            futures::select! {
122                event = swarm.next() => {
123                    match event.unwrap() {
124                        SwarmEvent::NewListenAddr { address, .. } => {
125                            tracing::info!(%address, "Listening on address");
126                        }
127                        event => panic!("{event:?}"),
128                    }
129                }
130                _ = delay => {
131                    // Likely listening on all interfaces now, thus continuing by breaking the loop.
132                    break;
133                }
134            }
135        }
136    });
137
138    // Connect to the relay server. Not for the reservation or relayed connection, but to (a) learn
139    // our local public address and (b) enable a freshly started relay to learn its public address.
140    swarm.dial(opts.relay_address.clone()).unwrap();
141    block_on(async {
142        let mut learned_observed_addr = false;
143        let mut told_relay_observed_addr = false;
144
145        loop {
146            match swarm.next().await.unwrap() {
147                SwarmEvent::NewListenAddr { .. } => {}
148                SwarmEvent::Dialing { .. } => {}
149                SwarmEvent::ConnectionEstablished { .. } => {}
150                SwarmEvent::Behaviour(BehaviourEvent::Ping(_)) => {}
151                SwarmEvent::Behaviour(BehaviourEvent::Identify(identify::Event::Sent {
152                    ..
153                })) => {
154                    tracing::info!("Told relay its public address");
155                    told_relay_observed_addr = true;
156                }
157                SwarmEvent::Behaviour(BehaviourEvent::Identify(identify::Event::Received {
158                    info: identify::Info { observed_addr, .. },
159                    ..
160                })) => {
161                    tracing::info!(address=%observed_addr, "Relay told us our observed address");
162                    learned_observed_addr = true;
163                }
164                event => panic!("{event:?}"),
165            }
166
167            if learned_observed_addr && told_relay_observed_addr {
168                break;
169            }
170        }
171    });
172
173    match opts.mode {
174        Mode::Dial => {
175            swarm
176                .dial(
177                    opts.relay_address
178                        .with(Protocol::P2pCircuit)
179                        .with(Protocol::P2p(opts.remote_peer_id.unwrap())),
180                )
181                .unwrap();
182        }
183        Mode::Listen => {
184            swarm
185                .listen_on(opts.relay_address.with(Protocol::P2pCircuit))
186                .unwrap();
187        }
188    }
189
190    block_on(async {
191        loop {
192            match swarm.next().await.unwrap() {
193                SwarmEvent::NewListenAddr { address, .. } => {
194                    tracing::info!(%address, "Listening on address");
195                }
196                SwarmEvent::Behaviour(BehaviourEvent::RelayClient(
197                    relay::client::Event::ReservationReqAccepted { .. },
198                )) => {
199                    assert!(opts.mode == Mode::Listen);
200                    tracing::info!("Relay accepted our reservation request");
201                }
202                SwarmEvent::Behaviour(BehaviourEvent::RelayClient(event)) => {
203                    tracing::info!(?event)
204                }
205                SwarmEvent::Behaviour(BehaviourEvent::Dcutr(event)) => {
206                    tracing::info!(?event)
207                }
208                SwarmEvent::Behaviour(BehaviourEvent::Identify(event)) => {
209                    tracing::info!(?event)
210                }
211                SwarmEvent::Behaviour(BehaviourEvent::Ping(_)) => {}
212                SwarmEvent::ConnectionEstablished {
213                    peer_id, endpoint, ..
214                } => {
215                    tracing::info!(peer=%peer_id, ?endpoint, "Established new connection");
216                }
217                SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => {
218                    tracing::info!(peer=?peer_id, "Outgoing connection failed: {error}");
219                }
220                _ => {}
221            }
222        }
223    })
224}
225
226fn generate_ed25519(secret_key_seed: u8) -> identity::Keypair {
227    let mut bytes = [0u8; 32];
228    bytes[0] = secret_key_seed;
229
230    identity::Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length")
231}