Please generate explore.fxt.xml and sessions.fxt.xml for r16_mdt_nr_mac.pcap. The capture parameters for decoding the file can be found in the original source of this pcap — Wireshark issue #19757.
The first scan saw only an undissected UDP payload; the L2-over-UDP heuristics then revealed a full 5G NR radio stack — mac-nr, rlc-nr, pdcp-nr, nr-rrc, nas-5gs — carried as MAC-NR over UDP. The outer link type here is plain Ethernet/IPv4/UDP (the wrapper rides 127.0.0.1), so no DLT_USER mapping is needed — the heuristics alone unwrap the fake-UDP payload. VisualEther's bundled 5g-nr-radio sample is a 100%-coverage match, so I authored both FXTs from it and wrote them into the pcap directory.
The issue's decode recipe went into visualether.toml via materialize_config, which also runs a decryption verification pass:
| Parameter | Value (from issue #19757) |
|---|---|
| Framing heuristics | mac_nr_udp, rlc_nr_udp, pdcp_nr_udp |
| PDCP-NR keys (UEId 1) | RRC cipher 95F2…FDDD, user-plane cipher 4CB1…BC5B |
| Decipher | pdcp-nr.decipher_signalling + decipher_userplane |
| MAC LCID→DRB | From configuration protocol |
| RLC→PDCP DRB call | 18-bit SN (UL & DL) |
Verification result: the capture negotiated cipher algorithm 2 (NEA2/AES), and 30 user-plane frames decrypted to SDAP / inner IP with UEId 1's keys. The probe also flagged that UEId 2 and UEId 3 are present but no keys were supplied for them — the issue only published one key set.
UEId 2 and UEId 3 share the same C-RNTI, so the same keys should apply to them.
Good call — mac-nr.ueid is a per-connection context index, not a device id, so the same C-RNTI (0x8000) across all three is one UE reconnecting. I added pdcp_nr_ue_keys rows for UEId 2 and 3 with the same RRC/UP cipher keys and re-materialized; the “no key for UEId 2/3” warnings are gone.
The user-plane decrypt count held at 30 frames, unchanged. That tells us the UEId 2 and 3 episodes carry no additional decryptable DRB user plane — they are signalling-only re-access episodes (Service Requests that never reach a data bearer in the capture). All the SDAP/IP/SIP traffic belongs to the first connection context. The keys are nonetheless in place for every context now, and there are no unkeyed UEs left flagged.
Generate the diagrams.
Rendered both views from the same recipe via the VisualEther MCP tools. The explore pass matched 151/151 frames; extract_sessions produced eight per-layer session types. Both used axis=port — the loopback wrapper shares one source/destination IP, so port-level grouping keeps the endpoints distinct. The decryption boundary shows up cleanly in the session counts — the upper-layer lanes exist only where the keys applied:
| Session type | Count | Meaning |
|---|---|---|
| rrc-connection / mac-ue | 2 | RRC + MAC per connection context |
| rlc-bearer / pdcp-bearer | 2 | radio-stack envelopes |
| nas-5gs | 2 | NAS signalling |
| sdap-bearer | 1 | deciphered DRB QoS flow |
| ip-flow | 3 | inner end-user IPv6 |
| sip | 1 | IMS signalling inside the user plane |
The first context decodes all the way up to SDAP → IPv6 → SIP; the re-access episodes decode through MAC/RLC/PDCP but carry no user plane. Live-reload is on, so subsequent FXT edits refresh the open browser tab automatically.
From the source. Issue #19757 flags two known decode problems that reproduce here: packet 38 (an RRC Reconfiguration) and packet 72 (an uplink IPv6 payload-length misalignment). If those frames look off in the diagram, that is the documented capture issue, not the FXT.
Walk me through the complete flow in the pcap file.
One UE (C-RNTI 0x8000, FDD), three connection episodes, ~49 s. The first episode runs the full attach and an IMS registration; the other two are short re-accesses.
| Phase | Frames | What happens |
|---|---|---|
| RACH + RRC setup | 1–4 | RAR (Msg2, Temp C-RNTI 0x1001, TA 0) → RRC Setup Request (Msg3, mo-Signalling) → RRC Setup (Msg4) + Contention Resolution → RRC Setup Complete + NAS Registration Request |
| NAS auth & security | 7–18 | Authentication Request/Response → NAS Security Mode Command/Complete → AS Security Mode Command/Complete (NEA2/NIA2) |
| Capability & bearer | 20–39 | UE Capability Enquiry/Information → RRC Reconfiguration (frame 38) adds SRB2 + DRB-1 (PDU Session 1, QFI 2, 18-bit PDCP SN, RLC AM) |
| User plane (decrypted) | 41–114 | ICMPv6 SLAAC (prefix 3000:0:0:2::) → TCP to P-CSCF [fd29:…:b4c7]:5060 → SIP REGISTER → 401 Unauthorized → TCP teardown → IPsec ESP |
| Release & re-access | 128–150 | RRC Release (+retx) → episode 2 RACH/Service Request → episode 3 RACH (truncated at capture end) |
Security ordering is correct: the NAS Security Mode Command (frame 11) precedes the AS Security Mode Command (frame 17), and the algorithm pair is NEA2/NIA2 throughout. The IMS exchange runs UE 3000:0:0:2::2 ↔ P-CSCF fd29:cc43:7fb9:2:20c:29ff:fe66:b4c7, and everything above the DRB only became visible because the keys deciphered DRB-1 / QFI 2.
Note. Packet 38 is exactly the DRB-establishing RRC Reconfiguration, and packet 72 is the first uplink ESP frame — the two frames issue #19757 was filed about.
Let's check whether the capture carries SFN and slot. If it does, please fold them into the analysis.
Yes — mac-nr.sfn and mac-nr.slot are on all 151 frames. Two specifics that shape every timing claim:
- Numerology. The max slot index is 9 → 10 slots per radio frame →
μ=0 / 15 kHz SCS, so 1 slot = 1 ms. FDD. - SFN wraps. The capture spans ~49.2 s (
03:06:55.792 → 03:07:45.027) and the 10-bit SFN wraps every 10.24 s, so it rolls over about five times. Cross-event timing must useframe.number/ timestamp; SFN/slot is reliable only inside a wrap window.
| Frame | SFN/slot | Δt | Event |
|---|---|---|---|
| 1 | 189/3 | 0.000 s | RAR (Msg2) |
| 4 | 192/6 | 0.034 s | RRC Setup Complete + Registration Request |
| 17 | 242/6 | 0.533 s | AS Security Mode Command |
| 38 | 421/8 | 2.325 s | RRC Reconfiguration (DRB-1 / QFI 2) |
| 53 | 558/3 | 3.663 s | SIP REGISTER |
| 128 | 433/7 (c1) | 12.684 s | RRC Release (note SFN wrapped) |
| 147 | 1013/3 (c4) | 49.200 s | episode 3 RACH |
With slot timing trustworthy intra-window, the round-trips fall out directly: RAR→Msg3 = 4 ms, Msg3→Msg4 = 2 ms, AS-security round-trip = 9 ms, reconfiguration round-trip = 17 ms, and the RRC Release poll-retransmit = 45 ms.
Compute the BSR → grant → SDAP scheduling cadence for episode 1.
Methodology first: this is a UE-side MAC trace with no PDCCH/DCI, so the uplink grant is never a visible message — I infer it from PUSCH occupancy (every uplink transmission means a grant was issued and used) and from the SR/BSR machinery. Almost every captured Short BSR — 56 of 58 — reports buffer-size index 0 (an empty, just-drained buffer); the only exceptions are two reports during bearer setup (frames 21–22) that carry a small backlog (index 16 and 13). The buffer-building report otherwise rides PUCCH (the SR, not captured) or a prior grant.
| Quantity | Measured | How |
|---|---|---|
| Cold trigger → first PUSCH | ~13 ms | DL RLC poll forces UE onto PUSCH from idle (f43 SFN435/3 → f44 SFN436/6) |
| Active grant cadence | 1 ms / slot | back-to-back PUSCH slots while the buffer is non-empty |
| Drain + tail | 2–4 + 4–5 | data drains in a few slots, then 4–5 zero-BSR PUSCHs release the grant |
| Macro burst period | ~1.1–1.25 s | 7 uplink bursts, paced by IMS/IPsec traffic, not the scheduler |
Worked example — the uplink SIP REGISTER, segmented over RLC: slots 1·2·3 of SFN 558 carry the message back-to-back (1 ms cadence), then slots 4·6·7·8 are the zero-BSR tail. So the loop is: (SR on PUCCH, unseen) → ~13 ms to first grant → a train of 1 ms grants → drain in 2–4 slots → 4–5 trailing zero-BSR PUSCHs, repeating roughly every 1.2 s.
Caveat. The DCI/grant and any PUCCH SR are not in this one-sided capture, so “trigger→grant” is a ~13 ms estimate from the RLC-poll round-trip; and because almost every visible BSR reads index 0 (only the two setup-phase reports show a small backlog), there is essentially no backlog curve — in steady state the buffer is empty by the time it's reported.
Let's do both — the downlink scheduling view (HARQ process usage and DL RLC poll→ACK timing) and a visual slot-occupancy chart.
HARQ — strict 8-process round-robin
The downlink HARQ process id walks the pool in perfect cyclic order, one process per PDSCH, regardless of content: …0,1,2,3,4,5,6,7,0,1,2…. No process is ever reused for the same transport block → zero DL HARQ retransmissions; the channel is error-free at this load. (HARQ retx itself is an L1 ACK/NACK mechanism with no NDI/retransmission flag in this trace, so only process usage is observable.) The uplink pool is larger — it cycles through 16 processes (0–15) rather than 8 — but is likewise never reused for a retransmission.
RLC poll → ACK — a sharp asymmetry
| Direction | Latency | Why |
|---|---|---|
| UL data → DL Status (gNB acks UE) | ~0.8 ms | gNB always has a DL opportunity on tap (next slot) |
| DL data → UL Status (UE acks gNB) | ~13 ms | UE must first obtain a UL grant — same latency as the UL loop |
The gNB also emits a Timing Advance Command every 600 ms; once user data stops, those keepalives are all that remain on the downlink until the RRC Release.
Slot-occupancy grid
U uplink PUSCH D downlink PDSCH X both (FDD — separate carriers) · idle. 1 slot = 1 ms.
| SFN | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | phase |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 189 | · | · | · | D | · | · | · | U | · | D | RACH: RAR·Msg3·Msg4 |
| 557 | · | · | · | · | · | U | · | · | · | · | SIP REGISTER burst |
| 558 | · | U | U | U | U | · | U | X | U | · | drain s1–3, BSR tail s4–8 |
| 801 | · | · | U | U | U | U | U | U | X | U | 8-slot grant train |
| 982 | · | · | · | D | · | · | · | · | · | · | 600 ms TA keepalive only |
The duty cycle is tiny: outside the ~7 bursts the only thing on the air is one TA command every 600 ms. Within a burst the gNB grants near-contiguous 1 ms slots; the X cells are a UL data slot coinciding with the gNB's immediate DL RLC Status — the 2-slot UL→DL ARQ turn made visible.
What are the timing adjustments we are seeing?
Effectively none — both timing-advance mechanisms come out null.
| Mechanism | Observed | Meaning |
|---|---|---|
| Initial TA in RAR (Msg2) | TA = 0 in all 3 episodes | zero propagation delay — UE co-located with gNB |
| TA Command MAC CE | T_A index = 31, TAG 0, every 600 ms | 31 is the neutral midpoint → adjustment = 0 |
timeAlignmentTimer (RRC) | infinity | UE can never lose alignment; CEs are housekeeping |
The TA Command field is relative: N_TA,new = N_TA,old + (T_A − 31) × 16 × 64 / 2^μ, so index 31 is a no-op. One step at 15 kHz SCS is ~0.52 µs of round-trip time (~78 m one-way), and the index never drifts off 31. So there is zero genuine timing correction anywhere in the capture — a static, near-zero-delay test setup. For the MDT context this matters: the repeated re-accesses are not triggered by loss of timing alignment (the timer is infinite and timing never moves); they are pure RRC connection-state cycling.
Now let's shift focus to the session-level flows. Do we see anything unusual? If so, let's analyze it across layers to get to the root cause.
Scanning all 15 sessions, three things stand out — none of them a radio or protocol fault.
| Symptom | Layer | Root cause | Real? |
|---|---|---|---|
PDCP MAC-I “Digest wrong” on the two Security Mode Commands (f17, f136), calculated 00000000 | PDCP | only cipher keys supplied, not integrity keys — Wireshark computes MAC-I as zero on the first integrity-protected PDU | No — tooling artifact |
| RLC retransmissions (f129 dup SN 7, f146 dup SN 3) | RLC AM | t-PollRetransmit on an un-ACKed RRC Release; UE already in RRC_IDLE; 0 NACKs / 0 Status PDUs | No — normal teardown |
| SIP registration shows only REGISTER → 401, then silence | SIP / IP-flow | IMS-AKA moves the authenticated re-REGISTER into IPsec ESP (opaque without SA keys) | No — expected IMS |
| Same UE appears as 2 sessions | session tracking | ~33 s gap before episode 3 > 30 s idle-timeout | No — config threshold |
The pre-security PDUs read MAC-I 00000000 [Matches calculated] (no integrity applied yet); the Security Mode Command is the first integrity-protected PDU, and calculated 00000000 is the dissector's tell that no integrity key was provided. Decryption is unaffected, which is exactly why all of the RRC and user plane still decodes. Everything resolves to decode-config, expected protocol behavior, or a session-tracker threshold — there is no genuine over-the-air fault.
Let's verify the IPsec/ESP hypothesis.
Pulled the per-packet ESP SPI and sequence from the decrypted DRB user plane. Three SPIs, each strictly unidirectional, between the same two hosts as the cleartext SIP REGISTER/401:
| SPI (direction) | ESP sequence | Frames |
|---|---|---|
0x00000100 UE→P-CSCF | 1,2,3,4,5,6,7,8,9,10 | 72,75,78,80,81,91,97×2,107,114 |
0x0000fbeb P-CSCF→UE | 1,2,3,4,5 | 77,84,90,102,106 |
0x0000fbec P-CSCF→UE | 1 (fragmented) | 111 |
This confirms the hypothesis, with a refinement:
- Same association. Every ESP packet is between UE
3000:0:0:2::2and P-CSCFfd29:…:b4c7, all on DRB-1 / QFI 2. - Fresh SAs, one continuous session. Each SPI's sequence starts at 1 and increments monotonically with no gaps — the SAs were created in direct response to the 401, with no replay or rekey.
- IMS signature. The UE contributes one inbound SPI; the P-CSCF contributes the consecutive pair
0xfbeb/0xfbec— the standard IMS IPsec SA arrangement (TS 33.203).
So the “incomplete” SIP session is definitively not a failure: the authenticated REGISTER and its 200 OK are flowing inside these SAs, just opaque because the issue published PDCP keys but not the IPsec SA keys.
Add esp.spi / esp.sequence to the ESP template and regenerate.
The ESP frames are caught by the generic inner-IPv6 template (priority 370, anchored on ipv6.nxt), so I added two params there. Confirmed the dissector shownames first (ESP SPI: 0x00000100 (256), ESP Sequence: 1) so the regexes match:
<param match="ESP SPI: (0x[0-9a-fA-F]+)" replace="🔐 ESP SPI: $1">esp.spi</param> <param match="ESP Sequence: (\d+)" replace="🔢 ESP seq: $1">esp.sequence</param>
They fire only on ESP frames (silently skipped on non-ESP IPv6), so nothing else changes. Re-validated clean (8 session types, 98% coverage) and the open tab live-reloaded. The regenerated ip-flow NDJSON now shows the SPI + sequence on every ESP frame — matching the manual tshark dump exactly.
Mirror a dedicated ESP template with the cleaner opcode label.
Moved the ESP fields into a dedicated template so the SA becomes the row label rather than a buried param, and reverted the params on the generic IPv6 template so ESP is owned by exactly one template:
- New template at priority 375 (between TCP 380 and generic IPv6 370). Because ESP/TCP/UDP are mutually-exclusive next-headers, it fires only on ESP frames.
- Opcode anchored on
esp.spi→🔐 ESP SA 0x…is the headline;esp.sequencerides as a param (distinct field, so it still fires alongside the opcode binding).
Validation confirmed esp.spi wins at 375 on all 16 ESP PDUs (across 15 frames — frame 97 carries two) while TCP stays at 380, ICMPv6 at 390, SIP at 385/386 (60 templates, 98% coverage). The ip-flow session now reads as an SA-to-SA flow:
| Frame | Direction | Label |
|---|---|---|
| 72 | UE → P-CSCF | 🔐 ESP SA 0x00000100 · seq 1 |
| 77 | P-CSCF → UE | 🔐 ESP SA 0x0000fbeb · seq 1 |
| 111 | P-CSCF → UE | 🔐 ESP SA 0x0000fbec · seq 1 (fragmented) |
| 114 | UE → P-CSCF | 🔐 ESP SA 0x00000100 · seq 10 |
The three IMS IPsec SAs and their clean monotonic sequences now read straight off the diagram — no need to drop to tshark.
Method. One pcap analyzed with VisualEther via its MCP integration with Claude — analyze_capture to detect the MAC-NR-over-UDP stack, materialize_config to bake the issue's PDCP keys (NEA2, 18-bit SN, LCID-from-config) into visualether.toml and verify decryption, then explore and extract_sessions for the per-message and per-session views, with validate_fxt on every FXT edit. Every field cited (SFN/slot, HARQ pid, RLC SN, PDCP sequence-analysis, ESP SPI/sequence, NAS security headers, cipher algorithm) was read from the rendered NDJSON and cross-checked with tshark over all 151 frames.