l4

package
v0.4.3 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package l4 generates the config for Mooring's managed Layer-4 (TCP/UDP) load balancer — an nginx `stream {}` proxy that fronts a fixed public port (e.g. DNS 53, DoT 853, MQTTS 8883) and fans connections across a service's INTERNAL replica pool. It is the L4 analog of the HTTP edge.

Like the HTTP edge, the config is RENDERED from typed structs and is NEVER authored by the operator (no nginx.conf to write, no portal field) — the one chokepoint, and every value is re-validated at render so a bad route can never reach the datapath.

Phase 1 (this file): the typed model + render + injection-safe validation, fully unit-tested. The supervised nginx child, the live replica-pool reconcile, the UDP health prober, and the OS-layer slice/firewall land in later phases.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Available

func Available() (bool, string)

Available reports whether this host can OWN the managed L4 load balancer — a supervised child nginx (stream module) with its own systemd slice + CAP_NET_BIND_SERVICE + egress firewall (the L4 analog of the edge, plan §6), Linux-only. Off Linux, or with no nginx binary, it is FAIL-CLOSED unavailable.

func PoolKey

func PoolKey(r Route) string

PoolKey identifies a route's pool slot — one listener is one (port, protocol). The reconciler keys discovered replica pools by this to merge them back onto the routes.

func Render

func Render(routes []Route) (string, error)

Render produces a complete, valid nginx `stream` config from the routes. It is fail-closed: any invalid route, or two routes claiming the same listen+protocol, is an error and nothing is emitted. Output is deterministic (routes are sorted).

func ValidateRoute

func ValidateRoute(r Route) error

ValidateRoute checks one L4 route, fail-closed. It mirrors (defense-in-depth) the definition-layer schema validation, and additionally rejects any value that could inject text into the generated nginx config.

func VerifyDigest

func VerifyDigest(nginxPath, want string) error

VerifyDigest checks the nginx binary's SHA-256 against a pinned digest (supply chain — refuse on mismatch, plan §6). An empty want skips the check.

Types

type Route

type Route struct {
	AppID    string   // owning project — discovery scopes Service to it (two apps may share a service name)
	Listen   int      // host port the L4 LB binds
	Protocol string   // "tcp" | "udp"
	Service  string   // selector → the service whose replicas receive traffic
	Port     int      // the service's INTERNAL container port
	LB       string   // "" (round_robin) | "least_conn" | "hash_client_ip"
	Pool     []string // host:port of each live replica; EMPTY → route is SKIPPED at render
}

Route is one managed L4 listener: a public port the LB owns, forwarded to a service's internal replica pool. Service/Port are SELECTORS (never a literal dial target the operator types); Pool, when populated by the reconciler, lists the live replica host:port endpoints. With an empty Pool the upstream is the Service:Port name, resolved on the compose network (Docker DNS) — the v1 fallback.

type RouteStore

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

RouteStore persists the L4 routes an app declares (mooring.yaml edge.l4_routes), keyed by project. The L4 LB config is rendered from the union of all projects' routes — never stored as nginx text. A listener (listen+protocol) is globally unique (DB constraint), so two apps can't claim the same public port.

func NewRouteStore

func NewRouteStore(db *store.DB) *RouteStore

NewRouteStore builds a store over the shared DB.

func (*RouteStore) List

func (s *RouteStore) List() ([]Route, error)

List returns every project's L4 routes (the input to a render), ordered deterministically. AppID is returned so the reconciler can scope replica discovery to the owning project; Pool is left empty for the reconciler to populate.

func (*RouteStore) ReplaceProject

func (s *RouteStore) ReplaceProject(ctx context.Context, project string, routes []Route) error

ReplaceProject atomically replaces all of one project's L4 routes with the given set (the deploy-time op: an app's mooring.yaml is the source of truth for its routes). Every route is validated first; a cross-project listener collision trips the UNIQUE(listen, protocol) constraint and fails the whole transaction (nothing is changed) so the deploy is blocked with a clear error rather than hijacking a port another app owns.

type Supervisor

type Supervisor struct {
	ConfigPath string // the live config file the master reads (Mooring-owned)
	Prefix     string // nginx -p prefix dir (Mooring-owned, e.g. /var/lib/mooring/l4)
	Digest     string // pinned SHA-256 of the nginx binary (optional)
	Log        *slog.Logger
	// contains filtered or unexported fields
}

Supervisor owns the child nginx that serves the L4 stream proxy. Config lives in a Mooring-owned file; the operator never authors it. Reconcile renders from typed routes, validates with `nginx -t`, and only then swaps the live config + reloads — a rejected render keeps the last-good config serving (fail-closed). The testConf / signal seams let the reconcile state machine be unit-tested without a real nginx.

func (*Supervisor) Reconcile

func (s *Supervisor) Reconcile(ctx context.Context, routes []Route) error

Reconcile renders the routes, validates the rendered config, and atomically swaps it in + reloads. It is fail-closed and serialized: a render error or an `nginx -t` rejection leaves the live config untouched (last-good keeps serving).

func (*Supervisor) Run

func (s *Supervisor) Run(ctx context.Context)

Run supervises the child nginx with capped backoff until ctx is cancelled. It is fail-closed: if the host can't own the L4 LB (non-Linux, no binary, digest mismatch) it logs and returns without starting anything. The systemd slice / user / caps / egress firewall are the OS deployment layer (plan §6); this owns the lifecycle. Not exercised off-Linux.

Jump to

Keyboard shortcuts

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