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
- type Approved
- type Broker
- func (b *Broker) Close() error
- func (b *Broker) CloseListeners(refs []*ListenerRef)
- func (b *Broker) Listen(runID string, a Approved, bindAddr string) (*ListenerRef, string, error)
- func (b *Broker) ListenAt(runID string, a Approved, bindAddr string, port int) (*ListenerRef, string, error)
- func (b *Broker) Revoke(runID string)
- type Event
- type ListenerRef
- type Options
Constants ¶
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 (*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 ¶
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.
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.