Control Plane¶
fairwave-control is the brain of the box: it holds identity and keys, enrolls nodes, reconciles configuration into the core and RAN, and exposes the northbound REST API v1 used by the CLI and operator UI.
It is a Go service (docs/adr/0003-control-plane-language.md) with a file-backed store in v0.1. The control plane is the only component that writes to core (Open5GS / free5GC) and srsRAN configuration.
Responsibilities¶
- Identity and keys - node identity generation, mesh root CA issuance (mTLS), key separation from SIM credentials.
- Enrollment - bootstrap-token-based onboarding of new boxes into a neighborhood.
- Reconcile loop - desired state → concrete config for Open5GS (templating) and srsRAN (supervision), continuously.
- Northbound REST - JSON API v1 for CLI, UI, and the agent.
- Southbound drivers - Open5GS config templating + reload, srsRAN process supervision, agent health intake.
- Policy enforcement - band allow-list, TX arm gate, APN policy.
Reconcile Loop¶
flowchart LR
STORE[(file-backed store)] -->|desired state| LOOP
LOOP[reconcile loop] -->|template| O5GS[Open5GS config + reload]
LOOP -->|supervise| RAN[srsRAN eNB process]
LOOP -->|probe| AGENT[fairwave-agent]
O5GS --> LOOP
RAN --> LOOP
The loop compares desired state (nodes, SIMs, policy, peers) against observed state (processes up, configs applied, heartbeat fresh) and converges. All mutations happen through the same code path, so the file store is the single source of truth.
State Machine¶
stateDiagram-v2
[*] --> provision: node init
provision --> register: bootstrap token + mTLS enroll
register --> on_air: country + license + band allow-list (tx/arm)
on_air --> peer: mesh join
peer --> breakout: route exchange + NAT up
on_air --> register: arm revoked
peer --> on_air: mesh left
Transitions are exposed as POST /v1/lifecycle/transition. Each transition re-validates prerequisites; a transition to on-air without an armed TX gate is refused.
Northbound REST API (v1)¶
JSON under /v1:
| Endpoint | Purpose |
|---|---|
GET /v1/healthz |
liveness |
GET /v1/status |
node + subsystem status |
GET/POST /v1/nodes |
node records, enrollment |
GET/POST /v1/sims |
SIM issue/revoke (IMSI hashes only) |
POST /v1/sims/import |
bureau batch import (CSV/JSON) |
POST /v1/sims/{imsi}/suspend · /resume |
fair-use enforcement (auto-suspend over quota) |
POST /v1/sims/{imsi}/quota |
set a fair-use data allowance |
GET /v1/sims/{imsi}/usage |
accumulated up/down bytes vs quota |
GET /v1/esim/codes · POST /v1/esim/issue · /revoke |
eSIM activation codes (SM-DP+) |
GET /v1/peers |
mesh peer list |
GET /v1/sessions |
active bearer/session view (with usage from collectors) |
GET/PUT /v1/policy |
APN/band/breakout policy |
POST /v1/spectrum/check |
band legality pre-flight |
POST /v1/tx/arm · /disarm |
TX gate (country, license ref, allow-list) |
POST /v1/lifecycle/transition |
state machine transitions |
GET /v1/tokens · POST /v1/tokens · DELETE /v1/tokens/{id} |
scoped API tokens |
GET /v1/alerts |
operational alerts |
GET /v1/audit |
append-only operator audit trail |
GET /v1/compliance/export |
regulator-ready compliance report (CSV) |
GET /v1/backup · POST /v1/restore |
encrypted state backup/restore |
GET /v1/version |
release string |
GET /metrics |
Prometheus exposition |
Southbound Drivers¶
- Open5GS driver: renders per-node config from templates (PLMN/TAC/APNs, MME/SGW/PGW addressing), writes it into the Open5GS container, triggers graceful reload, verifies the services come back healthy.
- HSS/UDM write-back drivers:
mongosh(Open5GS HSS via docker exec),dbctl, andfree5gc(UDR document upserts,hss.driver: free5gc). - srsRAN driver: supervises the eNB/gNB container; restarts on crash with backoff, surfaces S1 status and UE attach counts back into
/v1/status. - Usage collectors feed
GET /v1/sessionsand the fair-use quota pipeline: - GTP-U tap (
collector.upf): counts GTP-U packets on an interface the UE traffic transits (needsCAP_NET_RAW; 4G). - AMF OAM collector (
free5gc.amf_oam_url): polls the free5GC AMF'snamf-oamAPI for live 5G sessions. - CHF CDR collector (
free5gc.cdr_dir): reads the free5GC CHF's per-UE CDR files (TS 32.297 + BERChargingRecord) for byte totals measured from the core.
Persistence¶
v0.1 uses a file-backed store under the data directory:
/var/lib/fairwave/
├── store/ # JSON records: nodes, sims, peers, policy, lifecycle
├── keys/ # node keys, mesh CA material (0600)
├── vault/ # SIM vault: Ki/OPc encrypted at rest
└── logs/
Ki/OPc are never written to the store in plaintext and never logged. IMSIs are stored as truncated sha256 hashes. Migration to a database is a post-M6 item (design/roadmap.md).
Running¶
- Containerized by default in Docker Compose, or bare under systemd (
docs/software/fairwave-control.md). - Reads
FAIRWAVE_*env vars and a YAML config file; seedocs/software/fairwave-control.md.