Please download the pcap from srsRAN_Project issue #1289 and generate explore and session level flows. Please generate FXT files as well as visualether.toml.
Pulled du_f1u.zip from the issue attachments and extracted du_f1u.pcap (2.2 MB, 37,871 frames). analyze_capture detected the shape immediately: a bare GTP-U packet under DLT_USER — DLT=156 / “User 9” — which VisualEther auto-maps to the gtp dissector. This is the srsRAN F1-U interface (CU-UP↔DU user plane).
The GTP-U T-PDU is not plain IP. Every packet carries an NR RAN Container extension header holding an NR-U / NRUP frame (TS 38.425), and the payload below it is a PDCP-NR PDU. extract_fields confirmed two NR-U PDU types and no IP endpoints at all:
| nrup.pdu_type | Direction | Role |
|---|---|---|
0 DL User Data | CU-UP → DU | Downlink PDCP delivery |
1 DL Data Delivery Status | DU → CU-UP | Flow-control feedback (delivered/desired buffer) |
With zero IP addresses to axis on (extract_endpoints returned empty), I authored the FXTs with generic <message> elements and constant CU-UP/DU axis labels — a <source>/<destination> that watches gtp.teid (present on every packet) and replaces it wholesale:
<source><address match=".*" replace="CU-UP">gtp.teid</address></source> <destination><address match=".*" replace="DU">gtp.teid</address></destination>
Direction comes from the opcode value (nrup.pdu_type), one template per direction. Sessions key on gtp.teid — one session per F1-U tunnel — direction-agnostic and auto-start, with a 2 s idle-timeout since user-plane tunnels have no handshake. Both FXTs validated at 100% coverage. materialize_config wrote visualether.toml persisting the DLT_USER→gtp mapping so a plain visualether generate Just Works.
The explore diagram rendered all 37,871 messages; the session view split into 49 tunnels:
| Session outcome | Count |
|---|---|
| timeout::idle 2.000s | 45 |
| incomplete::mid-capture (still active) | 4 |
The four active tunnels are the high-volume data bearers (~8,000 messages each); the 45 idle-closed tunnels are shorter flows. The inner VoNR (SIP/RTP) the issue reporter wanted is below PDCP-NR and not yet visible — that comes later.
param fields can be marked with required="true" to match different templates using the same opcode.
Applied. DL User Data and DL Data Delivery Status differ by opcode value, so the opcode regex already separates them. But “final” vs “non-final” Data Delivery Status share the same opcode value (nrup.pdu_type) — they differ only in nrup.final_frame_ind. A <qualify> can’t live inside a <message>, so I gated a dedicated session-stop template with a required param:
<param required="true" match=".*(Frame is final).*"
replace="🏁 Final Frame: $1">nrup.final_frame_ind</param>
Only a “Frame is final” Data Delivery Status matches this gate and closes its tunnel as success::final-frame; every other Data Delivery Status fails the gate and falls through to the ongoing-flow template. This capture is entirely “not final”, so it falls through today (tunnels still close on the 2 s idle-timeout), but the FXT now closes tunnels cleanly whenever a final frame appears — and it cleared the FXT-LIFECYCLE “never closes gracefully” warning. Re-validated at 100% NR-U coverage.
Reconstruct the inner VoNR pcap from the PDCP.
First I inspected the raw T-PDU bytes rather than guessing a strip length. The layout is unambiguous and the PDCP is NEA0 / cleartext:
80 00 00 | 45 00 00 2c 8d b4 ... 0a 2e 00 be 0a 2a 00 b2 13 c4 13 c4 ... ┗━PDCP━┛ ┗━ inner IPv4 (0x45): 10.46.0.190 → 10.42.0.178, TCP 5060/5060
- Byte 0 =
0x80→ a PDCP-NR data PDU (D/C=1), 18-bit SN → 3-byte header. - Byte 3 =
0x45→ the inner IPv4 header starts right after — no SDAP header. - The inner payload is already readable: SIP over TCP/5060, a REGISTER to
ims.mnc070.mcc001.3gppnetwork.org, even DNS to 8.8.8.8.
So reconstruction is: strip the first 3 bytes off every non-empty T-PDU and rebuild a raw-IP pcap. I wrote reconstruct_inner.py — it runs tshark to pull frame.time_epoch + gtp.tpdu_data, validates each T-PDU (0x80… then 0x45 at offset 3), emits a text2pcap hexdump with per-packet timestamps, and rebuilds with text2pcap -l 101 (LINKTYPE_RAW):
tshark -r du_f1u.pcap -o 'uat:user_dlts:"User 9 (DLT=156)","gtp",...' \ -T fields -e frame.time_epoch -e gtp.tpdu_data … strip 3-byte PDCP header, keep inner IPv4 … text2pcap -t "%Y-%m-%d %H:%M:%S.%f" -l 101 inner.txt du_f1u_inner.pcap
Result — 2,960 inner IP packets, timestamps preserved; 34,911 DL Data Delivery Status frames correctly skipped (they carry no T-PDU); 0 non-IPv4. The protocol hierarchy of what was hidden inside the PDCP:
| Protocol | Frames | Detail |
|---|---|---|
| SIP | 6 | REGISTER / 100 / 401 (over TCP 5060) |
| RTP | 1,670 | Voice media, payload type 107 |
| RTCP | 14 | Media control |
| TLS | 273 | XCAP / Ut over HTTPS |
| ESP | 208 | IPsec (transport mode) |
| DNS | 59 | IMS / app discovery |
| HTTP | 4 | Provisioning |
Verified the reconstruction dissects cleanly: the first five frames are the TCP three-way handshake on port 5060 followed by the SIP REGISTER, all at the original capture timestamps. This is exactly the traffic issue #1289 reported as invisible — Wireshark stopped at GTP because the T-PDU is PDCP-NR, not IP.
Yes, generate the VoNR flows for du_f1u_inner.pcap.
analyze_capture on the reconstructed pcap detected 8 protocols (SIP, RTP, RTCP, DNS, TLS, HTTP, ESP/IPsec, TCP). I took VisualEther’s sip-rtp sample as the base (SIP/SDP + RTP/RTCP, with SIP-over-TCP support this capture needs) and extended it with DNS, TLS, HTTP and ESP/IPsec templates so the diagram tells the full VoNR story. Six session types — sip-dialog, sip-registration, rtp-stream, dns, tls, esp-ipsec — both FXTs validated at 100% coverage (RTP surfaced with -o rtp.heuristic_rtp:TRUE, required because there is no readable SDP to pin the dynamic ports). Session summary:
| Session type | Count | Outcome |
|---|---|---|
| dns | 23 | success::resolved |
| tls | 28 | idle-closed |
| esp-ipsec | 8 | 4 active + 4 idle-closed |
| sip-registration | 2 | incomplete::no-close |
| rtp-stream | 2 | idle-closed |
Finding. The cleartext SIP is only REGISTER→100→401 (IMS-AKA challenge) for two UEs — there is no visible INVITE, yet 1,670 RTP frames of voice flow between them. That is the expected IMS pattern: once the IPsec SA is established during registration, the INVITE/200 OK travel inside the ESP-encrypted tunnel (the 208 ESP frames, transport mode). The media stays visible; the signalling that set it up is protected. So the “incomplete” SIP registration is not a failure — the call setup is simply opaque without the IPsec SA keys.
Please explain the DNS queries in the du_f1u_inner capture. I see that two DNS queries are sent but one response is received.
It’s DNS resolver failover, not a lost response. Take transaction 0xda1e (connectivitycheck.platform.hicloud.com):
| Frame | Time | Src → Dst | Type |
|---|---|---|---|
| 6 | 0.14 s | UE → 8.8.8.8 | Query |
| 44 | 2.09 s | UE → 8.8.4.4 | Query (retransmit, same ID) |
| 45 | 2.55 s | 8.8.4.4 → UE | Response |
Both queries share the same DNS transaction ID and question. The UE hits its primary resolver 8.8.8.8 first, gets nothing, waits ~1.95 s, then retransmits the identical query to its secondary 8.8.4.4, which answers 0.46 s later. One logical lookup = two query packets + one response.
Why the primary never answers
Aggregated over the whole capture, the root cause is unambiguous:
| Resolver | Queries | Responses |
|---|---|---|
| 8.8.8.8 (primary) | 13 | 0 |
| 8.8.4.4 (secondary) | 23 | 23 |
Every DNS response comes from 8.8.4.4; the primary 8.8.8.8 is silent for every query — unreachable or filtered on the testbed’s N6/UPF egress. So every lookup pays a fixed ~2 s primary-timeout penalty before succeeding on the secondary. Because both query packets share the transaction ID, the dns session (keyed on dns.id) correctly stitches the retransmit and the response into one session that closes as success::resolved — which is why the session count reported 23 resolved lookups rather than double-counting the retries. These lookups (HiCloud / Huawei / AMap / Baidu endpoints) are the handset’s background connectivity checks riding the same DRB, and they survived reconstruction intact because they are cleartext UDP/53 — unlike the IPsec-protected SIP INVITE.
Method. Two pcaps analyzed with VisualEther via its MCP integration with Claude. For the F1-U transport: analyze_capture to detect bare GTP-U / DLT_USER and the NR-U/NRUP stack, hand-authored explore.fxt.xml / sessions.fxt.xml with constant CU-UP/DU axis labels and a required-param final-frame gate, validate_fxt on every edit, then explore + extract_sessions and materialize_config. The inner VoNR pcap was reconstructed with a tshark→strip-PDCP→text2pcap -l 101 pipeline, then diagrammed with an extended sip-rtp FXT (RTP heuristic on). Every field cited (nrup.pdu_type, gtp.teid, PDCP/IP layout, SIP methods, RTP streams, ESP frame counts, dns.id and per-resolver query/response counts) was read from the rendered NDJSON and cross-checked with tshark across all 37,871 + 2,960 frames.