diff --git a/connecting/creating-endpoint.mdx b/connecting/creating-endpoint.mdx index 8a3f65c..26d07c6 100644 --- a/connecting/creating-endpoint.mdx +++ b/connecting/creating-endpoint.mdx @@ -15,12 +15,14 @@ This method initializes a new endpoint and binds it to a local address, allowing to listen for incoming connections. ```rust -use iroh::Endpoint; +use iroh::{Endpoint, presets}; +use anyhow::Result; #[tokio::main] -async fn main() { - let endpoint = Endpoint::bind().await?; +async fn main() -> Result<()> { + let endpoint = Endpoint::bind(presets::N0).await?; // ... + Ok(()) } ``` diff --git a/connecting/dns-discovery.mdx b/connecting/dns-discovery.mdx index 8988d4e..6e02f7c 100644 --- a/connecting/dns-discovery.mdx +++ b/connecting/dns-discovery.mdx @@ -25,12 +25,12 @@ options](https://cal.com/team/number-0/n0-protocol-services?overlayCalendar=true ```rust -use iroh::Endpoint; +use iroh::{Endpoint, presets}; use iroh_tickets::endpoint::EndpointTicket; #[tokio::main] async fn main() -> anyhow::Result<()> { - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; println!("endpoint id: {:?}", endpoint.id()); diff --git a/connecting/gossip.mdx b/connecting/gossip.mdx index dc0be9e..c85f6ba 100644 --- a/connecting/gossip.mdx +++ b/connecting/gossip.mdx @@ -47,7 +47,7 @@ There are different ways to structure your application around topics, depending ### Example ```rust -use iroh::{protocol::Router, Endpoint, EndpointId}; +use iroh::{protocol::Router, Endpoint, EndpointId, presets}; use iroh_gossip::{api::Event, Gossip, TopicId}; use n0_error::{Result, StdResultExt}; use n0_future::StreamExt; @@ -56,7 +56,7 @@ use n0_future::StreamExt; async fn main() -> Result<()> { // create an iroh endpoint that includes the standard discovery mechanisms // we've built at number0 - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; // build gossip protocol let gossip = Gossip::builder().spawn(endpoint.clone()); diff --git a/connecting/local-discovery.mdx b/connecting/local-discovery.mdx index 0f86d2e..7c130c7 100644 --- a/connecting/local-discovery.mdx +++ b/connecting/local-discovery.mdx @@ -1,16 +1,20 @@ --- -title: "mDNS and Bluetooth" +title: "mDNS" --- -Local discovery adds the ability to use physical radios to discover other iroh -endpoints. This is useful for local networks where the internet may not be available or reliable. +The mDNS discovery mechanism will automatically broadcast your endpoint's +presence on the local network, and listen for other endpoints doing the same. When +another endpoint is discovered, the dialing information is exchanged, and a +connection can be established directly over the local network without needing a relay. -Local connections can be faster and more reliable than internet-based connections, especially in -environments with poor connectivity. They also enhance privacy by keeping communications within a local area. +Devices need to be connected to the same local network for mDNS discovery to +work. This can be a Wi-Fi network, an Ethernet network, or even a mobile +hotspot. mDNS is not designed to work over the internet or across different +networks. -## mDNS +## Usage -Local Discovery is _not_ enabled by default, and must be enabled by the user. +Local Discovery is _not_ enabled by default, and must be enabled explicitly. You'll need to add the `discovery-local-network` feature flag to your `Cargo.toml` to use it. @@ -23,23 +27,13 @@ iroh = { version = "0.nn", features = ["address-lookup-mdns"] } Then configure your endpoint to use local discovery concurrently with the default DNS discovery: ```rust -use iroh::Endpoint; +use iroh::{Endpoint, presets}; let mdns = iroh::address_lookup::mdns::MdnsAddressLookup::builder(); let ep = Endpoint::builder() .address_lookup(mdns) - .bind() + .bind(presets::N0) .await?; ``` -The mDNS discovery mechanism will automatically broadcast your endpoint's -presence on the local network, and listen for other endpoints doing the same. When -another endpoint is discovered, the dialing information is exchanged, and a -connection can be established directly over the local network without needing a relay. - For more information on how mDNS discovery works, see the [mDNS documentation](https://docs.rs/iroh/latest/iroh/address_lookup/mdns/index.html). - -## Bluetooth - -Bluetooth discovery is currently under development and will be available in a -future release of iroh. For more information, please [contact us](https://cal.com/team/number-0/n0-protocol-services). diff --git a/docs.json b/docs.json index 81ec2f1..a190daf 100644 --- a/docs.json +++ b/docs.json @@ -34,7 +34,7 @@ ] }, { - "group": "Forming a Network", + "group": "Creating Connections", "pages": [ "connecting/creating-endpoint", "connecting/custom-relays", @@ -46,7 +46,7 @@ ] }, { - "group": "Building your App", + "group": "Sending Data", "pages": [ "protocols/kv-crdts", "protocols/blobs", @@ -58,6 +58,14 @@ "protocols/using-quic" ] }, + { + "group": "Transports", + "pages": [ + "transports/tor", + "transports/nym", + "transports/bluetooth" + ] + }, { "group": "Deployment", "pages": [ diff --git a/examples/chat.mdx b/examples/chat.mdx index 3d413b0..9d9e1a7 100644 --- a/examples/chat.mdx +++ b/examples/chat.mdx @@ -134,12 +134,12 @@ Topics are the fundamental unit of communication in the gossip protocol. Here's ```rust use anyhow::Result; use iroh::protocol::Router; -use iroh::Endpoint; +use iroh::{Endpoint, presets}; use iroh_gossip::{net::Gossip, proto::TopicId}; #[tokio::main] async fn main() -> Result<()> { - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; println!("> our endpoint id: {}", endpoint.id()); let gossip = Gossip::builder().spawn(endpoint.clone()); @@ -324,7 +324,7 @@ use std::collections::HashMap; use anyhow::Result; use futures_lite::StreamExt; use iroh::protocol::Router; -use iroh::{Endpoint, EndpointId}; +use iroh::{Endpoint, EndpointId, presets}; use iroh_gossip::{ api::{GossipReceiver, Event}, net::Gossip, @@ -334,7 +334,7 @@ use serde::{Deserialize, Serialize}; #[tokio::main] async fn main() -> Result<()> { - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; println!("> our endpoint id: {}", endpoint.id()); let gossip = Gossip::builder().spawn(endpoint.clone()); @@ -535,7 +535,7 @@ use std::{collections::HashMap, fmt, str::FromStr}; use anyhow::Result; use clap::Parser; use futures_lite::StreamExt; -use iroh::{protocol::Router, Endpoint, EndpointAddr, EndpointId}; +use iroh::{protocol::Router, Endpoint, EndpointAddr, EndpointId, presets}; use iroh_gossip::{ api::{GossipReceiver, Event}, net::Gossip, @@ -591,7 +591,7 @@ async fn main() -> Result<()> { } }; - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; println!("> our endpoint id: {}", endpoint.id()); let gossip = Gossip::builder().spawn(endpoint.clone()); diff --git a/protocols/blobs.mdx b/protocols/blobs.mdx index c00836b..53a499a 100644 --- a/protocols/blobs.mdx +++ b/protocols/blobs.mdx @@ -104,7 +104,7 @@ This is what manages the possibly changing network underneath, maintains a conne async fn main() -> anyhow::Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; // ... @@ -124,7 +124,7 @@ It loads files from your file system and provides a protocol for seekable, resum async fn main() -> anyhow::Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; // We initialize an in-memory backing store for iroh-blobs let store = MemStore::new(); diff --git a/protocols/rpc.mdx b/protocols/rpc.mdx index ce9fb06..4fba995 100644 --- a/protocols/rpc.mdx +++ b/protocols/rpc.mdx @@ -169,7 +169,7 @@ But we said that we wanted to be able to seamlessly switch between remote or loc ```rust enum Client { Local(mpsc::Sender), - Remote(quinn::Connection), + Remote(noq::Connection), } impl Client { @@ -206,7 +206,7 @@ But what about all this boilerplate? **The `irpc` crate is meant solely to reduce the tedious boilerplate involved in writing the above manually.** -It does *not* abstract over the connection type - it only supports [iroh-quinn] send and receive streams out of the box, so the only two possible connection types are `iroh` p2p QUIC connections and normal QUIC connections. It also does not abstract over the local channel type - a local channel is always a `tokio::sync::mpsc` channel. Serialization is always using postcard and length prefixes are always postcard varints. +It does *not* abstract over the connection type - it only supports [noq] QUIC send and receive streams out of the box, so the only two possible connection types are `iroh` p2p QUIC connections and normal QUIC connections. It also does not abstract over the local channel type - a local channel is always a `tokio::sync::mpsc` channel. Serialization is always using postcard and length prefixes are always postcard varints. So let's see what our kv service looks like using `irpc`: @@ -286,8 +286,8 @@ converting the result into a futures `Stream` or the updates into a futures services that can be used in-process or across processes, not to provide an opinionated high level API. -For stream based rpc calls, there is an issue you should be aware of. The quinn -`SendStream` will send a finish message when dropped. So if you have a finite +For stream based rpc calls, there is an issue you should be aware of. The noq +QUIC `SendStream` will send a finish message when dropped. So if you have a finite stream, you might want to have an explicit end marker that you send before dropping the sender to allow the remote side to distinguish between successful termination and abnormal termination. E.g. the `SetFromStream` request from @@ -330,9 +330,9 @@ If you are reading from a remote source, and there is a problem with the connect But what about writing? E.g. you got a task that performs an expensive computation and writes updates to the remote in regular intervals. You will only detect that the remote side is gone once you write, so if you write infrequently you will perform an expensive computation despite the remote side no longer being available or interested. -To solve this, an irpc Sender has a [closed](https://docs.rs/irpc/0.5.0/irpc/channel/mpsc/enum.Sender.html#method.closed) function that you can use to detect the remote closing without having to send a message. This wraps [tokio::sync::mpsc::Sender::closed](https://docs.rs/tokio/latest/tokio/sync/mpsc/struct.Sender.html#method.closed) for local streams and [quinn::SendStream::stopped](https://docs.rs/iroh-quinn/latest/iroh_quinn/struct.SendStream.html#method.stopped) for remote streams. +To solve this, an irpc Sender has a [closed](https://docs.rs/irpc/0.5.0/irpc/channel/mpsc/enum.Sender.html#method.closed) function that you can use to detect the remote closing without having to send a message. This wraps [tokio::sync::mpsc::Sender::closed](https://docs.rs/tokio/latest/tokio/sync/mpsc/struct.Sender.html#method.closed) for local streams and [noq::SendStream::stopped](https://github.com/n0-computer/noq) for remote QUIC streams. -## Alternatives to iroh-quinn +## Alternatives to noq If you integrate iroh protocols into an existing application, it could be that you already have a rpc system that you are happy with, like [grpc](https://grpc.io/) or [json-rpc](https://www.jsonrpc.org/). @@ -361,9 +361,9 @@ and maintained. ## References - [postcard](https://docs.rs/postcard/latest/postcard/) -- [iroh-quinn](https://docs.rs/iroh-quinn/latest/iroh_quinn/) -- [RecvStream](https://docs.rs/iroh-quinn/latest/iroh_quinn/struct.RecvStream.html) -- [SendStream](https://docs.rs/iroh-quinn/latest/iroh_quinn/struct.SendStream.html) +- [noq](https://github.com/n0-computer/noq) +- [RecvStream](https://github.com/n0-computer/noq) +- [SendStream](https://github.com/n0-computer/noq) - [Stream](https://docs.rs/futures/latest/futures/prelude/trait.Stream.html) - [Sink](https://docs.rs/futures/latest/futures/sink/trait.Sink.html) - [snafu](https://docs.rs/snafu/latest/snafu/) diff --git a/protocols/using-quic.md b/protocols/using-quic.md index 0d58aba..fdd7578 100644 --- a/protocols/using-quic.md +++ b/protocols/using-quic.md @@ -2,9 +2,26 @@ title: "Using QUIC" --- -## Why this matters for iroh +Every endpoint uses QUIC over UDP by default — no configuration required. -iroh is built on top of QUIC, providing connectivity, NAT traversal, and encrypted connections out of the box. While iroh handles the hard parts of networking—holepunching, relay servers, and discovery—**you still need to design how your application exchanges data once connected**. +iroh's QUIC implementation is built on +[noq](https://github.com/n0-computer/noq), which includes multipath support and +QUIC NAT traversal. + +All connections are encrypted and authenticated using TLS 1.3. Holepunching, +relay fallback, and multipath are all handled at the QUIC layer automatically. + +## Custom transports + +QUIC over UDP is the default, but iroh supports plugging in additional custom +transports alongside it. + +All transports, even custom transports [Tor](/transports/tor), [Nym](/transports/nym), and +[Bluetooth](/transports/bluetooth) deliver QUIC datagrams. + +## Using QUIC + +While iroh handles the hard parts of networking—holepunching, relay servers, and discovery—**you still need to design how your application exchanges data once connected**. Many developers reach for iroh expecting it to completely abstract away the underlying transport. However, iroh intentionally exposes QUIC's powerful stream API because: @@ -15,7 +32,7 @@ Many developers reach for iroh expecting it to completely abstract away the unde Think of iroh as giving you **reliable, secure tunnels between peers**. This guide shows you how to use QUIC's streaming patterns to build efficient protocols inside those tunnels. Whether you're adapting an existing protocol or designing something new, understanding these patterns will help you make the most of iroh's capabilities. -iroh uses a fork of [Quinn](https://docs.rs/iroh-quinn/latest/iroh_quinn/), a pure-Rust implementation of QUIC maintained by [n0.computer](https://n0.computer). Quinn is production-ready, actively maintained, and used by projects beyond iroh. If you need lower-level QUIC access or want to understand the implementation details, check out the [Quinn documentation](https://docs.rs/iroh-quinn/latest/iroh_quinn/). +iroh uses [noq](https://github.com/n0-computer/noq), a pure-Rust QUIC implementation maintained by [n0.computer](https://n0.computer). noq is production-ready, actively maintained, and used by projects beyond iroh. If you need lower-level QUIC access or want to understand the implementation details, check out the [noq repository](https://github.com/n0-computer/noq). diff --git a/protocols/writing-a-protocol.mdx b/protocols/writing-a-protocol.mdx index b0145e8..1f00720 100644 --- a/protocols/writing-a-protocol.mdx +++ b/protocols/writing-a-protocol.mdx @@ -87,7 +87,7 @@ Now, we can modify our router so it handles incoming connections with our newly ```rs async fn start_accept_side() -> anyhow::Result { - let endpoint = iroh::Endpoint::bind().await?; + let endpoint = iroh::Endpoint::bind(iroh::presets::N0).await?; let router = iroh::protocol::Router::builder(endpoint) .accept(ALPN, Echo) // This makes the router handle incoming connections with our ALPN via Echo::accept! @@ -164,7 +164,7 @@ This follows the [request-response pattern](/protocols/using-quic#request-and-re ```rs async fn connect_side(addr: EndpointAddr) -> Result<()> { - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(iroh::presets::N0).await?; // Open a connection to the accepting endpoint let conn = endpoint.connect(addr, ALPN).await?; diff --git a/quickstart.mdx b/quickstart.mdx index e78583c..83b8a29 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -37,11 +37,13 @@ connection to the closest relay, and finds ways to address devices by `EndpointId`. ```rust +use iroh::{Endpoint, presets}; + #[tokio::main] async fn main() -> anyhow::Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; // ... @@ -81,14 +83,14 @@ round-trip latency, or whatever else you want to build on top of it, without bui ```rust use anyhow::Result; -use iroh::{protocol::Router, Endpoint, Watcher}; +use iroh::{protocol::Router, Endpoint, Watcher, presets}; use iroh_ping::Ping; #[tokio::main] async fn main() -> anyhow::Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; // bring the endpoint online before accepting connections endpoint.online().await; @@ -116,14 +118,14 @@ arguments and match on them: ```rust use anyhow::Result; -use iroh::{protocol::Router, Endpoint, Watcher}; +use iroh::{protocol::Router, Endpoint, Watcher, presets}; use iroh_ping::Ping; #[tokio::main] async fn main() -> Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; // Then we initialize a struct that can accept ping requests over iroh connections let ping = Ping::new(); @@ -137,7 +139,7 @@ async fn main() -> Result<()> { let addr = recv_router.endpoint().addr(); // create a send side & send a ping - let send_ep = Endpoint::bind().await?; + let send_ep = Endpoint::bind(presets::N0).await?; let send_pinger = Ping::new(); let rtt = send_pinger.ping(&send_ep, addr).await?; @@ -207,7 +209,7 @@ a router that accepts incoming ping requests indefinitely: ```rust // filepath: src/main.rs use anyhow::{anyhow, Result}; -use iroh::{Endpoint, protocol::Router}; +use iroh::{Endpoint, protocol::Router, presets}; use iroh_ping::Ping; use iroh_tickets::{Ticket, endpoint::EndpointTicket}; use std::env; @@ -215,7 +217,7 @@ use std::env; async fn run_receiver() -> Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; // Wait for the endpoint to be accessible by others on the internet endpoint.online().await; @@ -245,7 +247,7 @@ The sender parses the ticket, creates its own endpoint, and pings the receiver's ```rust // filepath: src/main.rs async fn run_sender(ticket: EndpointTicket) -> Result<()> { - let send_ep = Endpoint::bind().await?; + let send_ep = Endpoint::bind(presets::N0).await?; let send_pinger = Ping::new(); let rtt = send_pinger.ping(&send_ep, ticket.endpoint_addr().clone()).await?; println!("ping took: {:?} to complete", rtt); diff --git a/transports/bluetooth.mdx b/transports/bluetooth.mdx new file mode 100644 index 0000000..c22a139 --- /dev/null +++ b/transports/bluetooth.mdx @@ -0,0 +1,15 @@ +--- +title: "Bluetooth (BLE)" +--- + +Bluetooth Low Energy (BLE) transport support is not yet available. We plan to +work with the community to implement it. If you're interested in contributing, +reach out on [Discord](https://www.iroh.computer/discord). + +## Custom transport API + +The custom transport API lets anyone implement new transports by implementing a set of traits for low-level packet sending and receiving. Each transport defines its own address type and serialization format. + +See [Tor](/transports/tor) and [Nym](/transports/nym) for examples of custom transport implementations today. + +Custom transport support requires the `unstable-custom-transports` feature flag. The API is unstable and subject to change. See [PR #3845](https://github.com/n0-computer/iroh/pull/3845) for background. diff --git a/transports/nym.mdx b/transports/nym.mdx new file mode 100644 index 0000000..5f205b8 --- /dev/null +++ b/transports/nym.mdx @@ -0,0 +1,59 @@ +--- +title: "Nym" +--- + +The [iroh-nym-transport](https://github.com/n0-computer/iroh-nym-transport) crate routes iroh QUIC packets through the [Nym mixnet](https://nymtech.net/), providing traffic analysis resistance by shuffling and delaying packets across a network of mix nodes. + +This is useful when you need stronger metadata privacy than Tor provides — the mixnet obscures not just your IP address, but also communication patterns and timing. + + +Both iroh's custom transport API and this crate are experimental. Expect breaking changes. + + +## Installation + +```bash +cargo add iroh-nym-transport +``` + +## Usage + +```rust +use std::sync::Arc; +use iroh::{Endpoint, presets}; +use iroh_nym_transport::NymUserTransport; +use nym_sdk::mixnet::MixnetClient; + +let nym_client = MixnetClient::connect_new().await?; +let transport = Arc::new(NymUserTransport::new(nym_client)); + +let endpoint = Endpoint::builder() + .clear_ip_transports() + .add_custom_transport(transport) + .bind(presets::N0) + .await?; +``` + +Calling `clear_ip_transports()` disables QUIC over UDP so all traffic routes exclusively through the mixnet. + +You can only connect to other endpoints that also have the Nym transport enabled. + +## Performance characteristics + +The mixnet intentionally introduces latency and limits throughput as part of its privacy guarantees: + +| Metric | Direct QUIC | Nym mixnet | +|--------|-------------|------------| +| RTT | 50–200 ms | ~1–3 s | +| Throughput | 10+ Mbps | ~15–20 KiB/s | + +Nym is well suited for privacy-sensitive, latency-tolerant applications. It is not suitable for real-time communication or high-throughput file transfer. + +## Feature flag + +Custom transport support must be enabled: + +```toml +[dependencies] +iroh = { version = "*", features = ["unstable-custom-transports"] } +``` diff --git a/transports/tor.mdx b/transports/tor.mdx new file mode 100644 index 0000000..11e2716 --- /dev/null +++ b/transports/tor.mdx @@ -0,0 +1,67 @@ +--- +title: "Tor" +--- + +The [iroh-tor-transport](https://github.com/n0-computer/iroh-tor-transport) crate routes iroh connections through [Tor hidden services](https://www.torproject.org/), hiding both peers' IP addresses from each other and from the network. + +This is useful when IP address privacy is a hard requirement for your application. + +For background, see the [blog post](https://www.iroh.computer/blog/tor-custom-transport). + + +Both iroh's custom transport API and this crate are experimental. Expect breaking changes. + + +## Installation + +Add the dependency: + +```bash +cargo add iroh-tor-transport +``` + +You also need a running Tor daemon with the control port enabled: + +```bash +tor --ControlPort 9051 --CookieAuthentication 0 +``` + +## Usage + +```rust +use iroh::{Endpoint, SecretKey, presets}; +use iroh_tor_transport::TorCustomTransport; + +let secret_key = SecretKey::generate(&mut rand::rng()); +let transport = TorCustomTransport::builder() + .build(secret_key.clone()) + .await?; + +let endpoint = Endpoint::builder() + .secret_key(secret_key) + .preset(transport.preset()) + .bind(presets::N0) + .await?; +``` + +You can only connect to other endpoints that also have the Tor transport enabled. + +## How it works + +Connections are routed through Tor onion services. Each endpoint creates a hidden service address, which other endpoints dial over the Tor network. Neither side learns the other's real IP address. + +## Current limitations + +- Requires a separately installed and running Tor daemon +- Cookie authentication must be disabled (`--CookieAuthentication 0`) +- No automatic recovery if the Tor daemon crashes +- Limited to TCP streams over Tor + +## Feature flag + +Custom transport support must be enabled: + +```toml +[dependencies] +iroh = { version = "*", features = ["unstable-custom-transports"] } +```