1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! The smallest example showing how to use iroh and [`iroh::Endpoint`] to connect two devices.
//!
//! This example uses the default relay servers to attempt to holepunch, and will use that relay server to relay packets if the two devices cannot establish a direct UDP connection.
//! run this example from the project root:
//! $ cargo run --example listen
use std::time::Duration;
use iroh::{
Endpoint, RelayMode, SecretKey,
endpoint::{ConnectionError, presets},
};
use n0_error::{Result, StdResultExt};
use tracing::{debug, info, warn};
// An example ALPN that we are using to communicate over the `Endpoint`
const EXAMPLE_ALPN: &[u8] = b"n0/iroh/examples/0";
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
println!("\nlisten example!\n");
let secret_key = SecretKey::generate();
println!("public key: {}", secret_key.public());
// Build a `Endpoint`, which uses PublicKeys as endpoint identifiers, uses QUIC for directly connecting to other endpoints, and uses the relay protocol and relay servers to holepunch direct connections between endpoints when there are NATs or firewalls preventing direct connections. If no direct connection can be made, packets are relayed over the relay servers.
let endpoint = Endpoint::builder(presets::N0)
// The secret key is used to authenticate with other endpoints. The PublicKey portion of this secret key is how we identify endpoints, often referred to as the `endpoint_id` in our codebase.
.secret_key(secret_key)
// set the ALPN protocols this endpoint will accept on incoming connections
.alpns(vec![EXAMPLE_ALPN.to_vec()])
// `RelayMode::Default` means that we will use the default relay servers to holepunch and relay.
// Use `RelayMode::Custom` to pass in a `RelayMap` with custom relay urls.
// Use `RelayMode::Disable` to disable holepunching and relaying over HTTPS
// If you want to experiment with relaying using your own relay server, you must pass in the same custom relay url to both the `listen` code AND the `connect` code
.relay_mode(RelayMode::Default)
// you can choose a port to bind to, but passing in `0` will bind the socket to a random available port
.bind()
.await?;
let me = endpoint.id();
println!("endpoint id: {me}");
println!("endpoint listening addresses:");
// wait for the endpoint to be online
endpoint.online().await;
let endpoint_addr = endpoint.addr();
let local_addrs = endpoint_addr
.ip_addrs()
.map(|addr| {
let addr = addr.to_string();
println!("\t{addr}");
addr
})
.collect::<Vec<_>>()
.join(" ");
let relay_url = endpoint_addr.relay_urls().next().expect("missing relay");
println!("endpoint relay server url: {relay_url}");
println!("\nin a separate terminal run:");
println!(
"\tcargo run --example connect -- --endpoint-id {me} --addrs \"{local_addrs}\" --relay-url {relay_url}\n"
);
// accept incoming connections, returns a normal QUIC connection
while let Some(incoming) = endpoint.accept().await {
let mut accepting = match incoming.accept() {
Ok(accepting) => accepting,
Err(err) => {
warn!("incoming connection failed: {err:#}");
// we can carry on in these cases:
// this can be caused by retransmitted datagrams
continue;
}
};
let alpn = accepting.alpn().await?;
let conn = accepting.await?;
let endpoint_id = conn.remote_id();
info!(
"new connection from {endpoint_id} with ALPN {}",
String::from_utf8_lossy(&alpn),
);
// spawn a task to handle reading and writing off of the connection
tokio::spawn(async move {
// accept a bi-directional QUIC connection
// use the `noq` APIs to send and recv content
let (mut send, mut recv) = conn.accept_bi().await.anyerr()?;
debug!("accepted bi stream, waiting for data...");
let message = recv.read_to_end(100).await.anyerr()?;
let message = String::from_utf8(message).anyerr()?;
println!("received: {message}");
let message = format!("hi! you connected to {me}. bye bye");
send.write_all(message.as_bytes()).await.anyerr()?;
// call `finish` to close the connection gracefully
send.finish().anyerr()?;
// We sent the last message, so wait for the client to close the connection once
// it received this message.
let res = tokio::time::timeout(Duration::from_secs(3), async move {
let closed = conn.closed().await;
if !matches!(closed, ConnectionError::ApplicationClosed(_)) {
println!("endpoint {endpoint_id} disconnected with an error: {closed:#}");
}
})
.await;
if res.is_err() {
println!("endpoint {endpoint_id} did not disconnect within 3 seconds");
}
n0_error::Ok(())
});
}
// stop with SIGINT (ctrl-c)
Ok(())
}