Skip to content

fix(android): auto-failover relay when preferred Secure Link is dead - #666

Open
ophirhan wants to merge 1 commit into
Codename-11:devfrom
ophirhan:fix/route-demote-on-active-dead
Open

ophirhan wants to merge 1 commit into
Codename-11:devfrom
ophirhan:fix/route-demote-on-active-dead

Conversation

@ophirhan

@ophirhan ophirhan commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When leaving home Wi‑Fi, the Android Relay power-tools WebSocket could stay stuck on a preferred Secure Link route that was no longer reachable. Chat already recovered via Tailscale; Relay did not, because sticky Prefer + hysteresis kept publishing the dead higher-priority endpoint (and VPN/cell onAvailable often cancelled the network-loss path before a sustained-loss demotion). Separately, plain ws:// to a paired Tailscale/LAN candidate was blocked unless insecure mode was enabled, even though ConnectionSecurity already treats those hosts as overlay-encrypted.

This change demotes a preferred active route that stays dead past the existing network-loss grace window and allows trusted-overlay plain WebSocket URLs without the insecure toggle, so Relay can fall through automatically the way chat already does.

Changes

  • Schedule an active-route demotion job when re-resolve prefers a lower-priority healthy winner but hysteresis still holds a higher-priority dead activeEndpoint; after the grace window, mark sustained loss and republish the healthier route. Cancel the job on same/higher-priority publish, unregister, or disconnect.
  • Allow plain ws:// without insecure mode for loopback, private LAN, Tailscale CGNAT (100.64/10), and MagicDNS (.ts.net); keep blocking public cleartext WebSocket URLs.
  • Soften the connection insecure flag when the socket is trusted-overlay plain rather than public cleartext (aligns ConnectionManager with ConnectionSecurity’s overlay model).
  • Unit coverage: leave-home demotion in ConnectionManagerRouteTest; trusted-overlay vs public ws:// in ConnectionManagerUrlGuardTest.
  • CHANGELOG Unreleased notes for the user-visible failover and overlay plain-socket behavior.

