tunnelcat-sdk docs
Docs / Protocols / IPC

IPC Protocol

cmd/tunneld is the subprocess every non-iOS SDK adapter drives. It wraps tunnelcat-server's Authenticator/TunnelDialer/SOCKS5Server behind a duplex, newline-delimited JSON protocol on a local Unix domain socket. This page is the full contract — enough to write a 6th-language adapter without reading cmd/tunneld/main.go.

Transport

  • Unix domain socket at a path you choose, passed via tunneld -socket <path>.
  • Works identically on Linux, macOS, and Windows — Go's net.Listen("unix", ...) is natively supported on Windows 10 1803+ via AF_UNIX, so no separate named-pipe implementation exists or is needed.
  • One tunneld process serves one tunnel session. Spawn a new process (with a new socket path) per session.
  • Framing: one JSON object per line (\n-terminated), both directions.

Requests

{"id": <int>, "method": "<name>", "params": {...}}

id must be a positive integer you choose (increment per request); it's echoed back in the matching response so you can correlate replies even if multiple requests are in flight. params is omitted for methods that take none.

Responses

{"id": <int>, "result": {...}}
{"id": <int>, "error": "<message>"}

Exactly one of result / error is present.

Methods

connect

Logs in (Authenticator.Login()) and starts a local SOCKS5 listener.

{"server": "https://control:443", "apikey": "", "username": "", "password": "", "socksAddr": "127.0.0.1:0", "pollingOnly": false}

socksAddr may be "127.0.0.1:0" (or omitted) to let the OS pick a port — read the actual bound address back from the result. pollingOnly: true selects NewTunnelDialerPolling instead of the default streaming NewTunnelDialer — required against cmd/mockserver, since it only implements the polling side of the wire protocol (see Wire Protocol).

Result: {"connected": true, "socksAddr": "127.0.0.1:54321", "state": "connected"}

disconnect

No params. Tears down the SOCKS5 listener. Result: {"ok": true}.

status

No params. Result: {"connected": bool, "socksAddr": "...", "uptimeSec": int, "state": "..."}.

reconnect

No params. Re-runs Login() against the same credentials passed to the last connect. Errors if not currently connected.

Events (unsolicited, pushed on every open connection)

{"event": "state", "state": "connecting"|"connected"|"disconnected"|"error", "error": "<message, only when state is error>"}

Pushed immediately when state changes, rather than requiring the client to poll the status method. A client that only cares about request/response semantics can safely ignore any line containing an "event" key.

Example session

A full connect → status → disconnect exchange, events interleaved as they actually arrive:

 {"id":1,"method":"connect","params":{"server":"http://127.0.0.1:8443","apikey":"x","username":"u","password":"p","socksAddr":"127.0.0.1:1080","pollingOnly":true}}
 {"event":"state","state":"connecting"}
 {"event":"state","state":"connected"}
 {"id":1,"result":{"connected":true,"socksAddr":"127.0.0.1:1080","state":"connected"}}
 {"id":2,"method":"status"}
 {"id":2,"result":{"connected":true,"socksAddr":"127.0.0.1:1080","state":"connected"}}
 {"id":3,"method":"disconnect"}
 {"event":"state","state":"disconnected"}
 {"id":3,"result":{"ok":true}}
Implementing a 6th-language adapter

Every existing adapter follows the exact same shape: spawn tunneld -socket <random-temp-path>, poll for the socket file to appear (or watch the process exit early as a failure signal), connect a Unix socket, run a background reader thread/task that demuxes {"event":...} lines to your event callback and {"id":...} lines to whichever call is waiting on that id, and write one JSON object per line for each request. See any of the Python, Kotlin, C#, or Swift sources for a complete reference implementation of this pattern.