Configuration
The RIC server is configured from a single YAML file, schema-validated at load time by libcyaml. Mandatory fields are checked and the process fails loudly at startup on a missing or malformed value.
Default path: /etc/airpuls/nrtric.yml (override with -c <path>). The
in-tree src/nrtric.yml is the annotated reference — every field below
is documented there and, field-by-field, in src/ric/config.h.
Minimal config
Only ric.plmn and ric.ric_id are mandatory; everything else has a
default:
ric:
plmn:
mcc: "001" # Mobile Country Code (3 decimal digits)
mnc: "01" # Mobile Network Code (2 or 3 decimal digits)
ric_id: 1 # 20-bit RIC identifier
transport: — every connection
All addressing, ports, socket tuning and TLS material live here,
grouped by direction. The ric: block below carries identity and
RAN behaviour only.
transport.southbound: — E2 / SCTP toward RAN nodes
| Field | Default | Meaning |
|---|---|---|
bind_addrs |
["0.0.0.0"] |
SCTP bind address(es). All bound addresses are advertised to the peer (SCTP multihoming) |
port |
36421 |
SCTP listen port |
heartbeat_ms |
0 (disabled) |
SCTP heartbeat interval; 0/negative disables |
recv_bufsize |
1114112 (1 MB + envelope) |
Largest single indication the RIC will accept, bytes — sizes the southbound recvmmsg slot. The socket SO_RCVBUF is this plus 1 MiB of queueing headroom. Clamped to [65536, 16 MiB] |
worker_threads |
min(nproc/2, 4) |
Southbound worker pool size. Clamped [1, 16]; 0 = default; set 1 for serial |
SCTP multihoming and Docker
SCTP advertises all bound addresses to the peer, which may pick
any of them. On a host with virtual interfaces (Docker bridges,
VMs, VPNs), set bind_addrs to the specific IPs the E2 agents can
actually reach — otherwise the peer may select an unreachable
address and the association drops silently after E2 Setup.
Heartbeats are disabled by default for the same reason: a
Dockerised gNB advertising bridge IPs can trigger a spurious SCTP
ABORT around t+3s. Enable heartbeats only on flat L3
deployments.
Sizing recv_bufsize for raw-SRS traffic
This is an airpuls userspace slot size, not an SCTP protocol
limit — SCTP reassembles larger messages transparently. The RIC
keeps no reassembly buffer, so a message that exceeds the slot is
partial-delivered and dropped, logged once per message as
Dropping oversized SCTP message ... raise transport.southbound.recv_bufsize.
An E2SM-AIR channelEstimateFreq estimate is
antennas × ports × symbols × fft × 4 bytes, and one indication
carries exactly one UE — so UE count scales the rate, never the
message size:
| antennas × ports | 1 symbol @ FFT 4096 | 4 symbols |
|---|---|---|
| 4 × 4 | 256 KB | 1 MB |
| 8 × 4 | 512 KB | 2 MB |
| 8 × 8 | 1 MB — the default covers this | 4 MB |
The default is that 1 MB plus 64 KB of headroom for the message envelope, so it covers an 8×8 occasion at one symbol. Raise it for higher symbol counts:
Each southbound worker pre-allocates 16 slots of this size, so the default costs ~17 MB per worker and 4 MB would cost 64 MB.The socket SO_RCVBUF is requested as recv_bufsize + 1 MiB of
queueing headroom, but net.core.rmem_max caps whatever is asked for
— the headroom does not slip past it. SCTP has to buffer a whole
message before it can reassemble and deliver it, so if that cap
leaves less than recv_bufsize, messages the slot is sized for can
still be partial-delivered. The RIC checks the buffer it actually got
at startup and warns only in that case:
SO_RCVBUF holds ~212992 bytes, less than the 1114112-byte largest
indication (transport.southbound.recv_bufsize) — raise
net.core.rmem_max on this host ...
If you see it, raise net.core.rmem_max on the RIC host.
transport.northbound: — xApp IPC
| Field | Default | Meaning |
|---|---|---|
socket |
tcp://127.0.0.1:36422 |
xApp IPC endpoint. tcp://<ip>:<port>, tls://<ip>:<port> or unix://<path> — the scheme is mandatory. Empty string disables the listener |
cert_file |
— | PEM listener certificate. Required with a tls:// endpoint |
key_file |
— | PEM private key for cert_file |
client_ca_file |
— | PEM CA that connecting xApps' certificates must chain to |
require_cn_match |
true |
Treat the client certificate's CN as the xApp identity and reject a Hello claiming a different deployment_name |
max_payload_bytes |
16777216 (16 MiB) |
Max xApp ↔ RIC wire payload. Both peers must agree on a value ≥ the largest message exchanged |
worker_threads |
min(nproc/2, 4) |
Northbound worker pool size. Clamped [1, 16]; a unix:// endpoint clamps this to 1 with a startup WARNING |
tcp_quickack |
true |
Set TCP_QUICKACK on each xApp socket and re-arm after every recv; removes the ~40 ms delayed-ACK floor. Leave on |
The three tls paths travel as a unit — a partial block is a startup
error, as is a tls:// endpoint without them. See
Security for the full procedure.
Binding for cross-host xApps
tcp://127.0.0.1:36422 accepts loopback xApps only. For xApps on
other hosts, bind all interfaces: socket:
"tcp://0.0.0.0:36422". The bind host must be an IP literal, not a
hostname. A cross-host deployment should use tls:// — see
Security.
ric: — identity and RAN behaviour
Identity (mandatory)
| Field | Type | Meaning |
|---|---|---|
plmn.mcc |
string | Mobile Country Code, 3 decimal digits |
plmn.mnc |
string | Mobile Network Code, 2 or 3 decimal digits |
ric_id |
int | 20-bit RIC identifier |
PLMN is decimal
mcc / mnc are decimal digit strings ("001" / "01"), not the
BCD-hex form. The RIC BCD-encodes them on the wire and surfaces the
decoded decimal PLMN as a metrics label.
Procedure timers and behaviour
| Field | Default | Meaning |
|---|---|---|
control_timeout_ms |
5000 |
RIC Control (TRICcontrol) timer; 0 = no timeout |
send_ran_functions_rejected |
false |
Include the RANfunctionsRejected IE in the E2 Setup Response |
logging: — log level
The CLI flags -v (verbose) and -q (warnings + errors) override this;
default is info if neither is set.
telemetry: — metrics tier
The Prometheus listener lives here alongside the tier knob:
| Field | Default | Meaning |
|---|---|---|
port |
0 (source) / 9095 (container) |
Prometheus HTTP port; 0 disables |
listen_backlog |
128 |
Listen backlog on the metrics port (capped by net.core.somaxconn) |
See Observability for the metric catalogue.
telemetry:
mode: "production" # production | profile
endpoint_enabled: true # false suppresses the HTTP listener even if metrics_port > 0
production(default) — aggregate atomic counters + registry-walk gauges; constant-sized scrape body. The ship-mode tier.profile— adds per-(deployment, node)E2SM-EPI latency histograms. Essentially free for small deployments (keep it on to get the EPI latency breakdown); for large deployments the per-(deployment, node)cardinality grows the scrape body and adds a mutex per probe — switch back toproduction.
disable_ran_function: — SM denylist
Quarantine a misbehaving SM plugin at startup without rebuilding. A listed RAN Function ID is created and immediately destroyed, so the RIC neither accepts agent advertisements for it nor dispatches to it:
Known in-tree plugin IDs: 2 KPM, 3 RC, 4 CCC, 5 LLC, 100 EPI,
101 AIR.
Container configuration
The release image bakes a config with placeholders the entrypoint
substitutes at startup (e.g. auto-discovering the container eth0 IP for
bind_addrs). Selected values are overridable via environment
variables — see Docker Deployment for the full list.