CafeTele5G / 6G Engineering CT-5G-NTN-500 Enroll now ↗
Course/Hands-on Lab Guide
5 Labs · fully worked

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.

0

Lab Stack & Setup

One-time environment for all five labs
Ubuntu 22.04+~45 min16 GB RAM

◎ 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-tools and the real SDR captures in captures-served/.
  • Cloud option: a hosted ttyd shell gives a real terminal on the central lab box — no local install needed.
0.1

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)
Expected — the predictor fetches live TLEs and prints real Iridium-NEXT passes over your coordinates. If it prints “real overhead passes predicted”, your Skyfield/SGP4 chain works.
0.2

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
Gotcha — the UE’s IMSI, key (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”.
1

5G SA Core Baseline + UE Registration

Day 1 · the terrestrial reference every NTN comparison is measured against
Open5GSUERANSIMWireshark~60 min
1
Bring up 5GC

Start AMF/SMF/UPF/NRF/UDM/AUSF/PCF

2
Provision UE

Add IMSI/K/OPc subscriber

3
gNB + UE up

UERANSIM registers, PDU session

4
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 ping RTT figure (terrestrial ≈ 1–20 ms) recorded for comparison with Labs 3–4.
  • A saved baseline.pcapng.
1.1

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
1.2

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
Expected — the UE gets an IP on 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
1.3

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)
Gotcha — NAS payloads are ciphered after Security Mode Command. Capture from the start so you see the cleartext Registration Request and the pre-security messages, or use Open5GS NAS logging to follow the ciphered ones.

Lab 1 questions (deliverable)

  1. Which exact NAS messages are exchanged, in order, from Registration Request to a usable PDU session?
  2. On which reference point does each travel (N1/N2/N4), and what transport carries it (NAS-in-NGAP/SCTP, PFCP/UDP, GTP-U)?
  3. Record the terrestrial ping RTT. Keep it — Labs 3–4 will repeat this over a satellite link.
2

Satellite Link Modelling

Day 2 · turn the baseline link into a satellite link, one impairment at a time
tc-netemSkyfieldlink_budget~60 min

◎ 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.
2.1

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
Expected — RTT jumps to ~541 ms (GEO) / ~26 ms (LEO), matching TR 38.821 Table 4.2-2. This single change is what breaks the default RRC/RACH/HARQ timers you will fix in Lab 3.
2.2

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)
Expected — at θ=90° you should read C/N₀ ≈ 71.2 dB-Hz (d=600 km); at θ=30°, ≈ 66.1 dB-Hz (d=1075 km). The ~5 dB delta is pure FSPL from the longer slant path — cross-check with the Day-5 calculator.
2.3

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)
Expected — a LEO-600 pass lasts ≈ 8–9 min above 10°; the Doppler traces the S-curve (positive on approach → 0 at max elevation → negative on recede). This validates the numbers from the Day-2 quiz.

Lab 2 questions (deliverable)

  1. How much does RTT grow from terrestrial to GEO, and why is it 2× the one-way delay for a transparent payload?
  2. By how many dB does C/N₀ fall between 90° and 30° elevation, and which term of the equation causes it?
  3. Where on your pass is the Doppler rate (Hz/s) largest, and why?
3

NTN-Aware gNB Configuration

Day 3 · fix the timers the satellite delay just broke — SIB19, K-offset, TA, HARQ
OAI NTNSIB19ta_prach~75 min

◎ Objective

  • Configure an OAI NTN gNB with SIB19 ephemeris, cellSpecificKoffset, ta-Common and 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-Common for your orbit.
3.1

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)
Expected — the GEO 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.
3.2

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;
Gotcha — the OAI NTN gNB runs in RF-sim mode for this lab. Its NTN mode expects the UE to also be NTN-capable and GNSS-aware; a plain terrestrial UE will not apply the open-loop TA and its uplink will miss the reception window.
3.3

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
Expected — the same delay that kills RACH without K-offset succeeds once the offset shifts the UL scheduling by the RTT. This is the single most important NTN radio fix, made tangible.

Lab 3 questions (deliverable)

  1. Show that your configured ta-Common equals the one-way common delay for your orbit (value × 4.072 ns).
  2. Why does even 32 HARQ processes fail to fill a GEO pipe, and what carries reliability instead?
  3. Which fields would change if you switched this cell from a transparent to a regenerative payload?
4

End-to-End NTN Emulation

Day 4 · a UE registers through the 5GC over an emulated satellite link
Open5GSOAI NTNnetemGrafana~75 min
1
5GC + NTN gNB

Open5GS behind OAI NTN cell

2
GEO delay

netem 270 ms on the feeder path

3
Register

NAS over the sat RTT (fits T3510)

4
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.
4.1

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
4.2

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)
Expected — registration completes in ~2.5 s of accumulated NAS round-trips and stays well inside T3510 (15 s). This is the Day-4 call-flow lesson made real: NTN reuses NAS with generous timers.
4.3

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)
Gotcha — without discontinuous-coverage handling the UE may hit paging/registration failures during the gap. Rel-17/18 unavailability handling is what lets it sleep and resume — observe the timer behaviour and note it in your deliverable.

Lab 4 questions (deliverable)

  1. Compare your NTN registration time to the Lab 1 terrestrial baseline. Which NAS timer bounds it?
  2. How would an earth-fixed tracking-area mapping prevent a TAU storm as beams sweep?
  3. What did the UE do across the 30 s coverage gap, and what standard feature makes that graceful?
5

Link Budget & Pass Prediction (Planning)

Day 5 · plan coverage and close budgets like a real NTN engineer
Skyfieldlink_budgetcoverage~60 min

◎ 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.
5.1

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  ✔
Expected — your tool reproduces the chosen SC case to ≤ 0.06 dB. This is the calibration doctrine — every planning number must trace to a spec anchor. Try SC1/SC4/SC6 too.
5.2

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
5.3

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
Expected — a real schedule of usable windows. The longest gap tells you the continuity requirement your constellation must meet — connect this back to “how many satellites do I need?”.

Lab 5 questions (deliverable)

  1. Which single input change most improves C/N₀ for a handset — band, elevation floor, or G/T? Show the dB.
  2. For your coverage run, what is the governing constraint, and what design lever relaxes it?
  3. 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.