A long-running rc.33 daemon reached OOM with roughly 22 GiB resident and 24 GiB swapped. Live inspection found 40 GiB and 10/20 GiB anonymous container mappings; the active worker stack and tracing metadata identified iroh's scheduled path-open retry loop. In iroh 1.2.0 every failed connection requeues the same path, while each retry visits every connection again. Separate control and data connections multiply the queue geometrically. Vendor the published iroh 1.2.0 source and select it with a workspace Cargo patch. Deduplicate retry admission and cap distinct pending paths at 64, evicting the oldest before inserting a new candidate. Keep the current and next retry batches bounded without combining control/data connections or changing wire protocols, identity derivation, authentication or state. Document source provenance, the live evidence and removal conditions. Upstream PR 4551 merged on October 6, but inspection of the official 1.3.0 crate still shows the unconditional enqueue, so that upgrade alone is not a fix. No version bump, release tag or installed-service change. The default workspace suite includes the exact vendored admission module. Three regressions failed with the original enqueue and pass with the fix: repeated failures across connections, duplicate admission at capacity, and 10,000 distinct candidates with bounded length/capacity and FIFO eviction. Transport integration tests continue using real endpoints and encryption. Checks on Linux in nix develop: - cargo fmt --all -- --check - cargo clippy --locked --workspace --all-targets -- -D warnings - cargo test --locked --workspace --all-targets, with only the inherited TSUNAGI_CONTROL_SOCKET removed from the test environment - git diff --cached --check Initial full-suite runs failed six DNS readiness checks because the inherited system socket path overrode their temporary state directories. Clearing that environment variable resolves those failures without changing application code. Root/network-dependent ignored tests and other operating systems were not run.
96 lines
4.4 KiB
Rust
96 lines
4.4 KiB
Rust
//! The smallest example showing how to use iroh and [`iroh::Endpoint`] to connect to a remote endpoint and pass bytes using unreliable datagrams.
|
|
//!
|
|
//! We use the endpoint ID (the PublicKey of the remote endpoint), the direct UDP addresses, and the relay url to achieve a connection.
|
|
//!
|
|
//! 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 the `listen-unreliable` example first (`iroh/examples/listen-unreliable.rs`), which will give you instructions on how to run this example to watch two endpoints connect and exchange bytes.
|
|
use std::net::SocketAddr;
|
|
|
|
use clap::Parser;
|
|
use iroh::{Endpoint, EndpointAddr, RelayMode, RelayUrl, SecretKey, endpoint::presets};
|
|
use iroh_base::TransportAddr;
|
|
use n0_error::{Result, StdResultExt};
|
|
use tracing::info;
|
|
|
|
// An example ALPN that we are using to communicate over the `Endpoint`
|
|
const EXAMPLE_ALPN: &[u8] = b"n0/iroh/examples/0";
|
|
|
|
#[derive(Debug, Parser)]
|
|
struct Cli {
|
|
/// The id of the remote endpoint.
|
|
#[clap(long)]
|
|
endpoint_id: iroh::EndpointId,
|
|
/// The list of direct UDP addresses for the remote endpoint.
|
|
#[clap(long, value_parser, num_args = 1.., value_delimiter = ' ')]
|
|
addrs: Vec<SocketAddr>,
|
|
/// The url of the relay server the remote endpoint can also be reached at.
|
|
#[clap(long)]
|
|
relay_url: RelayUrl,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
tracing_subscriber::fmt::init();
|
|
println!("\nconnect (unreliable) example!\n");
|
|
let args = Cli::parse();
|
|
let secret_key = SecretKey::from_bytes(&rand::random());
|
|
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 an address to bind to, but passing in `None` will bind the socket to a random available port
|
|
.bind()
|
|
.await?;
|
|
|
|
// wait for the endpoint to be online
|
|
endpoint.online().await;
|
|
|
|
let endpoint_addr = endpoint.addr();
|
|
let me = endpoint_addr.id;
|
|
println!("endpoint id: {me}");
|
|
println!("endpoint listening addresses:");
|
|
endpoint_addr
|
|
.ip_addrs()
|
|
.for_each(|addr| println!("\t{addr}"));
|
|
let relay_url = endpoint_addr
|
|
.relay_urls()
|
|
.next()
|
|
.expect("Should have a relay URL, assuming a default endpoint setup.");
|
|
println!("endpoint relay server url: {relay_url}\n");
|
|
// Build a `EndpointAddr` from the endpoint_id, relay url, and UDP addresses.
|
|
let addrs = args
|
|
.addrs
|
|
.into_iter()
|
|
.map(TransportAddr::Ip)
|
|
.chain(std::iter::once(TransportAddr::Relay(args.relay_url)));
|
|
|
|
let addr = EndpointAddr::from_parts(args.endpoint_id, addrs);
|
|
|
|
// Attempt to connect, over the given ALPN.
|
|
// Returns a QUIC connection.
|
|
let conn = endpoint.connect(addr, EXAMPLE_ALPN).await?;
|
|
info!("connected");
|
|
|
|
// Send a datagram over the connection.
|
|
let message = format!("{me} is saying 'hello!'");
|
|
conn.send_datagram(message.as_bytes().to_vec().into())
|
|
.anyerr()?;
|
|
|
|
// Read a datagram over the connection.
|
|
let message = conn.read_datagram().await.anyerr()?;
|
|
let message = String::from_utf8(message.into()).anyerr()?;
|
|
println!("received: {message}");
|
|
|
|
Ok(())
|
|
}
|