Transport policy note (wss:// vs trusted-overlay plain)

AGENTS.md’s Android “wss:// only” line is the default product posture for public/cleartext internet hops. It is not a claim that overlay-encrypted plain schemes are forbidden:

  • ConnectionSecurity.kt already documents that encrypted includes overlay transports (Tailscale/WireGuard / secure proxy): a ws:// link over a tailnet is WireGuard-encrypted end-to-end and must not be labelled insecure; only plain scheme with no overlay warns.
  • Pairing already stores LAN/Tailscale Relay candidates as plain ws:// on private/CGNAT hosts in normal Secure Link + tailnet setups; chat HTTP surfaces follow the same overlay model.
  • This PR does not open public Internet cleartext: ws://relay.example.com / public unicast still require the existing insecure-mode path.
  • The change only stops requiring the Settings insecure toggle for hosts that ConnectionSecurity already treats as overlay-encrypted, so automatic leave-home failover can attach without a manual policy flip.

Maintainer call remains welcome if product wants Secure Link wss:// exclusively even on tailnet (would need paired wss candidates or a different attach path).

Verification

  • ./gradlew :app:testSideloadDebugUnitTest --tests com.hermesandroid.relay.network.relay.ConnectionManagerRouteTest --tests com.hermesandroid.relay.network.relay.ConnectionManagerUrlGuardTest — BUILD SUCCESSFUL
  • GitHub Actions on this PR head: Android Lint / Build / Test — pass; Required checks — pass.
  • On-device dogfood:
    • Device: OnePlus CPH2653
    • OS: Android 16 (API 36)
    • App: Hermes Relay sideload 1.18.1 dogfood build including this change
    • Prefer Secure Link kept; when preferred Secure Link was unreachable off home network, automatic fallthrough attached Relay on the paired Tailscale candidate (clients ≥ 1) without enabling insecure mode or manually flipping Prefer.
    • At-home re-check: Gateway footer shows Encrypted · TLS (not insecure amber) with Relay extensions Ready while Prefer Secure Link is active.
    • Public cleartext ws:// remains blocked without insecure mode (unit coverage).

Screenshots

Hosts redacted. Status proof (Encrypted chip / not insecure amber) and route overview chrome:

Device proof redacted

Leave-home demotion itself is timing/network behavior (covered by ConnectionManagerRouteTest); the insecure-warning change is primarily the absence of an amber insecure state for trusted-overlay plain sockets (gate + badge alignment), not a new layout.

Compatibility / risk

  • Standard Hermes / unmodified upstream hosts: client-only Android change. No plugin/server API changes.
  • Security: public Internet plain ws:// still requires insecure mode. Overlay plain is limited to loopback/private/Tailscale hosts aligned with existing ConnectionSecurity overlay model.
  • Prefer / Natural order preferences are preserved; demotion only applies when the preferred active route stays unreachable past the grace window.
  • Rollout/rollback: client-only; revert restores prior sticky hysteresis and stricter plain-ws:// gate.
  • No migrations or stored-schema changes.

Lineage / contributor credit

  • Source PR(s): N/A
  • Attribution preserved by: N/A

Checklist

  • Target branch is dev, unless this is a dev → main release PR or a focused production-tag hotfix PR to main
  • Scope is focused and related issues/PRs are linked
  • Android changes: lint and focused tests ran, or rationale is listed above
  • Translation changes: locale validation/review ran, or N/A is listed above
  • Server/plugin changes: focused tests ran, or N/A/rationale is listed above
  • Desktop changes: build/tests ran, or N/A/rationale is listed above
  • Docs/site changes: build or link/route checks ran, or N/A/rationale is listed above
  • UI changes were tested on a relevant device/emulator/desktop surface, or the missing proof is stated above
  • Commit messages follow Conventional Commits
  • CHANGELOG.md is updated for user-visible changes, or N/A is listed above
  • Public writing hygiene checked: no secrets, private infrastructure, personal names, or AI/process narration
  • Salvaged/replacement work links source PRs and preserves contributor authorship, or N/A is listed above

@ophirhan
ophirhan force-pushed the fix/route-demote-on-active-dead branch from ad6597b to 596e7f5 Compare October 3, 2026 11:26
@hermes-relay-triage hermes-relay-triage Bot added area:android Kotlin app needs-maintainer-review Automated triage exhausted; needs a human labels Oct 3, 2026
@hermes-relay-triage

Copy link
Copy Markdown

🤖 Hermes-Relay automated PR intake

Thanks for tackling Android Relay failover! The PR targets dev and includes all template sections, a focused test command with a reported successful result, an explicit lint omission rationale, on-device recovery notes, compatibility/rollback details, and lineage marked N/A.

Please add the device/Android version used for dogfooding and a screenshot or short recording showing the changed insecure-warning behavior. The stated plain-ws:// exception also needs maintainer confirmation against the repository's documented wss://-only policy. Android CI is still pending; intake does not verify implementation correctness.

A maintainer will review the transport-policy exception, device evidence, and implementation once the checks are available.

@ophirhan

ophirhan commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Intake follow-up

Addressed the automated intake notes:

Device / OS

  • OnePlus CPH2653 · Android 16 (API 36)
  • Sideload dogfood build 1.18.1 including this PR tip

Screenshot

Redacted device proof (Encrypted status chip / not insecure amber; hosts scrubbed):

proof

wss://-only policy

Default public posture remains TLS/wss://. This PR does not allow public cleartext WebSocket URLs.

It aligns the Relay socket gate with existing ConnectionSecurity overlay semantics: Tailscale/LAN plain is overlay-encrypted and must not be labelled or gated as “insecure,” while public ws:// still requires the insecure-mode path. Full write-up is in the updated PR body (Transport policy note).

CI

Android Lint / Build / Test and Required checks are green on the current head (intake was posted while checks were still pending).

@ophirhan

ophirhan commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Leave-home / Tailscale device proof (follow-up)

Earlier screenshot was taken at home on Secure Link — correct preferred-route behavior, but not the demotion claim.

This capture (Wi‑Fi off):

  • Device: OnePlus CPH2653 · Android 16 (API 36) · sideload 1.18.1
  • Routes: Current route = Tailscale (HTTP) · Reachable · Gateway Active / online
  • Chrome badge: Encrypted (Tailscale)
  • Scenario: home Wi‑Fi disabled → preferred Secure Link unreachable → auto path uses Tailscale (no manual Prefer)

Redacted screenshot (IPs blacked out): committed only on side branch chore/pr-evidence-666 as docs/pr-evidence/pr666-tailscale-leave-home-redacted.png — not in this PR’s diff.

Related dogfood hardening (separate PR): #668 — idle/retarget watchdog no longer session.interrupts live model waits (was mislabeled user_stop in host logs).

ophirhan added a commit to ophirhan/hermes-relay that referenced this pull request Oct 3, 2026
@ophirhan
ophirhan force-pushed the fix/route-demote-on-active-dead branch from 596e7f5 to 96844fb Compare October 10, 2026 07:50
@ophirhan
ophirhan force-pushed the fix/route-demote-on-active-dead branch from 96844fb to 0ae661f Compare October 18, 2026 04:39
@ophirhan

Copy link
Copy Markdown
Contributor Author

Rebased onto current dev (force-with-lease). Hygiene-scan clean.

Leave-home demotion proof was already on the PR earlier; head is now current with dev. Android checks re-running.

Ready for review when CI is green.

@ophirhan
ophirhan force-pushed the fix/route-demote-on-active-dead branch from 0ae661f to 272af37 Compare October 19, 2026 16:51
Leave-home stuck the power-tools WS on a sticky Prefer/hysteresis path
while chat already used Tailscale. Demote a dead higher-priority active
route after a short grace (even without Wi-Fi onLost), and allow plain
ws:// to Tailscale/LAN hosts without the insecure-mode toggle so the
paired tailnet candidate can attach automatically.
@ophirhan
ophirhan force-pushed the fix/route-demote-on-active-dead branch from 272af37 to bd2787b Compare October 21, 2026 12:58
@ophirhan

Copy link
Copy Markdown
Contributor Author

@Codename-11 gentle review request when you have a moment — rebased onto current dev, CI addressed on our side. Happy to iterate on anything you want changed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:android Kotlin app needs-maintainer-review Automated triage exhausted; needs a human

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant