1#![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 #[arg(long)]
40 mode: Mode,
41
42 #[arg(long)]
44 secret_key_seed: u8,
45
46 #[arg(long)]
48 relay_address: Multiaddr,
49
50 #[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 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 break;
133 }
134 }
135 }
136 });
137
138 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}