Bring up a complete 5G Standalone network with the Duranta gNB (an OpenAirInterface fork,
nr-softmodem) and the native OAI CN5G core on COSMOS, driving a USRP over the air on the sb1 or
grid domains, then attach a commercial Quectel 5G modem — register it, open a PDU session, reach
the internet, and measure real DL/UL throughput.
This tutorial builds a self-contained 5G SA cell using Duranta — an OAI (OpenAirInterface) fork
whose gNB binary is nr-softmodem and whose config is OAI libconfig (.conf) — paired with the
native OAI CN5G 5G core (the oai-amf / oai-smf / oai-upf / … Docker stack). The gNB runs on
an sb1 (or grid) compute node and drives a USRP (NI N310) over a real n41 (2.6 GHz) air
interface, serving a commercial Quectel RM520N-GL modem.
The stack is standardized with the other COSMOS 5G platforms: PLMN 00101 and UE pool
192.168.100.0/22 with internet access. A single one-shot script (start-all.sh) brings up the
whole thing. For the radio-less software version, see the companion
Duranta soft-only tutorial.
After completing this tutorial you will be able to:
nr-softmodem gNB + the OAI CN5G core driving a USRP on sb1/grid.LD_LIBRARY_PATH requirement and the dynamic AMF-IP handling.| Difficulty | Advanced |
| Estimated time | 60–90 min |
| Domain / sandbox | sb1 or grid (USRP radios) |
| Topic group | Cellular (4G/5G/O-RAN) |
| Last verified | 2026-07-15 |
Background knowledge
ssh/ip/iperf3 on Linux.Account & access
sb1 (or grid) for the gNB node, the USRP, and the UE node — seeDevices / nodes
| Resource | Role | Qty | Notes |
|---|---|---|---|
| a compute node with a USRP | Duranta gNB (nr-softmodem) + OAI CN5G |
1 | drives the gNB N310 (e.g. 10.39.3.1) |
| a UE node | UE host — Quectel modem | 1 | USB; AT /dev/ttyUSB2, QMI wwan0 |
| NI N310 | gNB radio | 1 | band n41, 40 MHz, 1T1R |
| Quectel RM520N-GL | commercial 5G UE | 1 | SIM provisioned to a 00101 identity |
Disk images
| Image | Load onto | Provides |
|---|---|---|
duranta.ndz |
gNB node | Duranta nr-softmodem (/opt/duranta, USRP build), OAI CN5G (/opt/oai-cn5g), one-shot start-all.sh + provision.sql, UHD 4.9 |
lte5gue.ndz |
UE node | Quectel tooling (qmicli), UHD 4.9 |
(duranta.ndz / lte5gue.ndz are symlinks to the latest dated builds.)
Software components
| Component | Version | Source |
|---|---|---|
| Duranta gNB (OAI fork) | preinstalled | /opt/duranta/cmake_targets/ran_build/build/nr-softmodem — no license needed |
| OAI CN5G (5GC) | develop (Docker) |
/opt/oai-cn5g/oai-cn5g-fed/docker-compose, docker-compose-basic-nrf.yaml |
| UHD | 4.9.0.0+ds1-1~noble2 | preinstalled |
qmicli (libqmi) |
distro | preinstalled on the UE host |
Spectrum / RF / special
absoluteFrequencySSB 518670), 40 MHz (106 PRB),00101, DNN oai, IMSI 001010000000032 (modem). UE pool192.168.100.0/22, gateway 192.168.100.1, internet-NAT'd (via oai-ext-dn).The gNB + core run on an sb1/grid node with a local USRP; the Quectel modem is on USB on the UE node:
gNB node (sb1 / grid) UE node
┌────────────────────────┐ ┌────────────────────────┐
│ Duranta nr-softmodem │ n41 2.6 GHz │ Quectel RM520N-GL │
│ OAI CN5G (Docker) │◄─────────────►│ │
│ UE pool 192.168.100/22 │ USRP N310 │ wwan0 → 192.168.100.x │
│ ext-dn NAT → internet │ (OTA) │ │
└────────────────────────┘ └────────────────────────┘
Duranta/OAI needs no license server — you just launch the binary (with LD_LIBRARY_PATH set,
because the build was relocated to /opt/duranta; start-all.sh handles it).
ssh <username>@console.<domain>.cosmos-lab.org # e.g. console.sb1... or console.grid...
-r 0 — also expands the Duranta root partition):omf load -i duranta.ndz -t <gnb-node> -r 0 -o 1200
omf load -i lte5gue.ndz -t <ue-node> -r 0 -o 1200
omf tell -a on -t <gnb-node>,<ue-node>
Why PLMN 00101? A soft UE can only form a 2-digit MNC, so
00101is the canonical testbed PLMN;
the COTS SIM is provisioned to match. See the platforms overview.
The image ships /root/duranta/start-all.sh, a validated one-shot that brings the whole stack up in the
right order:
ssh root@<gnb-node>
/root/duranta/start-all.sh
It (1) applies the RF-underflow IRQ fix; (2) brings up OAI CN5G (docker compose up -d); (3) provisions
the subscribers (provision.sql: PLMN 00101, DNN oai, IPv4); (4) reads the dynamic AMF container
IP and writes it into the gNB .conf (the OAI CN5G AMF IP changes across restarts — this step is
essential); (5) derives the DATA interface and pins the USRP route on it — never falling back to
CTRL; and (6) launches the gNB (nr-softmodem with LD_LIBRARY_PATH set). Confirm:
grep -aE 'got sync|is in service|NGAP_REGISTER_GNB_CNF' /tmp/oai-gnb.log | tail -3
docker compose -f /opt/oai-cn5g/oai-cn5g-fed/docker-compose/docker-compose-basic-nrf.yaml ps
[NR_RRC] cell PLMN 001.01 Cell ID ... is in service
[PHY] got sync (ru_thread)
got sync = the radio is locked and the cell is on air. To stop the gNB later, use
pkill -x nr-softmodem.
ssh root@<ue-node>
systemctl stop ModemManager
python3 - <<'PY'
import serial, time
s = serial.Serial("/dev/ttyUSB2", 115200, timeout=3)
at = lambda c, w=1.0: (s.write((c+"\r\n").encode()), time.sleep(w), s.read_all().decode())[2]
at('AT+QNWPREFCFG="mode_pref",NR5G')
at('AT+QNWPREFCFG="nr5g_disable_mode",0') # 0 = SA enabled
at('AT+CGDCONT=1,"IP","oai"') # IPv4 APN, matches the CN5G DNN "oai"
at('AT+COPS=2',2); at('AT+CFUN=0',3); at('AT+CFUN=1',5); at('AT+COPS=1,2,"00101",12',6) # lock PLMN
time.sleep(10)
print("C5GREG:", at('AT+C5GREG?')); print("QENG:", at('AT+QENG="servingcell"'))
s.close()
PY
echo Y > /sys/class/net/wwan0/qmi/raw_ip; ip link set wwan0 up
qmicli -d /dev/cdc-wdm0 --wds-start-network="apn=oai,ip-type=4" --client-no-release-cid
IP=$(qmicli -d /dev/cdc-wdm0 --wds-get-current-settings | awk '/IPv4 address/{print $NF}')
ip addr add $IP/22 dev wwan0; ip link set wwan0 mtu 1400 up
ip route replace 192.168.100.0/22 dev wwan0
ping -I wwan0 -c 5 192.168.70.131 # OAI ext-DN
ping -I wwan0 -c 5 8.8.8.8 # the internet
+C5GREG: 0,1 # registered, home, on NR5G-SA 001/01
IPv4 address: 192.168.100.2
5 packets transmitted, 5 received, 0% packet loss (8.8.8.8)
Use only a scoped route (
192.168.100.0/22 dev wwan0) — never adefault via wwan0. If
--wds-start-networkreturnsCallFailed, clear stale UPF state (see Troubleshooting). If the first
ping batch shows 100% loss, that's the modem leaving RRC-idle — the next batch succeeds.
cell PLMN 001.01 … is in service and got sync.+C5GREG: 0,1 on 001/01; the AMF logs 5GMM-REGISTERED for the SUPI.192.168.100.0/22; ping 192.168.70.131 and ping 8.8.8.8 = 0% loss.[HW] [xmit] tx samples 0 underflow in /tmp/oai-gnb.log.Measure air capacity with UDP. Run an iperf3 server inside the oai-ext-dn container (the data
network) and keep a keepalive ping running:
# on the gNB/core node:
docker exec -d oai-ext-dn iperf3 -s
# on the UE node:
ping -I wwan0 -i 0.3 192.168.70.131 >/dev/null 2>&1 &
iperf3 -c 192.168.70.131 -B $IP -u -b 150M -R -t 12 # DL
iperf3 -c 192.168.70.131 -B $IP -u -b 20M -t 12 # UL
Measured (band n41, 40 MHz, 30 kHz SCS, TDD DDDSU):
| UE | IP | RTT | DL (UDP) | UL (UDP) | Notes |
|---|---|---|---|---|---|
| Quectel RM520N-GL (COTS) | 192.168.100.2 |
~20 ms | ≈115 Mbit/s | ≈4 Mbit/s | air-limited; UL bounded by the single UL slot in DDDSU |
ssh root@<gnb-node> 'pkill -x nr-softmodem;
docker compose -f /opt/oai-cn5g/oai-cn5g-fed/docker-compose/docker-compose-basic-nrf.yaml down'
ssh root@<ue-node> 'qmicli -d /dev/cdc-wdm0 --wds-stop-network=disable-autoconnect 2>/dev/null'
omf tell -a offh -t <gnb-node>,<ue-node>
| Symptom | Likely cause | Fix |
|---|---|---|
gNB: dlopen(libparams_libconfig.so) … No such file → exits |
relocated build, stale rpath | export LD_LIBRARY_PATH=/opt/duranta/cmake_targets/ran_build/build:$LD_LIBRARY_PATH (start-all.sh does this) |
gNB aborts on UHD stream / start-all.sh says route via CTRL |
fronthaul route fell back to the control net | start-all.sh derives the DATA iface and refuses to launch over CTRL — re-run it (it pins the route on the DATA iface) |
gNB: NGSetup fails / AMF not associated |
OAI CN5G AMF IP is dynamic and the .conf has a stale one |
re-run start-all.sh (it reads docker inspect oai-amf and updates amf_ip_address) |
Modem registers but --wds-start-network → CallFailed |
stale UPF F-TEID after failed PDU cycles | docker compose restart oai-upf oai-smf |
[HW] [xmit] tx samples 0 != …, UE DL glitchy |
fronthaul NIC IRQs on the PHY cores | start-all.sh stops irqbalance + remaps DATA IRQs to housekeeping cores |
Modem won't camp (SEARCH) |
nr5g_disable_mode=1 disables SA on RM520N-GL |
set to 0; lock AT+COPS=1,2,"00101",12 |
Modem: Unknown DNN reject |
APN ≠ the CN5G DNN | AT+CGDCONT=1,"IP","oai" (not internet); clear back-off with AT+CFUN=1,1 |
UE ping 8.8.8.8 fails, ext-DN pings |
ext-dn NAT / route stale | docker exec oai-ext-dn ping 8.8.8.8 to confirm; restart the stack so ext-dn re-reads UE_NETWORK=192.168.100.0/22 |
nr-softmodem as annr-uesoftmodem as the client — no USRP. See the.conf TDD pattern for more UL atoai-gnb skill.omf-duranta; OAI libconfig + build — skill oai-gnb.basic-nrf config (/opt/oai-cn5g/oai-cn5g-fed/docker-compose); Quectel modem — skill quectel-5g-modem.Author(s): COSMOS team · Last verified: 2026-07-15 · Tested image/release: duranta.ndz (Duranta/OAI nr-softmodem USRP build + OAI CN5G) + lte5gue.ndz, PLMN 00101, pool 192.168.100.0/22 · Tags: duranta, oai, openairinterface, oai-cn5g, 5g, sa, sdr, usrp, quectel