relay

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package relay is the daemon's leg of the Cloudflare relay: one outbound WebSocket to a Worker, and every browser that reaches this machine multiplexed over it.

The direction is the point. The daemon binds 127.0.0.1 and nothing else, so remote reach cannot come from a port on this machine — it comes from a socket this machine opened. Everything the relay carries is defined by spec/relay-protocol.md and framed by internal/relaywire: binary messages are [4-byte channel][payload], the only text messages are the keepalives, and channel 0 carries the JSON control messages the relay and the daemon say to each other in the clear. Channels 1 and up are one browser's Noise session each, opaque here and served by channel.go.

The adapter owns exactly one thing: keeping that socket up. It dials, reads, dispatches, pings, and — when the socket dies, as a socket held open across the internet by a hibernating Durable Object regularly does — backs off and dials again, forever, until its context ends.

Index

Constants

This section is empty.

Variables

View Source
var ErrIncompleteConfig = errors.New("relay: incomplete config")

ErrIncompleteConfig is what New answers a Config it cannot dial with: a field missing it cannot invent, or a machine id the relay would never route. Wrapping rather than a sentinel per fault: the caller's decision is the same for all of them — this daemon is not configured for a relay — and the message names which field it was.

Functions

This section is empty.

Types

type Config

type Config struct {
	URL       string // the bare relay address: wss://flue-relay.<sub>.workers.dev
	Secret    string // the DAEMON_SECRET set at deploy time
	Origin    string // https origin the relay serves the UI on
	MachineID string // the machine's slot on the relay: the <id> of /daemon/<id>
}

Config is everything the adapter needs to reach a deployed relay.

All four fields are required: where to dial, which machine's slot to dial it as, what the dial presents, and the origin the relay serves browsers on. The daemon and the Worker share one long-lived DAEMON_SECRET, set at deploy time by `flue relay setup`.

type Server

type Server interface {
	ServeConn(ctx context.Context, mc daemon.MessageConn, meta daemon.ConnMeta)
	// PairDevice runs the pairing ceremony on a body whose provenance this
	// package has already checked — the announced origin being the relay this
	// daemon dialled. It answers the status and JSON body the Worker writes
	// back to the browser that posted it.
	PairDevice(body []byte, peer string) daemon.PairOutcome
	// SetRelayStatus reports what this transport is doing, in the daemon's own
	// vocabulary: daemon.RelayConnecting while there is no socket, and
	// daemon.RelayConnected with the configured origin while there is one. The
	// daemon builds pairing URLs and answers welcomes out of it, so it tracks
	// the socket rather than the configuration — a relay that is configured and
	// down is one nothing can be reached through.
	SetRelayStatus(status, origin string)
}

Server is the surface the adapter drives — implemented by *daemon.Server.

It is declared here rather than in daemon because it is this package that needs it: the daemon does not know a relay exists. Two calls, because the relay carries two kinds of thing — a browser's connection, and the one part of the ceremony that is an HTTP request rather than a WebSocket message.

type Transport

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

Transport dials the relay and serves channels until ctx ends. It reconnects with jittered exponential backoff and only returns when its context is done.

func New

func New(cfg Config, srv Server, identity noise.DHKey, devices *crypto.DeviceStore, log *slog.Logger) (*Transport, error)

New builds a transport, or says why the configuration cannot make one. A nil logger discards, so a caller that has not wired one up gets a working adapter rather than a panic on the first log line.

All three fields are required, and Origin is required for a reason worth stating: it is not decoration, it is the value every announced open and every forwarded pair is checked against (channel.go). An empty Origin makes both checks vacuous — a relay announcing `origin:""` matches it — and it is also what ConnMeta.Origin carries into the daemon, which is what pairing URLs are built from. A misconfigured field must fail here, at wiring time, rather than quietly disarming the check it exists for.

URL and the secret are required for the ordinary reason: without them there is nothing to dial and nothing to authenticate with, so Run would do nothing but fail and back off forever. The machine id is the same kind of required: it is the path this transport dials, and without one every dial would meet the Worker's "no such machine" 404 while the config looked complete. Held to the relay's own grammar too, not merely non-empty, because an id the Worker will not route earns exactly that 404 with a value in the field.

func (*Transport) Run

func (t *Transport) Run(ctx context.Context) error

Run dials the relay and keeps it dialled until ctx is done.

It returns nil on cancellation: being asked to stop, and stopping, is not an error the caller has to recognise and filter out — the same contract daemon.ListenAndServe keeps. Every other outcome is a reason to try again, so nothing else ever returns from here. A relay that is down, misconfigured, or answering 401 while its operator rotates a secret is a temporary state, and a daemon that gave up on it would never come back without a restart.

Jump to

Keyboard shortcuts

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