Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ jobs:

# The end-to-end suites that need no public infrastructure: the auth
# transcript and connectivity over two local iroh-relay instances, and the
# relay failover scenarios (which take ~6 minutes by design: a 60 s outage
# window and a 90 s restore probe). See e2e/README.md.
# relay failover scenarios (which take ~8 minutes by design: a 60 s outage
# window and a 90 s restore probe relay-only, then another 60 s window with
# direct paths allowed). See e2e/README.md.
e2e:
name: e2e (local relays)
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
- strict no backward compatibility
- strict no backward compatibility or legacy code paths
- run cargo clippy and test after rust code changes
- no cargo fmt
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "flexaccess-iroh"
version = "0.0.7"
version = "0.0.8"
edition = "2024"
description = "Shared iroh transport layer for FlexAccess applications: relay configuration and probing, endpoint building, in-place home-relay failover, and the endpoint-bound public-key auth transcript"
repository = "https://github.com/flexaccessdev/flexaccess-iroh"
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ hand into every repo.

| Module | Contents |
|---|---|
| `relay` | `RelayConfig` (default vs custom relays, which also decides whether n0 internet discovery is on; custom relays must number at least two distinct URLs; duplicates are collapsed before the count), the shared relay auth token, the per-relay startup probe (fails only when no relay is reachable) |
| `endpoint` | the common endpoint builder and `create_endpoint` |
| `relay_failover` | the server-side home-relay failover: after 60 s without a connected home relay, take the wedged relay out of the relay map so the forced net report homes the endpoint on another configured relay, in place; put it back once a probe shows it connectable |
| `relay` | `RelayConfig` (default vs custom relays, which also decides whether n0 internet discovery is on; custom relays must number at least two distinct URLs; duplicates are collapsed before the count), the shared relay auth token, the per-relay startup probe (fails only when no relay is reachable, and reports which relays did not come online) |
| `endpoint` | the common endpoint builder and `create_endpoint`, which binds **without** the relays that failed the startup probe (a relay that answers probes but cannot be connected would otherwise be preferred as home relay and keep the endpoint from ever coming online, so every client restarting during such an outage would fail to start) and hands them back for the failover to restore |
| `relay_failover` | the server-side home-relay failover: after 60 s without a connected home relay, take the wedged relay out of the relay map so the forced net report homes the endpoint on another configured relay, in place; put it back once a probe shows it connectable (likewise for relays left out at startup) |
| `auth` | the endpoint-bound public-key auth transcript over the [flexaccess-keys] format; each application passes its own domain-separation context |

Deliberately **not** in it: ALPNs, handshake wire formats, QUIC transport
Expand All @@ -35,9 +35,9 @@ takes the resulting `iroh::SecretKey` / `flexaccess_keys` values.

```toml
[dependencies]
flexaccess-iroh = { git = "https://github.com/flexaccessdev/flexaccess-iroh", tag = "v0.0.7" }
flexaccess-iroh = { git = "https://github.com/flexaccessdev/flexaccess-iroh", tag = "v0.0.8" }
# or, with mDNS local-network discovery on every endpoint (compiled out on iOS):
flexaccess-iroh = { git = "...", tag = "v0.0.7", features = ["mdns"] }
flexaccess-iroh = { git = "...", tag = "v0.0.8", features = ["mdns"] }
```

The `flexaccess_keys` crate is re-exported so a consumer signs and verifies
Expand Down
14 changes: 10 additions & 4 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ through real relays — deliberately not an application. Built with

