RAN Certification
RAN Certification is Stage 3 of Cloud Native Telecom Certification, after UPF Certification (Stage 1) and Control-Plane Certification (Stage 2). It certifies a 5G NG-RAN node in CU/DU split mode over its real interfaces: N2 (NGAP), F1-C (F1AP), E1 (E1AP), F1-U and N3 (GTP-U), and the air interface. You point the framework at a running split gNB, run it, and get a graded verdict per product class and, on a clean pass, a certificate.
Everything described here lives in github.com/TOSSI-Foundation/CNTC, under Apache-2.0.
Architecture
How a RAN is decomposed, and what CNTC certifies today
A gNB can be split in more than one place, and which split you deploy decides what a certificate can even mean. 3GPP defines eight functional split options between the RRC layer and the radio (TR 38.801), and two of them dominate real deployments.
| Split | Interface | Separates | CNTC |
|---|---|---|---|
| Option 2 | F1 | CU from DU | certified today |
| Option 2 (control/user) | E1 | CU-CP from CU-UP | certified today |
| Option 6 | FAPI, nFAPI | MAC from PHY, L2 from L1 | roadmap |
| Option 7.2x | Open Fronthaul | O-DU from O-RU | roadmap |
CNTC Stage 3 certifies the CU/DU split with a separated control and user plane, which is Option 2 over F1 plus the E1 split inside the CU. This is the decomposition 3GPP TS 33.523 uses for its Security Assurance Specification, and it defines exactly three product classes:
| Product class | 3GPP name | Interfaces driven | Tests |
|---|---|---|---|
| O-CU-CP | gNB-CU-CP | N2, F1-C, E1, RRC | 30 (16 essential) |
| O-CU-UP | gNB-CU-UP | E1, F1-U, N3 | 14 (7 essential) |
| O-DU | gNB-DU | F1-C, F1-U, the cell | 14 (7 essential) |
58 Level-1 requirements in total, 30 of them essential. The three verdicts are independent. One class can certify while another fails, which is exactly what happens on the reference stack.
A gNB deployed as a single process is still testable over the interfaces it does expose, N2 and N3, but it cannot be certified per product class, because the interfaces that would separate the classes are internal to it. That is a property of the deployment, not a limitation of the framework, and the framework says so rather than inventing a verdict.
Testing a radio without a radio
The obstacle everyone expects with RAN testing is the air interface. Certifying RRC behaviour and Access Stratum security sounds like it needs spectrum, radio hardware, and a signal analyser.
It does not, and the reason is structural. In a CU/DU split, every RRC message crosses F1 inside an F1AP container (TS 38.473 section 8.4). DLRRCMessageTransfer and ULRRCMessageTransfer carry the RRC PDU as an octet string, and Wireshark dissects it.
So RRC conformance and AS security activation are observable on an ordinary IP link, by capturing F1-C. The only thing still needed on the radio side is a UE that can complete an attach, and that can be a software UE over a virtual radio.
This is what makes RAN certification deployable on a single Linux box.
What it certifies
A RAN Certification PASS for a product class asserts that the product, on the stated rig:
- conforms to the protocol that defines each of its interfaces: NGAP on N2 (TS 38.413), F1AP on F1-C (TS 38.473), E1AP on E1 (TS 38.463), and RRC (TS 38.331) as observed inside the F1AP containers;
- establishes and carries the user plane on the TEIDs it signalled, with QFI marking on N3 (TS 38.425, TS 38.415, TS 29.281), and traffic reaches the data network end to end;
- activates Access Stratum security correctly: RRC is unprotected before the Security Mode Command and integrity protected after it, and the algorithms selected are not null when the policy requires protection (TS 33.511, TS 33.523);
- does not crash on malformed input on the interfaces it exposes, confirmed against a control run.
A verdict is only ever issued about what was measured. Requirements that could not be judged on a given deployment are recorded as such, with the reason, and are never promoted to a pass.
How it works
CNTC has two decoupled layers: an engine (ranbench) that measures, and an umbrella (cntc) that judges the serialized results.json against a versioned requirement catalog. Because they are decoupled, any past run can be re-graded without re-running it.
One attach, then the evidence is read
The engine brings the three product classes up in order, runs one UE attach through the whole stack, and tears everything down. The teardown is not cleanup, it is what makes the evidence readable: the RAN stack only closes its per-interface capture files at shutdown.
The decoded captures then answer the protocol, data path and security requirements. The seven robustness requirements are exercised separately, by restarting products and sending malformed input directly.
ranbench run --target <class>
|
|- acquire the rig lock one campaign at a time
|- core preflight AMF reachable, subscriber data servable
|
|- ONE UE ATTACH
| |- CU-CP up, wait for the F1-C listener [socket]
| |- CU-UP up, wait for the E1 association [socket]
| |- O-DU up, wait for cell activation
| |- UE attaches, registers, gets a PDU session
| |- ping the data network through the UE tunnel
| |- stop everything [captures flush]
|
|- DECODE with tshark
|- RUN THE SUITE 51 cases from the attach, 7 driven directly
|- DIAGNOSE any failures
|- GRADE against the catalog, write scorecard and certificate
A single class takes roughly two to five minutes.
What counts as evidence
Three sources, and nothing else:
| Evidence | Source | Used for |
|---|---|---|
| Wire capture | the stack's own per-interface pcaps, decoded with tshark | protocol conformance, security IEs, user plane |
| Socket state | ss -an --sctp | association establishment, recovery, listener readiness |
| Process state | pgrep | liveness, crash detection |
Product log files are used for diagnosis only, never for a verdict. The reason is practical and was measured on the reference stack: the O-CU-CP writes zero bytes to its log while running and flushes only at shutdown, so any question put to that log while the product is running answers "no" regardless of what the product actually did. Sockets and process state cannot lie in that way.
Prerequisites
- A split gNB under test, deployed by you, with its per-interface packet captures enabled. The reference stack is OCUDU, the Linux Foundation CU/DU project of srsRAN lineage, running as three processes (
ocucp,ocuup,odu). - A 5G core to peer with, providing an AMF for N2 and a UPF for N3. Bring your own: free5GC, Open5GS, OAI-CN5G, SD-Core, or a commercial core. The core is a peer, not the subject of the certificate.
- A UE simulator.
make ran-prereqsbuilds the OAI nr-UE with its ZeroMQ virtual radio, because a RAN cannot be exercised without a UE. - A Linux host with Python 3.10+, tshark, SCTP support, and passwordless sudo.
Captures are the prerequisite that matters. ranbench reads the wire. A RAN that cannot produce per-interface captures can still be checked for liveness and robustness, but its protocol and security requirements will record na rather than a verdict. Most stacks ship this capability with it disabled by default, and ran-doctor will tell you if it is off.
Install the framework
git clone https://github.com/TOSSI-Foundation/CNTC.git
cd CNTC
# deps + the OAI UE simulator (uses sudo)
make ran-prereqs
This installs what the tester needs and not the RAN or the core. The RAN is the subject of the certificate, so you deploy it. The UE is installed because it is the stimulus.
The OAI UE is licensed under the OAI Public License V1.1 by the OpenAirInterface Software Alliance. The script fetches and builds it into your environment; it is not distributed as part of CNTC, which is Apache-2.0, and ranbench invokes it as an external process.
List the product classes and their catalogs with make ran-list:
ranbench, split-gNB product classes and their CNTC catalogs:
du O-DU O-DU (gNB-DU), CNTC Level 1 14 tests, 7 essential, 14 implemented
cucp O-CU-CP O-CU-CP (gNB-CU-CP), Level 1 30 tests, 16 essential, 30 implemented
cuup O-CU-UP O-CU-UP (gNB-CU-UP), Level 1 14 tests, 7 essential, 14 implemented
Configure your RAN
Describe the RAN under test in a campaign config. The wizard writes one for you and derives the UE's radio parameters from the O-DU's own cell configuration, which removes the most common source of a failed attach.
# interactive wizard -> configs/<name>.yaml
make ran-configure
The key fields are:
ran.adapter, the RAN plugin.ocudutoday.ran.bin_dir, where the product binaries live.ran.configs, the YAML each product class runs with. The adapter parses these for the live F1-C, E1, F1-U and N3 addresses and for which captures are enabled, so redeploying or re-addressing the RAN needs no edit to the campaign config.core.adapter, the core peer. Usemanualfor any core you manage yourself, orfree5gc_k8sfor optional convenience helpers.drivers.ue_*, the UE binary and its radio parameters, which must agree with the O-DU's cell.subscribers, the SIM the UE presents.
Confirm the rig is ready before spending a run:
make ran-doctor CONFIG=configs/my-ran.yaml
[ok ] cli:tshark /usr/bin/tshark
[ok ] ran:build OCUDU, built: cucp, cuup, du
[ok ] ep:cucp 127.0.10.1:38472
[ok ] pcap:cucp e1ap, f1ap, ngap
[ok ] core free5gc_k8s (N2/N3 peer)
[ok ] amf n2 10.43.24.185:38412 accepting SCTP
[ok ] subscriber data UDM serves sm-data for imsi-208930000000001
RESULT: READY
The last two checks contact the core rather than trusting the config. Both catch failures that otherwise look like RAN faults: an unreachable AMF stops the attach at NG Setup, and a core that cannot serve subscription data lets registration succeed and then refuses the PDU session.
Run the certification
Certify one product class at a time:
make ran-run CONFIG=configs/my-ran.yaml TARGET=cucp CAMPAIGN=MY-RAN-CUCP
make ran-run CONFIG=configs/my-ran.yaml TARGET=cuup CAMPAIGN=MY-RAN-CUUP
make ran-run CONFIG=configs/my-ran.yaml TARGET=du CAMPAIGN=MY-RAN-DU
Or grade the whole gNB in one run, which produces the three scorecards plus a composite verdict:
make ran-run CONFIG=configs/my-ran.yaml TARGET=all CAMPAIGN=MY-RAN-001
One campaign per rig. A run does not merely read the RAN, it owns it. Bring-up begins by stopping every product and the UE, and the products write their captures to fixed paths. Two campaigns at once tear down each other's stack mid-attach and overwrite each other's evidence, and the results look like a broken RAN rather than a collision. A second concurrent run is refused.
Read the verdict and scorecard
Every run writes campaigns/<id>/scorecard.md and .html and embeds a verdict block in results.json.
====================================================================
CNTC VERDICT, profile: du-conformance (catalog v1.0.0)
O-DU (gNB-DU), CNTC Level 1 (Conformance & Observable Security)
====================================================================
[PASS] * DU-F1-01 F1 Setup Request -> Response status == pass
[PASS] * DU-F1-03 UE Context Setup, SRB/DRB admitted status == pass
[PASS] * DU-F1-07 F1 recovery after CU restart status == pass
[PASS] * DU-CELL-01 MIB + SIB1 consistent with F1 Setup status == pass
[PASS] * DU-UP-01 F1-U tunnel on the signalled TEID status == pass
[ na ] DU-SEC-01 F1-C transport protection na: loopback, not observable here
[PASS] * DU-NEG-01 Malformed GTP-U, no crash status == pass
essential gate (all): 7 passed / 0 failed / 0 na (of 7)
RESULT: PASS
Three outcomes, and the third is the important one:
- PASS, every essential requirement was exercised and met.
- FAIL, an essential requirement was exercised and not met.
- INCOMPLETE, an essential requirement could not be judged. It is never silently promoted to a pass.
Each run also prints the problems grouped by underlying fault, rather than as a list of symptoms to correlate by hand:
PROBLEMS FOUND
- the null ciphering algorithm NEA0 is in use, so nothing is encrypted
(2 requirements: CUCP-SEC-02, CUCP-SEC-03)
- no IPsec on the interfaces that leave the host
(1 requirement: CUCP-SEC-06)
What na means, and why there is a lot of it
na is not a hedge. It records that the evidence needed to judge a requirement was not obtained on this deployment, and it names what would be needed. Three real examples from the reference run:
- the core never sent an NGAP UE Context Release Command, so the gNB was never asked to release the UE context (core behaviour, not a RAN result);
- F1-C runs only between loopback addresses, so this deployment co-locates the endpoints and the traffic never leaves the host. Its transport protection is neither exercised nor observable here. Deploy the products on separate hosts to certify this requirement;
- the N2 path could not actually be interrupted on this rig, so no recovery was exercised and none could be judged. The AMF is reached through an address that is translated before the firewall rule applies (a Kubernetes ClusterIP).
Most test tools would have reported these three as failures against the RAN. None of them is a RAN defect. Reporting them honestly is the point of the framework.
The certificate
A certificate is issued automatically only when every essential requirement passes:
# PASS only
make ran-certify CAMPAIGN=MY-RAN-DU TARGET=du
It records the profile, the catalog version, the standards covered, and the essential-gate result, so it is auditable against the campaign that produced it. A run that fails or cannot judge an essential requirement yields no certificate.
Every campaign keeps its captures and the product logs under campaigns/<id>/raw/, so any finding can be re-checked against the evidence it was drawn from. That is the difference between a report and a certification: anyone can verify the claim themselves.
Live dashboard
# http://<host>:8050 (or: make dashboard-bg to run detached)
make dashboard
The dashboard renders every campaign: the three product classes with their verdicts, per-test cards with the decoded evidence, the problems found, and the certificate. A campaign appears the moment it starts and results stream in as they are decided.
Each test card separates two things that are deliberately not the same: what was observed, which decided the verdict, and the probable cause, which is inferred and tells you where to look. When a run's stimulus did not complete, the campaign page says so plainly and reports the root fault, quoted from the product's own error log.
Verified results
The framework was developed against a live OCUDU split gNB running as three processes on one host, peered with free5GC on Kubernetes, driven by an OAI nr-UE over a ZeroMQ virtual radio. All 58 requirements execute.
| Product class | Tests | pass | fail | na | Verdict |
|---|---|---|---|---|---|
| O-CU-CP | 30 | 17 | 3 | 10 | FAIL |
| O-CU-UP | 14 | 8 | 2 | 4 | FAIL |
| O-DU | 14 | 9 | 0 | 5 | PASS, certified 7/7 essential |
It found real defects rather than rubber-stamping.
The finding: algorithm downgrade
The security policy signals confidentiality as required, and the algorithm actually selected is the null cipher NEA0, while integrity receives a real algorithm. This is not a broken stack. It is specifically ciphering being downgraded.
Read directly off the wire from the E1AP Bearer Context Setup Request:
BearerContextSetupRequest
cipheringAlgorithm: nEA0 (0)
integrityProtectionAlgorithm: i-128-NIA2 (2)
securityIndication
integrityProtectionIndication: not-needed (2)
confidentialityProtectionIndication: required (0)
Confirmed independently on two product classes over two interfaces: CUCP-SEC-02 and CUCP-SEC-03 from the F1AP capture, CUUP-SEC-01 from the E1AP capture. You can verify it yourself from any published campaign:
tshark -r campaigns/MY-RAN-CUUP/raw/pcap/cuup.e1ap.pcap -Y e1ap.cipheringAlgorithm -V \
| grep -E "cipheringAlgorithm:|integrityProtectionAlgorithm:|ProtectionIndication:"
The second finding: unprotected transport
N2 and N3 leave the host with no IPsec security association covering them (CUCP-SEC-06, CUUP-SEC-04). F1-C, F1-U and E1 run between loopback addresses on this single-host rig, so their transport protection is not observable and records na naming the deployment change that would allow it to be certified.
Supported RAN stacks and cores
One adapter per RAN stack keeps them comparable. An adapter implements a small contract: describe, endpoint, liveness, start, stop, capture paths. The 58 test cases and the grading layer do not change, because they work on decoded captures and socket state rather than anything stack-specific.
| RAN | Split | State |
|---|---|---|
| OCUDU (Linux Foundation CU/DU, srsRAN lineage) | CU/DU, Option 2 with E1 | validated end to end |
| srsRAN Project | CU/DU, Option 2 | planned |
| OpenAirInterface gNB | CU/DU and monolithic | planned |
| O-RAN Software Community (O-RAN-SC) | CU/DU, Option 2 | planned |
For the core, any 5G core works. Set core.adapter: manual and ranbench will not probe or modify it; you confirm it is up with the SIM provisioned. A free5gc_k8s adapter adds optional convenience helpers (subscriber provisioning, an N2 reachability probe, a subscription-data check). Adapters declare only what their core can do, and a core that declares nothing is a fully supported configuration.
Troubleshooting
ran-doctorreports no pcaps enabled. The RAN stack's per-interface capture is off, which is the usual default. Enable it in each product's config. Without it, protocol and security requirements can only recordna.- The UE never synchronises to the cell. The UE's radio parameters do not match the O-DU's cell.
make ran-configurederives them from the O-DU config for exactly this reason. Check ARFCN, SSB position, bandwidth, subcarrier spacing and sample rate. - Registration succeeds but the PDU session is refused. This is a core fault, not a RAN fault.
ran-doctormakes the same subscription-data request the SMF makes and reports it. - The run reports STIMULUS INCOMPLETE. The attach did not finish, so requirements past the break recorded
naand the run does not measure the RAN. The console and the campaign page name the root fault, quoted from the product's own log. - A second run is refused. Another campaign is driving the rig. Runs cannot overlap.
- A product exits at startup with "Connection refused". Some stacks make exactly one association attempt and exit on failure.
ranbenchwaits for the peer's listener to be bound before starting anything that dials it, and retries the bring-up as a unit.
What is coming
Stage 3 today certifies the CU/DU split at Level 1. Four extensions are planned, and the framework was built so that each of them adds catalogs and adapters rather than changing the grading core.
More decomposition points
The CU/DU split is the first split CNTC certifies, not the only one worth certifying.
FAPI and nFAPI, the L2 to L1 boundary. The Small Cell Forum's Functional API (SCF 222) defines the interface between MAC and PHY, and nFAPI (SCF 225) wraps it in a network transport so L2 and L1 can be separate products from separate vendors. That is functional split Option 6, and it is exactly the kind of boundary a certification framework belongs at: two independently supplied halves that must interoperate against a published specification. The plan is a product class pair for L2 and L1 with its own catalog, driven and observed over the P5 configuration and P7 data interfaces the same way F1 is driven today.
O-RAN Open Fronthaul, the O-DU to O-RU boundary. Split Option 7.2x with its Control, User, Sync and Management planes is the other split with real multi-vendor deployment, and the O-RAN ALLIANCE has published both interoperability and security requirements for it. Certifying an O-RU against its O-DU, and the fronthaul itself, extends the same model down one more layer.
Monolithic gNB. A gNB deployed as a single process can be certified over the interfaces it does expose, N2 and N3, with a catalog scoped to what is externally observable. It will never yield three product-class certificates, because the interfaces that separate those classes are internal, and the framework will say so rather than implying otherwise.
More open-source RAN stacks
The RAN-specific code is confined to one adapter, which implements a small contract: describe, endpoint, liveness, start, stop, capture paths. The 58 requirements and the grading layer work on decoded captures and socket state, so they do not change when the stack does.
Support is planned for the other open-source RAN implementations in wide use, including srsRAN Project, the OpenAirInterface gNB and the O-RAN Software Community stack. Contributions of new adapters are welcome, and an adapter is the smallest useful contribution anyone can make to this project.
Level 2, adversarial robustness
Level 1 asks whether the product does the right thing when its peers behave. Level 2 asks what it does when they do not: a peer that replays an old message, presents a wrong key, attempts a bidding-down attack, or reuses a key that should have been refreshed.
18 Level-2 requirements are already drafted as roadmap catalogs in the repository, anchored to TS 33.511 and TS 33.523:
| Product class | Level 2 requirements |
|---|---|
| O-CU-CP | 10 |
| O-CU-UP | 4 |
| O-DU | 4 |
They cover RRC replay protection, integrity check failure handling, key refresh without reuse, bidding-down prevention at handover, and algorithm selection at gNB change. They need a deliberately misbehaving peer to execute, which is the work still to do.
Performance and scale
Level 1 asks whether the RAN is correct. It does not yet ask how much it can carry. A performance profile for the RAN, graded relative to a baseline on the same rig class in the same way the UPF performance profile works, is the natural next axis: attach rate, concurrent UE capacity, and throughput per bearer.
Standards and references
Protocol specifications
| Specification | Covers |
|---|---|
| 3GPP TS 38.413 | NG Application Protocol (NGAP), the N2 interface |
| 3GPP TS 38.412 | NG signalling transport (SCTP) |
| 3GPP TS 38.473 | F1 Application Protocol (F1AP), the F1-C interface |
| 3GPP TS 38.472 | F1 signalling transport (SCTP) |
| 3GPP TS 38.463 | E1 Application Protocol (E1AP), the E1 interface |
| 3GPP TS 38.462 | E1 signalling transport (SCTP) |
| 3GPP TS 38.331 | NR Radio Resource Control (RRC) |
| 3GPP TS 38.321 | NR Medium Access Control (MAC), random access |
| 3GPP TS 38.425 | NR user plane protocol (NR-U), F1-U |
| 3GPP TS 38.415 | PDU session user plane protocol, QFI marking on N3 |
| 3GPP TS 29.281 | GPRS Tunnelling Protocol User Plane (GTP-U) |
| 3GPP TS 23.501 | System architecture for the 5G System |
| 3GPP TS 23.502 | Procedures for the 5G System |
Security assurance specifications
| Specification | Covers |
|---|---|
| 3GPP TS 33.523 | SCAS for the split gNB: gNB-CU-CP, gNB-CU-UP and gNB-DU product classes |
| 3GPP TS 33.511 | SCAS for the gNB |
| 3GPP TS 33.501 | Security architecture and procedures for the 5G System |
| 3GPP TS 33.117 | Catalogue of general security assurance requirements |
Architecture
| Specification | Covers |
|---|---|
| 3GPP TS 38.401 | NG-RAN architecture, the CU/DU and CU-CP/CU-UP split |
| 3GPP TR 38.801 | Study on new radio access technology, the eight functional split options |
Specifications for the roadmap
| Specification | Covers |
|---|---|
| Small Cell Forum SCF 222 | 5G PHY FAPI: the data path and PHY mode control, the MAC to PHY interface |
| Small Cell Forum SCF 225 | 5G nFAPI: a network transport wrapper around SCF 222, forming the Split Option 6 interface |
| O-RAN.WG4 | Open Fronthaul specifications: Control, User and Sync plane, and Management plane |
| O-RAN.WG5 | Interoperability profiles for the higher layer interfaces |
| O-RAN.WG11 | Security requirements and protocols for O-RAN interfaces |
Software under test and tooling
| Project | Role | License |
|---|---|---|
| OCUDU | The RAN under test, CU/DU split | BSD-3-Clause-Open-MPI |
| OpenAirInterface nr-UE | UE simulator, the stimulus | OAI Public License V1.1 |
| free5GC | 5G core peer used in validation | Apache-2.0 |
| Wireshark / tshark | Wire decode | GPL-2.0-or-later |
| CNTC | This framework | Apache-2.0 |
CNTC is a project of the TOSSI Foundation. RAN Certification is Stage 3, after UPF Certification (Stage 1) and 5G Core Control Plane Certification (Stage 2).