- Update CI (unit-test.yml, pr-test.yml) to use yaoapp/tai:1.2.0 with new default ports (gRPC:19100, HTTP:8099, VNC:16080, Docker:12375) - Add explicit 0.0.0.0 bind for containerized Tai instances - Fix sandbox/v2 grpc.go default port fallback (9100 → 19100) - Fix tai/tunnel/proxy.go fallback ports (8080→8099, 6080→16080) - Sync tai SDK and sandbox/v2 documentation with implementation - Add new docs: api.md, registry.md, tunnel.md Made-with: Cursor
3.1 KiB
Package tunnel
WebSocket tunnel handlers for Tai nodes that cannot be reached directly (e.g. behind NAT/firewall). Provides Gin HTTP handlers mounted on the Yao server.
Handlers
| Method | Route | Handler | Description |
|---|---|---|---|
GET |
/ws/tai |
HandleControl |
Control channel WebSocket |
GET |
/ws/tai/data/:channel_id |
HandleData |
Data channel WebSocket |
ANY |
/tai/:taiID/proxy/*path |
HandleProxy |
HTTP reverse proxy via tunnel |
GET |
/tai/:taiID/vnc/*path |
HandleVNC |
VNC WebSocket proxy via tunnel |
All WebSocket endpoints require Authorization: Bearer <token> header.
HandleControl
Manages the long-lived control WebSocket for a Tai node.
Flow:
- Authenticate Bearer token via OAuth
- Upgrade to WebSocket
- Read
registermessage (JSON):
{
"type": "register",
"tai_id": "tai-abc123",
"machine_id": "m-001",
"version": "1.2.0",
"server": "http://yao-server:5099",
"ports": {"grpc": 19100, "http": 8099, "vnc": 16080, "docker": 12375},
"capabilities": {"docker": true, "host_exec": true},
"system": {"os": "linux", "arch": "amd64", "hostname": "host-01", "num_cpu": 8}
}
- Register node in global registry with
Mode="tunnel" - Reply
{"type": "registered", "tai_id": "tai-abc123"} - Loop reading messages:
{"type": "ping"}→ update heartbeat, reply{"type": "pong"}
- On disconnect → unregister node
HandleData
Handles on-demand data channel connections from Tai.
Flow:
- Authenticate Bearer token
- Extract
:channel_idfrom URL - Upgrade to WebSocket
- Wrap WebSocket as
net.Conn(bidirectional byte bridge) - Call
registry.AcceptDataChannel(channelID, clientID, conn)
The channel_id must match a pending RequestChannel call. The clientID (from token) must match the Tai node that owns the channel.
HandleProxy
HTTP reverse proxy for tunnel-connected Tai nodes.
Flow:
- Look up Tai node from
:taiIDin registry - Get the node's HTTP port (from
node.Ports["http"], default 8099) - Open a tunnel data channel to that port via
RequestChannel - Forward the incoming HTTP request through the tunnel
- Read the response and stream it back to the client
HandleVNC
VNC WebSocket proxy for tunnel-connected Tai nodes.
Flow:
- Look up Tai node from
:taiIDin registry - Get the node's VNC port (from
node.Ports["vnc"], default 16080) - Open a tunnel data channel to that port via
RequestChannel - Upgrade the client connection to WebSocket
- Bridge client WebSocket ↔ tunnel data channel (binary messages)
Internal Types
wsConn
wsConn wraps gorilla/websocket.Conn to implement net.Conn for bidirectional byte bridging. This allows tunnel data channels to be treated as standard TCP connections by the registry's bridge logic.
type wsConn struct { ... }
func (c *wsConn) Read(p []byte) (int, error) // reads WS binary messages
func (c *wsConn) Write(p []byte) (int, error) // writes WS binary messages
func (c *wsConn) Close() error
// Also implements: LocalAddr, RemoteAddr, SetDeadline, SetReadDeadline, SetWriteDeadline