Skip to content

feat: add peer-to-peer LAN tunneling for remote terminal sessions - #8

Merged
coneilen merged 7 commits into
mainfrom
feature/lan-tunneling
Feb 6, 2026
Merged

coneilen merged 7 commits into
mainfrom
feature/lan-tunneling

Conversation

@coneilen

@coneilen coneilen commented Feb 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add LAN peer discovery via mDNS and encrypted WebSocket tunneling for remote terminal session management
  • Remote sessions use namespaced IDs (tunnel:{instanceId}:{sessionId}) that route transparently through the existing IPC/renderer flow — no changes needed to Terminal component
  • Identity derived from git config --global user.email (SHA-256 hash) — only machines with the same git email discover each other

Architecture

10 new files in src/main/tunnel/:

  • protocol.ts — wire message types, session ID helpers
  • identity.ts — git email detection, stable instance UUID
  • crypto.ts — DH key exchange (modp14), AES-256-GCM encrypt/decrypt
  • discovery.ts — mDNS publish/browse via bonjour-service
  • server.ts — WebSocket server with handshake, auth, session routing (port retry 9500-9510)
  • client.ts — WebSocket client with request correlation, auto-reconnect (exponential backoff 1s-30s)
  • manager.ts — orchestrator owning Discovery/Server/Clients, bridges SessionManager events

Renderer (src/renderer/):

  • TunnelHostItem.svelte — host display with status dot, connect/disconnect
  • tunnels.ts store — sidebar context, host list, selected machine
  • tunnelHelpers.ts — renderer-side session ID utilities

8 modified files: session manager (emit bridging), ipc (tunnel routing + 8 new handlers), index (lifecycle), preload (tunnel API namespace), env.d.ts (types), Sidebar (Local/Remote tabs), TabBar (context filtering), NewSessionDialog (remote target), App.svelte (tunnel event wiring)

New dependencies: bonjour-service, ws, @types/ws

Test plan

  • Run two instances: ELECTRON_USER_DATA=/tmp/tm1 npm run dev and ELECTRON_USER_DATA=/tmp/tm2 npm run dev
  • Both discover each other in Remote tab (same git email)
  • Connect from A to B — auto-approved (same identity hash)
  • View B's sessions in A's Remote sidebar
  • Click remote session — terminal output streams in real-time
  • Type in remote session — input reaches remote PTY
  • Create new session on remote machine from A
  • Disconnect — clean removal from sidebar
  • Instances with different git emails do NOT discover each other
  • Kill one instance — other detects disconnect, shows "disconnected" status
  • tsc --noEmit passes clean
  • electron-vite build succeeds

🤖 Generated with Claude Code

coneilen and others added 7 commits February 5, 2026 15:41
Enable discovery and management of terminal sessions across machines
on the same LAN using mDNS (bonjour-service) for peer discovery and
encrypted WebSocket connections (ws) with DH key exchange + AES-256-GCM.

Remote sessions use namespaced IDs (tunnel:{instanceId}:{sessionId})
that route transparently through the existing IPC and renderer flow.
Identity is derived from git email (SHA-256 hash) so only machines
belonging to the same user can discover and connect to each other.

New files: protocol, identity, crypto, discovery, server, client,
manager (main process); tunnel stores, TunnelHostItem (renderer).
Modified: session manager (emit bridging), ipc (tunnel routing),
index (lifecycle), preload (tunnel API), sidebar (Local/Remote tabs),
tab bar (context filtering), new session dialog (remote target),
App.svelte (tunnel event wiring).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The default import `import Bonjour from 'bonjour-service'` resolves to
the wrong export when externalizeDepsPlugin transpiles to CJS require.
The constructor lives at the named export `{ Bonjour }`, not `.default`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The referer.address field is the network-level source IP of the mDNS
multicast packet, which can be a gateway/router address on bridged or
multi-subnet networks. This caused ECONNREFUSED when connecting to
peers since the client would try the router IP instead of the host IP.

Now uses service.addresses (from A/AAAA DNS records) with preference
for routable IPv4, skipping loopback and link-local. Also explicitly
binds WebSocket server to 0.0.0.0 for consistent cross-platform
behavior and adds diagnostic logging for address resolution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mDNS is unreliable on Windows — macOS has native Bonjour but Windows
often fails to advertise services even with firewall rules. This meant
Mac couldn't discover Windows machines on the LAN.

Add a UDP broadcast beacon as a parallel discovery mechanism:
- Each instance broadcasts a JSON beacon every 5s on UDP port 41832
- Receivers filter by identityHash match (same as mDNS)
- Hosts are swept after 20s without a beacon
- Both mDNS and beacon feed into the same registerHost() method

Also adds reverse discovery: when a client connects to our WebSocket
server, we register it as a known host using the TCP socket's remote
address, providing a third discovery path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
255.255.255.255 limited broadcast is unreliable across machines — some
OS kernels only send it out on one interface and some routers don't
forward it. Now enumerates network interfaces and sends to each
subnet's directed broadcast address (e.g. 192.168.10.255).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 100ms fixed delay before writing the command was too short for
PowerShell which can take several seconds to initialize. Now uses a
debounce approach: waits for a 300ms gap in shell output (indicating
the prompt is ready) before sending the command, with a 5s fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PowerShell 5.1 (Windows default) doesn't support the || operator.
Use $LASTEXITCODE check instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coneilen
coneilen merged commit 1574e4d into main Feb 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant