Scope
This gets you to a cell that real handsets attach to, and a core that registers them. It does not cover the project's NGAP proxy, feature extraction or ML pipeline.
The order below is load-bearing. Step 3 must complete before Step 4 or the Open5GS
build fails at the first #include; Step 5 must complete before anything
registers at all.
Hardware
| Item | Requirement |
|---|---|
| SDR | USRP B210, on a genuine USB 3.0 port. 20 MHz n78 needs 23.04 Msps, which USB 2.0 cannot sustain. |
| UEs | 5G SA handsets with programmable MILENAGE USIMs, and/or UERANSIM software UEs |
| RF containment | Faraday bag or shielded room. Band n78 is licensed spectrum. |
| Host | ≥16 GB RAM |
Step 00UHD firmware and B210 detection
One-time per machine:
uhd_images_downloader # FPGA images → /usr/lib/uhd/images/
uhd_find_devices # expect: type=b200, plus your board serialuhd_find_devices succeeding is not proof the link is fast
enough — it succeeds on USB 2.0 too. Check the negotiated speed now, rather than after an
hour of compiling.
lsusb -t # the B210 line must end in 5000M (USB 3.0), not 480M (USB 2.0)Step 01System dependencies
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
meson ninja-build build-essential flex bison \
cmake libsctp-dev libgnutls28-dev libgcrypt-dev \
libssl-dev libidn11-dev libbson-dev libmicrohttpd-dev \
libcurl4-gnutls-dev libnghttp2-dev libyaml-dev \
libtalloc-dev libpcsclite-dev pcscd libtins-dev \
libfftw3-dev libmbedtls-dev libboost-all-dev \
libconfig++-dev libyaml-cpp-dev libzmq3-dev cppzmq-dev \
libuhd-dev uhd-host python3-pipA "dpkg was interrupted" error is typically an abandoned interactive iperf3
postinst prompt. Clear it non-interactively:
sudo DEBIAN_FRONTEND=noninteractive dpkg --configure -a
Step 02MongoDB 8.0
Kali has no MongoDB package of its own — use the upstream Debian repository.
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \
sudo gpg --dearmor -o /usr/share/keyrings/mongodb-server-8.0.gpg
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] \
https://repo.mongodb.org/apt/debian bookworm/mongodb-org/8.0 main" | \
sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
sudo apt-get update && sudo apt-get install -y mongodb-org
sudo mkdir -p /var/lib/mongodb /var/log/mongodb
sudo mongod --dbpath /var/lib/mongodb --logpath /var/log/mongodb/mongod.log --fork
mongosh --quiet --eval "db.adminCommand('ping')"Step 03Kali libmongoc fixups
This is what makes a Kali install differ from the upstream Open5GS instructions, and the step most likely to break again later.
Debian and Kali ship libmongoc 2.x under the mongoc2 name, with headers in a
versioned directory and a pkg-config file that does not use the upstream name. Open5GS
2.7.7 expects the 1.x layout — a libmongoc-1.0.pc and a top-level
<mongoc.h>. Three distinct failures follow.
Fix 1 — pkg-config name symlinks
PC=/usr/lib/x86_64-linux-gnu/pkgconfig
sudo ln -sf $PC/mongoc2.pc $PC/libmongoc-1.0.pc
sudo ln -sf $PC/mongoc2-static.pc $PC/libmongoc-static-1.0.pc
sudo ln -sf $PC/bson2.pc $PC/libbson-1.0.pc
sudo ln -sf $PC/bson2-static.pc $PC/libbson-static-1.0.pcmeson setup fails with Dependency libmongoc-1.0 not found.
Fix 2 — header shims
Kali installs headers at /usr/include/mongoc-<version>/mongoc/mongoc.h,
but Open5GS does #include <mongoc.h>. Create a one-line shim at the top
of each versioned include directory.
This testbed was first built against mongoc 2.3.1; Kali has since moved to 2.3.3.
A shim written for one version is silently invisible to the next, and the build then fails
with the same fatal error: mongoc.h: No such file or directory as if the fixup
had never been applied at all.
MONGOC_INC="$(pkg-config --variable=prefix libmongoc-1.0)/include/mongoc-$(pkg-config --modversion libmongoc-1.0)"
BSON_INC="$(pkg-config --variable=prefix libbson-1.0)/include/bson-$(pkg-config --modversion libbson-1.0)"
echo '#include "mongoc/mongoc.h"' | sudo tee "$MONGOC_INC/mongoc.h"
echo '#include "bson/bson.h"' | sudo tee "$BSON_INC/bson.h"Verify the shim resolves before building anything:
printf '#include <mongoc.h>\n#include <bson.h>\nint main(void){return 0;}\n' > /tmp/probe.c
gcc $(pkg-config --cflags libmongoc-1.0) -c /tmp/probe.c -o /dev/null && echo "shim OK"The shims live inside the versioned include directory, so an apt upgrade
that bumps libmongoc-dev strands them in the old directory. Existing binaries
keep working — only rebuilds break, which makes this a confusing failure to hit months later.
Step 04Build Open5GS 2.7.7
git clone --depth 1 --branch v2.7.7 https://github.com/open5gs/open5gs.git
cd open5gsFix 3 — disable the bundled test suite
tests/common/context.c calls mongoc_collection_count(), removed
in libmongoc 2.x. The tests fail to compile even though the daemons build fine.
sed -i 's/^if build_tests/if false # build_tests/' meson.buildA build-only change: it disables Open5GS's own unit tests and touches no core functionality, which is why the repository ships no patch file for it.
Build and install
meson setup build --prefix=/usr/local
ninja -C build -j"$(nproc)"
sudo ninja -C build install
sudo ldconfig
/usr/local/bin/open5gs-amfd -v # Open5GS v2.7.7| Artefact | Location |
|---|---|
| Binaries | /usr/local/bin/open5gs-*d |
| Configs | /usr/local/etc/open5gs/*.yaml |
| SUCI home-network keys | /usr/local/etc/open5gs/hnet/ |
Step 05Configure Open5GS for PLMN 001/01
A stock install starts, registers nothing, and returns 504 Gateway Timeout
on every discovery request. Four separate config problems cause it, and
all four must be fixed together — fixing three still yields a dead core.
1 — NRF must allow the test PLMN
nrf.yaml defaults to mcc: 999, mnc: 70 and rejects every NF
registering as anything else: PLMN-ID[MCC:001,MNC:01] is not allowed.
nrf:
serving:
- plmn_id:
mcc: 001
mnc: 01
sbi:
server:
- address: 127.0.0.10
port: 77772 — Every NF needs an explicit serving section
ausf, udm, udr, pcf,
nssf and bsf ship without one, so they register under the default
PLMN 999/70 and the AMF cannot discover them for 001/01.
<nf>:
serving:
- plmn_id:
mcc: 001
mnc: 013 — Route through the NRF, not the SCP
Every NF config defaults to client.scp: http://127.0.0.200:7777. The SCP
expects a SEPP for inter-PLMN routing, which a single-PLMN private network does not have,
so discovery fails with No SEPP configured. Change client.scp to
client.nrf in every NF config.
sbi:
client:
nrf:
- uri: http://127.0.0.10:77774 — SBI address map
| NF | SBI address | NF | SBI address |
|---|---|---|---|
| NRF | 127.0.0.10 | UDM | 127.0.0.12 |
| SMF | 127.0.0.4 | PCF | 127.0.0.13 |
| AMF | 127.0.0.5 | NSSF | 127.0.0.14 |
| UPF | 127.0.0.7 | BSF | 127.0.0.15 |
| AUSF | 127.0.0.11 | UDR | 127.0.0.20 |
AMF — NGAP, GUAMI, TAI and network name
amf:
sbi:
server:
- address: 127.0.0.5
port: 7777
client:
nrf:
- uri: http://127.0.0.10:7777
ngap:
server:
- address: 127.0.0.5
guami:
- plmn_id: { mcc: 001, mnc: 01 }
amf_id: { region: 2, set: 1 }
tai:
- plmn_id: { mcc: 001, mnc: 01 }
tac: 1
plmn_support:
- plmn_id: { mcc: 001, mnc: 01 }
s_nssai:
- sst: 1
security:
integrity_order: [NIA2, NIA1, NIA0]
ciphering_order: [NEA0, NEA1, NEA2]
network_name:
full: srsRAN 5G Test
short: srsTest
amf_name: open5gs-amf0
time:
t3512:
value: 540SMF and UPF — UE subnet
session:
- subnet: 10.45.0.0/16
gateway: 10.45.0.1
dnn: internet
smf.yaml additionally takes DNS (8.8.8.8, 8.8.4.4)
and mtu: 1400. The gtpc/gtpu and
freeDiameter entries in the installed file are 4G EPC legacy, unused in
5G SA — leave them alone.
The UPF creates the TUN interface but neither brings it up nor assigns the gateway address. Until you do, a UE gets an IP and passes no traffic.
sudo ip link set ogstun up
sudo ip addr add 10.45.0.1/16 dev ogstunUDM home-network keys
udm.yaml references six SUCI concealment keys under
/usr/local/etc/open5gs/hnet/, installed by ninja install.
UDM will not start if they are missing. Test USIMs using the SUCI
null-scheme never exercise them, but they must still be present.
Step 06Provision subscribers
Subscribers go directly into MongoDB. The full profile set — five physical USIMs and three
UERANSIM software profiles — is in COMP997_srsRAN_subscribers.md, with a
ready-to-paste insertMany block.
Ki and OPc are redacted throughout the public repository. Substitute your own USIM
credentials for the REDACTED placeholders. The IMSI prefix
001010000000xxx is not sensitive: MCC 001 / MNC 01 is the 3GPP test PLMN,
assigned to no real operator.
{
imsi: "001010000000001",
msisdn: [], imeisv: [],
security: { k: "REDACTED", op: null, opc: "REDACTED",
amf: "8000", sqn: NumberLong("0") },
ambr: { downlink: { value: 1, unit: 3 }, uplink: { value: 1, unit: 3 } },
slice: [{ sst: 1, default_indicator: true,
session: [{ name: "internet", type: 3,
ambr: { downlink: { value: 1, unit: 3 },
uplink: { value: 1, unit: 3 } },
qos: { index: 9, arp: { priority_level: 8,
pre_emption_capability: 1,
pre_emption_vulnerability: 1 } } }] }],
access_restriction_data: 32, network_access_mode: 0,
subscriber_status: 0, operator_determined_barring: 0, __v: 0
}mongosh open5gs --eval 'db.subscribers.find({}, {imsi:1, _id:0}).sort({imsi:1})'A new SIM's first attach logs
Authentication failure(Synch failure[count=0]). That is normal MILENAGE SQN
resync and registration completes on the immediate retry. If authentication keeps failing,
reset: mongosh open5gs --eval 'db.subscribers.updateMany({}, {$set: {"security.sqn": NumberLong("0")}})'
Step 07Build srsRAN Project 25.10
srsRAN_Project was archived on GitHub in December 2025. Its default branch now holds
only a README pointing at GitLab — so a plain git clone appears to succeed
and gives you nothing. You must clone an explicit release tag.
git clone --depth 1 --branch release_25_10 https://github.com/srsran/srsRAN_Project.git
mkdir -p srsRAN_Project/build && cd srsRAN_Project/build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DENABLE_EXPORT=ON \
-DENABLE_UHD=ON \
-DENABLE_ZEROMQ=ON \
-DBUILD_TESTING=OFF
make -j"$(nproc)" gnb
Binary lands at build/apps/gnb/gnb. srsRAN builds cleanly under GCC 15 — unlike
Open5GS, it needs no patches. It also declares
cmake_minimum_required(VERSION 3.14), comfortably above the 3.5 floor CMake 4.x
enforces, so no CMAKE_POLICY_VERSION_MINIMUM workaround is needed.
gNB configuration
cu_cp:
amf:
addr: 127.0.0.5
port: 38412
bind_addr: 127.0.0.1
supported_tracking_areas:
- tac: 1
plmn_list:
- plmn: "00101"
tai_slice_support_list:
- sst: 1
ru_sdr:
device_driver: uhd
device_args: type=b200,serial=YOUR_B210_SERIAL,num_recv_frames=64,num_send_frames=64
srate: 23.04
otw_format: sc12
tx_gain: 89
rx_gain: 50
cell_cfg:
dl_arfcn: 632628
band: 78
channel_bandwidth_MHz: 20
common_scs: 30
plmn: "00101"
tac: 1
pci: 1
log:
filename: /tmp/gnb.log
all_level: infoTwo settings are not optional:
num_recv_frames=64,num_send_frames=64— without these the B210 underruns regardless of USB generation.tx_gain: 89(the B210 maximum) andrx_gain: 50. Attx_gain: 80some handsets never saw the cell while others attached fine — a confusing failure, because the cell is genuinely on-air throughout.
Step 08UERANSIM v3.2.6 — optional
Only needed for the SW-Std / SW-Ext / SW-Min software UE profiles. Skip if you are working with physical handsets only.
Kali / GCC 15 build fixups
UERANSIM's 2022-era code relies on standard-library headers that older GCC pulled in transitively; GCC 15 does not. Two changes:
src/ext/yaml-cpp/emitterutils.cpp— add#include <cstdint>.- Top-level
CMakeLists.txt— force-include globally, which is cheaper than patching every offending file:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include cstdint -include cstring -include cstdio -include string")Capability-enquiry patch
There is no RRC UECapabilityEnquiry / UECapabilityInformation
implementation on either the gNB or the UE side, so no
UERadioCapabilityInfoIndication is ever emitted — meaning the NGAP proxy has
nothing to intercept for software profiles. ueransim.patch in the repository
adds it.
git apply /path/to/ueransim.patch
cp /path/to/ueransim-config/gnb.yaml config/gnb.yaml
make build # produces build/nr-gnb, build/nr-ue, build/nr-cliIf nr-gnb is left running across a restart of whatever it connects to, it
can wedge in a broken internal AMF-context state (AMF context not found with id: 0)
while its SCTP transport still looks healthy. Restart it fresh rather than trusting its
reconnect logic.
Its GTP/UDP task will also fail to bind (Address already in use) when a real
srsRAN gNB is running, since both claim port 2152 on loopback. Harmless for NGAP signalling;
user-plane data will not flow for UERANSIM UEs in that configuration.
Bring-up and verification
Start order matters: MongoDB, then NRF and SCP, then the remaining NFs, then AMF and SMF, then UPF, then the gNB.
sudo mongod --dbpath /var/lib/mongodb --logpath /var/log/mongodb/mongod.log --fork
for nf in nrf scp ausf udm udr pcf nssf bsf amf smf upf; do
sudo /usr/local/bin/open5gs-${nf}d > /tmp/${nf}.log 2>&1 &
sleep 1
done
sudo ip link set ogstun up
sudo ip addr add 10.45.0.1/16 dev ogstun 2>/dev/null || true
sudo sysctl -w net.ipv4.ip_forward=1
UPLINK=$(ip route show default | awk '{print $5; exit}')
sudo iptables -t nat -A POSTROUTING -s 10.45.0.0/16 -o "$UPLINK" -j MASQUERADE
sudo /path/to/srsRAN_Project/build/apps/gnb/gnb \
-c /root/.config/open5gs/gnb.yml > /tmp/gnb.log 2>&1 &Verification checklist
| Check | Command | Expect |
|---|---|---|
| AMF listening on N2 | ss -lntu | grep 38412 | a listening socket |
| NG Setup succeeded | grep -i "ng setup" /tmp/gnb.log | success, PLMN 00101 |
| UE registered | grep "Registration complete" /tmp/amf.log | one line per UE |
| UE got an IP | grep "UE IPv4" /tmp/smf.log | address in 10.45.0.0/16 |
| Data path | ping -I ogstun 10.45.0.2 | replies |
| RF underflows | grep -c underflow /tmp/gnb.log | near zero |
Open5GS logs carry ANSI colour codes; strip them with
sed 's/\x1b\[[0-9;]*m//g' before grepping if matches look oddly absent.
Confirming the cell is actually transmitting
Protocol-level evidence is the primary check: PRACH detection in /tmp/gnb.log,
NG Setup, and a successful registration. For an independent RF-layer confirmation, a
wideband hackrf_sweep is not reliable — it produces noisy readings with
no stable peak. Use GQRX narrowband instead.
| GQRX setting | Value |
|---|---|
| Frequency | 3489420000 Hz |
| Sample rate | 20000000 |
| LNA / IF gain | 16 dB |
| VGA / BB gain | 20 dB |
| RF amp | off — the B210 transmits at max gain right beside it |
The carrier appears as a sharp spike around −35 to −40 dBFS against a −85 to −90 dBFS noise floor, with visible TDD burst structure in the waterfall.
The LNA / VGA / RF-amp sliders live in the Input controls tab of the Receiver Options dock, not in "Configure I/O devices". And GQRX does not auto-start — click ▶ (Start/Stop DSP) or the waterfall stays black no matter how correct your settings are.
Troubleshooting
Install-time and bring-up failures encountered on this testbed, with root causes.
| Symptom | Cause | Fix |
|---|---|---|
apt-get fails: "dpkg was interrupted" | Abandoned interactive postinst prompt | DEBIAN_FRONTEND=noninteractive dpkg --configure -a |
meson: Dependency libmongoc-1.0 not found | Kali names the file mongoc2.pc | pkg-config symlinks — Step 3, Fix 1 |
fatal error: mongoc.h: No such file or directory | Headers live at mongoc-<ver>/mongoc/mongoc.h | Header shims — Step 3, Fix 2. If this appears after a previously working build, libmongoc-dev was upgraded and the shim is stranded in the old directory. |
implicit declaration of function 'mongoc_collection_count' | Removed in libmongoc 2.x; Open5GS tests still call it | Disable tests — Step 4, Fix 3 |
git clone of srsRAN yields only a README | GitHub repo archived Dec 2025 | Clone --branch release_25_10 |
Every AMF discovery returns 504; SCP logs No SEPP configured | NFs routing via SCP, which needs a SEPP | client.scp → client.nrf |
NRF logs PLMN-ID[MCC:001,MNC:01] is not allowed | nrf.yaml still serving default 999/70 | Step 5, item 1 |
| AMF discovery for AUSF/UDM returns empty | Those NFs registered under PLMN 999/70 | Add serving: to every NF |
| UDM will not start | Missing hnet/ SUCI key files | Reinstall — they come from ninja install |
UE gets an IP but no traffic; ping -I ogstun → "Network is unreachable" | UPF leaves ogstun DOWN | ip link set ogstun up |
| One handset sees the cell, another does not | tx_gain too low for that device at that distance | tx_gain: 89, rx_gain: 50 |
Thousands of Real-time failure in RF: underflow, despite NG Setup succeeding | B210 on USB 2.0. Both lsusb and uhd_find_devices succeed on USB 2.0 — presence is not proof of link speed. | Move to a true USB 3.0 (xhci_hcd) root hub; confirm 5000M in lsusb -t. Isolated single-slot underflows under load remain normal. |
UE registers but never gets a PDU session; gNB loops UE did not request a PDU session … Requesting UE release | Device's APN/DNN profile requests something other than internet (often ims) with no fallback | Fix on the device's APN config, not the network. Check /tmp/amf.log for the requested DNN. |
Recurring Ue requested DNN "ims" Not Supported every ~16s | Handset probing for a VoNR/IMS bearer Open5GS does not provide | Harmless. Disable VoNR on the handset, or ignore. |
| Registration succeeds, then drops every 15–90s | RF link quality — PUSCH SINR collapsing to −20…−35 dB means uplink is failing outright | Antenna positioning. Not a software fault. |
CPE unreachable by ping despite a working session | Many CPEs silently drop unsolicited ICMP | Check for live flows instead: tcpdump -i ogstun host <ip> |
Network parameters
| MCC / MNC | 001 / 01 (3GPP test PLMN) |
|---|---|
| TAC | 1 |
| Band | n78 (TDD, 3300–3800 MHz) |
| DL ARFCN | 632628 (3489.42 MHz) |
| SSB ARFCN | 632256 |
| Channel bandwidth | 20 MHz |
| Subcarrier spacing | 30 kHz |
| S-NSSAI | SST=1 (eMBB) |
| AMF NGAP | 127.0.0.5:38412 |
| UE IP pool | 10.45.0.0/16 |
| UPF TUN | ogstun (10.45.0.1/16) |
| DNN | internet |
| DNS | 8.8.8.8, 8.8.4.4 |
| TX / RX gain | 89 dB / 50 dB |
Legal and safety
Band n78 is licensed spectrum. Transmit only inside a Faraday enclosure or shielded room, or under a licence that permits it. The PLMN 001/01 used throughout is the 3GPP test network, assigned to no real operator, which keeps a stray UE from mistaking this cell for a commercial one — but that is not a substitute for RF containment.
This testbed exists to study attacks against capability negotiation on a private network with subscribers you control. Do not point it at devices or subscribers that are not yours.