Bring up a complete Amarisoft 5G Standalone (SA) network — lteenb (the gNB), ltemme (the 5G core) and lteue (a software UE) — transmitting over the air through USRP software-defined radios on the COSMOS sb1 or grid domains. By the end, the UE is registered, holds a PDU session with an address in the COSMOS UE pool, reaches the internet, and you have measured real downlink and uplink throughput.
This tutorial builds a self-contained 5G SA cell with the commercial Amarisoft stack. One node runs the gNB (lteenb) together with Amarisoft's unified core (ltemme) and drives one USRP (an NI N310). A second node runs Amarisoft's software UE (lteue) on a second USRP (an N310 or an X310). The two radios are RF-coupled on the rig, so the link between them is a genuine band n41 (2.6 GHz) air interface, not a software loopback. As a variation, a commercial Quectel modem attaches to the same cell.
The reason to run this tutorial is that it is the shortest path on COSMOS to a working over-the-air (OTA) 5G SA link with a vendor-grade stack. Amarisoft ships the base station, the core and a UE emulator as one consistent product, so there are no cross-vendor interoperability questions: every problem you meet is either radio, host or licensing. That makes it a useful reference point when you later compare the open-source stacks against it.
The pieces talk to each other as shown in the Topology figure. The gNB and the core run on the same host and talk NGAP over a local SCTP association (the N2 interface); user traffic leaves the core on a tun interface and is NAT'd to the internet (N6). The gNB streams I/Q samples to its USRP over the domain's DATA network with jumbo frames; the USRP radiates; the UE's USRP receives and streams samples back to lteue on the second node.
Compared with the previous step on the learning path (the soft-only tutorials, where gNB and UE exchange samples over ZMQ or RFsim inside one host), this tutorial adds real radios, real propagation and a second host. Amarisoft has no radio-less software loopback, so there is no soft-only Amarisoft tutorial; step 1 of the path uses OCUDU or Duranta instead. Compared with its two siblings in step 2 (OCUDU and Duranta over the air with an SDR), Amarisoft also emulates the UE, so you do not need a commercial modem to complete the tutorial.
The stack uses the COSMOS standard identities: PLMN 00101 and UE pool 192.168.100.0/22 with internet access. Two Amarisoft-specific behaviours account for most first-time failures: the floating licence seat (one per component tag, for the whole testbed) and the fact that lteue does not attach until you power it on through its API. Both are explained where they occur.
Two concepts from the foundations pages matter here.
Figure — The attach sequence from cell search to user data. lteue performs every UE step in software once power_on is sent; ltemme plays the AMF, SMF and UPF roles in one process.
After completing this tutorial you will be able to:
lteenb + ltemme) over the air on a COSMOS USRP.lteue) to that cell, including the required power_on API trigger.power_on symptom, and stop Amarisoft without touching other users' processes.| Difficulty | Advanced |
| Estimated time | 60–90 min |
| Domain / sandbox | sb1 or grid (USRP radios) |
| Topic group | Cellular (4G/5G/O-RAN) |
| Learning path | Step 2 of 7 — Over the air with an SDR. Previous: OCUDU 5G SA soft or Duranta soft single host. Siblings in this step: OCUDU cross-vendor, Duranta + OAI CN5G. Next: OCUDU 7.2 with ProtO-RU |
| Last verified | 2026-07-15 · text revised 2026-09-24 |
Background knowledge
ssh/ip/iperf3 on Linux — see OMF commands.Account & access
sb1 (or grid) — see Make a reservation.Devices / nodes (example node names are from sb1; the same applies on grid)
| Resource | Role | Qty | Notes |
|---|---|---|---|
| a compute node with a USRP | gNB (lteenb) + 5G core (ltemme) |
1 | drives the gNB N310 (e.g. 10.39.3.1) |
| a second node with a USRP | soft UE (lteue) |
1 | drives a second USRP over UHD |
| NI N310 | gNB radio | 1 | band n41, 100 MHz, 1T1R |
| NI N310 or X310 | UE radio | 1 | N310 for 40/100 MHz; X310 for 40 MHz |
Disk images
| Image | Load onto | Provides |
|---|---|---|
amarisoft.ndz (→ amarisoft-20261002.ndz) |
gNB node | Amarisoft lteenb + ltemme, UHD 4.9, start.sh, the scoped stop.sh, jumbo/SDR net setup, UE-pool NAT |
lte5gue.ndz |
UE node | Amarisoft lteue, UHD 4.9 + trx_uhd, SDR50 driver, amari_cmd.py, UE configs |
amarisoft.ndz and lte5gue.ndz are stable symlinks to the current dated builds; amarisoft.ndz points to amarisoft-20261002, which carries the scoped /root/amarisoft/stop.sh used in Cleanup. Both images ship Amarisoft 2026-09-11 by default and keep 2026-06-12, the release this page was verified on: amari-select 2026-06-12 enb on the gNB node and amari-select 2026-06-12 ue on the UE node repeat it exactly.
Software components
| Component | Version | Source |
|---|---|---|
| Amarisoft lteenb / ltemme / lteue | 2026-06-12 | preinstalled in the images (/opt/amarisoft/) |
| UHD | 4.9.0.0+ds1-1~noble2 | preinstalled (from the ppa:gnuradio/gnuradio-releases PPA) |
| Amarisoft floating license | — | network server 10.50.0.8:9006 (tags gNB, 5gC, ue) |
Spectrum / RF / special
dl_nr_arfcn 518670), 100 MHz or 40 MHz channel, 30 kHz SCS, TDD (DDDSU). Conducted / shielded RF path on the rig — no external antenna work.00101; IMSI 001010123456780 (lteue) and 001010000000032 (Quectel modem), both in the core's ue_db. UE pool 192.168.100.0/22, gateway 192.168.100.1, internet-NAT'd.
Figure — This tutorial is the middle panel, over the air: gNB + SDR, an RF path, and a UE. Here the UE is also software (lteue on its own USRP) rather than a commercial modem.
Two nodes on the same domain (sb1 or grid), each with its own USRP, RF-coupled on the rig:
gNB node UE node
┌────────────────────────┐ ┌────────────────────────┐
│ lteenb (gNB) + ltemme │ n41 2.6 GHz │ lteue (soft UE) │
│ 5G core │ OTA air link │ │
│ USRP N310 (gNB RU) │◄────────────────►│ USRP N310 (or X310) │
└────────────────────────┘ (RF coupled) └────────────────────────┘
│ license
▼ 10.50.0.8:9006 (floating seats: gNB / 5gC / ue)
Walk through it by plane:
lteenb and ltemme run on the same host and form an NGAP association over SCTP port 38412 on loopback addresses (N2). NAS signalling between the UE and the core (N1) rides inside that association and over the air.ltemme also contains the user-plane function. It terminates the UE's PDU session on tun interfaces (192.168.100.1 for the internet APN, 192.168.101.1 for the default APN) and start.sh adds the NAT rule that carries UE traffic to the internet (N6).The setup images both nodes and powers them on. Nothing Amarisoft-specific runs yet.
ssh <username>@console.<domain>.cosmos-lab.org # e.g. console.sb1... or console.grid...
omf tell -a offh -t <gnb-node>,<ue-node>
-r 0):omf load -i amarisoft.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>
omf stat -t <gnb-node>,<ue-node>
Licence seats are shared by the whole testbed — read this before Step 1. Amarisoft refuses to run any component without a licence, and there is one seat per tag (
gNB,5gC,ue) across all COSMOS domains. A client that is still running anywhere — on any host, in any sandbox, including a leftover from an earlier session — holds its tag until the process exits. A second client asking for the same tag is refused, and a refused Amarisoft client does not print an error: it segfaults immediately with no banner (for exampleltemme[…]: segfault at 104), which looks exactly like a broken binary or a bad image. Before you start, make sure you have no Amarisoft process left from an earlier run on your own nodes (pgrep -x ltemme,pgrep -x lteenb-avx2,pgrep -x lteue-avx2— read-only checks) and stop any you find with the procedure in Cleanup. Do not stop Amarisoft processes on nodes you have not reserved: ask the holder or COSMOS staff.
The helper script start.sh performs four actions in the order they depend on each other: it configures the jumbo-frame SDR network (without it, lteenb segfaults during cell initialisation at high sample rates), applies the UE-pool NAT, starts the core (ltemme), and then starts the gNB (lteenb). The argument selects the channel bandwidth.
ssh root@<gnb-node>
/root/amarisoft/start.sh 100 # bandwidth in MHz: 20 | 40 | 100 (100 needs the N310)
Confirm that the licence was granted and the radio came up — look for the License server and RF0 lines in the gNB log:
grep -aE 'RF0:|License server' /tmp/lteenb.log | tail -3
License server: 10.50.0.8 (0b-b4-46-dc-2f-83-58-cb)
RF0: sample_rate=122.880 MHz dl_freq=2593.350 MHz ul_freq=2593.350 MHz (band n41) dl_ant=1 ul_ant=1
What to look for: sample_rate=122.880 MHz means a 100 MHz cell on the N310 (start.sh 40 gives 61.440 MHz); band n41 and 2593.350 MHz confirm the carrier. The License server: line proves the gNB seat was granted.
Next, verify that the gNB associated with the core over NGAP (SCTP port 38412). The gNB's standard output has no obvious "NG setup" line, so the socket table is the reliable check:
ss -np -A sctp | grep -E 'ESTAB.*38412'
ESTAB 0 0 127.0.1.100:38412 127.0.1.1:44246 users:(("ltemme",...))
What to look for: one ESTAB line owned by ltemme means NG setup succeeded. Finally, confirm the tun interfaces are on the standard pool: ip -4 -br addr show | grep tun should show 192.168.100.1 (the internet APN) and 192.168.101.1 (the default APN).
lteue is started with a UHD/USRP config that matches the cell bandwidth. The UE image ships three pre-built configs: ue-uhd-100.cfg (N310, 100 MHz), ue-uhd-40.cfg (N310, 40 MHz) and ue-uhd-40-x310.cfg (X310, 40 MHz), all on PLMN 00101 / IMSI 001010123456780. The UE radio link also needs jumbo frames, which the first command sets.
The Amarisoft binaries are interactive: they read commands from standard input and exit when it closes. The launch line therefore feeds lteue from tail -f /dev/null (a stdin that never closes) inside a new session (setsid), so the whole pipeline forms one process group that Cleanup can stop as a unit.
ssh root@<ue-node>
cd /opt/amarisoft/lteue-linux-2026-06-12
ip link set DATA2 mtu 9000 # jumbo frames to the UE's USRP
setsid bash -c "tail -f /dev/null | ./lteue-avx2 config/ue-uhd-100.cfg" >/tmp/ue_run.log 2>&1 &
sleep 12
grep -aE 'RF0:' /tmp/ue_run.log | tail -1
RF0: sample_rate=122.880 MHz dl_freq=2593.350 MHz ul_freq=2593.350 MHz (band n41) dl_ant=1 ul_ant=1
Keep this ssh session open until you have finished Step 4 and the Performance measurement. A
tail -f /dev/nullpipeline started from an interactive ssh session can end with that session; when its stdin closes,lteueexits cleanly with no error. The UE-sweep tutorial launches the same binaries undersystemd-rununits for exactly this reason.
What to look for: the RF0 line matching the gNB's (same sample rate, same frequency) shows the SDR is streaming. The UE is not yet attached: lteue boots into 5GMM-NULL CM-IDLE — powered off, with no cell search — and stays there until you power it on.
lteue exposes a remote control API as a WebSocket on com_addr [::]:9002. Sending power_on for UE 1 makes it start cell search; from there it runs the whole attach sequence in the figure above without further input. The nodes have no Python WebSocket module, so the image bundles a self-contained raw-WebSocket client, amari_cmd.py:
python3 /opt/amarisoft/lteue-linux-2026-06-12/amari_cmd.py 9002 '{"message":"power_on","ue_id":1}'
Then read the live per-UE state and PHY metrics from the same API:
python3 /opt/amarisoft/lteue-linux-2026-06-12/amari_cmd.py 9002 '{"message":"ue_get"}'
"ue_list":[{"imsi":"001010123456780", ... "dl_mcs":22.5,"ul_mcs":24,"rrc_state":"idle"}]
What to look for: a non-zero dl_mcs and ul_mcs mean the link adaptation has settled on a usable modulation and coding scheme in both directions — a good OTA link. rrc_state returns to idle between bursts of traffic; that is normal RRC inactivity behaviour, not a detach. If power_on does not take the first time (the API can race just after launch), send it again.
Writing your own API client? The UE's WebSocket server drops a handshake that carries no
Origin:header as a bare connection close — no HTTP response, no error — soue_getreturns nothing and every UE looks unregistered.amari_cmd.pysends the header; copy it rather than rolling a new client.
When the PDU session comes up, lteue runs its ue-ifup script, which renames the UE's PDU interface to pdn0 and moves it into a network namespace named ue1. The namespace keeps the UE's routing table separate from the node's own, so the node's management traffic is never routed over the emulated UE. Always test the data plane from inside that namespace. This lteue config uses the default APN, so the UE lands on 192.168.101.x (gateway 192.168.101.1):
ip netns exec ue1 ip -br addr show pdn0
ip netns exec ue1 ping -c 4 192.168.101.1 # core gateway
ip netns exec ue1 ping -c 4 8.8.8.8 # the internet
pdn0 UNKNOWN 192.168.101.2/24
4 packets transmitted, 4 received, 0% packet loss
What to look for: pdn0 holds an address inside 192.168.100.0/22 (the COSMOS pool spans 192.168.100.0–192.168.103.255), the core gateway answers, and the internet answers. ltemme does not NAT on its own; the image's start.sh adds the MASQUERADE rule for 192.168.100.0/22, which is why the internet ping works.
lteue. A Quectel RM520N-GL on the UE node attaches to the same cell on the internet APN and receives a 192.168.100.x address: AT+QNWPREFCFG="mode_pref",NR5G, AT+CGDCONT=1,"IP","internet", AT+COPS=1,2,"00101",12, then bring up wwan0 with qmicli. The full modem procedure, including recovery from a stale NAS context, is on Commercial UE: the Quectel modem. It reaches the same air capacity (UDP DL well above 100 Mbit/s at 100 MHz).recv/send_frame_size to 7900 (fits an 8000-byte path MTU) via the gnb-amari-*-l3.cfg configs, and pin the SDR subnet to the data interface (ip route replace 10.39.0.0/16 via <data-gw> dev <data-iface>) so it never falls back to the control net. 40 MHz attaches and passes data over L3; 100 MHz is marginal (L3 latency degrades the link).| Check | Command | Expected |
|---|---|---|
| Licence granted, radio streaming | grep -aE 'RF0:\|License server' /tmp/lteenb.log (gNB node) |
License server: 10.50.0.8 … and RF0: sample_rate=… (band n41) |
| gNB ↔ core NG setup | ss -np -A sctp \| grep -E 'ESTAB.*38412' (gNB node) |
one ESTAB line owned by ltemme |
| UE registered, link adapted | amari_cmd.py 9002 '{"message":"ue_get"}' (UE node) |
5GMM-REGISTERED, non-zero dl_mcs / ul_mcs |
| PDU session and core reachability | ip netns exec ue1 ping -c 4 192.168.101.1 |
0% packet loss |
| Internet reachability | ip netns exec ue1 ping -c 4 8.8.8.8 |
0% packet loss |
Measure air-interface capacity with UDP; TCP is limited by round-trip time on this link and understates what the radio can carry. Run an iperf3 server on the core gateway and drive it from inside the UE namespace. Keep a background keepalive ping running during the test: without it the UE drops to RRC idle between iperf3's control messages and the control socket breaks after a few seconds.
# on the gNB/core node:
iperf3 -s -B 192.168.101.1 -D
# on the UE node, inside netns ue1:
ip netns exec ue1 ping -i 0.3 192.168.101.1 >/dev/null 2>&1 & # keepalive
ip netns exec ue1 iperf3 -c 192.168.101.1 -u -b 150M -R -t 15 -i 5 # DL (reverse)
ip netns exec ue1 iperf3 -c 192.168.101.1 -u -b 30M -t 12 -i 4 # UL
Read the receiver line of each iperf3 run: for UDP it reports what actually arrived, whereas the sender line reports what was offered.
Measured on this rig (band n41, DDDSU TDD, 30 kHz SCS):
| UE radio | Bandwidth | DL (UDP) | UL (UDP) | Notes |
|---|---|---|---|---|
| N310 | 100 MHz | ≈132 Mbit/s | ≈24 Mbit/s | dl_mcs 20 / ul_mcs 14 |
| X310 | 40 MHz | ≈56 Mbit/s | ≥8 Mbit/s | dl_mcs 25 — full-rate for 40 MHz |
| N310 | 40 MHz | ≈16–57 Mbit/s | ≥8 Mbit/s | link/SINR of the specific USRP is the limit, not config |
UL is bounded by the TDD pattern (one UL slot per 2.5 ms DDDSU period), so it is much lower than DL by design. The spread in the last row is a property of that particular radio's link: the X310 reached ≈56 Mbit/s against the same gNB.
Stopping the Amarisoft processes frees the licence seats; until they exit, nobody else on the testbed can start the same components. Do this before you release the nodes.
On the gNB node, use the image's scoped stop script:
ssh root@<gnb-node> /root/amarisoft/stop.sh
stop.sh identifies Amarisoft processes by the executable they are actually running (/proc/<pid>/exe under /opt/amarisoft/), not by text in their command line, and signals each one's process group — first SIGTERM, then SIGKILL only if it has not exited. Two reasons for this design:
pkill -9 -x ltemme kills every process with that name on the host, including another project's core; pkill -f lteenb goes further and matches anything whose arguments contain the text — a log viewer, an editor, even your own ssh command. On a shared host that stops other users' experiments. The executable path cannot be faked by a command line.tail -f /dev/null | ./<binary> in its own session, so its group contains exactly its own tail. Other tail -f /dev/null keep-alives on the host (they are common) are untouched. Sending SIGTERM first lets the binary release the USRP cleanly, so the next run does not inherit a wedged radio.On the UE node, the scoped stop script is part of the gNB image, so stop lteue the same way by hand: match the executable it is running and signal its process group, which is the setsid pipeline from Step 2. Define this helper in your shell on the UE node:
stop_exe() { # signal the process GROUP of every process executing exactly $1 (default TERM)
local exe sig=${2:-TERM} d st pgid
exe=$(readlink -f "$1")
for d in /proc/[0-9]*; do
[ "$(readlink "$d/exe" 2>/dev/null)" = "$exe" ] || continue
st=$(cat "$d/stat" 2>/dev/null) || continue
st=${st##*) } # drop "pid (comm) ", safe for any comm
pgid=$(echo "$st" | awk '{print $3}') # remaining fields: state ppid pgrp
kill -"$sig" -- "-$pgid" 2>/dev/null
done
}
ssh root@<ue-node>
stop_exe /opt/amarisoft/lteue-linux-2026-06-12/lteue-avx2
sleep 3; pgrep -x lteue-avx2 || echo stopped
What to look for: stopped. If a PID is still printed, repeat the call as stop_exe /opt/amarisoft/lteue-linux-2026-06-12/lteue-avx2 KILL.
Then power the nodes off:
omf tell -a offh -t <gnb-node>,<ue-node>
Only run omf save -n <node> if you deliberately modified an image and want to keep it.
| Symptom | Likely cause | Fix |
|---|---|---|
ltemme (or lteenb, lteue) segfaults immediately, no banner |
The licence seat for that tag is held by another client somewhere on the testbed — most often a leftover process from an earlier session. A refused client segfaults instead of reporting the refusal. | Check your own nodes first (pgrep -x ltemme, pgrep -x lteenb-avx2, pgrep -x lteue-avx2) and stop any leftover with the Cleanup procedure. If your nodes are clean, the holder is elsewhere: ask COSMOS staff, who can identify it from the licence server's log. Never kill Amarisoft processes on hosts you have not reserved. |
lteenb prints License error: no license found |
licence server unreachable, or the config has no license_server block |
check bash -c '</dev/tcp/10.50.0.8/9006' && echo ok |
| A packet capture on port 9006 shows no licence traffic | The licence server listens on 9006 and 9050; a client whose server_addr has no port uses 9050 |
Capture on both ports before concluding the server is unreachable |
UE stuck in 5GMM-NULL CM-IDLE, SDR streaming |
lteue not powered on (it does not auto-attach) |
send power_on via the API (Step 3) |
ue_get returns nothing from a home-made API client |
the client sends no Origin: header and the WebSocket handshake is silently dropped |
use amari_cmd.py, or add the header |
lteue exits: Unsupported bandwidth… |
X310 (184.32 MHz clock) can't auto-pick a 40 MHz rate | put sample_rate: 46.08 inside the cell block; X310 is 40 MHz-max |
lteue exits after the ssh session that started it closed |
the tail -f /dev/null stdin ended with the session and lteue exited cleanly |
keep the session open, or launch under systemd-run as the UE-sweep tutorial does |
PDU accepts but pdn0 DOWN / no ping |
net-tools (ifconfig) missing → ue-ifup can't configure the tun |
apt-get install -y net-tools, restart the UE |
| tuns show old IPs (192.168.3/4) after a re-run | ltemme reused stale tuns | ip link del tun0 tun1 then restart ltemme (recreates them at the pool .1) |
gNB log: [MPM.RPCServer] A timeout event occured!, gNB exits |
N310 MPM wedged by rapid lteenb restarts |
omf tell -a reset -t <N310-host>, then one clean start.sh — do not rapid-cycle the N310 |
UE ping 8.8.8.8 fails but gateway pings |
UE-pool NAT missing | ensure start.sh ran (it adds MASQUERADE -s 192.168.100.0/22) + ip_forward=1 |
For the attach stage at which a failure occurs (no RACH, RRC but no registration, registration but no PDU session), use the sequence figure in the Background section and 5G SA fundamentals to narrow down which component to inspect.
On this wiki
External
lteenb, ltemme and lteue; the copies bundled under /opt/amarisoft/<release>/doc/ match the installed version.trx_uhd.Author(s): COSMOS team · Last verified: 2026-07-15 · text revised 2026-09-24 · Tested image/release: amarisoft.ndz (gNB) + lte5gue.ndz (UE), Amarisoft 2026-06-12, PLMN 00101, pool 192.168.100.0/22; current images: amarisoft-20261002 + lte5gue-20261002 (Amarisoft 2026-09-11 default, 2026-06-12 selectable) · Tags: amarisoft, 5g, sa, sdr, usrp, gnb, lteue