01UserAuthor the FXTs from issue #19757

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.

02ClaudeDecode setup & key verification

The first scan saw only an undissected UDP payload; the L2-over-UDP heuristics then revealed a full 5G NR radio stackmac-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:

ParameterValue (from issue #19757)
Framing heuristicsmac_nr_udp, rlc_nr_udp, pdcp_nr_udp
PDCP-NR keys (UEId 1)RRC cipher 95F2…FDDD, user-plane cipher 4CB1…BC5B
Decipherpdcp-nr.decipher_signalling + decipher_userplane
MAC LCID→DRBFrom configuration protocol
RLC→PDCP DRB call18-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.

03UserSame C-RNTI → reuse keys

UEId 2 and UEId 3 share the same C-RNTI, so the same keys should apply to them.

04ClaudeKeys applied to all three contexts

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.

05UserGenerate the diagrams

Generate the diagrams.

06ClaudeExplore & per-session render

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 typeCountMeaning
rrc-connection / mac-ue2RRC + MAC per connection context
rlc-bearer / pdcp-bearer2radio-stack envelopes
nas-5gs2NAS signalling
sdap-bearer1deciphered DRB QoS flow
ip-flow3inner end-user IPv6
sip1IMS 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.

07UserWalk me through the flow

Walk me through the complete flow in the pcap file.

08ClaudeEnd-to-end, RACH to IMS

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.

PhaseFramesWhat happens
RACH + RRC setup1–4RAR (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 & security7–18Authentication Request/Response → NAS Security Mode Command/Complete → AS Security Mode Command/Complete (NEA2/NIA2)
Capability & bearer20–39UE 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–114ICMPv6 SLAAC (prefix 3000:0:0:2::) → TCP to P-CSCF [fd29:…:b4c7]:5060SIP REGISTER401 Unauthorized → TCP teardown → IPsec ESP
Release & re-access128–150RRC 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.

09UserSFN & slot?

Let's check whether the capture carries SFN and slot. If it does, please fold them into the analysis.

10ClaudeSFN/slot present, 15 kHz, ~49 s

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 use frame.number / timestamp; SFN/slot is reliable only inside a wrap window.
FrameSFN/slotΔtEvent
1189/30.000 sRAR (Msg2)
4192/60.034 sRRC Setup Complete + Registration Request
17242/60.533 sAS Security Mode Command
38421/82.325 sRRC Reconfiguration (DRB-1 / QFI 2)
53558/33.663 sSIP REGISTER
128433/7 (c1)12.684 sRRC Release (note SFN wrapped)
1471013/3 (c4)49.200 sepisode 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.

11UserBSR→grant→SDAP cadence

Compute the BSR → grant → SDAP scheduling cadence for episode 1.

12ClaudeUplink scheduling loop

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.

QuantityMeasuredHow
Cold trigger → first PUSCH~13 msDL RLC poll forces UE onto PUSCH from idle (f43 SFN435/3 → f44 SFN436/6)
Active grant cadence1 ms / slotback-to-back PUSCH slots while the buffer is non-empty
Drain + tail2–4 + 4–5data drains in a few slots, then 4–5 zero-BSR PUSCHs release the grant
Macro burst period~1.1–1.25 s7 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.

13UserDL view + slot chart

Let's do both — the downlink scheduling view (HARQ process usage and DL RLC poll→ACK timing) and a visual slot-occupancy chart.

14ClaudeHARQ round-robin & slot grid

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

DirectionLatencyWhy
UL data → DL Status (gNB acks UE)~0.8 msgNB always has a DL opportunity on tap (next slot)
DL data → UL Status (UE acks gNB)~13 msUE 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.

SFN0123456789phase
189···D···U·DRACH: RAR·Msg3·Msg4
557·····U····SIP REGISTER burst
558·UUUU·UXU·drain s1–3, BSR tail s4–8
801··UUUUUUXU8-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.

15UserTiming adjustments?

What are the timing adjustments we are seeing?

16ClaudeAll-null timing advance

Effectively none — both timing-advance mechanisms come out null.

MechanismObservedMeaning
Initial TA in RAR (Msg2)TA = 0 in all 3 episodeszero propagation delay — UE co-located with gNB
TA Command MAC CET_A index = 31, TAG 0, every 600 ms31 is the neutral midpoint → adjustment = 0
timeAlignmentTimer (RRC)infinityUE 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.

17UserSession-level anomalies

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.

18ClaudeThree, root-caused across layers

Scanning all 15 sessions, three things stand out — none of them a radio or protocol fault.

SymptomLayerRoot causeReal?
PDCP MAC-I “Digest wrong” on the two Security Mode Commands (f17, f136), calculated 00000000PDCPonly cipher keys supplied, not integrity keys — Wireshark computes MAC-I as zero on the first integrity-protected PDUNo — tooling artifact
RLC retransmissions (f129 dup SN 7, f146 dup SN 3)RLC AMt-PollRetransmit on an un-ACKed RRC Release; UE already in RRC_IDLE; 0 NACKs / 0 Status PDUsNo — normal teardown
SIP registration shows only REGISTER → 401, then silenceSIP / IP-flowIMS-AKA moves the authenticated re-REGISTER into IPsec ESP (opaque without SA keys)No — expected IMS
Same UE appears as 2 sessionssession tracking~33 s gap before episode 3 > 30 s idle-timeoutNo — 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.

19UserVerify the IPsec/ESP hypothesis

Let's verify the IPsec/ESP hypothesis.

20ClaudeESP SPI/sequence confirms IMS SAs

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 sequenceFrames
0x00000100  UE→P-CSCF1,2,3,4,5,6,7,8,9,1072,75,78,80,81,91,97×2,107,114
0x0000fbeb  P-CSCF→UE1,2,3,4,577,84,90,102,106
0x0000fbec  P-CSCF→UE1 (fragmented)111

This confirms the hypothesis, with a refinement:

  • Same association. Every ESP packet is between UE 3000:0:0:2::2 and P-CSCF fd29:…: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.

21UserAdd esp.spi / esp.sequence

Add esp.spi / esp.sequence to the ESP template and regenerate.

22ClaudeParams added & verified

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.

23UserDedicated ESP template

Mirror a dedicated ESP template with the cleaner opcode label.

24ClaudeSA as the headline opcode

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.sequence rides 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:

FrameDirectionLabel
72UE → P-CSCF🔐 ESP SA 0x00000100 · seq 1
77P-CSCF → UE🔐 ESP SA 0x0000fbeb · seq 1
111P-CSCF → UE🔐 ESP SA 0x0000fbec · seq 1 (fragmented)
114UE → 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.