# Changelog Source: https://docs.iroh.computer/about/changelog # FAQ Source: https://docs.iroh.computer/about/faq ## What problem does iroh solve? iroh is built on QUIC and doesn't try to reinvent the wheel; it combines existing IETF standards in a new way. The concrete problem it solves is connecting two devices that can't easily reach each other today. Say one device is in your home WLAN behind a NAT and the other is on a 4G network or behind a different NAT at work. In most cases iroh can establish a direct connection between the two via [hole punching](/concepts/nat-traversal), giving you the highest possible bandwidth and the lowest possible latency. Reliably getting that direct connection across arbitrary networks was not a solved problem until now. On top of that, iroh lets you dial a device by its public key (its [Endpoint ID](/concepts/endpoints)) instead of by IP address, so you don't need to know or care where the other device currently sits on the network. ## Is iroh a replacement for IP addresses? No, iroh is an addition, not a replacement. IP isn't going anywhere, and iroh runs on top of it, adding two capabilities: the ability to dial an endpoint by its public key, and the ability to get a direct connection whenever one is possible. Because applications dial by key rather than by address, iroh is also well positioned for the future. If some other technology eventually replaces the IP address paradigm, nothing changes from the application developer's point of view: you still dial by key, and iroh makes sure under the hood that you get the best possible connection, over IP or otherwise. ## How is iroh different from other peer-to-peer networks? The main difference is that iroh tries to use existing IETF standards as much as possible instead of reinventing the wheel. An iroh connection is just a QUIC connection, using TLS and TLS ALPNs for protocol negotiation. If you look at one in Wireshark, it's indistinguishable from any other QUIC connection, so existing tooling works and what you learn using iroh transfers to plain QUIC and vice versa. Many iroh contributors come out of the P2P world and had a bit of "abstraction fatigue" after years of working on traditional P2P networks. So where many P2P networks ship their own DHT for discovery, iroh resisted that temptation and instead uses the biggest DHT that already exists, [BitTorrent's Mainline DHT](/connecting/dht-address-lookup), for peer-to-peer [address lookup](/concepts/address-lookup). Some "regular P2P networks" actually run on iroh under the hood, including [Holochain](https://blog.holochain.org) and various P2P chat apps. ## Does iroh use relay servers? Yes, and relay servers are a core part of what makes iroh connections reliable. See the [Relays concept page](/concepts/relays) for a full overview. Relays serve two roles: they assist with [NAT Traversal](/concepts/nat-traversal) to help establish direct P2P connections, and they act as an encrypted fallback when a direct connection can't be made. In practice, roughly 9 out of 10 connections go direct; the relay is only a stepping stone. Because relays are **stateless** (they route encrypted packets but store nothing), they're cheap to run and easy to scale. There's no database to sync, no state to migrate, and automatic failover across relay instances is built in. By default iroh is configured with four public relay servers run by number 0 (two in the US, one in Europe, and one in Asia), free to use for development and testing. To prevent abuse, throughput through public relays is rate-limited. For production workloads, you can run a [dedicated relay](/concepts/relays#dedicated-relays) or [self-host your own](/concepts/relays#deploy-your-own-relay). ## Can relays read the traffic they route? **No, all connections in iroh are end-to-end encrypted.** We use QUIC which is based on TLS 1.3. From the perspective of our QUIC implementation, the relay is "just another UDP socket" for sending encrypted packets around. Because the relays are relaying traffic, they theoretically know that Endpoint ID X talks to Endpoint ID Y and how many bytes are sent this way, but only for as long as these endpoints haven't established a direct connection yet. However, we don't record this data on our relays. ## How secure is iroh's end-to-end encryption? Iroh provides a secure, encrypted, forward and backward-secret, authenticated data channel between you and the recipient and protects you both from eavesdroppers. This assumes the Endpoint ID you're connecting to was exchanged securely, e.g. via scanning a QR code, sharing a link with the Endpoint ID in an encrypted chat app or using a trusted server and the corresponding secret keys haven't been compromised. The established connection is a QUIC connection, which together with TLS 1.3 specifies how it's encrypted. This specification is widely used, for example as part of the latest generation of HTTP, HTTP3. Instead of PKI-based certificates, at the moment iroh uses self-signed certificates with Endpoint IDs to authenticate both ends of the connection, borrowing [the libp2p handshake specification](https://github.com/libp2p/specs/blob/master/tls/tls.md). In the future, we plan on switching to the [raw public key TLS certificate type](https://datatracker.ietf.org/doc/html/rfc7250) instead. To make use of this end-to-end encryption, no additional setup in iroh is required, it is always enabled. Be aware of security caveats to forward secrecy when using the [opt-in 0-RTT feature](https://docs.rs/iroh/latest/iroh/endpoint/struct.Connecting.html#method.into_0rtt). ## What if number 0 stops running relay servers? You're not dependent on us. The relay code is [open-source](https://github.com/n0-computer/iroh/tree/main/iroh-relay), and running your own is possible. The easiest path is a [dedicated relay via Iroh Services](/concepts/relays#dedicated-relays), which gives you an isolated, managed relay with uptime guarantees. If you'd rather self-host, you need a server with a public IP and a DNS name pointing to it. Automatic TLS via [ACME](https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment) is built in. Either way, configure the relay URL for your endpoint (see [Use your own relay](/add-a-relay)) and you're set. Running your own relay doesn't affect interoperability. Your endpoints can still connect to peers using other relay servers, and since relays are stateless and logic lives at the client, can be swapped independently. ## What are the risks of running a public relay? Running a public, unauthenticated relay means you act as the home relay for anyone who has your relay in their relay map and is close to you in terms of latency. The main practical consequence is traffic: you might get a lot of it, so you'll want to configure rate limiting, as we do on our [public relays](/iroh-services/relays/public). The security exposure is limited. The traffic you relay is fully end-to-end encrypted and cannot be decrypted by the relay. The only information a relay has is what it needs to function: the endpoint IDs and IP addresses of the endpoints currently connected to it, plus which endpoints are paired. A relay has no egress to the open internet, so if you're comparing it to Tor, running a relay is like running a guard/middle relay, not an exit node. ## Is establishing a connection without relays or when offline possible? Yes. When you share a `EndpointAddr`s with "direct addresses", then iroh will try to use these addresses to establish a connection with or without a relay. If you're in a local network together you can enable [local network address lookup](/connecting/local-address-lookup) to help establish connections in LANs even when the `EndpointAddr` doesn't contain direct addresses. ## How can I control which relay servers iroh connects to? Iroh will only talk to relay servers that it knows URLs for. By default iroh is configured with 3 relay servers from the [default `RelayMap`](https://docs.rs/iroh/latest/iroh/defaults/prod/index.html). If your endpoint has address lookup services configured (which is the default with the `N0` preset), then iroh might connect to relay servers discovered that way. By changing iroh's relay mode or relay map you can control the home relay the endpoint connects to, and by wrapping or writing your own `AddressLookup` service, you gain control over the relay URLs iroh can discover. ## How can I monitor endpoint connection status to update a UI? If you want a proxy for general internet connectivity, you can watch whether the endpoint currently has a relay connection: ```rust theme={null} endpoint.watch_addr() .map(|addr| addr.relay_urls().next().is_some()) ``` This returns an `impl Watchable` that reflects whether the endpoint believes it's connected to a relay, a reasonable signal for "has outside connectivity". ## How do you limit which nodes can join? Iroh gives you full control over which endpoints are allowed to connect via [endpoint hooks](/connecting/endpoint-hooks). Hooks let you intercept incoming connections before they're accepted, so you can allow or reject them based on the connecting endpoint's ID, your own allowlist/denylist logic, or any application-specific policy. ## What is "Address Lookup" in iroh and which one should I enable? For most usage, using the services that are enabled with the `iroh::endpoint::presets::N0` preset is the best default. Address Lookup helps iroh find ways to connect to a specific Endpoint ID. The Endpoint ID on its own can only be used to identify if you're talking to the right recipient, but doesn't tell how to address the recipient on its own. Via configured address lookup mechanisms, iroh resolves an Endpoint ID to IP addresses and relay URLs that help to actually attempt a connection. For more information on available address lookup mechanisms, take a look at the [address lookup docs](/concepts/address-lookup). It's also possible to combine multiple address lookup mechanisms at once, or write your own. We think it's particularly helpful to write application-specific address lookup mechanisms that are tailored to an application's need. ## Does using the Mainline DHT mean BitTorrent clients respond to iroh lookups? First, peer-to-peer [address lookup over the Mainline DHT](/connecting/dht-address-lookup) is an optional feature that you have to enable explicitly. It's disabled by default, partly so that mobile apps don't look like BitTorrent clients and get flagged by the OS. Mainline itself is very frugal in terms of resource use. When you do a P2P address lookup, any Mainline server node could respond: each [BEP 44](https://www.bittorrent.org/beps/bep_0044.html) record is stored on 20 random Mainline nodes. So yes, a BitTorrent client that participates in the DHT as a server and has been running long enough to be in the routing tables will respond. ## What ports does iroh use? Iroh listens on **Two UDP ports**: one for IPv4 and one for IPv6, used for direct P2P connections; configurable via [`endpoint::Builder`](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html) Iroh will work behind firewalls that only allow TCP outbound, but direct connections won't be possible in that case; all traffic will fall back to the relay. It's totally possible that you maintain connections to multiple relays at a time, if you're connected to iroh endpoints that have another home relay than yours. Each of these connections will use another TCP socket. ## How would onion-routing work with iroh? Iroh supports custom transports, which means you can route connections over Tor. Check out our [blog post on using iroh with Tor](https://www.iroh.computer/blog/tor-custom-transport) for a walkthrough of how this works in practice. ## Will iroh support WebRTC, BLE, LoRa, or another transport? Out of the box, iroh supports IPv4, IPv6 and relay transports. There's such a large variety of potentially interesting transports out there that we can't build them all in without turning the codebase into an unmaintainable maze of feature flags. Instead, iroh lets you implement custom transports that live in a completely separate crate. Existing experimental ones include [Tor](/transports/tor), [Nym](/transports/nym) and [Bluetooth (BLE)](/transports/bluetooth). For a walkthrough of how custom transports work under the hood, see the [custom transports blog post](https://www.iroh.computer/blog/iroh-0-97-0-custom-transports). ## Can iroh help route around internet segmentation or censorship? iroh doesn't solve every problem created by internet segmentation (DNS control, TLS certificate revocation, and so on), but it's very much possible today to assemble iroh components into a setup that lets you forget about segmentation while you use it. iroh is designed from the ground up to build on existing internet technologies while avoiding lock-in and dependencies on browser vendors or other large players. For example, you can route iroh connections over custom transports like [Tor](/transports/tor) or [Nym](/transports/nym), or connect over local links with [Bluetooth](/transports/bluetooth) or direct WiFi, giving application developers the building blocks to route around segmentation. ## How is iroh development funded? The company behind iroh is number 0. It is partly venture capital and partly founder backed (as in: founders have invested their own money). Number 0 is healthy and has investors we actually think are a value-add. We earn revenue through [Iroh Services](https://services.iroh.computer), which provides managed relay and DNS address lookup infrastructure to keep your endpoints connected, from free public infrastructure for development and testing to dedicated cloud deployments for production. We rely on iroh remaining open source, and are committed to keeping it that way, including server-side code for relays and DNS address lookup. ## How does iroh compare to Tailscale? Tailscale and iroh both get devices connected across NATs, but they operate at different layers. Tailscale is built to be global to your device: it sets up a network interface that all of your applications share. iroh is built to be embedded into each individual application. That difference matters. With iroh, connectivity lives inside your app rather than in a single global bridge, which gives developers and users a much more fine-grained and bespoke setup. You can ship an Android or iOS app that uses iroh direct connections under the hood, and the person using it never has to know or care that iroh is involved, with no separate VPN or daemon to install and manage. If you want one virtual network spanning all the apps on a machine, Tailscale is a natural fit. If you want direct, encrypted connections built directly into your application, iroh is designed for that. ## How does iroh compare to WebRTC? Both iroh and WebRTC solve the same core problem (establishing direct P2P connections across NATs) but they make very different tradeoffs. **WebRTC** was designed for real-time media (audio and video) in browsers. It brings a complex stack: ICE for NAT traversal, STUN/TURN for relay, DTLS for encryption, SCTP for data channels, and a signaling layer that you have to implement yourself (WebRTC deliberately leaves signaling unspecified). This flexibility is powerful but adds significant complexity and is more expensive to run at scale. **Iroh** is built around QUIC (TLS 1.3) and focuses on reliable, encrypted data connections. The API is simpler: you connect to an endpoint ID and get a QUIC connection. Relays are stateless and cheap. Hole-punching works roughly 9 out of 10 times. There's no signaling layer to design; address lookup mechanisms handle address resolution. * **Browser support.** Both WebRTC and iroh work in browsers. [Get started with iroh in the browser](https://docs.iroh.computer/deployment/other-languages#webassembly-and-browsers). However, WebRTC remains the only choice for hole-punched connections due to the current state of Web APIs. * **Media streaming.** WebRTC has first-class support for audio and video. Iroh is a general-purpose data transport, but you can run [MoQ (Media over QUIC)](https://www.iroh.computer/blog/secure-video-everywhere) on top of iroh for low-latency media streaming. * **Simplicity.** Iroh's connection model is significantly simpler to reason about and integrate. No SDP, no ICE candidate negotiation, no signaling server to build. If you're building a native app, iroh will generally be easier to work with and perform better. If you need browser support, WebRTC remains the default choice; iroh doesn't run in the browser. ## How does iroh compare to MQTT? MQTT and iroh solve different problems, though they can overlap in IoT and device communication scenarios. **MQTT** is a publish/subscribe messaging protocol built around a central broker. Devices connect to the broker, publish messages to topics, and subscribe to topics they care about. It's lightweight, well-suited to constrained hardware, and great for fan-out messaging (one message to many subscribers). The tradeoff is that the broker sits in the middle of all communication; it sees everything, and it's a single point of failure and scaling pressure. **Iroh** is a P2P transport. There's no broker; endpoints connect directly to each other, and all traffic is end-to-end encrypted. This means no central party can observe your data, and there's no broker to scale or maintain. The main practical differences: * **Pub/sub.** MQTT has native topic-based pub/sub built in. Iroh doesn't, though [iroh-gossip](https://github.com/n0-computer/iroh-gossip) provides gossip-based fan-out on top of iroh connections. * **Privacy.** MQTT brokers require you to encrypt messages at the application level. Iroh connections are always end-to-end encrypted out of the box. * **Central infrastructure.** MQTT requires a broker that all clients can reach. Iroh works across NATs without any central server, using relays only as a fallback. * **Constrained devices.** MQTT was designed for low-power, low-bandwidth hardware. Iroh's QUIC-based stack has higher resource requirements, but we do have solutions for constrained IoT environments. [Get in touch](https://iroh.computer/contact) if this is relevant to your use case. If you need fan-out messaging to many subscribers, MQTT is a proven fit. If you need direct, private, encrypted connections between devices (especially across NATs) iroh is the stronger choice. ## How do iroh and libp2p compare? Iroh is designed to be modular: you get a solid, reliable connection layer and compose protocols on top of it. Libp2p bundles more functionality (DHT, pubsub, transport negotiation) but those components are more tightly coupled, which makes the system harder to configure and reason about. The iroh project was founded by developers deeply involved with libp2p who wanted a library where you spend time on your protocol, not on networking configuration. Where iroh tends to win: * **Peer discovery and NAT traversal.** Iroh's [NAT traversal](/concepts/nat-traversal) and address lookup story is significantly smoother. Getting peers to find each other reliably is one of the hardest parts of libp2p in practice; iroh has largely solved this. * **Direct messaging.** Iroh gives you reliable, encrypted QUIC connections directly to specific peers. Straightforward to build routing or direct message delivery on top of. * **Gossip.** [iroh-gossip](https://github.com/n0-computer/iroh-gossip/) provides gossipsub-like fan-out on top of iroh connections. * **Blob transfer.** Iroh has a built-in [blob transfer protocol](https://docs.rs/iroh-blobs) for exchanging hash-addressed data, similar to what you'd use IPFS for. Where libp2p has more: * **DHT.** Libp2p has a Kademlia-based DHT; iroh does not. If your design depends on DHT-based routing or content discovery, that's a gap. If your focus is on the protocol you're building rather than the networking layer beneath it, iroh is designed to get out of your way. ## What languages can I use iroh from? Is there a Go version? iroh's focus is the Rust implementation, which is easy to use directly from Rust, C and C++ and to embed into languages like [JavaScript](/languages/javascript), [Python](/languages/python), [Swift](/languages/swift) and [Kotlin](/languages/kotlin). See the [Languages](/languages) overview for the full list of official bindings. There's no official Go version. Because iroh is a combination of existing standards (QUIC plus the multipath extension and a small amount of custom TLS logic), a native Go implementation is possible in theory using a Go QUIC library that supports multipath, and there are third-party efforts such as [go-iroh](https://github.com/tmc/go-iroh). Our own focus stays on the Rust implementation. ## Do you support keys other than Ed25519? No. Iroh endpoint IDs are Ed25519 keys, and that's intentional. Ed25519 is deeply integrated across the stack: it's used to sign `EndpointAddr`s for Pkarr and other address lookups, as the raw public key trust root in mTLS, and for authentication in the relay protocol. Supporting pluggable key types would require threading that complexity through all of these systems simultaneously, with significant risk and little practical benefit for most use cases. If you have a specific need around key types, [open an issue](https://github.com/n0-computer/iroh/issues) and we're happy to discuss it. ## Is iroh post-quantum-secure? No. Iroh uses Ed25519 for signing and X25519/P-256 for ECDH. These algorithms are not post-quantum-secure. Adopting the current best post-quantum-secure algorithm, for example Xyber, would incur a very significant network overhead: A Xyber public key is 37x larger than an Ed25519 public key. This has implications for connection establishment speed: For example, the initial handshake for a connection wouldn't fit into a normal UDP packet anymore. It also means DNS packets used for DNS address lookup at the moment might get fragmented, etc. It would also mean Endpoint IDs would be exactly 37x as big. To support post-quantum-cryptography, we would need to trade off usability with the risk should a sufficiently powerful quantum computers would become real. We believe it is much more important to serve existing use cases efficiently, so they have encryption *today*. We fully believe the work on post-quantum-cryptography is good and important and follow developments closely. # Release & Support Policies Source: https://docs.iroh.computer/about/release-policy How iroh versions are released, how long they're supported, and what wire-protocol compatibility you can rely on. This page describes how iroh is versioned, what compatibility guarantees we make between versions, and how long each release is supported. ## Release Types | Type | Description | Cycle | Example | | ----------------- | ----------------------------------------------------------------------------------------------------- | --------- | ------------- | | Major | New features, breaking changes | ≥6 months | `1.x.x` | | Minor | Incremental features, improvements | ≥4 weeks | `1.2.x` | | Patch | Bug fixes, security updates | As needed | `1.2.3` | | Release candidate | An early preview of an upcoming release, with higher confidence that the API is approaching stability | As needed | `1.0-rc` | | Canary | An early preview of an upcoming release with an unstable API | As needed | `0.97` | | Experimental | Fork or branch for a particular use case or prototype | As needed | `branch-name` | ## Wire Protocol Compatibility The wire protocol must remain backward-compatible with the *non-deprecated* parts of the *previous* major version series. It may break compatibility with versions older than the last. * `2.x`'s wire protocol must be backward-compatible with `1.x` * `3.x`'s wire protocol must be backward-compatible with `2.x` * `3.x`'s wire protocol *may* be backward-incompatible with `1.x` The wire protocol must also remain compatible across minor versions within the same major series. For example, `2.x` must connect with any `2.1` through `2.x`. | Version | Compatible with | | ------- | --------------- | | 1.x | 1.0 ≤ 2.0.x | | 2.x | 2.0 ≤ 3.0.x | ### Recommendations * Before deploying the next major version (e.g. `v2.0`), ensure all devices have been updated to the latest previous major (e.g. `v1.x`). * Before deploying the next minor version (e.g. `v2.1`), ensure all devices have been updated to the latest major (e.g. `v2.0`). ## Support Policy **Full Support**: The version is fully supported. Number 0 provides timely bug fixes, security patches, and ongoing maintenance. Expect active development and prompt response to issues. **Extended Support**: A paid support tier for versions that have exited Full Support. Under Extended Support, Number 0 provides critical bug fixes and security patches for a defined period. It includes service level agreements (SLAs) covering response and resolution times for critical issues, including security vulnerabilities and high-severity defects. **Maintenance Mode**: The version is stable and no longer under active development. Only critical bug fixes or security patches may be provided, solely at Number 0's discretion. No new features or enhancements will be introduced. **End of Life (EOL)**: The version is no longer supported. No bug fixes, security updates, or maintenance will be provided. Continued use is at the customer's own risk, and upgrading is strongly recommended. | Release | Full Support | Maintenance Mode | Extended Support | | ----------------- | ------------ | ------------------------------- | ------------------------------------------ | | Major | 1 year | 1–3 years after release | [Contact us](mailto:support@iroh.computer) | | Minor | 3 months | 3 months – 1 year after release | [Contact us](mailto:support@iroh.computer) | | Canary | N/A | N/A | [Contact us](mailto:support@iroh.computer) | | Experimental | N/A | N/A | N/A | | Release Candidate | N/A | N/A | N/A | ### Examples 1. If you are using `0.35` in production, `0.35` is a minor version of iroh that is older than 1 year. To get bug fixes, feature backports, or security patches, you need to purchase an Extended Support package. 2. If you find a bug in a version which is older than 3 months, the team may choose to patch the issue in the latest minor release. If you do not want to update to the latest minor and would prefer a backport to your current minor version, you must purchase an Extended Support package to fund the labor of backporting and maintaining a minor version older than 3 months. For more on support tiers and SLAs, see the [Support page](/iroh-services/support) or [contact us](https://iroh.computer/services/support). ## Public Relay Version Support Number 0 runs public relays for the **latest major version** of iroh. For relay support for older versions, please [deploy a dedicated relay](https://services.iroh.computer). # Roadmap Source: https://docs.iroh.computer/about/roadmap # Use your own relay Source: https://docs.iroh.computer/add-a-relay Configure dedicated relays so your endpoints don't share infrastructure with the public network By default, iroh endpoints use the public relays maintained by [n0.computer](https://n0.computer) to facilitate connections when direct peer-to-peer links aren't possible. The public relays are great for development and testing, but production deployments should run their own. ## Configure your endpoint Once you have one or more relay URLs, configure your endpoint to use them: Your dedicated relays require authentication by default. Your endpoint authenticates to them with your project's API key. The `iroh_services::preset()` builder handles this for you: it mints a short-lived access token scoped to your endpoint's key and configures the endpoint to use your relays. Add the `iroh-services` crate to your project: ```bash theme={null} cargo add iroh-services ``` Then build a preset and bind your endpoint with it: ```rust theme={null} use iroh::Endpoint; #[tokio::main] async fn main() -> anyhow::Result<()> { // Build a preset pointing at your dedicated relays, authenticated with // your project's API key. In production, load the key from a config file // or environment variable instead of hardcoding it. let preset = iroh_services::preset() .relays([ "YOUR_RELAY_URL_US", "YOUR_RELAY_URL_EU", ])? .api_secret_from_str("YOUR_API_KEY")? .build()?; // Bind the endpoint with the preset, then wait until it's online to // confirm it has an authorized connection to a relay. let endpoint = Endpoint::bind(preset).await?; endpoint.online().await; Ok(()) } ``` Custom relay URLs are available on Pro and Enterprise projects. On a free project, pass your API key to the preset without `relays(...)` to authenticate against the public relays and surface your relay traffic on the dashboard. Relays you self-host outside of Iroh Services are configured directly with `RelayMode::Custom`; see [Dedicated Infrastructure](/deployment/dedicated-infrastructure). ## Why use your own relay? Running dedicated relays gives you: * **Isolation**: your traffic isn't mixed with other applications * **Performance**: relays close to your users reduce latency and improve NAT traversal success * **Capacity**: the shared public infrastructure is rate-limited * **Redundancy**: distribute relays across regions or cloud providers for failover * **Compliance**: keep relayed traffic inside your own network or jurisdiction Iroh's relay architecture is uniquely suited to multi-relay deployments because relays are stateless. Clients automatically fail over between relays in your list, so adding capacity or surviving an outage is just a matter of running more relay processes. See [Dedicated Infrastructure](/deployment/dedicated-infrastructure) for the deeper architecture story. ## Get a relay You have two paths. Pick **managed** if you want a relay running today. But you're never locked in! You can always self-host later if you want. Sign up for Iroh Services and spin up a managed relay for your project in minutes. Run the `iroh-relay` binary on a server with a public IP and DNS name. Automatic TLS via ACME is built in. ## Recommended setup For production, run at least two relays in different geographic regions, for example one in North America and one in Europe. iroh clients try multiple relays automatically, so if one becomes unreachable they'll seamlessly fall back to another. Each relay handles up to 60,000 concurrent connections. For larger deployments, run multiple relays per region or [contact us](https://cal.com/team/number-0/n0-protocol-services) to size up. ## Learn more For the full architecture story (why stateless relays make uptime management easier, how multi-cloud resilience works, and managed relay deployment steps) see [Dedicated Infrastructure](/deployment/dedicated-infrastructure). # Compatibility Source: https://docs.iroh.computer/compatibility Send data to and from any device running anywhere, big or small: cloud servers, tablets, or embedded systems. The core peer-to-peer technology is open source and built on open standards, so you're never locked in. ## Operating Systems iroh supports all major operating systems. | OS | Supported | | --------------------- | ----------------------------------------------------------- | | Linux | Yes | | macOS | Yes | | Windows | Yes | | Android | Yes | | iOS | Yes | | WebAssembly (browser) | [Yes](/deployment/other-languages#webassembly-and-browsers) | | FreeRTOS | Yes | ## Hardware iroh is regularly tested on a wide range of hardware, from servers to microcontrollers. | Hardware | Notes | | -------------------------- | ---------------------- | | x86\_64 servers & desktops | Fully supported | | Apple Silicon (M-series) | Fully supported | | Raspberry Pi | Fully supported | | ESP32 | Supported with caveats | For hardware you don't see here, or to integrate your custom hardware into our testing frameworks on every iroh release, [contact us.](mailto:support@iroh.computer) ### ESP32 Resource Requirements We have built a version of iroh which has been tested on ESP32 with very constrained resources: * **4 MiB Flash / 4 MiB RAM**: working, though 4 MiB flash is right at the limit * **4 MiB Flash / 2 MiB RAM**: also works in testing To use ESP32 in production, [contact us for licensing & support.](mailto:support@iroh.computer) ## Network Transports | Transport | Status | | ------------------- | ---------------------------- | | UDP / IP (internet) | Yes | | WiFi / LAN | Yes | | Ethernet | Yes | | Bluetooth (BLE) | [Yes](/transports/bluetooth) | | Tor | [Yes](/transports/tor) | | Nym | [Yes](/transports/nym) | If there is a transport you need that is not listed, [contact us.](mailto:support@iroh.computer) # Address Lookup Source: https://docs.iroh.computer/concepts/address-lookup Address Lookup is the glue that connects an [Endpoint ID](/concepts/endpoints#endpoint-identifiers) to something we can dial. Address Lookup services resolve Endpoint IDs to either their home Relay URL or direct-dialing information. More details can be found in the address lookup module [documentation](https://docs.rs/iroh/latest/iroh/address_lookup/index.html) Address Lookup services form an automated system for an [Endpoint](/concepts/endpoints) to retrieve addressing information. Each iroh endpoint will automatically publish their own addressing information with configured address lookup services. Usually this means publishing which [Home Relay](/concepts/relays) an endpoint is findable at, but they could also publish their direct addresses. ## Address Lookup Services iroh ships several address lookup implementations. **By default, iroh uses DNS/Pkarr address lookup** to resolve EndpointIDs to addresses. It publishes signed records to a server and resolves them via DNS. | Name | Description | Default | | --------------------------------------------- | ---------------------------------------------------------------- | ---------- | | [DNS/Pkarr](#endpoint-address-lookup-via-dns) | publishes signed records to a server and resolves them over DNS | ✅ Enabled | | [Local](/connecting/local-address-lookup) | uses an mDNS-like system to find endpoints on the local network | ❌ Disabled | | [DHT](/connecting/dht-address-lookup) | publishes the same signed records to the BitTorrent Mainline DHT | ❌ Disabled | ## Background on address lookup and DNS/Pkarr We want iroh to establish connections with as little friction as possible. Our first big push toward chipping away at this goal was adding [NAT traversal](/concepts/nat-traversal) into iroh. Now, devs no longer need to worry about opening up ports on their servers / firewalls or be resigned to only creating connections to computers inside their local NAT. But even with NAT traversal, you need to know *where* to dial. Dialing an endpoint in iroh needs either an IP address to talk to, or the URL of a relay to which the remote endpoint is connected. To make things easier, you can use [tickets](/concepts/tickets) early-on. Tickets are easily encodable bytestrings that contain an endpoint ID, socket addresses and a relay URL. Tickets work well, but they are long - and they expire: People change networks or relays, and then old tickets cannot find you anymore. So while tickets work well in some scenarios, they are not frictionless or universally usable. But what if you didn't need to know the relay URL or socket address in order to dial an endpoint? Could we enable iroh users to dial peers using *the least amount of information possible*, ie can we enable dialing *only by EndpointID*, and no other address data? ### The solution We had two "guiding lights" while doing research on global endpoint address lookup: First, we needed to see a path forward that would allow for a fully distributed topology, even if our first solution had a federated structure. If we (or some contributor) wanted to create or opt into a fully p2p version of global endpoint address lookup in the future, that needed to work in tandem with whatever federated solution we came up with. Second, it was very important for us as an organization that we do not invent protocols and specs unnecessarily. Can we instead build on standards that have stood the test of time (and scrutiny) in novel ways to solve our problem? It turns out, we can! And we can do it using one of the oldest and most dependable technologies we have on the internet: DNS. Using the DNS standard along side Pkarr (public-key addressable resource records), **we now have global endpoint address lookup in iroh**! ## The approach We are using two standards: the domain name system [(DNS)](https://datatracker.ietf.org/doc/html/rfc1035) and [Pkarr](https://app.pkarr.org/). DNS is typically used for resolving domain names into their associated IP addresses. But DNS actually relates *domain names* to [*resource records*](https://datatracker.ietf.org/doc/html/rfc1035#section-3.2), of which IP addresses are one type. Pkarr allows us to publish DNS records that resolve **elliptic curve keys** (not domain names) to resource records instead. Our **EndpointIDs** in iroh are elliptic curve public keys. This means we can publish an association between iroh EndpointIDs and some resource records. Importantly, these records are signed, so that you can verify that the record was actually published by the endpoint with the given EndpointID. As long as iroh has an EndpointID and its associated relay URL (the address of the relay server that endpoint uses to hole-punch and proxy relay packets), we can dial that endpoint. So the Pkarr packet currently only needs to contain the EndpointID and the relay URL of its preferred relay server (which we call its "home relay"). When Pkarr publishing is enabled on your iroh endpoint, your endpoint will create a Pkarr packet with its EndpointID and relay URL, sign it, and defaults to publishing on an `iroh-dns-server` instance that is run by [n0.computer](https://n0.computer). From there, others can discover your dialing information by resolving your EndpointID using regular DNS. It's worth noting that others must still learn your endpoint ID for this to work. The following sections describe the format of the Pkarr publishing records and endpoint address lookup via DNS queries in greater detail. ## Endpoint address lookup via DNS When connecting to an unknown `EndpointId`, the DNS address lookup mechanism in iroh will perform a DNS query to discover relays or addresses for the endpoint. The DNS address lookup is configured with a *origin domain* (which defaults to *dns.iroh.link*, a server run by n0). iroh will then perform a DNS query through the configured DNS resolver (which defaults to using the host system's nameservers): `_iroh.. TXT` * `_iroh` is the record name defined in this spec * `` is the [z32](https://crates.io/crates/z32) encoding of the 32-byte long `EndpointId` (which is a string of 52 characters) * `` is the configured origin domain * `TXT` is the queried record type The returned TXT records must contain a string value of the form `key=value`, as defined in [RFC1464](https://www.rfc-editor.org/rfc/rfc1464). This spec defines the following attributes: * `relay=`: The home relay for this endpoint, e.g. `https://euw1-1.derp.iroh.network`. * `addr= ..` A space-separated list of socket addresses for this iroh endpoint. Each address is an IPv4 or IPv6 address with a port (e.g. `1.2.3.4:7367` or `[::1]:3521`) **Ready to use DNS address lookup?** Learn how to configure DNS address lookup in your application in the [DNS Address Lookup guide](/connecting/dns-address-lookup). ## Endpoint announces via `pkarr` Endpoints announce their address information in [Pkarr](https://github.com/Nuhvi/pkarr/) signed packets. The TXT records, as described below, are added to the `answers` section of a DNS server. Their name must be `_iroh..` The encoded endpoint ID must be the root name, no other origin but `.` (the single dot) is permitted. Those packets are published to a Pkarr relay server, which is a HTTP service handling PUT requests with the signed packets. iroh's Pkarr server is [`iroh-dns-server`](https://crates.io/crates/iroh-dns-server), which serves the received records over DNS. Pkarr packets can also be published onto the BitTorrent Mainline DHT, as specified by Pkarr. iroh supports this through [DHT address lookup](/connecting/dht-address-lookup), which is opt-in. DNS servers that support this spec will receive these Pkarr signed packets, check their signature and format validity, and then serve each contained record, with the DNS server's configured *origin domain* appended to all record names. **Want to publish via Pkarr?** This is handled automatically when using DNS address lookup. See the [DNS Address Lookup guide](/connecting/dns-address-lookup) for configuration details. # Endpoints Source: https://docs.iroh.computer/concepts/endpoints An *endpoint* is the main API interface to create connections to, and accept connections from other iroh endpoints. The connections are end-to-end encrypted. Endpoints have a `EndpointID` (the public half of an Ed25519 keypair) and the private key used to sign and decrypt messages. Generally, an application will have a single endpoint instance. This ensures all the connections made share the same connections to other iroh endpoints, while still remaining independent connections. This will result in more optimal network behaviour. ## Connections Either endpoint might be operating as the "server", so we use `connect` and `accept` to distinguish between the two. The `connect` method is used to create a new connection to a remote endpoint, while `accept` is used to accept incoming connections from a remote endpoint. Connections are full-fledged QUIC connections, giving you access to most features of QUIC / HTTP3, including bidirectional and unidirectional streams. A [Relay](/concepts/relays) server can be used to make the connections reliable. Due to the light-weight properties of QUIC streams a stream can only be accepted once the initiating peer has sent some data on it. ## Endpoint Identifiers Each endpoint in iroh has a unique identifier (`EndpointID`) created as a cryptographic key. This can be used to globally identify an endpoint. Because `EndpointIDs` are cryptographic keys, they are also the mechanism by which all traffic is always encrypted for a specific endpoint only. By default, creating an endpoint generates a **new random identity** each time — a fresh keypair, and therefore a new `EndpointID`. If you want the same `EndpointID` to persist across restarts (so peers can reconnect and your tickets keep working), store the endpoint's `SecretKey` and load it on every launch. See [Persistent identity](/connecting/creating-endpoint#persistent-identity) for a working example. See the [EndpointID](https://docs.rs/iroh/latest/iroh/type.EndpointId.html) documentation for more information. # Endpoint Addresses Endpoint Addresses or [`EndpointAddrs`](https://docs.rs/iroh/latest/iroh/struct.EndpointAddr.html) are a common struct you'll interact when working with iroh to tell iroh what & where to dial. In rust they look like this: ```rust theme={null} pub struct EndpointAddr { pub id: EndpointId, pub addrs: BTreeSet, } ``` You'll interact with `EndpointAddr`s a fair amount when working with iroh. It's also quite normal to construct addresses manually from, say, endpoint identifiers stored in your application database. When we call [`connect`](https://docs.rs/iroh/latest/iroh/endpoint/struct.Endpoint.html#method.connect) on an [Endpoint](https://www.iroh.computer/docs/concepts/endpoint), we need to pass either a `EndpointAddr`, or something that can turn into a `EndpointAddr`. In iroh `Endpoint`s will have different fields populated depending on where they came from, and the address lookup services you've configured your endpoint with. ### Interaction with address lookup From the above struct, the only *required* field is the `id`. And because of this, there's an implementation of `From` that can turn `EndpointIDs` directly into EndpointAddrs. *but this will only work if you have an address lookup service that can resolve EndpointIDs enabled*. Thankfully, we enable address lookup by default: ```rust theme={null} use iroh::{Endpoint, endpoint::presets}; // enables dialing by EndpointAddrs that only have EndpointIDs by default: let ep = Endpoint::builder(presets::N0) .bind() .await?; ``` This is why we actively encourage configuring an address lookup service, and DNS is the most common one we recommend. Because we're in p2p land dialing details & even home relays for an endpoint can change on very short notice, making this data go stale quickly. Endpoint Identifiers are a practical source of stability that counteracts this. ### When to provide full details If you have full dialing details, it's well worth providing them as part of a `EndpointAddr` passed to `connect`. Iroh can use this to skip the network roundtrip required to either do initial address lookup, or update cached addresses. So if you have a source of up to date home relay & dialing info, provide it! ### What to persist in your application When storing endpoint information in your application database, what you should persist depends on whether you're using address lookup: **If you're using address lookup (recommended):** Store just the `EndpointID`. When you need to connect, construct an `EndpointAddr` from the ID and let address lookup resolve the current dialing details. This is the most robust approach since relay URLs and direct addresses can change frequently in P2P networks. ```rust theme={null} // Store in your database let endpoint_id: EndpointId = ...; // Later, when connecting let addr = EndpointAddr::from(endpoint_id); endpoint.connect(addr, ALPN).await?; ``` **If you're not using address lookup:** You'll need to store full `EndpointAddr` information (including the `addrs` field with relay and direct address information). Without address lookup, iroh has no way to resolve an `EndpointID` to dialing details. Keep in mind that stored dialing details can become stale quickly. Providing outdated information may slow down connection establishment as iroh tries addresses that no longer work before falling back to other methods. ## Building on Endpoints Endpoints are a low-level primitive that iroh exposes on purpose. For some projects like [dumbpipe](https://dumbpipe.dev), endpoints are 95% of what you need to connect any two devices on the planet. For others, like [Blobs](/protocols/blobs), endpoints are the foundation that higher-level protocols are built on. Most projects will not work with endpoints beyond constructing one and feeding it to one or more [protocols](/concepts/protocols). # NAT Traversal Source: https://docs.iroh.computer/concepts/nat-traversal NAT traversal is the set of techniques iroh uses to establish direct peer-to-peer connections between devices that sit behind NATs and firewalls. With NAT traversal, iroh applications can: * **Connect directly** between devices without manual network configuration * **Reduce latency** by avoiding servers when possible * **Save bandwidth** by keeping data transfer peer-to-peer * **Improve resilience and reliability** by not depending on all traffic being routed through third parties All of this happens automatically. You don't need to understand the technical details to benefit from it. ## The Problem: NATs and Firewalls Block Direct Connections Imagine you're trying to connect two devices directly, both sitting behind home or office routers. The routers' firewalls block incoming connections because they don't recognise them as responses to outbound traffic. The combination of NAT (which translates addresses) and firewall rules (which filter traffic) makes direct connections challenging. Most home and office networks use NAT, which acts like a one-way door: devices inside the network can reach out to the internet, but the internet can't easily reach back in. This causes reliance on central servers, which can introduce latency and reliability issues. Traditionally, this problem was solved by: * **Port forwarding**: Manually configuring your router to allow specific connections (tedious and requires technical knowledge) * **Relay servers**: Routing all traffic through a third-party server (slow and expensive) ## How NAT Traversal Works in iroh iroh's NAT traversal is built on top of QUIC and integrates tightly with the [relay system](/concepts/relays). The core technique is simultaneous outbound connection (sometimes called "holepunching") where both peers send packets to each other at the same time, causing their respective firewalls and NATs to create mappings that allow the traffic through. Note that despite the "NAT" in the name, this technique applies equally to firewalls that don't involve NAT. iroh uses it whenever a direct connection needs to be established through any restrictive network boundary. NAT traversal succeeds in the vast majority of real-world conditions; roughly 9 out of 10 network configurations allow a direct connection to be established. iroh's implementation is deterministic: if it works between two devices once, it will continue to work as long as their networking setup stays stable. Where it fails (some corporate firewalls or cellular networks) iroh automatically falls back to the relay. ### 1. Initial Contact Through a Relay Both peers first connect to a shared [relay server](/concepts/relays). The relay acts as a meeting point where peers can coordinate before a direct path is established. The relay server observes each node's public IP address and port (the address from which it sees incoming traffic). This *reflective address* can be used by the remote peer to reach through the firewall, provided the firewall considers it expected traffic. ### 2. Sharing Connection Information Through the relay, peers exchange their: * **Public IP addresses** (how they appear to the outside internet) * **Port numbers** (the specific door number the router assigned them) * **Local addresses** (in case they're on the same network) Both nodes simultaneously send UDP datagrams to each other's reflective addresses. Since both are sending on the same 4-tuple (source IP, source port, destination IP, destination port), the firewalls recognise the incoming packets as matching outbound traffic and allow them through. The relay server doesn't actively participate in this process. It simply forwards encrypted packets between nodes without knowledge of whether they contain application data or coordination messages. ### 3. Simultaneous Outbound Connection Both peers try to connect to each other **at the same time**. When peer A sends a packet to peer B's public address, A's NAT creates a mapping and the firewall creates a temporary rule allowing responses from B. When peer B sends a packet to peer A at the same moment, B's NAT and firewall do the same. Because both mappings are now established and both firewalls expect traffic from each other, the packets get through and a direct connection is formed. Alice and Bob, each behind a home router, first reach each other through the relay, then both send packets simultaneously to punch through their NATs; once the direct path is validated the relay path falls away and traffic flows directly ### 4. Fallback to Relay If NAT traversal fails (some networks use particularly strict configurations), iroh automatically falls back to routing traffic through the relay server. This ensures connections always work, even if they can't be direct. ## Read more * [iroh's NAT traversal implementation](https://docs.rs/iroh/latest/iroh/endpoint/index.html#nat-traversal) * [Relay system documentation](/concepts/relays) * [Dedicated infrastructure setup](/deployment/dedicated-infrastructure) * [Endpoint configuration](https://docs.rs/iroh/latest/iroh/endpoint/struct.Endpoint.html) # Protocols Source: https://docs.iroh.computer/concepts/protocols A **protocol** defines how two iroh endpoints exchange messages and coordinate behavior. Once you've established an iroh connection, protocols determine what happens next: transferring files, syncing documents, broadcasting messages, or whatever your application needs. iroh provides the pool of encrypted QUIC connections - protocols define what to do with them. ## Client-server protocols Sometimes it is best to have classical client and server roles at the application level. Iroh connections do support this, and many n0 protocols are written in this way. [Sendme], [dumbpipe], [iroh-blobs] and all [irpc] based protocols have clear client and server roles at the application level. ## Peer-to-peer protocols Unlike HTTP's client/server model where one side only sends requests and the other only responds, peer-to-peer protocols support **both** initiating and accepting connections on the same endpoint. This means: * Any peer can request data from any other peer * Any peer can offer data to any other peer * Roles can change over time (today's client is tomorrow's server) [Gossip] and [docs] are examples of peer-to-peer protocols. You can still build client/server-style protocols if your application needs that distinction, but iroh's P2P foundation gives you more flexibility. ## Building blocks, not a framework iroh is designed as a set of composable building blocks. The core `iroh` crate gives you endpoints and connections. From there, you choose: **Use existing protocols** - Pick from protocols built by the iroh community: * [`iroh-blobs`](/protocols/blobs) - Content-addressed blob storage and transfer * [`iroh-docs`](/protocols/documents) - Collaborative key-value documents with CRDTs * [`iroh-gossip`](/connecting/gossip) - Topic-based message broadcasting in swarms * [`iroh-automerge`](https://github.com/n0-computer/iroh-examples/tree/main/iroh-automerge) - Automerge document sync (experimental) **Build your own** - Create custom protocols using: * Raw QUIC streams via iroh's connection API (most common approach) * RPC frameworks like [`irpc`](https://github.com/n0-computer/irpc) with [`iroh-irpc`](https://github.com/n0-computer/irpc) Each protocol is a separate crate you can add as needed. You're never locked into a particular set of protocols - mix and match, or build something entirely new. ## How protocols work ### ALPN identifiers Protocols use [Application-Layer Protocol Negotiation](https://datatracker.ietf.org/doc/html/rfc7301) (ALPN) strings to identify themselves. When an endpoint accepts a connection, it uses the ALPN to route to the correct protocol handler. For example, `iroh-blobs` uses the ALPN `/iroh-bytes/VERSION`. When a peer connects with this ALPN, the endpoint knows to handle it as a blobs request. You can also use multiple ALPN identifiers for version negotiation. A connecting peer might offer `/my-protocol/2` and `/my-protocol/1`, letting the accepting peer respond with whichever version it supports. ### The accept loop When your endpoint receives an incoming connection, it checks the requested ALPN and routes to the appropriate protocol handler. This is similar to HTTP routing, but happens at the connection level rather than per-request. You can handle this routing yourself by accepting connections directly, or use iroh's [`Router`](https://docs.rs/iroh/latest/iroh/protocol/struct.Router.html) to automatically route connections to registered protocol handlers. ## Learn more Ready to start building with protocols? **Using existing protocols:** * [Blob storage with iroh-blobs](/protocols/blobs) * [Document collaboration with iroh-docs](/protocols/documents) * [Message broadcasting with iroh-gossip](/connecting/gossip) **Building your own:** * [Write your own protocol guide](/protocols/writing-a-protocol) * [QUIC streaming patterns](/protocols/using-quic) * [Router API documentation](https://docs.rs/iroh/latest/iroh/protocol/struct.Router.html) [Sendme]: https://github.com/n0-computer/sendme [dumbpipe]: https://github.com/n0-computer/dumbpipe [iroh-blobs]: /protocols/blobs [irpc]: https://docs.rs/irpc [Gossip]: /connecting/gossip [docs]: /protocols/documents # Relays Source: https://docs.iroh.computer/concepts/relays Relays are servers that temporarily route encrypted traffic until a direct, P2P connection is feasible. Once this direct path is set up, the relay server steps back, and the data flows directly between devices. This approach allows Iroh to maintain a secure, low-latency connection, even in challenging network situations. There are situations where a direct connection can’t be established, and in those cases traffic falls back to running through the relay. Relay servers do not have access to the data being transmitted, as it’s encrypted end-to-end. Relays serve two roles in iroh: 1. **NAT Traversal.** When two endpoints first connect, they exchange network information through the relay to attempt a direct P2P connection. 2. **Encrypted traffic fallback.** If a direct connection can't be established (due to strict NATs, firewalls, or other network conditions), traffic flows through the relay instead. Once a direct path is established, the relay steps back and data flows peer-to-peer. Relay servers **cannot read** any of the traffic they handle; it's encrypted end-to-end. In practice, roughly 9 out of 10 networking conditions allow a direct connection. iroh's NAT traversal implementation is deterministic: if it works between two devices once, it will continue to work as long as their networking setup stays stable. ## Deploy your own relay Sign up for Iroh Services and spin up a managed relay for your project in minutes. ## Why relays are better Unlike traditional servers, relay servers are **stateless**. They don't store your application data; they just facilitate connections. * **No database synchronization.** Nothing to replicate across relay instances. * **No state migration.** When a relay goes down, clients reconnect to another with no data loss. * **Simple scaling.** Spin relay instances up or down like any lightweight service. * **Automatic failover.** Iroh can attempt to connect to multiple relays automatically; as long as one is reachable, your peers find each other. This inverts the traditional model: relays are disposable connection facilitators, while your clients own all the application state and logic. ## Public relays Iroh hardcodes a set of public relays provided by [n0.computer](https://n0.computer), free to use. Public relays: * Rate-limit traffic to prevent abuse * Carry no uptime or performance guarantees * Are shared across all iroh developers worldwide **Public relays are suitable for development and testing.** For production, use dedicated relays. ## Dedicated relays Dedicated relays are relay servers provisioned exclusively for your project, either managed through Iroh Services or self-hosted. They offer: * **Authenticated by default**: only your project's endpoints can connect, authenticated automatically with your API key. You can turn authentication off from the dashboard if you want any endpoint that has the URL to be able to use it. * **Isolation**: no noisy neighbors; your traffic is yours alone * **Uptime guarantees**: SLAs available via [Iroh Services](/iroh-services/relays) * **Version locking**: stay on a specific iroh version or run blue/green deployments * **Multi-region & multi-cloud**: deploy relays across regions and providers for resilience ## Authentication Dedicated relays require authentication by default, so a relay only carries traffic for endpoints that belong to your project. Authentication is built in — there's nothing extra to wire up beyond your [API key](/iroh-services/access). Here's how it works: 1. **Your relay knows your project.** When Iroh Services provisions a relay, it tells the relay which of your project's API keys are allowed to use it. This allow-set is kept in sync as you create or revoke keys. 2. **Your endpoint presents a token, not the key itself.** When you build an endpoint with the `iroh_services::preset()` builder and your API key, the SDK mints a short-lived, signed access token scoped to that endpoint's identity. The endpoint presents this token when it connects to the relay. Your API key never leaves your application. 3. **The relay verifies and admits.** The relay checks that the token was signed by one of your project's allowed keys and hasn't expired, then admits the connection. An endpoint with no token, an expired token, or a token from a revoked key is refused at the handshake. A few things worth knowing: * **Revocation is live.** Delete an API key and its access is withdrawn from your relays right away, including connections that are already open. The relay will kick off clients using that key, and they won't be able to reconnect until they have a valid token again. * **Authentication is a toggle, not a different relay.** Turning authentication off from the dashboard tells the relay to admit any endpoint without a token. Turn it back on and the token check resumes for new connections. See [Turn authentication off](/iroh-services/relays/managed#turn-authentication-off). * **Turning authentication off doesn't make a relay discoverable.** Your relay is never listed or advertised anywhere. With authentication off, any endpoint that already knows the relay's URL can use it, but it still has to know the URL. * **Authentication is about access, not privacy.** All relay traffic is end-to-end encrypted regardless. Authentication controls *who may use* your relay; it doesn't change what the relay can see (it still can't read your traffic). Self-hosted relays you run outside of Iroh Services manage their own access, so this is handled for you only on managed relays. See [API Keys](/iroh-services/access) for where your key comes from. ## Deploy your own relay Sign up for Iroh Services and spin up a managed relay for your project in minutes. Or self-host using the open-source relay binary: * [Relay source code](https://github.com/n0-computer/iroh/tree/main/iroh-relay) * [Relay binary releases](https://github.com/n0-computer/iroh/releases) * [Dedicated infrastructure guide](/deployment/dedicated-infrastructure) # Tickets Source: https://docs.iroh.computer/concepts/tickets A ticket is a serializable token that packages dialing information for an iroh endpoint. They're a convenience pattern you can use in your application to make sharing connection details easier - but they're entirely optional. Have a ticket? Try pasting it into the [iroh ticket explorer](https://ticket.iroh.computer) to break it down! Here's an [example](https://ticket.iroh.computer?ticket=docaaacarwhmusoqf362j3jpzrehzkw3bqamcp2mmbhn3fmag3mzzfjp4beahj2v7aezhojvfqi5wltr4vxymgzqnctryyup327ct7iy4s5noxy6aaa) ## What is a ticket? Here's an example ticket: ```text theme={null} docaaacarwhmusoqf362j3jpzrehzkw3bqamcp2mmbhn3fmag3mzzfjp4beahj2v7aezhojvfqi5wltr4vxymgzqnctryyup327ct7iy4s5noxy6aaa ``` Yes, they're long. But they pack a lot of information: an endpoint address (endpoint ID, relay URL, and direct addresses) plus optional application-specific data like a document ID or blob hash. The key insight: instead of making users copy and paste just a hash and then figure out where to find the data, tickets combine *what* you want with *where to get it from*. This gives iroh everything needed to establish a connection immediately. Tickets work well in QR codes, can be sent via messaging apps, or published to a coordination server. They're particularly useful for short-lived sessions where both devices are online at the same time. ## When to use tickets **Use tickets when:** * Bootstrapping peer connections without a central coordination server * Building apps where users manually share connection info (QR codes, copy/paste) * You want a single token that contains both content identifiers and connection details * Short-lived sessions where both peers are online simultaneously **Don't use tickets when:** * You have a central server or database to coordinate connections * You're building long-lived connections where dialing details change frequently * You can cache `EndpointID`s and let iroh resolve dialing details at runtime If you have *any* means of coordinating (a database, server, or gossip protocol), we recommend you work with `EndpointID`s directly instead of tickets. Let iroh handle the address lookup and connection details transparently. ## Creating tickets The `iroh-tickets` crate provides utilities for creating and parsing tickets. Here's how to create a basic endpoint ticket: ```rust theme={null} use iroh_tickets::endpoint::EndpointTicket; let ticket = EndpointTicket::new(endpoint.addr()); println!("Share this ticket: {ticket}"); ``` You can also create application-specific ticket types that include additional data beyond the endpoint address. ## Ticket patterns Tickets typically follow this pattern: 1. An ASCII prefix indicating the ticket type (e.g., `endpoint`, `blob`, `doc`) 2. A base32-lowercase-encoded payload containing [postcard-encoded](https://postcard.jamesmunns.com/wire-format.html) data The postcard encoding keeps tickets compact despite containing multiple pieces of information. ## Security considerations **Tickets contain IP addresses**: When you create a ticket, it embeds the IP addresses you're currently reachable at. Sharing a ticket means sharing your IP address with whoever receives it. This is intentional - it enables direct connections without a central server. This is actually *better* than many P2P systems that broadcast your IP to all peers. With tickets, you form a "cozy network" between peers you explicitly choose to connect with. **Tickets are reusable**: Unlike what the name might imply, tickets are not single-use tokens. Once created, a ticket can be used multiple times by anyone who has it. **Application-specific tickets may contain secrets**: Depending on what your application includes in a ticket (like write capabilities for a document), tickets can grant ongoing access. Treat them accordingly. **Tickets can go stale**: The dialing information in a ticket (especially IP addresses) can become outdated as network conditions change. For long-lived connections, prefer caching `EndpointID`s and letting iroh resolve current dialing details. ## Building your own ticket types The `iroh-tickets` crate is designed to be extended. You can create custom ticket types for your application that bundle your own data alongside endpoint addresses. See the [crate documentation](https://docs.rs/iroh-tickets) for details. # Connect two endpoints Source: https://docs.iroh.computer/connect-two-endpoints Build a ping program that connects two endpoints over iroh In this tutorial we'll build a small ping program using iroh and the [iroh-ping](https://github.com/n0-computer/iroh-ping) protocol. One endpoint runs as a **receiver**, prints a ticket, and waits for incoming pings. The other runs as a **sender**, dials the receiver using the ticket, and reports the round-trip time. Ping is a simple request/response protocol with sender and receiver roles. But unlike IP ping ([ICMP]), iroh ping works even if both devices are behind a NAT. The full example can be [viewed on GitHub](https://github.com/n0-computer/iroh-ping/blob/main/examples/quickstart.rs). ## Set up Pick your language and create a project with the iroh bindings installed. Each tab assumes you already have the language toolchain on your machine. For full setup details per language (Xcode project for Swift, NDK targets for Kotlin/Android, etc.) see the [language guides](/languages/index). ```bash Rust theme={null} cargo init ping-pong cd ping-pong cargo add iroh iroh-ping iroh-tickets anyhow tracing-subscriber cargo add tokio --features full # work in src/main.rs ``` ```bash Python theme={null} mkdir ping-pong && cd ping-pong python -m venv .venv && source .venv/bin/activate pip install iroh # work in main.py ``` ```bash Swift theme={null} swift package init --type executable --name ping-pong cd ping-pong # add to Package.swift dependencies: # .package(url: "https://github.com/n0-computer/iroh-ffi", from: "1.0.0") # and to your target dependencies: # .product(name: "IrohLib", package: "iroh-ffi") # work in Sources/ping-pong/main.swift ``` ```bash Kotlin theme={null} # scaffold a Gradle project, then add the iroh dependency: # dependencies { implementation("computer.iroh:iroh:1.0.0") } # (Maven Central — requires mavenCentral() in your repositories block) # work in a Main.kt with `fun main(...)` ``` ```bash JavaScript theme={null} mkdir ping-pong && cd ping-pong npm init -y npm pkg set type=module npm install @number0/iroh # work in main.mjs ``` ## Protocols and ALPN A **protocol** defines how two endpoints exchange messages. Just like HTTP defines how web browsers talk to servers, iroh protocols define how peers communicate over iroh connections. Each protocol is identified by an **ALPN** (Application-Layer Protocol Negotiation) string. When a connection arrives, the router uses the ALPN string to decide which handler processes the data. `iroh-ping` is a diagnostic protocol that lets two endpoints exchange lightweight ping/pong messages to prove connectivity and measure round-trip latency. You can build your own protocol handlers or use existing ones like iroh-ping. To write your own protocol, see the [protocol documentation page](/protocols/writing-a-protocol). ## What is a ticket? When an iroh endpoint comes online, it has an address containing its Endpoint ID, relay URL, and direct addresses. The address is a structured representation that other iroh endpoints can use to dial it. An `EndpointTicket` wraps this address into a serializable format: a short string you can copy and paste. Share this string with senders so they can dial the receiver without manually exchanging networking details. This out-of-band information must reach the sender somehow so that endpoints can discover each other while still bootstrapping a secure, end-to-end encrypted connection. In this example we just use a string for users to copy and paste, but in your app you could publish it to a server, send it as a QR code, or pass it as a URL query parameter. It's up to you. For more on how this works, see [Tickets](/concepts/tickets) and [Address Lookup](/concepts/address-lookup). ## The receiver The receiver creates an iroh endpoint, brings it online, prints a ticket containing its address, and accepts incoming ping requests until you press Ctrl+C. The Rust version uses the `iroh-ping` protocol crate; the other languages open the bidirectional stream by hand and echo the payload back, since iroh-ping isn't bound yet. ```rust Rust theme={null} use anyhow::Result; use iroh::{Endpoint, endpoint::presets, protocol::Router}; use iroh_ping::Ping; use iroh_tickets::{Ticket, endpoint::EndpointTicket}; async fn run_receiver() -> Result<()> { let endpoint = Endpoint::bind(presets::N0).await?; endpoint.online().await; let ping = Ping::new(); let ticket = EndpointTicket::new(endpoint.addr()); println!("{ticket}"); let _router = Router::builder(endpoint) .accept(iroh_ping::ALPN, ping) .spawn(); tokio::signal::ctrl_c().await?; Ok(()) } ``` ```python Python theme={null} async def receiver(): iroh.iroh_ffi.uniffi_set_event_loop(asyncio.get_running_loop()) ep = await iroh.Endpoint.bind(iroh.EndpointOptions(alpns=[list(ALPN)])) print("ticket:", str(iroh.EndpointTicket(ep.addr()))) incoming = await ep.accept_next() conn = await (await incoming.accept()).connect() bi = await conn.accept_bi() msg = await bi.recv().read_to_end(1024) await bi.send().write_all(msg) await bi.send().finish() await ep.close() ``` ```swift Swift theme={null} func receiver() async throws { let ep = try await Endpoint.bind( options: EndpointOptions(preset: presetN0(), alpns: [ALPN]) ) let ticket = try EndpointTicket(addr: ep.addr()) print("ticket: \(ticket)") let incoming = try await ep.acceptNext()! let conn = try await incoming.accept().connect() let bi = try await conn.acceptBi() let msg = try await bi.recv().readToEnd(sizeLimit: 1024) try await bi.send().writeAll(buf: msg) try await bi.send().finish() try await ep.close() } ``` ```kotlin Kotlin theme={null} suspend fun receiver() { val ep = Endpoint.bind( EndpointOptions(preset = presetN0(), alpns = listOf(ALPN)), ) println("ticket: ${EndpointTicket(ep.addr())}") val incoming = ep.acceptNext()!! val conn = incoming.accept().connect() val bi = conn.acceptBi() val msg = bi.recv().readToEnd(1024u) bi.send().writeAll(msg) bi.send().finish() ep.shutdown() } ``` ```javascript JavaScript theme={null} async function receiver() { const ep = await Endpoint.bind({ alpns: [ALPN] }) console.log('ticket:', EndpointTicket.fromAddr(ep.addr()).toString()) const incoming = await ep.acceptNext() const conn = await (await incoming.accept()).connect() const bi = await conn.acceptBi() const msg = await bi.recv.readToEnd(1024) await bi.send.writeAll(msg) await bi.send.finish() await ep.close() } ``` **Python: what is `uniffi_set_event_loop`?** iroh's Rust runtime runs on its own threads, and when an operation completes (a connection arrives, a read finishes) one of those threads has to wake your Python code up. A Rust thread has no running asyncio loop, so the bindings cannot find yours on their own: this call hands them your loop explicitly. Make it the first line of any coroutine you pass to `asyncio.run()`, before touching any other iroh API. Without it, calls from Rust back into Python fail with `RuntimeError: no running event loop`, which typically shows up as an `await` that never completes. See the [Python guide](/languages/python#register-your-event-loop) for details. ## The sender The sender creates its own endpoint, parses the receiver's ticket, and dials. Rust delegates to `iroh-ping`; the other languages open a bidirectional stream, write `hello`, await the echo, and time the round trip. ```rust Rust theme={null} async fn run_sender(ticket: EndpointTicket) -> Result<()> { 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); Ok(()) } ``` ```python Python theme={null} async def sender(ticket_str): iroh.iroh_ffi.uniffi_set_event_loop(asyncio.get_running_loop()) ep = await iroh.Endpoint.bind(iroh.EndpointOptions()) addr = iroh.EndpointTicket.parse(ticket_str).endpoint_addr() conn = await ep.connect(addr, list(ALPN)) bi = await conn.open_bi() start = time.monotonic() await bi.send().write_all(list(b"hello")) await bi.send().finish() await bi.recv().read_to_end(1024) print(f"ping took: {(time.monotonic() - start) * 1000:.2f} ms") await ep.close() ``` ```swift Swift theme={null} func sender(_ ticketStr: String) async throws { let ep = try await Endpoint.bind(options: EndpointOptions(preset: presetN0())) let addr = try EndpointTicket.parse(str: ticketStr).endpointAddr() let conn = try await ep.connect(addr: addr, alpn: ALPN) let bi = try await conn.openBi() let start = Date() try await bi.send().writeAll(buf: Data("hello".utf8)) try await bi.send().finish() _ = try await bi.recv().readToEnd(sizeLimit: 1024) let ms = Date().timeIntervalSince(start) * 1000 print(String(format: "ping took: %.2f ms", ms)) try await ep.close() } ``` ```kotlin Kotlin theme={null} suspend fun sender(ticketStr: String) { val ep = Endpoint.bind(EndpointOptions(preset = presetN0())) val addr = EndpointTicket.parse(ticketStr).endpointAddr() val conn = ep.connect(addr, ALPN) val bi = conn.openBi() val ms = measureTimeMillis { bi.send().writeAll("hello".toByteArray()) bi.send().finish() bi.recv().readToEnd(1024u) } println("ping took: $ms ms") ep.shutdown() } ``` ```javascript JavaScript theme={null} async function sender(ticketStr) { const ep = await Endpoint.bind() const addr = EndpointTicket.fromString(ticketStr).endpointAddr() const conn = await ep.connect(addr, ALPN) const bi = await conn.openBi() const start = performance.now() await bi.send.writeAll(Array.from(Buffer.from('hello'))) await bi.send.finish() await bi.recv.readToEnd(1024) console.log(`ping took: ${(performance.now() - start).toFixed(2)} ms`) await ep.close() } ``` ## Wiring main Parse the command-line argument to decide whether to run as receiver or sender. Each language tab is a complete file — drop the receiver and sender from the previous sections above the entry point. ```rust Rust theme={null} use std::env; use anyhow::anyhow; #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); let mut args = env::args().skip(1); let role = args .next() .ok_or_else(|| anyhow!("expected 'receiver' or 'sender' as the first argument"))?; match role.as_str() { "receiver" => run_receiver().await, "sender" => { let ticket_str = args .next() .ok_or_else(|| anyhow!("expected ticket as the second argument"))?; let ticket = EndpointTicket::deserialize(&ticket_str) .map_err(|e| anyhow!("failed to parse ticket: {}", e))?; run_sender(ticket).await } _ => Err(anyhow!("unknown role '{}'; use 'receiver' or 'sender'", role)), } } ``` ```python Python theme={null} import asyncio import sys import time import iroh ALPN = b"iroh-tutorial/ping/0" # ... receiver() and sender() from the sections above ... if __name__ == "__main__": if sys.argv[1] == "receiver": asyncio.run(receiver()) else: asyncio.run(sender(sys.argv[2])) ``` ```swift Swift theme={null} import Foundation import IrohLib let ALPN = Data("iroh-tutorial/ping/0".utf8) // ... receiver() and sender() from the sections above ... let args = CommandLine.arguments switch args[1] { case "receiver": try await receiver() case "sender": try await sender(args[2]) default: fatalError("usage: receiver | sender ") } ``` ```kotlin Kotlin theme={null} import computer.iroh.* import kotlinx.coroutines.runBlocking import kotlin.system.measureTimeMillis private val ALPN = "iroh-tutorial/ping/0".toByteArray() // ... receiver() and sender() from the sections above ... fun main(args: Array) = runBlocking { when (args[0]) { "receiver" -> receiver() "sender" -> sender(args[1]) else -> error("usage: receiver | sender ") } } ``` ```javascript JavaScript theme={null} import { Endpoint, EndpointTicket } from '@number0/iroh' const ALPN = Array.from(Buffer.from('iroh-tutorial/ping/0')) // ... receiver() and sender() from the sections above ... const [cmd, arg] = process.argv.slice(2) if (cmd === 'receiver') await receiver() else if (cmd === 'sender') await sender(arg) else console.error('usage: receiver | sender ') ``` ## Run it In one terminal, start the receiver. It will print a ticket. Copy that ticket and run the sender in another terminal — you should see the round-trip time printed. ```bash Rust theme={null} # terminal 1 cargo run -- receiver # terminal 2 cargo run -- sender ``` ```bash Python theme={null} # terminal 1 python main.py receiver # terminal 2 python main.py sender ``` ```bash Swift theme={null} # terminal 1 swift run ping-pong receiver # terminal 2 swift run ping-pong sender ``` ```bash Kotlin theme={null} # terminal 1 ./gradlew run --args="receiver" # terminal 2 ./gradlew run --args="sender " ``` ```bash JavaScript theme={null} # terminal 1 node main.mjs receiver # terminal 2 node main.mjs sender ``` **Connection issues?** If the sender can't reach the receiver, see the [troubleshooting guide](/troubleshooting) to enable detailed logging or use `iroh-doctor` to diagnose network problems. ## Optional: send metrics to Iroh Services If you want to see how your endpoints are performing (direct data rate, NAT traversal success, traffic volume) you can wire in [Iroh Services](/iroh-services/quickstart) as an optional client. Add the dependency: ```bash theme={null} cargo add iroh-services ``` Then in `run_receiver` (and/or `run_sender`), conditionally connect to Iroh Services if the `IROH_SERVICES_API_SECRET` environment variable is set. If the variable isn't set the connection is skipped silently and your endpoint runs as before. If it is set, your endpoint shows up in the Iroh Services dashboard with live metrics. ```rust theme={null} use iroh_services::Client; // ... after `endpoint.online().await;` let _services_client = match Client::builder(&endpoint).api_secret_from_env() { Ok(builder) => { let client = builder.name("ping-receiver")?.build().await?; println!("Connected to Iroh Services"); Some(client) } Err(_) => None, }; ``` Get an API key from the [API Keys page](/iroh-services/access), then run with the env var set: ```bash theme={null} IROH_SERVICES_API_SECRET=YOUR_API_KEY cargo run -- receiver ``` ## More tutorials You've now built a ping tool. The full example is [on GitHub](https://github.com/n0-computer/iroh-ping). If you're hungry for more, check out: * [Blob storage with iroh-blobs](/protocols/blobs) * [Build your own P2P chat app](/examples/chat) * [Rust API documentation](https://docs.rs/iroh) [ICMP]: https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol # Creating an Endpoint Source: https://docs.iroh.computer/connecting/creating-endpoint An endpoint is the core building block of an iroh node. It manages network connections, handles incoming and outgoing traffic, and provides the necessary infrastructure for implementing protocols. Once you have an Endpoint, you can use it to create connections or accept incoming connections from other endpoints. ## Creating an Endpoint This method initializes a new endpoint and binds it to a local address, allowing it to listen for incoming connections. ```rust theme={null} use iroh::{Endpoint, endpoint::presets}; use anyhow::Result; #[tokio::main] async fn main() -> Result<()> { let endpoint = Endpoint::bind(presets::N0).await?; // ... Ok(()) } ``` 1. `bind` creates the endpoint and starts listening for incoming connections 2. `await` keyword is used to wait for the endpoint to be created in an asynchronous context. Each time you `bind` without supplying a secret key, iroh generates a **brand new random identity**. That means a different `EndpointId` — and therefore different tickets and addresses — every time your program starts. If you want a stable identity that survives restarts, see [Persistent identity](#persistent-identity) below. ## Persistent identity An endpoint's identity is an Ed25519 keypair. The public half is its `EndpointId` (the stable address other endpoints dial); the private half is its `SecretKey`. By default the builder generates a fresh `SecretKey` on every launch, so your `EndpointId` changes each run. For most applications — anything where peers reconnect to you over time, or where you hand out a ticket that should keep working — you want a **persistent identity**. To get one, generate a `SecretKey` once, store it, and load the same key on every subsequent launch: ```rust theme={null} use iroh::{Endpoint, SecretKey, endpoint::presets}; use anyhow::Result; #[tokio::main] async fn main() -> Result<()> { // Load a previously stored key, or generate and persist a new one. let secret_key = match std::fs::read("endpoint.key") { Ok(bytes) => SecretKey::from_bytes(&bytes.as_slice().try_into()?), Err(_) => { let key = SecretKey::generate(); // Treat this file like a password: anyone with it can // impersonate your endpoint. Store it securely. std::fs::write("endpoint.key", key.to_bytes())?; key } }; let endpoint = Endpoint::builder() .secret_key(secret_key) .bind(presets::N0) .await?; // This prints the same EndpointId on every run. println!("our endpoint id: {}", endpoint.id()); Ok(()) } ``` Your `SecretKey` is sensitive: anyone who has it can impersonate your endpoint. Store it the way you'd store a private key or password — file permissions, a keychain, or an OS secret store — not in source control. ## Next Steps With an endpoint created, you can now start discovering and connecting to other endpoints. Explore the following guides to learn more: * [DNS Global Address Lookup](/connecting/dns-address-lookup) * [Local Network Address Lookup](/connecting/local-address-lookup) * [Gossip and Topic Broadcast](/connecting/gossip) * [Writing a Protocol](/protocols/writing-a-protocol) # DHT Source: https://docs.iroh.computer/connecting/dht-address-lookup DHT address lookup publishes and resolves endpoint records on the [BitTorrent Mainline](https://en.wikipedia.org/wiki/Mainline_DHT) distributed hash table. The records are the same signed records that [DNS address lookup](/connecting/dns-address-lookup) uses. The difference is where they are stored. DNS address lookup publishes them to a hosted server and resolves over DNS, while DHT address lookup puts them on the BitTorrent Mainline DHT. That removes the dependency on a hosted server: any endpoint can publish and resolve without a central party, at the cost of slower lookups than DNS. Bob publishes his signed record to several nodes of the Mainline DHT; Alice resolves it by querying several nodes DHT address lookup is not enabled by default. It lives in the separate [`iroh-mainline-address-lookup`](https://crates.io/crates/iroh-mainline-address-lookup) crate, which you add alongside `iroh`. ```toml theme={null} [dependencies] iroh = "1" iroh-mainline-address-lookup = "0.4" ``` Add the `DhtAddressLookup` to the endpoint with `Endpoint::builder`. You can run it next to the default DNS address lookup, or build from `presets::Minimal` to use only the DHT. ```rust theme={null} use iroh::{Endpoint, endpoint::presets}; use iroh_mainline_address_lookup::DhtAddressLookup; #[tokio::main] async fn main() -> anyhow::Result<()> { let endpoint = Endpoint::builder(presets::N0) .address_lookup(DhtAddressLookup::builder()) .bind() .await?; // your code here Ok(()) } ``` DHT address lookup also combines well with [mDNS address lookup](/connecting/local-address-lookup) for both global and local address lookup without depending on centralized infrastructure. For the record format shared with DNS address lookup, see the [Address Lookup concept page](/concepts/address-lookup). # DNS Source: https://docs.iroh.computer/connecting/dns-address-lookup By default, iroh uses DNS address lookup to find other endpoints by their `EndpointId`. An endpoint publishes a signed record that maps its `EndpointId` to its home relay URL (and optionally its direct addresses), and resolves the same kind of record for endpoints it wants to dial. Bob publishes a signed DNS record with his home relay to dns.iroh.link via an HTTPS PUT; Alice resolves it with a DNS lookup ## How records are published and resolved Each endpoint creates a set of records with its addressing information, and puts it into a signed [Pkarr](https://pkarr.org) packet. The packet is signed by the endpoint's secret key. The endpoint publishes this packet to a DNS/Pkarr server via HTTP. Other endpoints can then resolve these packets either via DNS queries or via HTTP. [Number 0](https://n0.computer) provides a set of public DNS/Pkarr servers that are free to use, and are configured by default. You're more than welcome to run production systems using the public relays if you find performance acceptable. The public servers do rate-limit traffic, there is no guaranteed uptime. If you need more capacity or uptime guarantees or SLAs, you can run your own DNS server, or [contact us about hosted DNS options](https://cal.com/team/number-0/n0-protocol-services?overlayCalendar=true). You can read more about the design of the DNS/Pkarr address lookup system on the [address lookup](/concepts/address-lookup) page. ## Using DNS address lookup DNS address lookup is part of the `presets::N0` defaults, so you do not need to enable it explicitly. The `N0` preset adds a publisher and a resolver, both pointed at the n0-hosted server at `dns.iroh.link`. To discover another endpoint over DNS you need its `EndpointId`, either directly or from a [ticket](/concepts/tickets) that contains one. Resolution then happens automatically when you connect. ```rust theme={null} use iroh::{Endpoint, endpoint::presets}; #[tokio::main] async fn main() -> anyhow::Result<()> { // Bind an endpoint. let endpoint = Endpoint::bind(presets::N0).await?; endpoint.online().await?; // We print its Endpoint ID. let endpoint_id = endpoint.id(); println!("endpoint id: {endpoint_id}"); // Another endpoint needs only the Endpoint ID to connect, // because both endpoints use DNS/Pkarr address lookup services // via the `N0` preset. let other = Endpoint::bind(presets::N0).await?; let _conn = other.connect(endpoint_id, b"your-alpn").await?; // It works :) Ok(()) } ``` ## Use your own server Two endpoints must publish to and resolve from the same server to find each other. To use your own deployment of [`iroh-dns-server`](https://crates.io/crates/iroh-dns-server), build the endpoint from `presets::Minimal` (which adds no address lookup) and configure the publisher and resolver yourself. `PkarrPublisher::builder` takes the relay URL to PUT records to. `DnsAddressLookup::builder` takes the origin domain to query under. ```rust theme={null} use iroh::{ Endpoint, address_lookup::{DnsAddressLookup, PkarrPublisher}, endpoint::presets, }; use url::Url; #[tokio::main] async fn main() -> anyhow::Result<()> { let pkarr_relay: Url = "https://my-dns-server.example/pkarr".parse()?; let origin_domain = "my-dns-server.example".to_string(); let endpoint = Endpoint::builder(presets::Minimal) .address_lookup(PkarrPublisher::builder(pkarr_relay)) .address_lookup(DnsAddressLookup::builder(origin_domain)) .bind() .await?; // your code here Ok(()) } ``` By default the `PkarrPublisher` publishes only the home relay URL, not direct IP addresses. To publish addresses as well, set an `AddrFilter` on the builder with `PkarrPublisher::builder(url).addr_filter(AddrFilter::unfiltered())`. ## Disable DNS address lookup DNS address lookup is optional. To build an endpoint without it, use `presets::Minimal` instead of `presets::N0`. `Minimal` sets up a crypto provider but adds no address lookup and no relays, so the endpoint is not published anywhere. ```rust theme={null} use iroh::{Endpoint, endpoint::presets}; #[tokio::main] async fn main() -> anyhow::Result<()> { let endpoint = Endpoint::builder(presets::Minimal).bind().await?; // your code here Ok(()) } ``` # Observing & Rejecting Connections Source: https://docs.iroh.computer/connecting/endpoint-hooks Available in iroh v.96 and later. Endpoint Hooks allow you to intercept the connection-establishment process of an iroh `Endpoint`. They are a lightweight, flexible mechanism for observing connection events or rejecting connections based on conditions. The latter can be used to implement custom authentication schemes. Hooks run at two points: 1. **Before an outgoing connection starts**. No packets have been sent yet. 2. **After the QUIC/TLS handshake completes** for both incoming and outgoing connections. The remote endpoint ID, ALPN, and other metadata are available, but no application data has been sent or received yet. Hooks are registered with `Endpoint::builder(preset).hooks(...)`. If multiple hooks are installed, they run in the order they were added, and a rejection from any hook short-circuits the rest. Note that hooks cannot *use* connections, they can only *observe* or *reject* them. This is an important separation of concerns: If hooks were allowed to use the connections in any way, they could interfere with the actual protocols running within these connections. Hooks can, however, *reject* connections before they are passed on to protocol handlers. This makes it possible to implement custom authentication schemes with hooks that work without any support from the protocols running in these connections. > **Note:** Hooks live on the `Endpoint` instance. Never store an `Endpoint` inside your hook type (even indirectly), or it may cause reference-counting cycles and prevent clean shutdown. ## Example: Observing connection events This example shows a minimal hook implementation that logs the context available at each stage and spawns a task to watch the connection's network paths. It does not alter behavior, only observes. ```rust theme={null} use iroh::{ Endpoint, EndpointAddr, endpoint::{AfterHandshakeOutcome, BeforeConnectOutcome, Connection, EndpointHooks, presets}, }; use n0_future::StreamExt; use tracing::info; /// Our hooks instance. /// /// As we are only observing, we don't need to hold any state, thus we use a zero-sized struct. #[derive(Debug)] struct LogHooks; /// To use hooks, you need to implement the `EndpointHooks` trait. impl EndpointHooks for LogHooks { // Runs before an outgoing connection begins. async fn before_connect( &self, remote_addr: &EndpointAddr, alpn: &[u8], ) -> BeforeConnectOutcome { info!(?remote_addr, ?alpn, "attempting to connect"); BeforeConnectOutcome::Accept } // Runs after the handshake for both incoming and outgoing connections. // // The hook receives the `Connection` by reference. We shouldn't clone the connection here, // because this would prevent "close-on-drop" semantics that are often expected in other // code paths. Instead, we use a weak handle and a stream of path events. async fn after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome { let remote = conn.remote_id().fmt_short(); info!(%remote, alpn=?conn.alpn(), side=?conn.side(), "connection established"); let mut path_events = conn.path_events(); let handle = conn.weak_handle(); tokio::spawn(async move { while let Some(event) = path_events.next().await { info!(%remote, ?event, "path event"); // Upgrade the weak handle briefly to inspect the connection or path state. if let Some(conn) = handle.upgrade() { for path in conn.paths().iter() { // Inspect path state or log as desired. info!(%remote, dst=?path.remote_addr(), rx=path.stats().udp_rx.bytes, "path info"); } } } // The path event stream closes once the connection closes. info!(%remote, "connection closed"); }); AfterHandshakeOutcome::Accept } } #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); // Install the hooks on our endpoint. let _endpoint = Endpoint::builder(presets::N0) .hooks(LogHooks) .bind() .await?; // Use `endpoint` normally... Ok(()) } ``` ## Example: Rejecting connections Hooks can be used to enforce policy. If a hook returns a rejection result, the connection is immediately aborted. The example below rejects all incoming connections after the handshake. Outgoing connections will still dial. In real applications, you would inspect the `Connection` and reject connections by checking the connection's remote id or ALPN against authentication state in your app. ```rust theme={null} use iroh::endpoint::{AfterHandshakeOutcome, Connection, Endpoint, EndpointHooks, Side, presets}; #[derive(Debug)] struct RejectIncomingHook; impl EndpointHooks for RejectIncomingHook { async fn after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome { // Unconditionally reject all incoming connections. // In actual apps, you could conditionally allow or accept by checking the connection's // ALPN and remote id. if conn.side() == Side::Server { AfterHandshakeOutcome::Reject { error_code: 403u32.into(), reason: b"rejected".into(), } } else { AfterHandshakeOutcome::Accept } } } #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let _endpoint = Endpoint::builder(presets::N0) .hooks(RejectIncomingHook) .bind() .await?; Ok(()) } ``` ## More examples There are a few fully-featured examples for using hooks in the iroh repository. ### [Authentication layer](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs) Demonstrates how to build an authentication flow on top of hooks. This pattern keeps authentication separate from your application protocols while still integrating cleanly with iroh’s connection lifecycle. * We implement a dedicated “auth” protocol (with its own ALPN) for performing a pre-authentication handshake. * Outgoing connections run the pre-auth step before starting other connections. * Incoming connections are checked against a set of authorized remote ids. * If an incoming connection comes from a peer that hasn't successfully performed pre-auth, the connection is rejected. ### [Monitoring connection and path events](https://github.com/n0-computer/iroh/blob/main/iroh/examples/monitor-connections.rs) This example demonstrates how hooks can feed information to external tasks, giving you flexible observability. * A hook forwards a weak handle for each connection to a monitoring task. * The monitor can record events and stats. ### [Aggregating information about remote endpoints](https://github.com/n0-computer/iroh/blob/main/iroh/examples/remote-info.rs) This example implements a `RemoteMap` that tracks and aggregates information about all remotes our endpoint knows about. This can be useful if your app needs to choose between remotes, or for building diagnostic tools. * A hook forwards a weak handle for each connection into a worker task. * The worker maintains a map of all remotes with counts of active connections and observed statistics (e.g. latency/RTT, whether relay/IP paths were used, etc). * The `RemoteMap` exposes a simple API to query all known remotes and their aggregate metrics. # Gossip Broadcast Source: https://docs.iroh.computer/connecting/gossip Gossip protocols let peers broadcast messages to subscribers of a topic without relying on a central broker. Each node tracks a small set of neighbors and forwards messages redundantly so they reach interested peers even when nodes join, leave, or fail. In P2P networks each node typically only knows a handful of neighbors. Gossip protocols accept that partial view and use probabilistic forwarding so messages eventually reach most or all subscribers. Characteristics * Peers may join, leave, change addresses, or drop messages at any time. * Gossip spreads information redundantly across overlapping paths, which improves resilience but increases traffic. ## iroh-gossip The `iroh-gossip` protocol is based on *epidemic broadcast trees* to disseminate messages among a swarm of peers interested in a *topic*. The implementation is based on the papers [HyParView](https://asc.di.fct.unl.pt/~jleitao/pdf/dsn07-leitao.pdf) and [PlumTree](https://asc.di.fct.unl.pt/~jleitao/pdf/srds07-leitao.pdf). ### Installation ``` cargo add iroh-gossip ``` Iroh provides a Router that takes an Endpoint and any protocols needed for the application. Similar to a router in webserver library, it runs a loop accepting incoming connections and routes them to the specific protocol handler, based on ALPN. ### Structuring your application Every gossip network should have a `TopicID`. This `TopicId` should be a string shared between all endpoints that want to take part in the broadcast group. There are different ways to structure your application around topics, depending on your use case: * **One topic for all peers** – Use a single topic ID when everyone should receive all messages. Scales to a few thousand peers. * **Scoped topics** – Use separate topic IDs (or hashes derived from them) for distinct groups, spaces, or documents. ### Example ```rust theme={null} use iroh::{protocol::Router, Endpoint, EndpointId, endpoint::presets}; use iroh_gossip::{api::Event, Gossip, TopicId}; use n0_error::{Result, StdResultExt}; use n0_future::StreamExt; #[tokio::main] async fn main() -> Result<()> { // create an iroh endpoint that includes the standard address lookup mechanisms // we've built at number0 let endpoint = Endpoint::bind(presets::N0).await?; // build gossip protocol let gossip = Gossip::builder().spawn(endpoint.clone()); // setup router let router = Router::builder(endpoint) .accept(iroh_gossip::ALPN, gossip.clone()) .spawn(); // gossip swarms are centered around a shared "topic id", which is a 32 byte identifier let topic_id = TopicId::from_bytes([23u8; 32]); // and you need some bootstrap peers to join the swarm let bootstrap_peers = bootstrap_peers(); // then, you can subscribe to the topic and join your initial peers let (sender, mut receiver) = gossip .subscribe(topic_id, bootstrap_peers) .await? .split(); // you might want to wait until you joined at least one other peer: receiver.joined().await?; // then, you can broadcast messages to all other peers! sender.broadcast(b"hello world this is a gossip message".to_vec().into()).await?; // and read messages from others while let Some(event) = receiver.next().await { match event? { Event::Received(message) => { println!("received a message: {:?}", std::str::from_utf8(&message.content)); } _ => {} } } // clean shutdown makes sure that other peers are notified that you went offline router.shutdown().await.std_context("shutdown router")?; Ok(()) } fn bootstrap_peers() -> Vec { // insert your bootstrap peers here, or get them from your environment vec![] } ``` ### Getting bootstrap peers To join a gossip topic, you need to provide a list of bootstrap peers that are already members of the topic. These peers will help you discover other peers in the topic. You can obtain bootstrap peers through various means, such as: * A predefined list of known peers hardcoded in your application. * A configuration file or environment variable. * An address lookup service that provides a list of active peers for the topic. #### Sharing a ticket You can share an endpoint ticket with others to let them join the gossip topic. An endpoint ticket contains all the dialing information needed to connect to your endpoint. ```rust theme={null} use iroh_tickets::endpoint::EndpointTicket; let ticket = EndpointTicket::new(endpoint.addr()); println!("Share this ticket to let others join the gossip topic: {ticket}"); ``` #### Using a public rendezvous server You can also use iroh's rendezvous servers to find bootstrap peers for a topic. To do this, you can create an endpoint ticket for relay to use to discover peers. For more information on how to broadcast tickets on the relay server, see [Iroh Services](/iroh-services). ### Picking a topic ID A topic ID is a 32 byte identifier that identifies a gossip topic. You can pick any value you like, but it's recommended to use a cryptographic hash of a meaningful string to avoid collisions with other applications. We recommend using a stable hash function to derive topic IDs from human-readable strings. For example, you can use SHA-256 to hash a string like "com.example.myapp.mytopic" to produce a unique 32-byte topic ID: ```rust theme={null} use sha2::{Sha256, Digest}; let topic_name = "com.example.myapp.mytopic"; let mut hasher = Sha256::new(); hasher.update(topic_name.as_bytes()); let topic_id_bytes = hasher.finalize(); let topic_id = TopicId::from_bytes(topic_id_bytes.into()); ``` ### Waiting for peers Before sending messages, you might want to wait until you have connected to at least one other peer. This is because the gossip protocol works by forwarding messages between connected peers, so if you are not connected to anyone, your messages won't go anywhere. Call the `joined` method on the `Receiver` to block execution until at least one peer has joined the topic: ```rust theme={null} receiver.joined().await?; ``` ## How gossip compares to centralized systems * Central brokers (Kafka, MQTT, Pulsar) keep authoritative topic membership and provide stronger delivery guarantees, but introduce a single point of failure and operational overhead. * Gossip trades strict guarantees for decentralization, simplicity, and resilience in highly dynamic environments. ## When to use gossip * Lightweight broadcast where eventual delivery is acceptable. * Dynamic membership (peers frequently join/leave or change addresses). * No single point of failure is desired. # mDNS Source: https://docs.iroh.computer/connecting/local-address-lookup The mDNS address lookup 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. Devices need to be connected to the same local network for mDNS address lookup 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. Three devices on the same network — an iPhone, an Android phone and an embedded device — behind one router; each multicasts an mDNS announcement of its key and local address, and every device's known-peers list grows as the announcements arrive ## Usage mDNS address lookup is not enabled by default. It lives in the separate [`iroh-mdns-address-lookup`](https://crates.io/crates/iroh-mdns-address-lookup) crate, which you add alongside `iroh`. ```toml theme={null} [dependencies] iroh = "1" iroh-mdns-address-lookup = "0.4" ``` Add the `MdnsAddressLookup` to the endpoint with `Endpoint::builder`. We run it next to the default DNS address lookup, so connections still work when the two endpoints are not on the same local network. ```rust theme={null} use iroh::{Endpoint, endpoint::presets}; use iroh_mdns_address_lookup::MdnsAddressLookup; #[tokio::main] async fn main() -> anyhow::Result<()> { let endpoint = Endpoint::builder(presets::N0) .address_lookup(MdnsAddressLookup::builder()) .bind() .await?; // your code here Ok(()) } ``` For more on how mDNS address lookup works, see the [`iroh-mdns-address-lookup` documentation](https://docs.rs/iroh-mdns-address-lookup). # Dedicated Infrastructure Source: https://docs.iroh.computer/deployment/dedicated-infrastructure By default, iroh will use public shared infrastructure to facilitate connections over address lookup and end-to-end encryption over relays. This infrastructure comprises: 1. [Relays](/concepts/relays) 2. [Address Lookup](/concepts/address-lookup) Relays forward traffic when direct connections are not possible as well as facilitates NAT traversal for direct connections. These servers are managed and maintained by [n0.computer](https://n0.computer), and are shared by a global public network of developers. We recommend using the public relays for development and testing, as they are free to use and require no setup. However, for production systems, we recommend using dedicated relays instead. Sign up for Iroh Services and spin up a managed relay for your project in minutes. Learn how to self-host a relay for your project. ## Using dedicated relays To use a specific set of relays with your iroh endpoint, configure your relay URLs as part of an iroh-services preset: ```rust Rust theme={null} use iroh::{Endpoint, RelayMap, RelayMode, RelayUrl, endpoint::presets}; #[tokio::main] async fn main() -> anyhow::Result<()> { let relay_url1: RelayUrl = "YOUR_RELAY_URL_US".parse()?; let relay_url2: RelayUrl = "YOUR_RELAY_URL_EU".parse()?; let endpoint = Endpoint::builder(presets::N0) .relay_mode(RelayMode::Custom(RelayMap::from_iter([ relay_url1, relay_url2, ]))) .bind() .await?; Ok(()) } ``` ```python Python theme={null} import asyncio import iroh async def main(): relay_mode = iroh.RelayMode.custom_from_urls([ "YOUR_RELAY_URL_US", "YOUR_RELAY_URL_EU", ]) ep = await iroh.Endpoint.bind( iroh.EndpointOptions(preset=iroh.preset_n0(), relay_mode=relay_mode) ) asyncio.run(main()) ``` ```swift Swift theme={null} import IrohLib let relayMode = try RelayMode.customFromUrls(urls: [ "YOUR_RELAY_URL_US", "YOUR_RELAY_URL_EU", ]) let ep = try await Endpoint.bind(options: EndpointOptions( preset: presetN0(), relayMode: relayMode )) ``` ```kotlin Kotlin theme={null} import computer.iroh.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { val relayMode = RelayMode.customFromUrls(listOf( "YOUR_RELAY_URL_US", "YOUR_RELAY_URL_EU", )) val ep = Endpoint.bind( EndpointOptions(preset = presetN0(), relayMode = relayMode), ) ep.shutdown() } ``` Managed relays from Iroh Services **supply authentication by default**, and your endpoints authenticate to them with your project's API key. For that flow, and full deployment steps, see the [managed relay guide](/iroh-services/relays/managed). ## Why use dedicated relays in production? Using dedicated relays can provide several benefits, including improved performance, enhanced security, better uptime guarantees, and greater control over your network infrastructure. By using your own servers, you can optimize connection speeds and reduce latency for your specific use case. ## Recommended setup We recommend setting up at least two relays for redundancy in different regions. For example, you could set up one relay in North America and another in Europe. ## Why this architecture is powerful This approach makes uptime management significantly easier compared to traditional client-server architectures: **Stateless servers, stateful clients**\ Unlike traditional servers that store your application's data and state, relay servers are just connection facilitators. All your business logic and data lives in your clients. This means: * **No database synchronization** - You don't need to worry about keeping multiple server databases in sync or handling data replication * **No state migration** - When a relay goes down, clients simply reconnect to another relay without any data loss or state transfer * **Simple server management** - Relay servers are lightweight and easy to spin up or down. No complex deployment procedures or data migration steps **Automatic failover**\ iroh clients automatically try multiple relays when connecting. If one relay is unavailable, clients seamlessly fall back to another relay in your list without application-level retry logic. Your peers will find each other as long as at least one relay is reachable. **Multi-cloud resilience**\ For even better guarantees, you can distribute relays across multiple cloud providers. If one provider experiences an outage, your application keeps running on relays hosted elsewhere. Since relays don't store state, you can freely mix providers without worrying about cross-cloud data consistency. **Cost-effective scaling**\ Adding capacity means spinning up more lightweight relay instances, not provisioning databases or managing complex stateful server infrastructure. You can easily scale up for peak usage and scale down during quiet periods. This architecture inverts the traditional model: instead of treating servers as precious stateful resources and clients as disposable, relay-based architectures treat relays as disposable connection facilitators while clients own the application state and logic. # Custom Metrics Source: https://docs.iroh.computer/deployment/metrics iroh provides support for collecting metrics about your application's performance and behavior using the `iroh-metrics` crate. This crate offers a simple and flexible way to define, collect, and report various metrics. ## Example ```rust theme={null} use iroh_metrics::{Counter, MetricsGroup}; /// Define your metrics: #[derive(Debug, Default, MetricsGroup)] #[metrics(name = "ping")] pub struct Metrics { /// count of valid ping messages sent pub pings_sent: Counter, /// count of valid ping messages received pub pings_recv: Counter, } // elsewhere... let metrics = Metrics::default(); // increment count of pings we've received metrics.pings_recv.inc(); ``` ## Exporting metrics * *Prometheus*: The `iroh-metrics` crate supports exporting metrics to Prometheus. * *iroh services*: [Contact us](mailto:support@iroh.computer) if you are interested in custom metrics hosting without prometheus. # Security & Privacy Source: https://docs.iroh.computer/deployment/security-privacy iroh is designed with security and privacy as core principles. This document outlines the key security and privacy features of iroh, as well as best practices for deploying and using iroh in a secure manner. ## Data Privacy with End-to-End Encryption All data transmitted between iroh endpoints is protected with end-to-end encryption. This means that data is encrypted on the sender's device and can only be decrypted by the intended recipient. Even relay servers that facilitate connections between endpoints cannot read the data being transmitted. End-to-end encryption is achieved using modern cryptographic algorithms and protocols, ensuring that data remains confidential and secure during transit. By default, iroh uses Ed25519 keys for endpoint identities and encryption. If you require different cryptographic algorithms, you can configure iroh to use them during endpoint creation. [Contact us for assistance](https://n0.computer/) with custom cryptographic configurations. ## Public Relays iroh is by default configured to use shared public infrastructure that is operated by [n0.computer](https://n0.computer) . Because traffic is end-to-end encrypted, relays are not able to read any of the traffic that they forward or help connect. However, the relays are able to see metadata about connections, such as source and destination IP addresses, connection times, and the amount of relayed data. We recommend that you do not use the public relays for sensitive or confidential data. If you need more control over your relay infrastructure, we recommend that you use [dedicated infrastructure](/deployment/dedicated-infrastructure) for production systems. We monitor the public relays for abuse and malicious activity. If we detect abuse, we reserve the right to block offending IP addresses or users from accessing the public relays. ## Protecting leakage of IP Addresses Any direct connection between two devices stands a high chance of being the fastest connection, but always requires IP addresses to be disclosed. As with the majority of the deployed internet stack today, when two endpoints establish a direct connection, they expose and exchange their IP addresses to each other. IP address privacy considerations are primarily relevant for consumer or peer-to-peer applications where strangers or untrusted parties connect directly over the internet. In these scenarios, developers cannot guarantee anonymity or trust between endpoints, and exposing IP addresses can lead to privacy risks such as location tracking or targeted attacks. To mitigate these risks, we recommend the following strategies to protect IP address privacy when using iroh, depending on your specific use case and threat model: ### Basic Protection: Use dedicated infrastructure If you own the infrastructure, run your own DNS server, and manage the devices connecting to your network, you have control over the network topology and can implement appropriate security measures. We do not recommend using public relays for production systems, as this is shared public infrastructure that has no guarantees around privacy or uptime. We recommend reviewing our [dedicated infrastructure](/deployment/dedicated-infrastructure) guidance to set up relay and DNS infrastructure that fits your needs. ### More protection: Use Tor or Similar Onion Routing Services like Tor provide onion routing, which encrypts packet metadata for each relay in the route. This offers strong IP privacy guarantees through multi-hop routing with layered encryption. ### Upcoming #### Relay-Only Mode Upcoming releases of iroh will support disabling hole-punching to send all traffic exclusively through relays. This provides IP privacy with some important caveats: * **Single-hop routing**: Traffic passes through one relay, not multiple hops * **Trust required**: The relay operator can technically see which endpoints are communicating and their IP addresses * **Data remains encrypted**: The relay cannot read the actual content due to end-to-end encryption This mode is suitable when you trust your relay infrastructure but still want to avoid direct IP exposure between endpoints. #### Multi-Hop Relay Routing We've explored the possibility of adding multi-hop relay routing to iroh. While technically feasible, this feature is not currently on the near-term roadmap. It's important to note that even with multi-hop relay routing, this would not be equivalent to onion routing. True onion routing requires encrypting packet metadata for each relay in the route, which would require significant protocol changes. If these features are interesting to you, please [contact us](https://n0.computer/) to discuss your specific requirements. # Examples Source: https://docs.iroh.computer/examples ## Examples Example code repositories demonstrating iroh usage. Example CLI tool for direct file transfers between devices Example voice and video calling application Example CLI for piping data between machines through NATs Example presence demo with iOS, macOS, and Android apps plus Python and Node console readers, built on the iroh-ffi bindings A curated list of iroh projects, libraries, and resources from across the community. ## Applications Full applications built with iroh. Hubris is an offline-first, peer-to-peer note-taking app for visual thinkers. No folders, no hierarchies, just a canvas with local AI built in. Collaborate freely with no limits on file size or sync. AltSendme is a desktop app that connects your devices directly & transfer files, without any accounts or configuration. A multiplayer extension for Godot based on iroh. Have a lan party with iroh (iroh-lan = hamachi - account - install) Raspberry Pi and Pi Pico GPIO Remote control A mobile code editor for iOS and Android that tunnels to your desktop over an encrypted iroh P2P connection, letting you read code, view changes, and run AI agents from your phone. # Build a chat app Source: https://docs.iroh.computer/examples/chat Build a peer-to-peer chat application using iroh gossip