serialbroker

package
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package serialbroker serves host serial devices to containers over RFC2217.

Each approved device gets its own listener. That is also the authentication model: RFC2217 carries no credentials, so reachability is the only control, and it is a narrowing rather than a boundary.

The listener binds one container-facing host address instead of every interface. That is all it does. The run's allowed-host-ports entry is an egress permit applied inside the owning container under a strict network policy — it lets that container out, it does not keep anyone else away. Any process on the host, any other container that can route to the bound address, and on Linux any sender whose packet reaches a local address on any interface can connect. What prevents two clients driving one device is the exclusive claim plus one session at a time, not reachability.

Index

Constants

View Source
const RecordFull = "full"

RecordFull is the Approved.Record value that enables payload capture.

Variables

This section is empty.

Functions

This section is empty.

Types

type Approved

type Approved struct {
	Name   string           // config name, e.g. "esp32"
	Device serialdev.Device // the resolved host device
	Record string           // "events" (default) or "full"
	// Baud is the initial line rate from moat.yaml's `baud:`. Zero means the
	// client's SET-BAUDRATE governs from the first command.
	Baud int
}

Approved is a device a run may use.

type Broker

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

Broker owns the listeners and device claims for every registered run.

func New

func New(opts Options) *Broker

New creates a Broker.

func (*Broker) Close

func (b *Broker) Close() error

Close shuts every listener and session down.

func (*Broker) CloseListeners

func (b *Broker) CloseListeners(refs []*ListenerRef)

CloseListeners releases exactly the listeners the given refs point at. listenSerial uses it to roll back a partially-failed registration without touching the live listeners of the same run: re-registration after a transient daemon failure must not tear down devices a running container is using. A ref whose listener was already released (or re-created by a later registration) is a no-op for it — the registry entry is only removed when it still points at that listener.

func (*Broker) Listen

func (b *Broker) Listen(runID string, a Approved, bindAddr string) (*ListenerRef, string, error)

Listen opens a listener for one approved device and returns a handle for scoped rollback plus its host:port address. The port is OS-assigned.

A device already claimed by another run is refused: two runs sharing a serial line would interleave bytes and corrupt both sessions.

Re-listening a device this run already holds is idempotent and returns the existing address: the run manager re-registers with the daemon after a transient daemon failure, and a run must not conflict with its own claim.

bindAddr overrides the broker's fallback BindAddr for this listener. RFC2217 carries no authentication, so the address is the reachability control: the run manager passes the container-facing address of the run's network, and an empty value means the broker's configured default.

func (*Broker) ListenAt

func (b *Broker) ListenAt(runID string, a Approved, bindAddr string, port int) (*ListenerRef, string, error)

ListenAt is Listen bound to one exact port. The container's MOAT_SERIAL_*_URL froze the port at container create, so restoring a run after a daemon restart must re-bind the same number — rebinding elsewhere would leave the run's device silently dead while the daemon looks healthy. A taken port fails with a named error rather than falling back to an OS-assigned one.

func (*Broker) Revoke

func (b *Broker) Revoke(runID string)

Revoke closes every listener and session belonging to a run. It is safe to call for a run that has no devices.

type Event

type Event struct {
	RunID  string
	Device string // config name, e.g. "esp32"
	Kind   string // attach, detach, error, conflict, settings, modem, control, break, purge
	Detail string

	DevicePath   string // host device node
	VID          string
	PID          string
	DeviceSerial string // the device's USB serial number, if it has one
	Record       string // record mode in force for this session

	TxBytes int64 // bytes sent to the container
	RxBytes int64 // bytes received from the container
}

Event describes something worth recording about a device session.

The device's USB identity travels with every event so an audit trail records which physical device was reached, not just the name the config gave it.

type ListenerRef

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

ListenerRef names one listener a specific Listen call opened, so a caller can roll back exactly that set without disturbing the run's other devices.

type Options

type Options struct {
	// OpenPort opens a host serial device. Defaults to serialport.Open; tests
	// substitute a fake so no hardware is required.
	OpenPort func(path string) (serialport.Port, error)

	// Log receives session events. Optional.
	Log func(Event)

	// OpenRecorder returns a sink for captured payload bytes, used only when a
	// device is configured with record: full. Optional; without it, full
	// capture degrades to events only rather than failing the session.
	OpenRecorder func(runID, device string) (io.WriteCloser, error)

	// BindAddr is the fallback address for listeners when ListenOpts carries
	// none. Defaults to loopback — the only address safe to expose without
	// authentication. The run manager knows the container's gateway and passes
	// it per device; a broker configured for all interfaces is a deployment
	// choice that must be made explicitly, not a default.
	BindAddr string

	// HandshakeTimeout bounds how long an accepted connection may hold the
	// device without completing a negotiation, a com-port command, or sending
	// payload. Zero uses the default. See handshakeTimeout in session.go for
	// why this is an absolute budget rather than an idle timeout.
	HandshakeTimeout time.Duration
}

Options configures a Broker.

Jump to

Keyboard shortcuts

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