Skip to content

feat: serve remote network diagnostics from the FFI#242

Open
okdistribute wants to merge 3 commits into
mainfrom
rae/feat-remote-net-diagnostics
Open

feat: serve remote network diagnostics from the FFI#242
okdistribute wants to merge 3 commits into
mainfrom
rae/feat-remote-net-diagnostics

Conversation

@okdistribute

Copy link
Copy Markdown
Contributor

Closes #241.

The dashboard's Run Diagnostics button dials back into an endpoint over the n0/n0des-client-host/1 ALPN, authorized by a capability token the endpoint grants to the platform. Both halves were Rust-only, so the button stayed disabled for every FFI endpoint. This exposes them with one option plus one mount:

# grant: one flag on the client the app already creates
svc = await ServicesClient.create(ep, ServicesOptions(
    api_secret=KEY,
    remote_diagnostics=True,
))

# serve: mount at bind (router apps)...
ep = await Endpoint.bind(EndpointOptions(
    protocols={client_host_alpn(): services_client_host()},
))
# ...or one match arm in a manual accept loop
if alpn == client_host_alpn():
    asyncio.create_task(client_host.handle_connection(conn))

Design notes

  • remote_diagnostics grants NetDiagnosticsCap::GetAny to the platform endpoint named in the API secret, in a background task owned by the client that retries with backoff. The feature targets endpoints with bad networks, which are exactly the ones likely to start offline; a one-shot grant would silently strand them. Requires an api-secret credential (the ssh-key path carries no platform endpoint to grant to).
  • Serving has two entry points because the FFI's internal router only exists when protocols are registered at bind, and iroh::protocol::Router accepts no post-spawn registrations: services_client_host() (a built-in ProtocolCreator) for bind-time mounting, and ClientHost.handle_connection() for manual accept loops. iroh-js has no router, so it gets only the latter.
  • handle_connection resolves when the remote closes the connection; the docs in both bindings warn against awaiting it inline in the accept loop.
  • One uniffi change covers Swift, Kotlin, and Python; iroh-js mirrors it in napi with regenerated index.js/index.d.ts (the regen also picks up the 1.0.0-rc.1 version-check strings; the committed copy predated the version bump).

Tests

  • Rust: a full dial-back round trip against an FFI endpoint (auth with a server-issued token, real diagnostics report over irpc, mirroring iroh-services' own client_host tests; needs the new irpc/irpc-iroh dev-deps). Note it is slow by construction (~20s of offline relay/net-report timeouts; no FFI knob to shorten them). Plus a cheap accept-loop wiring test and option-validation tests.
  • JS/Python/Kotlin: validation tests mirrored per the repo convention; the ssh-key rejection tests assert on the guard's message so they cannot pass vacuously.
  • Not verifiable here: the real dashboard end-to-end. Plan is to verify with the hello-iroh-ffi demo apps once this ships in a release.

Upstream observations (out of scope, filing separately if wanted)

  • verify_rcan in iroh-services checks issuer/audience/origin but never token expiry, so dial-back grants effectively outlive DEFAULT_CAP_EXPIRY.
  • ClientHost::handle_connection holds the connection until the remote closes; closing server-side after the report would free handlers sooner.
  • The FFI's ssh_key_pem credential path cannot set a remote, so ClientBuilder::build() always fails with MissingRemote; pre-existing and untouched here.

🤖 Generated with Claude Code

okdistribute and others added 2 commits June 11, 2026 22:42
The dashboard's Run Diagnostics button dials back into an endpoint, which
needs a capability grant and a handler for the n0/n0des-client-host/1
ALPN. Neither was reachable through the bindings, so the button stayed
disabled for every FFI endpoint (#241).

ServicesOptions gains remote_diagnostics: when set, create() grants
NetDiagnosticsCap::GetAny to the platform endpoint named in the API
secret. The grant runs in a background task owned by the client and
retries with backoff, since the feature targets endpoints with bad
networks, which are exactly the ones likely to start offline. Serving
the dial-back has two entry points because the internal router only
exists when protocols are registered at bind: services_client_host()
returns a ProtocolCreator to mount under client_host_alpn() at bind
time, and the ClientHost object exposes handle_connection() for apps
that drive their own accept loop.

The round-trip test mirrors iroh-services' own client_host tests over
irpc, hence the new dev-dependencies. Python and Kotlin get the cheap
validation tests; they exercise the uniffi-generated API names in CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same surface as the uniffi core: a remoteDiagnostics option on
ServicesClient.create that grants the net-diagnostics capability in a
retrying background task, plus ClientHost.handleConnection and
clientHostAlpn() for serving the dial-back from a manual accept loop.
iroh-js has no router, so there is no bind-time mount here; the
handleConnection docs warn that the promise resolves only when the
remote closes, so it must not be awaited inline in the accept loop.

The regenerated index.js also picks up the 1.0.0-rc.1 version-check
strings; the committed copy was generated before the version bump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@n0bot n0bot Bot added this to iroh Jun 12, 2026
@github-project-automation github-project-automation Bot moved this to 🚑 Needs Triage in iroh Jun 12, 2026
IrohError is a uniffi object error, so the Rust Display text does not
land in the foreign exception's native message: Kotlin's Exception
.message property is null and Python's str(e) is bare. Both carry the
text in the exported message() method instead. The ssh-key guard tests
asserted on the native message and failed in CI; the Rust and JS
variants were unaffected (napi maps the message through).

Verified locally against freshly generated bindings: 20/20 python,
20/20 kotlin JVM.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 🚑 Needs Triage

Development

Successfully merging this pull request may close these issues.

Expose remote network diagnostics (ClientHost dial-back + capability grant)

1 participant