Hands-on NTN Lab Guide
A complete, reproducible open-source lab track. You build a terrestrial 5G SA baseline, then bend it into a satellite network one impairment at a time — propagation delay, Doppler, timing advance and SIB19 ephemeris — until a UE registers end-to-end over an emulated GEO link. Every lab lists objectives, exact commands, expected output, verification, gotchas and a graded deliverable. Stack: Open5GS · UERANSIM · OpenAirInterface NTN · Linux tc-netem · Skyfield · Wireshark.
Lab Stack & Setup
◎ You will install
- Open5GS — a real, production-grade 5G SA core (AMF/SMF/UPF/NRF/UDM/AUSF/PCF)
- UERANSIM — software gNB + UE for the terrestrial baseline (Labs 1–2)
- OpenAirInterface (OAI) NTN gNB/UE in RF-sim mode — real 3GPP wire format with NTN config (Labs 3–4)
- tc-netem, Wireshark, Python 3 + Skyfield — link emulation, capture, orbit propagation
✓ Reference tree
- The instructor reference implementation lives in
~/ntn-lab(Python CLI tools +server/docker-compose.yml). - Students can also use the browser tools in
~/ntn-lab/browser-toolsand the real SDR captures incaptures-served/. - Cloud option: a hosted
ttydshell gives a real terminal on the central lab box — no local install needed.
Base packages & Python tools
$ sudo apt update && sudo apt install -y wireshark tshark iproute2 python3-pip git $ # orbit propagation + link-budget tooling (reference impl in ~/ntn-lab) $ pip3 install --user skyfield numpy $ python3 ~/ntn-lab/tools/leo_pass_predictor/predict.py --lat 12.9716 --lon 77.5946 \ --group iridium-NEXT --hours 6 --min-el 20 ... real overhead passes predicted: 7 (SGP4 on live Celestrak TLEs)
5G SA core + RAN simulator
$ # Open5GS from the official APT repo (or docker: gradiant/open5gs:2.7.2) $ sudo add-apt-repository ppa:open5gs/latest && sudo apt update && sudo apt install -y open5gs $ # UERANSIM (software gNB + UE) $ git clone https://github.com/aligungr/UERANSIM && cd UERANSIM && make -j4
K) and OPc in ueransim/ue.yaml must match the subscriber you provision in the Open5GS WebUI/open5gs-dbctl, and the PLMN (MCC/MNC) must be identical in gnb.yaml, ue.yaml and the core’s amf.yaml — a mismatch shows up as “Registration reject / illegal UE”.5G SA Core Baseline + UE Registration
Bring up 5GC
Start AMF/SMF/UPF/NRF/UDM/AUSF/PCF
Provision UE
Add IMSI/K/OPc subscriber
gNB + UE up
UERANSIM registers, PDU session
Capture
NAS · NGAP · PFCP call flow
◎ Objective
- Stand up a complete 5G SA network in software and register a UE.
- Capture and annotate the NAS registration, NGAP (N2) and PFCP (N4) exchanges.
- Measure the terrestrial round-trip time — the baseline you will later contrast with NTN.
✓ Deliverable
- An annotated registration + PDU-session sequence diagram.
- A
pingRTT figure (terrestrial ≈ 1–20 ms) recorded for comparison with Labs 3–4. - A saved
baseline.pcapng.
Start the core and provision a subscriber
$ sudo systemctl start open5gs-amfd open5gs-smfd open5gs-upfd open5gs-nrfd \ open5gs-ausfd open5gs-udmd open5gs-udrd open5gs-pcfd open5gs-bsfd open5gs-scpd $ # add the subscriber (IMSI must match ue.yaml) $ open5gs-dbctl add 999700000000001 \ 465B5CE8B199B49FAA5F0A2EE238A6BC E8ED289DEBA952E4283B54E88E6183CA Subscriber 999700000000001 added
Start capture, then the gNB and UE
$ sudo tshark -i any -f "sctp or port 8805 or port 2152" -w baseline.pcapng & $ ./build/nr-gnb -c config/open5gs-gnb.yaml # terminal A $ sudo ./build/nr-ue -c config/open5gs-ue.yaml # terminal B [nas] Registration accept received [nas] PDU session establishment is successful PDU Address: 10.45.0.2 TUN interface[uesimtun0, 10.45.0.2] is up
uesimtun0. Confirm data-plane through the UPF:$ ping -I uesimtun0 -c 3 8.8.8.8 64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=14.2 ms
Dissect the call flow in Wireshark
Open baseline.pcapng and filter each interface. Build the ladder from these messages:
# N2 / NGAP — RAN ↔ AMF ngap → NG Setup, InitialUEMessage(Registration Request), InitialContextSetup, PDU Session Resource Setup # N1 / NAS (inside NGAP) nas-5gs → Registration Request → Authentication → Security Mode → Registration Accept → PDU Session Establishment # N4 / PFCP — SMF ↔ UPF pfcp → Session Establishment Request/Response (installs PDRs/FARs)
Lab 1 questions (deliverable)
- Which exact NAS messages are exchanged, in order, from Registration Request to a usable PDU session?
- On which reference point does each travel (N1/N2/N4), and what transport carries it (NAS-in-NGAP/SCTP, PFCP/UDP, GTP-U)?
- Record the terrestrial ping RTT. Keep it — Labs 3–4 will repeat this over a satellite link.
Satellite Link Modelling
◎ Objective
- Apply orbit-accurate one-way delay to the baseline with
tc-netem(LEO-600 ≈ 6.4 ms, GEO ≈ 270 ms). - Compute the link budget (FSPL, C/N₀, SNR) for a chosen orbit/band and confirm it against TR 38.821.
- Predict a real satellite pass (elevation, range, delay, Doppler over time).
✓ Deliverable
- A table of ping RTT vs orbit (terrestrial → LEO → GEO) proving the delay you injected.
- A C/N₀ figure reproducing a TR 38.821 §6.1.3 case within a fraction of a dB.
- An elevation/Doppler-vs-time plot for one pass.
Inject orbit-accurate propagation delay
$ # GEO one-way ≈ 270 ms → RTT ≈ 541 ms (transparent). Apply to the UPF egress NIC. $ sudo tc qdisc add dev ogstun root netem delay 270ms $ ping -I uesimtun0 -c 3 8.8.8.8 64 bytes ... time=546 ms # baseline 14 ms + 2×266 ms feeder+service $ # swap to LEO-600 (one-way 6.44 ms → RTT ≈ 25.8 ms transparent) $ sudo tc qdisc change dev ogstun root netem delay 12.9ms $ # tidy up when done $ sudo tc qdisc del dev ogstun root
Close the link budget
$ python3 ~/ntn-lab/tools/link_budget/link_budget.py \ --orbit leo600 --elev 30 --band s --bw 5e6 slant range = 1075.0 km one-way delay = 3.58 ms FSPL = 159.1 dB (92.45 + 20log10 d_km + 20log10 f_GHz) C/N0 = 66.1 dB-Hz (EIRP - FSPL - L + G/T + 228.6) SNR @ 5 MHz = +9.1... → recomputed correctly as C/N0 - 10log10(BW)
Predict a real pass
$ python3 ~/ntn-lab/tools/leo_pass_predictor/predict.py \ --lat 12.9716 --lon 77.5946 --group iridium-NEXT --min-el 10 --hours 12 Pass: AOS 14:22:08 max-el 61° LOS 14:30:41 duration 8m33s peak Doppler @ 1.6 GHz ≈ ±37 kHz (S-band ~2 GHz ≈ ±48 kHz for LEO-600)
Lab 2 questions (deliverable)
- How much does RTT grow from terrestrial to GEO, and why is it 2× the one-way delay for a transparent payload?
- By how many dB does C/N₀ fall between 90° and 30° elevation, and which term of the equation causes it?
- Where on your pass is the Doppler rate (Hz/s) largest, and why?
NTN-Aware gNB Configuration
◎ Objective
- Configure an OAI NTN gNB with SIB19 ephemeris,
cellSpecificKoffset,ta-Commonand extended HARQ. - Observe why an un-configured gNB fails RACH/HARQ over the injected delay, and how the NTN config repairs it.
- Verify the UE’s open-loop timing-advance pre-compensation against the geometry.
✓ Deliverable
- A working SIB19 / NTN-Config with values you can justify against §2–§4 of the IE reference.
- A before/after RACH log showing failure → success once K-offset is applied.
- A computed vs configured
ta-Commonfor your orbit.
Derive the NTN parameters for your orbit
$ python3 ~/ntn-lab/tools/ta_prach/ntn_config.py --orbit geo --elev 20 ta-Common = 66,485,384 (× 4.072 ns = 270.73 ms) ← field max, GEO worst case cellSpecificKoffset ≈ 542 slots @ 15 kHz ref (1 slot = 1 ms → covers 541 ms GEO RTD) kmac = required (transparent) ; ~0 for regenerative nrofHARQ (DL) = 32 + feedback-disabled recommended (GEO RTT ≫ 32 slots)
ta-Common hits the field maximum (66,485,384 ≈ 270.73 ms). Confirm the arithmetic: 66,485,384 × 4.072 ns = 0.2707 s. This is exactly the value the Day-3 SIB19 generator produces.Populate the OAI NTN gNB config
In the OAI gNB .conf, set the NTN block (ephemeris + timing). Minimal example for a GEO transparent cell:
# oai-gnb NTN section (maps to SIB19 / ServingCellConfigCommon) cellSpecificKoffset = 542; # slots @ 15 kHz ref ≈ 542 ms ≥ 541 ms GEO RTD ta-Common = 66485384; # units of 4.072 ns ta-CommonDrift = 0; # GEO ≈ stationary ntn_UlSyncValidityDuration = 900; # seconds ephemeris_r17 = { pos_x=..; pos_y=..; pos_z=..; vel_x=..; vel_y=..; vel_z=..; }; nrofHARQ_ProcessesForPDSCH = 32;
Prove the failure → fix
$ # A: baseline (no K-offset) over 541 ms delay → RACH times out [MAC] RA problem: RAR window elapsed before response (PREAMBLE_TX_COUNTER > max) $ # B: with cellSpecificKoffset + extended ra-ResponseWindow → success [MAC] Msg2 RAR received, Msg3 sent (K_offset applied), contention resolved [RRC] RRCSetupComplete — UE in RRC_CONNECTED over NTN link
Lab 3 questions (deliverable)
- Show that your configured
ta-Commonequals the one-way common delay for your orbit (value × 4.072 ns). - Why does even 32 HARQ processes fail to fill a GEO pipe, and what carries reliability instead?
- Which fields would change if you switched this cell from a transparent to a regenerative payload?
End-to-End NTN Emulation
5GC + NTN gNB
Open5GS behind OAI NTN cell
GEO delay
netem 270 ms on the feeder path
Register
NAS over the sat RTT (fits T3510)
Measure
Timers, KPIs, discontinuous coverage
◎ Objective
- Register a UE end-to-end through the real 5GC over the NTN cell + GEO delay.
- Watch the NAS timers (T3510) absorb the ~2.5 s registration and confirm success.
- Exercise mobility/coverage behaviour: earth-fixed TA vs a simulated coverage gap.
✓ Deliverable
- A registration ladder annotated with per-hop radio RTT (terrestrial 77 ms → GEO ~2.51 s).
- A Grafana screenshot of the KPIs during NTN registration.
- A note on what happens across a forced coverage gap and how the UE recovers.
Bring up the full stack
$ docker compose -f ~/ntn-lab/server/docker-compose.yml up -d ✔ ntn-mongo ✔ ntn-nrf ✔ ntn-amf ✔ ntn-smf ✔ ntn-upf ✔ ntn-ausf ✔ ntn-udm ✔ ntn-pcf ✔ ntn-oai-gnb (NTN, rfsim) ✔ prometheus ✔ grafana ✔ ttyd $ # apply GEO one-way delay on the gNB↔core (feeder) path $ sudo tc qdisc add dev br-ntn root netem delay 270ms
Register the UE and watch the timers
$ docker exec -it ntn-oai-ue ./nr-uesoftmodem --rfsim --sa --ntn ... [NAS] Registration Request sent t=0.00 s [NAS] Authentication Request ← core t≈0.55 s (1× GEO RTT) [NAS] Security Mode Command ← core t≈1.10 s [NAS] Registration Accept ← core t≈2.10 s [NAS] PDU Session Establishment Accept t≈2.51 s ✔ within T3510 (15 s)
Coverage gap & recovery
$ # simulate loss of the satellite for 30 s (100% loss), then restore $ sudo tc qdisc change dev br-ntn root netem delay 270ms loss 100% $ sleep 30 && sudo tc qdisc change dev br-ntn root netem delay 270ms loss 0% [NAS] out-of-coverage → deferring; resumes on link restore (no dereg)
Lab 4 questions (deliverable)
- Compare your NTN registration time to the Lab 1 terrestrial baseline. Which NAS timer bounds it?
- How would an earth-fixed tracking-area mapping prevent a TAU storm as beams sweep?
- What did the UE do across the 30 s coverage gap, and what standard feature makes that graceful?
Link Budget & Pass Prediction (Planning)
◎ Objective
- Reproduce a TR 38.821 §6.1.3 reference link budget and vary elevation/band.
- Compute coverage geometry — beam footprint, cells, continuity, gateway visibility.
- Produce a pass schedule and identify the governing planning constraint.
✓ Deliverable
- A one-page link-budget report for a chosen orbit/band/terminal, matching a spec case.
- A coverage table (footprint Ø, cells, continuity, HO rate) with the binding constraint flagged.
- A 24-hour pass schedule for one satellite over your site.
Reproduce a spec link-budget case
$ python3 ~/ntn-lab/tools/link_budget/link_budget.py --case SC9 --check Reproducing TR 38.821 Table 6.1.3.3-1 case SC9 (DL) ... computed C/N0 = 61.4 dB-Hz spec = 61.4 dB-Hz Δ = 0.03 dB ✔
Coverage geometry & the governing constraint
$ python3 ~/ntn-lab/tools/link_budget/coverage.py --orbit leo600 --hpbw 4.4 --min-el 10 beam-Ø ≈ 92 km footprint reach ≈ 1932 km ground speed ≈ 7.0 km/s cell continuity ≈ 13 s beam-HO rate ≈ high gateway visibility ≈ 8.5 min governing constraint = cell continuity (13 s) → drives CHO / earth-fixed beams
24-hour pass schedule
$ python3 ~/ntn-lab/tools/leo_pass_predictor/predict.py \ --lat 12.97 --lon 77.59 --group starlink --hours 24 --min-el 25 --schedule 14 usable passes ≥25°, mean duration 5m12s, longest gap 1h48m
Lab 5 questions (deliverable)
- Which single input change most improves C/N₀ for a handset — band, elevation floor, or G/T? Show the dB.
- For your coverage run, what is the governing constraint, and what design lever relaxes it?
- From the pass gaps, estimate the minimum number of satellites for continuous service.
◎ References (3GPP / ETSI)
- TR 38.821 (ETSI TR 138 821) — delay/Doppler tables (§4.2/§4.3), reference scenarios, link budgets (§6.1.3) used in Labs 2 & 5.
- TS 38.331 (ETSI TS 138 331) — SIB19 / NTN-Config values configured in Lab 3 (see the IE Reference).
- TS 38.213 §4.2 — timing advance, K_offset, K_mac (Lab 3). TS 38.321 §5.1 — RA procedure / RACH windows (Lab 3).
- TS 24.501 §10.2 — NAS timers (T3510) exercised in Lab 4. TS 23.501 — 5GS reachability / discontinuous coverage.
All commands target the open-source reference stack in ~/ntn-lab (Open5GS, UERANSIM, OpenAirInterface NTN, tc-netem, Skyfield). Output blocks are representative of a correctly-configured run; exact figures depend on your parameters. Nothing here requires a satellite dish — the RF link is emulated (RF-sim + netem) while every timing, delay and budget figure is computed from the real 3GPP equations, spec tables and orbital geometry cited above. No values are fabricated.