nr-oru on a USRP over O-RAN 7.2 fronthaulSplit the Duranta gNB at the O-RAN 7.2 fronthaul: an O-DU (nr-softmodem + DPDK/xran) on one sb1 server, the OAI soft O-RU (nr-oru) driving a USRP N310 on a second sb1 server, eCPRI over VLAN 4 between them, OAI CN5G behind the O-DU and a commercial modem as the UE. The result is a working 7.2 split with no vendor O-RU — the same fronthaul path the LiteOn/Benetel setups use, with an SDR in the O-RU role.
Status (2026-09-16): WORKING end to end. The rig is now entirely on osc — O-DU
srv19.osc, O-RUsrv20.oscdriving its local USRP B210 over USB 3, UEnode1.osc— and the modem registers, gets an IP and passes traffic: DL 30.7–34.3 / UL 7.3 Mbit/s, 0 % ping loss to the local DN and to the internet, RSRP −91, SINR 18. The long-standing Msg3 failure was a configuration mismatch, not a code defect:nr-orusupports only an uncompressed U-plane (upstreamdoc/ORAN_FHI7.2_ORU_Tutorial.md§7.5), while the O-DU was running BFP-9. See Troubleshooting for the full elimination trail.
The O-RAN 7.2 split moves the low PHY (IFFT/FFT, CP, PRACH extraction) into the O-RU and carries frequency-domain IQ as eCPRI over Ethernet. Duranta ships both halves: the O-DU is the ordinary nr-softmodem with the fhi_72 transport (Intel xran library on a DPDK-bound E810 VF), and nr-oru is a software O-RU that implements the O-RU side of the same protocol and uses a USRP as its RF front end (Split 8 southbound).
Running the O-RU in software lets you study the 7.2 timing model (T1a/T2a/Ta4 windows, C-plane/U-plane sequencing, PRACH C-plane) with counters and logs on both ends, something a commercial O-RU never shows you. It also exposes the parts that a hardware O-RU does in its FPGA: sample-to-frame anchoring, TDD switching and gain handling — which is exactly where the open items of this tutorial live.
Everything here runs on sb1: two servers with Intel E810 fronthaul NICs on the shared VLAN-4 fabric, the sb1 N310 as the radio, and the Quectel modem on sdr1-in3.sb1 as the UE. Do not drive an sb1 radio from another domain: the osc↔sb1 data gateway cannot carry a 30.72 Msps IQ stream alongside other users' streams (measured 2026-09-07).
nr-oru soft O-RU + USRP on another, connected by eCPRI over VLAN 4 with PTP (G.8275.1) on both ends.drop/ecpri/cp/up/pusch/prach errors), and the O-RU's packet-processor statistics and timing histograms.| Difficulty | Advanced |
| Estimated time | 90–120 min |
| Domain / sandbox | sb1 (two E810 servers + one N310 + sdr1-in3 modem host) |
| Topic group | Cellular (4G/5G/O-RAN) |
| Last verified | 2026-09-16 on osc (O-DU srv19.osc ↔ O-RU srv20.osc + its USB-3 B210, UE node1.osc), image duranta-20260916 (Duranta 2026.w37) — registration, PDU session and data plane all verified |
| Upstream source | Duranta tag 2026.w37 (commit 1143f75) + the COSMOS patch set, including the four nr-oru fixes — image duranta-20260917 |
| Upstream source | Duranta (LF Networking) — github.com/duranta-project/openairinterface5g, tag 2026.w36 (commit b3930e39f5) + COSMOS patches |
Background knowledge
Account & access
console.sb1.cosmos-lab.org — see SSH access.Devices / nodes
| Resource | Role | Qty | Notes |
|---|---|---|---|
srv1-lg1.sb1 |
O-DU (nr-softmodem + fhi_72) + OAI CN5G |
1 | Intel E810 (ens5f0, PCI 0000:86:00.0) on the fronthaul VLAN 4 |
srv2-lg1.sb1 |
soft O-RU (nr-oru) |
1 | E810 ens5f0 on VLAN 4 (eCPRI) + ens5f1 on the 10.39/16 data net to the N310 |
USRP N310 (e.g. sdr1-s1-lg1.sb1, sfp0 10.39.2.1) |
RF front end of the soft O-RU | 1 | 122.88 MHz master clock; RX on RX2, TX on TX/RX; 10 GbE, MTU 9000, frame size 7900 |
sdr1-in3.sb1 |
UE host (Quectel RM520N-GL, IMSI 001010000000032) |
1 | AT on /dev/ttyUSB2, QMI on /dev/cdc-wdm0 |
Disk images
| Image | Load onto | Provides |
|---|---|---|
duranta.ndz (= duranta-20260907c) |
both servers | nr-softmodem (fhi_72/xran), nr-oru, OAI CN5G, /root/oru/ configs, bring-up scripts, cosmos-ofh-isolation |
(duranta.ndz is a symlink to the latest dated build. The sdr1-in3 modem host keeps its own image.)
Software components
| Component | Version | Source |
|---|---|---|
Duranta nr-softmodem / nr-oru |
tag 2026.w36 + COSMOS patches (TA-alias guard, M-Plane get-xml/get-yang) |
/opt/duranta in the image |
| Intel xran (fhi_lib) | K release 11.1.6, DPDK 23.11 | built into the image |
| UHD | 4.9 | image |
linuxptp (ptp4l, phc2sys) |
image, G.8275.1 profile /etc/linuxptp/ptp4l-g8275.conf |
image |
| OAI CN5G | docker images in /opt/oai-cn5g |
image |
nr-oru fixes (this tutorial) |
configs/nroru-b210/patches/nr-oru-fixes-2026-09-07.diff in oai/duranta-cosmos |
applied on the O-RU host (see Setup 6) |
Spectrum / RF / special
Band n78, 51 PRB / 20 MHz at 3550.08 MHz (SSB ARFCN 636672, pointA 636060), 30 kHz SCS, TDD DDDSU (2.5 ms). The N310 transmits at ~35 dB TX gain (OAI_ORU_TX_GAIN=30, i.e. 65 − 30) which covers a UE in the same room; RX gain 40–60 dB. Keep the O-RU's own TX leakage in mind: with the antennas 1 m apart the uplink noise floor rises ~10 dB while transmitting.
sdr1-in3.sb1 ───(air, n78)──► N310 (sdr1-s1-lg1) ◄── 10 GbE (10.38/16, MTU 9000) ── srv2-lg1.sb1 : nr-oru (soft O-RU)
Quectel …032 TX/RX out, RX2 in │ E810 VF pair, VLAN 4 (eCPRI C/U-plane), PTP slave
│
fronthaul fabric (VLAN 4, G.8275.1 grandmaster)
│
srv1-lg1.sb1 : nr-softmodem O-DU (fhi_72/xran, DPDK VF pair, PTP slave)
+ OAI CN5G (docker, AMF/SMF/UPF) + NAT to the lab/internet
Control plane: both servers via console.sb1 / their CTRL interfaces. Fronthaul: two SR-IOV VFs on each server's E810 port, bound to vfio-pci, on VLAN 4; the O-DU addresses the O-RU VF MACs (00:11:22:33:64:66/67), the O-RU addresses the O-DU VF MACs (00:11:22:33:64:68/69). Radio: the O-RU host reaches the N310's SFP over the sb1 10 GbE data net (no gateway). UE: the modem on sdr1-in3 camps on the cell over the air.
ssh <username>@console.sb1.cosmos-lab.org
omf tell sdr1-in3 — it is a modem host with its own power policy; leave it as it is):omf tell -a offh -t srv1-lg1,srv2-lg1
omf load -i duranta.ndz -t srv1-lg1,srv2-lg1 -r 0 -o 1200
omf stat and repeat the on if needed:omf tell -a on -t srv1-lg1,srv2-lg1
sleep 30; omf stat -t srv1-lg1,srv2-lg1
isolcpus; the tool derives the right set from each host's NUMA/NIC layout):for h in srv1-lg1 srv2-lg1; do ssh root@$h 'cosmos-ofh-isolation --apply && systemctl reboot'; done
Wait ~2 min, then ssh root@srv1-lg1 'grep -o "isolcpus=[^ ]*" /proc/cmdline' should print the host's set (srv1-lg1: 7,9,…,23).nr-oru fixes and rebuild (⚠️ until they are in the image):ssh root@srv2-lg1 'cd /opt/duranta && git apply /root/nr-oru-fixes-2026-09-07.diff && cd cmake_targets/ran_build/build && ninja nr-oru oai_usrpdevif'
The patch file is in the oai/duranta-cosmos repo (configs/nroru-b210/patches/); copy it to the node first with scp.omf tell -a on -t sdr1-s1-lg1
ssh root@srv2-lg1 'for i in $(seq 1 30); do ping -c1 -W1 10.39.2.1 >/dev/null && break; sleep 5; done; uhd_find_devices --args addr=10.39.2.1'
Expected: serial: 3176DF5 … product: n310 … claimed: False.nr-oru is not a drop-in for a vendor O-RU: it accepts only an uncompressed U-plane, and its timing
windows are its own. Get these wrong and the symptoms are misleading — the cell radiates, the UE camps and
RACH even reaches Msg3, but every Msg3 fails to decode.
| Where | Setting | Value | Why |
|---|---|---|---|
O-DU fhi_72.fh_config.[0] |
iq_width, iq_width_prach |
16 | nr-oru supports uncompressed IQ only (upstream doc/ORAN_FHI7.2_ORU_Tutorial.md §7.5). With BFP-9 the O-DU decompresses the O-RU's uncompressed uplink as if it were BFP — pure noise |
O-RU fronthaul |
comp_type |
"none" |
the matching half of the same rule |
O-DU fhi_72.fh_config.[0] |
T1a_cp_dl / T1a_cp_ul / T1a_up / Ta4 |
(285,470) / (285,429) / (300,450) / (900,950) | the upstream reference pair (gnb.band77.mu1.106rb.fhi.1x1.conf). Values tuned for a routed N310 (T1a up to 2400 µs) fall outside the O-RU's T2a window, so every DL packet is discarded and nothing reaches the air |
Gains for a UE a few metres away: OAI_ORU_TX_GAIN=10 (→ 79.75 dB actual — the knob is an attenuation
from the B210's 89.75 dB maximum) and OAI_ORU_RX_GAIN=114 (→ 70 dB actual — this one passes through a
~44 dB offset). Both are exported by oru-bringup.sh.
This experiment has two hosts, PTP, DPDK VFs and a radio to sequence; there is no useful command-by-command block beyond the two bring-up scripts, which are themselves the manual path. An Ansible mode is not yet available for this modality (the RFsim and OTA Duranta tutorials get theirs first).
The pair of files lives in the image under /root/oru/: gnb-b210oru.conf (O-DU) and ru-n310.conf (O-RU). They are derived from the upstream reference pair gnb.sa.band77.106prb.fhi72.1x1-oru.conf / ru.sa.band77.106prb.fhi72.1x1-oru.conf, resized to 51 PRB because a USRP O-RU cannot run the 106-PRB / 61.44 Msps carrier of the reference on a B210 and the 20 MHz cell keeps the O-RU CPU budget comfortable. The parts that must agree on both sides:
| parameter | O-DU (gnb-b210oru.conf) |
O-RU (ru-n310.conf) |
|---|---|---|
| carrier | SSB 636672 / pointA 636060, 51 PRB, band 78 | carrier_tx/rx = [3550080] kHz, tx_bw/rx_bw = [51] |
| PRACH | prach_ConfigurationIndex = 157 (format B4, the only one nr-oru supports) |
prach_config_index = 157, prach_eaxc_offset = 1, prach_kbar = 4 |
| TDD | period 5, 3 DL / 1 UL slots, 6/4 symbols | tdd_period 5, num_dl_slots 3, num_ul_slots 1, num_dl_symbols 6, num_ul_symbols 4 |
| fronthaul | dpdk_devices = ("0000:86:01.0","0000:86:01.1"), ru_addr = 64:66/67, iq_width 9, comp_hdr_type dynamic, kbar 4 |
dpdk_devices = ("0000:86:01.0","0000:86:01.1"), du_mac_addr = 64:68/69, comp_type = "bfp" |
| timing | T1a_cp_dl (1800,2400), T1a_cp_ul (1000,2000), T1a_up (1500,2200), Ta4 (900,950) |
T2a_up (300,4000), T2a_cp (285,4000) |
| cores (isolated set) | xran system 3 / io 5 / worker 7; L1 rx 8 / tx 10; ru_thread_core 6 |
rx_core 5, north_cores (4), south_core 7, ul_worker_cores (6,8), tx_core 9, tp_cores [10,11] |
Two libconfig gotchas found on 2026.w36: the O-RU's core lists must be written as lists ( … ), not arrays [ … ]; and the O-RU reads the numerology key as mu, while the shipped reference file still says numerology.
⚠️ On sb1 the PCI addresses above must be checked against the host: ssh root@srv1-lg1 'basename $(readlink /sys/class/net/ens5f0/device)' (E810 PF), and the VFs appear as …:01.0/…:01.1 after the bring-up creates them. Edit dpdk_devices in both files if they differ, and replace PF=DATA1a at the top of both bring-up scripts with the sb1 interface name (ens5f0).
ssh root@srv1-lg1 'bash /root/du-oru-bringup.sh'
The script stops any running DU, sets the performance governor, selects the static fronthaul library, creates the two VFs (MAC set after the vfio bind — the bind wipes it), pins all NIC IRQs off the isolated cores (the srv-class PF stall otherwise), mounts 2 MB hugepages, starts ptp4l/phc2sys (step once, then slew), brings up OAI CN5G with the AMF address rewritten into the conf, and launches the DU. Expected tail:
=== [4] PTP: step ONCE, then slew ===
stepped: |offset| = 69 ns after 5s
slewing: |offset| = 48 ns
=== [7] core ===
AMF OK
=== [8] launch DU (O-DU for nr-oru) ===
DU-ALIVE
=== [9] verify ===
NGSetupResponse
in service
fronthaul: [o_du0][drop errors 0 ecpri errors 0 cp errors 0 up errors 0 pusch errors 0 prach errors 0]
===== DU-ORU-BRINGUP-DONE =====
The DU now transmits DL C/U-plane toward the O-RU MACs and waits for uplink; [o-du 0][rx 0 pps …] in /tmp/du_oru.log is normal until the O-RU is up.
ssh root@srv2-lg1 'OAI_ORU_RX_GAIN=40 OAI_ORU_TX_GAIN=30 bash /root/oru-bringup.sh'
Same host preparation as the DU (VFs with the O-RU MACs, IRQ pinning, hugepages, PTP), then: wait for the N310 to be unclaimed (an MPM session from a previous run makes UHD fail with socket closed), re-apply the data route to the radio, and launch nr-oru. The script then checks that uplink U-plane actually flows within 60 s and restarts the O-RU once if not (a networked USRP occasionally comes up with a dead RX stream). Expected tail:
=== [8] launch nr-oru (soft O-RU on the N310) ===
RU-ALIVE
=== [9] verify ===
usrp: Actual master clock: 122.880000MHz
usrp: Actual RX sample rate: 30.720000MSps
usrp: Actual TX sample rate: 30.720000MSps
=== [10] UL-LIVENESS ===
UL live: U-plane sent 23188
===== ORU-BRINGUP-DONE =====
Gains: OAI_ORU_RX_GAIN is the USRP RX gain in dB (40 is right for the sb1 N310; 75 is beyond its range); OAI_ORU_TX_GAIN is an attenuation from the maximum (65 dB on the N310), so 30 gives 35 dB.
O-DU side (every second in /tmp/du_oru.log):
ssh root@srv1-lg1 'grep -a "o-du 0\]\[rx" /tmp/du_oru.log | tail -1; grep -a "o_du0\]\[drop" /tmp/du_oru.log | tail -1'
[o-du 0][rx 1145688 pps 21504 kbps 86688][tx 1912379 pps 31872 kbps 225022][Total Msgs_Rcvd 1145688]
[o_du0][drop errors 0 ecpri errors 0 cp errors 0 up errors 0 pusch errors 0 prach errors 0]
O-RU side — the packet-processor statistics are printed every 256 frames in /root/nr-oru.log (a COSMOS addition; upstream prints them only at exit):
ssh root@srv2-lg1 'grep -aE "Total C-Plane|U-Plane Packets sent|Timing (Early|Late)|Application Too Slow|DL U-Plane \(mean" /root/nr-oru.log | tail -7'
Total C-Plane Packets received: 700822 (DL: 448525, UL: 224264, PRACH: 28033, Other: 0)
Total U-Plane Packets sent: 2326530
C-Plane Timing Early Errors: 945 C-Plane Timing Late Errors: 31
U-Plane Timing Early Errors: 984 U-Plane Timing Late Errors: 59738
Application Too Slow Errors: 223185
DL U-Plane (mean: 61.00 symbols) window [114, 8]: >=+31:49152
Read them as deltas: the early/late and "Too Slow" counters must stop growing after start-up (a few thousand at start are the pre-anchor backlog); the DL U-plane histogram must sit inside the [T2a_max, T2a_min] window. The O-RU's self-diagnosis report (OVERALL STATUS: PASS, DL/UL symbol processing time and safety margin) appears in the same log.
ssh root@node1.osc 'systemctl stop ModemManager
python3 /root/duranta-ota_modem.py configure /dev/ttyUSB2 00101 77:78 oai
python3 /root/duranta-ota_modem.py wait-reg /dev/ttyUSB2 240 00101'
+QENG: "servingcell","NOCONN","NR5G-SA","TDD",001,01,000000003,3,1,636672,78,3,-91,-10,18,1,-
REGISTERED
ssh root@node1.osc 'echo Y > /sys/class/net/wwan0/qmi/raw_ip; ip link set wwan0 up
qmicli -p -d /dev/cdc-wdm0 --wds-start-network="apn=\'oai\',ip-type=4" --client-no-release-cid
qmicli -p -d /dev/cdc-wdm0 --wds-get-current-settings | grep -E "IPv4 (address|gateway)"'
Add the address and per-target routes (never a default route via the modem), then measure against the
oai-ext-dn container on the O-DU host:
UE IP 192.168.100.3 gw 192.168.100.4
ping local 10/10 received, 0 % loss, 22.7 ms ping remote 10/10, 0 % loss, 23.8 ms
DL 30.7–34.3 Mbit/s UL 7.3 Mbit/s
/tmp/du_oru.log: NGSetupResponse, cell … is in service, [o-du 0][rx N pps 21504] with all six o_du0 counters at 0./root/nr-oru.log: RU-ALIVE, Total U-Plane Packets sent increasing, timing-error counters flat, OVERALL STATUS: PASS.AT+QENG="servingcell" → …,000000003,3,…), O-DU logs Initiating RA procedure on each attempt.AT+QENG="servingcell" shows NR5G-SA on cell id 3 / PCI 3 with RSRP ≈ −91 and SINR ≈ 18, +C5GREG: 0,1; the O-DU logs UE RNTI … in-sync PH 38 dB PCMAX 23 dBm.wwan0, 0 % ping loss to the ext-DN and to 8.8.8.8, iperf3 receiver lines DL 30.7–34.3 / UL 7.3 Mbit/s (51 PRB, 1×1, DDDSU).ssh root@srv2-lg1 'pkill -x nr-oru; pkill -x ptp4l; pkill -x phc2sys'
ssh root@srv1-lg1 'pkill -x nr-softmodem; pkill -x ptp4l; pkill -x phc2sys; cd /opt/oai-cn5g/oai-cn5g-fed/docker-compose && docker compose -f docker-compose-basic-nrf.yaml down'
ssh root@sdr1-in3 'systemctl start ModemManager'
omf tell -a offh -t srv1-lg1,srv2-lg1 # leave the N310 and sdr1-in3 powered on
| Msg3 never decodes (RA failed at state WAIT_Msg3) while PRACH and RAR work | the O-DU compresses (BFP-9) but nr-oru only does uncompressed | set iq_width/iq_width_prach to 16 on the O-DU and comp_type = "none" on the O-RU |
| Cell never radiates; O-RU reports hundreds of thousands of U-Plane Timing Early Errors | the O-DU's T1a lead is outside the O-RU's T2a window (classically: values copied from a routed-N310 setup) | use the upstream reference windows above; check the O-RU's DL C-Plane/DL U-Plane means land inside its printed window |
| UE sees the cell at RSRP ≈ −111 with SINR ≈ 3 | OAI_ORU_TX_GAIN raised instead of lowered — it is an attenuation | lower the value (10 ≈ 79.75 dB actual) |
| O-RU's UL floor looks like noise and Msg3 has no SNR | OAI_ORU_RX_GAIN is not the actual gain (≈ 44 dB offset): 50 gives 6 dB | use 114 for ≈ 70 dB actual; check Actual RX gain in /root/nr-oru.log |
| Symptom | Likely cause | Fix |
|---|---|---|
nr-oru: Assertion (num_dpdk_devices > 0 …) at start |
the script launched with the wrong -O conf (e.g. the DU's) |
check the CONF= at the top of oru-bringup.sh; the O-RU conf is ru-n310.conf |
O-DU rx 0 pps, O-RU PRACH UP: Missing C-Plane forever |
DU frames go to another RU's MAC (wrong conf) or wrong VLAN | tcpdump on the O-RU VF (re-bind one VF to iavf briefly): the destination MAC must be the O-RU VF MAC; both VFs vlan 4 |
O-RU: [ORU south] missed UL symbol … (now …) 100 frames apart, UL pending queue full |
O-RU anchored its frame counter on a stale sample (networked USRP RX backlog) | fixed by the nr-oru patch (drains 2 s before anchoring; get_timestamp in the USRP driver) |
O-RU: South Write Coalescing Stats: calls=0, Application Too Slow climbing, cell not radiating |
DL reorder deadlock at start (ready ring holds pre-anchor jobs) | fixed by the patch (pre-anchor DL symbols dropped) |
PRACH weak (energy 19 dB), Msg3 never decodes, prach_I0 = 0.0 dB, raw RX rms ≈ 1 LSB |
RX gain is 0 dB — nr-oru never applies max_rxgain |
OAI_ORU_RX_GAIN=40 (patch); check Actual RX gain: in the log |
UHD Operating over USB 2 (B210) → ERROR_CODE_OVERFLOW at 30.72 Msps |
the B210 linked at USB 2.0 | reseat/replace the USB-3 cable; a host power cycle does not help |
IOError: socket closed or Timed out getting recv buff for management transaction at start |
N310 still claimed by the previous instance, or the data route to the radio went via the 1 GbE control path | wait for uhd_find_devices … claimed: False; sdr_net_setup <addr> (route via the 10 GbE data NIC, MTU 9000) — both are now in the bring-up |
nr_symbol_fep_ul / get_freq_range_from_freq assertions |
carrier not on the CLI (2026.w36 soft UE/RU) | pass -C/-r/--band/--ssb as the gNB log's "Command line parameters for OAI UE" line says |
O-DU TTI processing delay detected bursts, xran xran_queue_prach_length assert |
a per-sample debug probe in the fhi_72 TX path (was in an older image) | use duranta-20260907c or newer (probe removed) |
| Other users' USRP streams show underflows when the O-RU starts | the O-RU's IQ stream crossed the osc↔sb1 data gateway | keep O-RU host and radio in the same domain (this page: both sb1) |
2026.w36 · upstream soft O-RU doc ORAN_FHI7.2_ORU_Tutorial.md.oai/duranta-cosmos, configs/nroru-b210/ (README = the runbook and current state).Author(s): COSMOS team · Last verified: 2026-09-16 (registration + PDU + data plane) · Tested image/release: duranta.ndz = duranta-20260907c, Duranta tag 2026.w36 + COSMOS patches, nr-oru fixes 2026-09-07 · Tags: duranta, oai, oran, 7.2, fronthaul, nr-oru, soft-oru, usrp, n310, xran, dpdk, 5g, sa