Shared design notes and operational guides for the iroh-based programs in this org. These three ship different products but share one transport foundation — iroh endpoints, relays, address lookup, and NAT traversal — so that layer is documented once, here, instead of drifting across three repos.
| Repo | What it is |
|---|---|
| tunnel-rs | TCP/UDP port forwarding over iroh. Reference program for relay-only setups |
| ezvpn | Full VPN (TUN device, IP routing) over iroh |
| flextunnel | SOCKS5/HTTP proxy and port forwarding over iroh |
Since 2026-09 this design is also code, once:
flexaccess-iroh, a Rust
crate all three programs depend on by git tag. It carries RelayConfig (at
least two custom relays) and the per-relay startup probe, the common endpoint
builder (and relay-only mode) that binds without the relays that fail the
probe, the server-side in-place home-relay failover that restores them, and
the endpoint-bound public-key auth transcript over
flexaccess-keys. A fix to
any of that lands in the crate and reaches every program on its next tag bump,
instead of being ported by hand three times. The crate also carries the e2e
suites for this layer (e2e/, run in its CI), so a product's own e2e suite
only has to cover what the product adds.
What stays in each program is what makes it that program: ALPNs, handshake
formats, QUIC transport tuning, identity and key files (the crate takes
values, never paths), connection-path status UIs, and the serve loops that
run the failover alongside their accept loops. ezvpn, which builds on a fork of iroh, redirects the
crate's iroh to that fork with [patch.crates-io] so the graph holds one
iroh.
- relays-and-address-lookup.md — the core shared design. Default vs custom relays, how that single choice also decides whether n0 internet discovery is on, relay hints, the shared relay auth token, the strict per-relay startup probe, and relay-only mode. Start here.
- relay-failover.md — how a custom-relay server stays reachable when its home relay stops working: what iroh 1.1.0 recovers on its own, the in-place failover for the case it does not (take the wedged relay out of the relay map, restore it once connectable), how a process that starts during such an outage still comes online (bind without the relay the probe cannot connect), why that needs at least two custom relays and no address lookup service, and the crate's e2e suite that proves it, behind a relay-only and a live direct connection alike.
- nat-traversal-and-transport.md — what the
three programs get from
iroh::Endpointand never implement themselves: connection establishment, hole punching and relay fallback, NAT traversal by NAT type (including symmetric NAT, and why Kubernetes networking depends on the CNI rather than on Kubernetes itself), the QUIC/TLS 1.3 encryption stack, and performance characteristics. - self-hosting.md — running your own iroh relay: local dev, production with TLS, the single-port Cloudflare Tunnel setup, relay access tokens, and how to verify a relay end to end.
- relay-discovery-findings.md — the analysis (against iroh 1.0.2 internals) behind making internet discovery non-configurable and tying it to the relay mode.
- iroh-relay-connection-trace.md — what
actually happens on
endpoint.online(), the relay WebSocket upgrade, and how to troubleshoot a relay by hand withcurl.
tunnel-rs is the reference implementation for relay-only deployments: it is the
only one of the three exposing relay-only as a first-class user-facing mode
(--relay-only), it carries the sequential per-relay failover dial path, and it
is the first consumer of the shared relay failover. The fully offline two-relay
suites that exercise the relay layer itself live with the crate
(e2e/ in flexaccess-iroh); tunnel-rs's run_e2e.sh --relay-only is the
check that a real tunnel runs over a relay. When bringing up a self-hosted
relay, validate it with tunnel-rs first — the relay it proves out serves all
three programs. See self-hosting.md.
In scope: anything about the iroh transport layer that is (or should be) the same in all three — relay configuration and validation, address lookup and discovery, NAT traversal behavior, relay operations.
Out of scope: each program's own architecture, protocol, authentication, and
product-specific configuration (tunnel sources and targets, VPN addressing,
proxy listeners, and every other setting that is not the iroh transport). Those
stay in their own repos. The app-independent Ed25519 key format and tooling used
by those product-specific authentication protocols lives separately in
flexaccess-keys.
The three repos link here rather than duplicating this material, and they
depend on flexaccess-iroh
rather than carrying their own copies of the code. When the shared
relay/discovery behavior changes, change it in the crate, tag a release, bump
the tag in each program, and update this repo in the same change. Note any
deliberate per-repo divergence in the "Where this lives in each repo" table in
relays-and-address-lookup.md.