| Subcommand | Role |
|---|---|
| `server` | binds an endpoint with the shared builder (`--relay-url`, `--relay-only`, an optional `E2E_SERVER_SECRET` identity), runs `relay_failover::fail_over_home_relay` beside its accept loop, and answers one request per connection: the endpoint-bound auth transcript from `flexaccess_iroh::auth` against `--authorized-keys`, then an echo of the client's message. Logs `EndpointId: …` and `Waiting for clients to connect` when ready. |
| `client` | builds an ephemeral endpoint the same way, dials `--server-id` through the configured relays (every custom relay as a hint), proves `--private-key-file`, checks the echo. Exits `0` on `Echo OK`, `3` when the server rejects the key, `1` otherwise. Logs the path it connected over (`via Relay <url>` / `via Direct <addr>`). |
| `server` | binds an endpoint with the shared builder (`--relay-url`, `--relay-only`, an optional `E2E_SERVER_SECRET` identity), runs `relay_failover::fail_over_home_relay` beside its accept loop, and answers each connection: the endpoint-bound auth transcript from `flexaccess_iroh::auth` against `--authorized-keys`, then an echo of the client's message, then an echo of every further stream the client opens. Logs `EndpointId: …` and `Waiting for clients to connect` when ready. |
| `client` | builds an ephemeral endpoint the same way, dials `--server-id` through the configured relays (every custom relay as a hint), proves `--private-key-file`, checks the echo. Exits `0` on `Echo OK`, `3` when the server rejects the key, `1` otherwise. Logs the selected path it connected over (`via Relay <url>` / `via Direct <addr>`) followed by every path of the connection with its RTT and whether it is selected (`[paths: …]`), the same view flextunnel's connection-path status shows. With `--hold-secs N` it then keeps the connection open, echoing every `--echo-interval-secs` on a fresh stream and logging `Echo #n OK via <path> [paths: …]`, so a script can watch a live connection's paths through a relay outage. |
| `keygen` | writes a client key in the shared flexaccess-keys format (mode 0600) and its `authorized_keys` entry |
| `fake-relay` | a relay that answers the net-report probe (`GET /ping`) but refuses relay connections — the outage shape the failover exists for |
| `delay-proxy` | a TCP proxy adding latency to each new connection, so the relay behind it always measures slower |
Expand All @@ -24,12 +24,18 @@ through real relays — deliberately not an application. Built with
Everything a product adds — its ALPN, QUIC tuning, config files, forwarding —
is left out, so a failure here is a failure of this crate or of iroh.

The harness is built **without** the crate's `mdns` feature, and refuses to
run with direct paths allowed if it was built with it: every process here is
on one host, so mDNS would hand a client the server's address directly and
the relays would never be exercised. Without it, the handshake goes through
the relay and a direct path is learned through it, exactly as off-LAN.

## Suites

| Script | What it checks | Needs |
|---|---|---|
| `run_e2e.sh` | **Auth + connectivity.** An unlisted key is rejected (exit 3, explicit rejection, server logs why); an authorized client authenticates and gets its echo; a second client on the *same* key uses a *distinct* ephemeral iroh identity; under `--relay-only`, every connection ran through a relay. | internet for the default relays; or `--local-relays` / `--relay-url` ×2 for a fully offline run |
| `run_relay_failover.sh` | **Relay failover**, fully offline against two local `iroh-relay --dev` instances, relay-only. Phase A: relays down *before* startup (per-relay probe: startup fails only when none is reachable; a single custom relay is rejected). Phase B: a relay dies *after* startup (iroh re-homes on its own; both down fails new clients; both back recovers). Phase C: the home relay answers probes but refuses connections (the in-place failover removes it after 60 s, the server homes on the other relay without restarting, and the restore probe puts it back once it is connectable again). | `iroh-relay` |
| `run_relay_failover.sh` | **Relay failover**, fully offline against two local `iroh-relay --dev` instances (relay-only until phase D). Phase A: relays down *before* startup (per-relay probe: startup fails only when none is reachable; a single custom relay is rejected). Phase B: a relay dies *after* startup (iroh re-homes on its own; both down fails new clients; both back recovers). Phase C: the home relay answers probes but refuses connections (the in-place failover removes it after 60 s, the server homes on the other relay without restarting, and the restore probe puts it back once it is connectable again). Phase D: the same wedge with direct paths allowed and a client holding a live connection whose paths are relay1 + direct; the failover still moves the server onto the other relay in the background, the held connection keeps echoing over its direct path (ending up direct-only: iroh drops the wedged relay path and does not add a peer's new home relay to an established connection), a new relay-only client's connection path shows the new home relay, and a client that restarts with the same two-relay configuration during the outage comes online, reaches the server through the new home relay, and goes direct again. | `iroh-relay` |

```sh
cargo install iroh-relay --features server # one-time
Expand All @@ -39,7 +45,7 @@ cargo install iroh-relay --features server # one-time
./e2e/run_e2e.sh --local-relays --relay-only # ... relay paths only
./e2e/run_e2e.sh --relay-url URL --relay-url URL [--relay-only] # your own relays

./e2e/run_relay_failover.sh # ~6 minutes: 60 s failover window + 90 s restore probe
./e2e/run_relay_failover.sh # ~8 minutes: two 60 s failover windows + a 90 s restore probe
```

Each suite prints a `PASS`/`FAIL` line per scenario and exits non-zero on
Expand Down
Loading
Loading