Bring up a complete 5G Standalone network with the permissively-licensed OCUDU gNB (built on
srsRAN Project) and a native Open5GS core on COSMOS, driving a USRP over the air on the sb1 or
grid domains, then attach two different UEs to the same cell — a commercial Quectel 5G modem
and Amarisoft's soft lteue — register each, open a PDU session, reach the internet, and measure real
DL/UL throughput.
This tutorial builds a license-free 5G SA cell using OCUDU — a combined CU/DU/RU built on srsRAN
Project (standard srsRAN YAML config, no license server) — paired with a native Open5GS 5G core.
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.
The point of the tutorial is cross-vendor interoperability: the same OCUDU cell and Open5GS core
serve two completely different UEs back-to-back — a commercial off-the-shelf Quectel RM520N-GL modem
and Amarisoft's software UE (lteue) running over a second USRP. You will learn the one core-side fix
that makes non-srsUE UEs work (IPv4-only PDU sessions), why the testbed PLMN is 00101, and the
RF-underflow fix that keeps the radio glitch-free. All UEs get an IP in the standard 192.168.100.0/22
pool with internet access.
For the radio-less software version, see the companion
OCUDU soft-only tutorial.
After completing this tutorial you will be able to:
sb1/grid.lteue to the same cell/core.| 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(s), and the UE node — seeDevices / nodes
| Resource | Role | Qty | Notes |
|---|---|---|---|
| a compute node with a USRP | OCUDU gNB (gnb) + Open5GS core |
1 | drives the gNB N310 (e.g. 10.39.3.1) |
| a UE node | UE host — Quectel modem or soft lteue |
1 | one UE at a time on the same cell |
| NI N310 | gNB radio | 1 | band n41, 40 MHz, 1T1R |
| NI N310 or X310 | soft-UE radio (for lteue) |
1 | driven by lteue over UHD |
| Quectel RM520N-GL | commercial 5G UE | 1 | USB on the UE node; AT /dev/ttyUSB2, QMI wwan0 |
Disk images
| Image | Load onto | Provides |
|---|---|---|
ocudu.ndz |
gNB node | OCUDU gnb (/opt/ocudu), Open5GS (/opt/open5gs), launch scripts, UHD 4.9, IRQ-affinity service |
lte5gue.ndz |
UE node | Amarisoft lteue, UHD 4.9 + trx_uhd, amari_cmd.py, UE configs (soft-UE path); qmicli (modem path) |
(ocudu.ndz / lte5gue.ndz are symlinks to the latest dated builds.)
Software components
| Component | Version | Source |
|---|---|---|
| OCUDU gNB (srsRAN Project) | preinstalled | /opt/ocudu/build/apps/gnb/gnb — no license needed |
| Open5GS 5GC | build-tree | /opt/open5gs, launcher /root/open5gs/start.sh |
Amarisoft lteue |
2026-06-12 | preinstalled in the UE image (soft-UE path only) |
qmicli (libqmi) |
distro | preinstalled on the UE host (COTS-modem path) |
Spectrum / RF / special
00101 (MCC 001 / MNC 01). Soft UEs (lteue, srsue) can only form a 2-digit MNC,192.168.100.0/22, gateway 192.168.100.1,The gNB + core run on an sb1/grid node with a local USRP; a second node hosts the UE (modem on USB,
or soft lteue on a second USRP):
gNB node (sb1 / grid) UE node
┌───────────────────────┐ ┌────────────────────────┐
│ OCUDU gnb (srsRAN) │ n41 2.6 GHz │ Quectel RM520N-GL │
│ Open5GS 5GC │◄─────────────►│ —or— │
│ ogstun 192.168.100.1 │ USRP N310 │ lteue → 2nd USRP │
│ AMF 127.0.0.5:38412 │ (OTA) │ │
└───────────────────────┘ └────────────────────────┘
OCUDU needs no license server — you just launch the binary.
ssh <username>@console.<domain>.cosmos-lab.org # e.g. console.sb1... or console.grid...
-r 0):omf load -i ocudu.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 splits its IMSI as MCC(3)+MNC(2)+MSIN(10) — it cannot form a 3-digit
MNC. Pointing one at a 3-digit PLMN makes it emit a mismatched SUCI → the AMF returns Registration
reject [95]. Only a COTS modem can do a 3-digit MNC. So the testbed standardises on 00101.
ssh root@<gnb-node>
/root/open5gs/start.sh # NFs + ogstun 192.168.100.1/22 + NAT→internet + provisions subscribers
ss -lnp -A sctp | grep 38412
LISTEN 0 0 127.0.0.5:38412 0.0.0.0:* users:(("open5gs-amfd",...))
The launcher provisions subscribers from subscriber_db.csv, sets up ogstun on 192.168.100.1/22
with a MASQUERADE rule (so UEs reach the internet), and pins the NFs to housekeeping cores.
Cross-vendor PDU fix — do this once. The srsRAN/OCUDU gNB supports IPv4 only. Amarisoft
lteueand many COTS modems request a dual-stack IPv4v6 PDU session by default, which the gNB
rejects every time ([NGAP] Unsupported PDU Session Type: ipv4v6). Force each subscriber's session to
IPv4 (type:1):mongosh open5gs --quiet --eval 'db.subscribers.updateMany({}, {$set: {"slice.0.session.0.type": 1}})'
Also provision the modem's SIM (IMSI 001010000000032) if it is not already present — see the modem
path below.
Launch the gNB with the config for your reserved USRP (gnb-ocudu-40.yml targets a local N310 — edit
ru_sdr.device_args addr= to match your radio):
cd /opt/ocudu/build
setsid bash -c './apps/gnb/gnb -c /root/ocudu/gnb-ocudu-40.yml' >/tmp/gnb.log 2>&1 &
sleep 15
grep -aE 'Cell pci|Cell was activated|gNB started' /tmp/gnb.log | tail -3
ss -np -A sctp | grep -E 'ESTAB.*38412'
Cell pci=1, bw=40 MHz, 1T1R, band n41
==== gNB started ===
ESTAB 0 0 127.0.0.5:38412 127.0.0.1:... users:(("open5gs-amfd",...))
An established SCTP association = NG Setup succeeded and the cell is on air. Stop the gNB later with
pkill -x gnb — never pkill -f apps/gnb/gnb, which matches (and kills) your own SSH shell.
RF underflow. If the gNB log shows
[RF] Real-time failure in RF: underflowand UEs see CQI 1–2 /
drop mid-registration, the NIC IRQs are landing on the gNB PHY cores. The image fixes this: an enabled
ocudu-irq-affinity.servicepins the DATA-iface IRQs to housekeeping cores andopen5gs/start.sh
pins the NFs there too. Verify withsystemctl is-enabled ocudu-irq-affinity.
mongosh open5gs --quiet --eval '
db.subscribers.deleteOne({imsi:"001010000000032"});
db.subscribers.insertOne({imsi:"001010000000032",
slice:[{sst:1,default_indicator:true,session:[{name:"internet",type:1,
ambr:{uplink:{value:1,unit:3},downlink:{value:1,unit:3}},
qos:{index:9,arp:{priority_level:8,pre_emption_capability:1,pre_emption_vulnerability:1}}}]}],
ambr:{uplink:{value:1,unit:3},downlink:{value:1,unit:3}},
security:{k:"9E53736BC519AB701CCAA585BDD529EC",amf:"9001",
op:"11111111111111111111111111111111",opc:null},
subscribed_rau_tau_timer:12, network_access_mode:2, subscriber_status:0,
access_restriction_data:32, schema_version:1, __v:0});'
(Adjust IMSI / K / OP to your own SIM.)
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 (1 DISABLES SA on this modem!)
at('AT+CGDCONT=1,"IP","internet"') # IPv4 APN
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(8)
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=internet,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.100.1 # core gateway
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(it would
black-hole the node's management SSH). If the first ping batch shows 100% loss, that's the modem
leaving RRC-idle — the next batch succeeds.
lteue (soft UE, over a second USRP)ssh root@<ue-node>
cd /opt/amarisoft/lteue-linux-2026-06-12
ip link set DATA2 mtu 9000
setsid bash -c "tail -f /dev/null | ./lteue-avx2 config/ue-ocudu-00101.cfg" >/tmp/ue_run.log 2>&1 &
sleep 12
python3 amari_cmd.py 9002 '{"message":"power_on","ue_id":1}' # lteue does NOT auto-attach; retry if it races
sleep 10
ip netns exec ue1 ip -br addr show pdn0
ip netns exec ue1 ping -c 4 192.168.100.1
ip netns exec ue1 ping -c 4 8.8.8.8
The soft UE registers and gets an IP in 192.168.100.0/22 — same cell, same core as the modem.
(⚠️ after killing lteue with pkill, wait ~20 s for the USRP MPM claim to release before
relaunching, or the next lteue aborts with Someone tried to claim this device again.)
Cell pci=1 … band n41 and an established SCTP association on port 38412.+C5GREG: 0,1 on 001/01; or lteue reaches 5GMM-REGISTERED.192.168.100.0/22; ping 192.168.100.1 and ping 8.8.8.8 = 0% loss.[RF] … underflow (IRQ-affinity service active).Measure air capacity with UDP (TCP is RTT-limited). Run an iperf3 server bound to ogstun and keep
a keepalive ping running so the UE never drops to RRC-idle:
# on the gNB/core node:
iperf3 -s -B 192.168.100.1 -D
# on the UE node — Quectel path:
ping -I wwan0 -i 0.3 192.168.100.1 >/dev/null 2>&1 &
iperf3 -c 192.168.100.1 -B $IP -u -b 150M -R -t 12 # DL
iperf3 -c 192.168.100.1 -B $IP -u -b 40M -t 12 # UL
# lteue path: prefix each iperf3/ping with ip netns exec ue1
Measured (band n41, 40 MHz, 30 kHz SCS):
| UE | IP | DL (UDP) | UL (UDP) | Notes |
|---|---|---|---|---|
| Quectel RM520N-GL (COTS) | 192.168.100.2 |
≈120 Mbit/s | ≈20 Mbit/s | real modem RF frontend |
Amarisoft lteue (soft) |
192.168.100.x |
≈14–50 Mbit/s | ≈20–30 Mbit/s | soft UE over a second USRP |
UL is bounded by the TDD pattern.
ssh root@<gnb-node> 'pkill -x gnb; pkill -x iperf3; /root/open5gs/stop.sh'
ssh root@<ue-node> 'pkill -x lteue-avx2; 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: NG Setup … unknown-PLMN-or-SNPN |
core PLMN ≠ gNB plmn |
align both to 00101; restart the gNB (NG Setup is sent once) |
Registration OK but no PDU; Unsupported PDU Session Type: ipv4v6 |
UE requested dual-stack; gNB is IPv4-only | set subscriber session.type:1 (Step 1), power-cycle the UE |
Soft UE: Registration reject [95] |
3-digit MNC on a soft UE | use PLMN 00101 end-to-end |
| gNB relaunch → SSH exit 255, no output | pkill -f apps/gnb/gnb killed your shell |
use pkill -x gnb |
[RF] … underflow, UE CQI 1–2 |
NIC IRQs on the PHY cores | ensure ocudu-irq-affinity.service is active |
gNB aborts: free_buff_pool::pop … Assertion |
USRP streaming wedged (shared radio switched stacks) | omf tell -a reset the USRP host, then one clean start |
Modem detects the cell but won't camp (SEARCH) |
nr5g_disable_mode=1 disables SA on RM520N-GL |
set nr5g_disable_mode to 0; lock AT+COPS=1,2,"00101",12 |
UE ping 8.8.8.8 fails, gateway pings |
no internet NAT | open5gs/start.sh adds MASQUERADE -s 192.168.100.0/22 ! -o ogstun |
lteue aborts: Someone tried to claim this device again |
previous lteue didn't release the USRP claim |
wait ~20 s after pkill (or use SIGTERM, not -9) before relaunch |
srsue + Open5GS over a ZMQ virtual radio on one node — no USRP.gnb-ocudu-40-l3.yml (recv/send_frame_size 7900) and ip route replace 10.39.0.0/16 via <data-gw> dev <data-iface> so the USRP subnet never falls back to the control net.omf-ocudu; srsRAN-Project YAML — skill ocudu-gnb (docs.srsran.com).open5gs-core; Quectel modem — skill quectel-5g-modem.Author(s): COSMOS team · Last verified: 2026-07-15 · Tested image/release: ocudu.ndz (OCUDU srsRAN + Open5GS) + lte5gue.ndz, PLMN 00101, pool 192.168.100.0/22 · Tags: ocudu, srsran, open5gs, 5g, sa, sdr, usrp, quectel, lteue, cross-vendor