Skip to content

feat(tunnel): add tunnel up/down IPC endpoints [RD-209] - #1507

Merged
racerxdl merged 2 commits into
mainfrom
teske/rd-tunnel-up-down
Jun 3, 2026
Merged

racerxdl merged 2 commits into
mainfrom
teske/rd-tunnel-up-down

Conversation

@racerxdl

@racerxdl racerxdl commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Separates the tunnel netstack lifecycle from authentication. Until now the tunnel came up only on login and tore down only on logout, conflating 'who am I' with 'is the netstack running'.

Adds two IPC endpoints that drive Manager.BringUp/TearDown without touching the token:

Endpoint Behavior
POST /v1/tunnel/up Bring the netstack online using the persisted token. Synchronous, idempotent (already_up). Returns 409 not_logged_in when logged out (no token to dial with).
POST /v1/tunnel/down Tear the netstack down, keep the token. Idempotent (already_down). User stays logged in.

This lets a user pause/resume the tunnel without re-authenticating.

Changes

  • ipc.Service: Up/Down methods + TunnelUpResponse/TunnelDownResponse.
  • ipc.Server: handlers, routes, and a new ErrNotLoggedIn sentinel that maps to 409 (distinct from 401 control-token rejection).
  • ipc.Client: Up/Down round-trip methods.
  • daemonService: Up reuses BringUp (folds ErrAlreadyUp into already_up); Down reuses TearDown; neither touches the token or config file.
  • openapi.yaml: both endpoints + the not_logged_in code.

Testing

  • Handler-level: 200 success, already_up/already_down, 409 not_logged_in.
  • Service-level: logged-out → ErrNotLoggedIn, idle Down no-op, Down keeps the token (lifecycle/auth separation).
  • go build ./tunnel/..., go vet ./tunnel/..., go test ./tunnel/... all green.

Consumer

Paired with hoophq/hsh PR (tunnel up/down CLI + unified login). The CLI degrades gracefully against an older daemon (501).

Automated by MisterMal

Separate tunnel lifecycle from authentication. Until now the tunnel
came up only on login and tore down only on logout, conflating 'who am
I' with 'is the netstack running'. This adds two endpoints that drive
Manager.BringUp/TearDown without touching the token:

  POST /v1/tunnel/up    bring the netstack online using the persisted
                        token. Synchronous, idempotent (already_up),
                        409 when logged out (no token to dial with).
  POST /v1/tunnel/down  tear the netstack down, keep the token.
                        Idempotent (already_down). User stays logged in.

This lets a user pause/resume the tunnel without re-authenticating.

Changes:
- ipc.Service: add Up/Down methods with TunnelUp/DownResponse types.
- ipc.Server: handlers + routes; new ErrNotLoggedIn sentinel mapping to
  409 (distinct from 401 control-token rejection).
- ipc.Client: Up/Down round-trip methods.
- daemonService: Up reuses BringUp (folds ErrAlreadyUp into already_up);
  Down reuses TearDown; both leave the token and config untouched.
- openapi.yaml: document both endpoints + the not_logged_in code.
- tests: handler-level (200/409/idempotent) and service-level
  (logged-out -> ErrNotLoggedIn, idle Down no-op, Down keeps token).

 🤖 Generated with Mister Maluco

Co-Authored-By: MisterMal <teskeslab@lucasteske.dev>
@racerxdl racerxdl added the minor Bumps the minor version on release (new features) label Jun 3, 2026
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Migration Safety Analysis

No database migrations were changed in this PR. Safe to deploy to sandbox.

@sandromello

Copy link
Copy Markdown
Contributor

✅ Build Completed with Success, Version=1507.0.0-g977c18d

The daemon fetched the connection list once at bring-up and never
again, so connections created or deleted on the gateway never showed
up until a full logout/login. This adds automatic + manual refresh.

Concurrency-safe registry (tunnel/tunnelmgr/registry.go):
  Replaces the bare subTypeByName map (shared by reference into the
  netstack accept/handler closures AND Snapshot — an unsynchronized
  data race waiting to happen) with connRegistry, an RWMutex-guarded
  name -> {subtype, active} store. The accept path reads it via
  subTypeOf(); a refresh writes it via reconcile(). Snapshot now
  carries a value-copied []ConnInfo of active connections instead of
  the live map, removing the shared-map hazard end to end.

Refresh mechanics (manager.go):
  - loadConnections() factors out fetch + allocate + reconcile, shared
    by buildTunnel (initial) and the new Manager.Refresh (re-load).
  - Refresh re-fetches and reconciles into the LIVE tunnel without
    touching the netstack, routes, or in-flight flows. New names become
    routable immediately (allocator + resolver hold live refs); deleted
    ones are marked inactive (hidden from listings, new SYNs rejected)
    but keep their reserved IP — the allocator is append-only and
    deterministic, so a reappearing connection regains its address.

Triggers:
  - Periodic: daemonService.StartAutoRefresh runs a ticker
    (--refresh-interval, default 60s, HSH_TUNNELD_REFRESH_INTERVAL
    override; 0 disables) for the daemon lifetime. It no-ops while the
    tunnel is down and never tears the tunnel down on a fetch blip.
  - Manual: POST /v1/connections/refresh + ipc.Client.RefreshConnections.

Tests:
  - registry: reconcile add/retire/reactivate, idempotence, and a
    -race concurrent reader/writer regression guard.
  - ipc handler: 200 + running/count, down no-op.
  - service: refresh no-op when down, Manager.Refresh no-op when idle.
  All of ./tunnel/... green under -race.

 🤖 Generated with Mister Maluco

Co-Authored-By: MisterMal <teskeslab@lucasteske.dev>
@racerxdl

racerxdl commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Update: periodic + on-demand connection-list refresh added

Expanded this PR to also fix the 'connection list never updates' gap (the daemon fetched once at bring-up and never again).

  • Concurrency-safe connRegistry replaces the bare subTypeByName map that was shared by reference into the netstack closures and Snapshot — that was an unsynchronized data race. Snapshot now carries a value-copied []ConnInfo of active connections.
  • Manager.Refresh re-fetches + reconciles into the live tunnel without touching the netstack/routes/in-flight flows. New connections become routable immediately; deleted ones are hidden but keep their reserved (deterministic) IP.
  • Periodic: StartAutoRefresh ticker (--refresh-interval, default 60s, HSH_TUNNELD_REFRESH_INTERVAL override, 0 disables).
  • Manual: POST /v1/connections/refresh.
  • Tests incl. a -race concurrent reader/writer guard; all ./tunnel/... green under -race.

Paired CLI command hsh tunnel refresh in hoophq/hsh#28.

Automated by MisterMal

@sandromello

Copy link
Copy Markdown
Contributor

✅ Build Completed with Success, Version=1507.0.0-gb8e0f4d

@racerxdl
racerxdl merged commit 9e09d86 into main Jun 3, 2026
23 checks passed
@racerxdl
racerxdl deleted the teske/rd-tunnel-up-down branch June 3, 2026 16:42
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

minor Bumps the minor version on release (new features)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants