tunnel

package
v0.0.0-...-a4571ee Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package tunnel carries the wire protocol over a single outbound connection.

One session multiplexes many channels — control, tool calls, and interactive consoles — so the agent opens exactly one connection outward and never needs an inbound rule. Multiplexing and per-channel flow control come from yamux: a console channel streaming `tail -f` has its own window and cannot starve a tool call sharing the connection.

The session layer works over any net.Conn, which keeps the websocket out of the core and makes the protocol testable without a network. Both sides import this package so the framing has one implementation rather than two that must agree.

Index

Constants

View Source
const WSPath = "/tunnel"

WSPath is the URL path that carries the tunnel websocket. It is wire contract: an agent and a SaaS that disagree on it never reach the Hello exchange, so it lives here where both sides import it.

Variables

This section is empty.

Functions

func AcceptWS

AcceptWS upgrades an inbound request and returns it as a net.Conn.

func DialWS

func DialWS(ctx context.Context, url string, hdr http.Header, tlsClient *http.Client) (net.Conn, error)

DialWS opens an outbound websocket and returns it as a net.Conn.

Outbound-only over 443 is the whole connectivity story: it traverses corporate egress proxies with no inbound rule and no VPN.

func NewTLSClient

func NewTLSClient(cfg *tls.Config) *http.Client

NewTLSClient returns the http.Client DialWS needs for a wss URL secured by cfg. The transport is HTTP/1.1 only — the websocket upgrade is an HTTP/1.1 mechanism, and negotiating h2 would break it.

Types

type Backoff

type Backoff struct {
	Min    time.Duration
	Max    time.Duration
	Factor float64
	// Jitter is the fraction of the computed delay that is randomised, 0..1.
	// 0 makes the schedule deterministic, which is what the tests want.
	Jitter float64
	// contains filtered or unexported fields
}

Backoff schedules reconnect attempts.

The agent's connection is the customer's only path to support, so it retries indefinitely rather than giving up — but it must not become a thundering herd when the SaaS restarts and every enrolled cluster reconnects at once. Hence the jitter, which is the part that matters at fleet scale.

func DefaultBackoff

func DefaultBackoff() Backoff

DefaultBackoff is a sane reconnect schedule: fast enough that a brief SaaS restart is invisible, slow enough that an hour-long outage does not generate a million connection attempts.

func (Backoff) Delay

func (b Backoff) Delay(n int) time.Duration

Delay returns the wait before attempt n, counting from 0.

func (Backoff) Reconnect

func (b Backoff) Reconnect(
	ctx context.Context,
	connect func(context.Context) (*Session, error),
	sleep func(context.Context, time.Duration) error,
) (*Session, error)

Reconnect calls connect until it returns without error or ctx ends, waiting per the backoff schedule between attempts. sleep is injectable so the retry policy can be tested without real time passing.

It returns the first successful session, or ctx.Err().

type Channel

type Channel struct {
	net.Conn
	Open tunnelproto.ChannelOpen
	// contains filtered or unexported fields
}

Channel is an accepted stream plus the request that opened it.

Read is overridden rather than inherited from the embedded Conn. The header is parsed with a json.Decoder, which reads in chunks and keeps whatever it over-read — for a console channel that surplus is the first bytes the user typed. Reading the raw stream afterwards would silently drop them, so the decoder's buffered remainder is spliced in front.

func (*Channel) Read

func (c *Channel) Read(p []byte) (int, error)

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is one multiplexed tunnel.

func Accept

func Accept(ctx context.Context, conn net.Conn, admit func(tunnelproto.Hello) tunnelproto.HelloAck) (*Session, error)

Accept performs the SaaS side over an established conn: it becomes the multiplexing server, accepts the control stream and applies admit to the agent's Hello.

A refused agent still receives its HelloAck before the session closes — the reason has to reach the operator, and a bare disconnect tells them nothing.

func Dial

func Dial(ctx context.Context, conn net.Conn, hello tunnelproto.Hello) (*Session, error)

Dial performs the agent side of session setup over an established conn: it becomes the multiplexing client, opens the control stream and sends Hello.

A refusal is returned as an error carrying the SaaS's operator-readable reason, because the agent's log is where somebody will look first.

func (*Session) AcceptChannel

func (s *Session) AcceptChannel(ctx context.Context) (*Channel, error)

AcceptChannel accepts the next channel and validates its open request.

Validation here is not redundant with the sender's: the agent must never trust the SaaS, so an open naming an address — or crossing the plane boundary — is rejected on receipt regardless of what the sender checked.

func (*Session) Ack

func (s *Session) Ack() tunnelproto.HelloAck

Ack is the handshake result the peer returned (agent side) or produced (SaaS side).

func (*Session) AwaitKillSwitch

func (s *Session) AwaitKillSwitch() (tunnelproto.KillSwitch, error)

AwaitKillSwitch blocks until the peer sends one.

func (*Session) Close

func (s *Session) Close() error

Close drops the session and every channel on it — including live console sessions, which is the point: a kill switch that waits politely is not one.

func (*Session) IsClosed

func (s *Session) IsClosed() bool

IsClosed reports whether the session has gone away.

func (*Session) OpenChannel

func (s *Session) OpenChannel(ctx context.Context, id uint32, kind tunnelproto.ChannelKind, target tunnelproto.Target) (net.Conn, error)

OpenChannel opens a new channel to a symbolic target.

The request is validated before it is sent even though the peer validates on receipt; a caller should never knowingly emit something the peer will reject.

func (*Session) SendKillSwitch

func (s *Session) SendKillSwitch(k tunnelproto.KillSwitch) error

SendKillSwitch tells the peer to drop everything. The customer holds this control, so it takes effect on the peer's next read rather than waiting for anything in flight to finish.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL