Fix GSMTAP linktype + type/sub-type values so Wireshark decodes PCAP

pcap.py: LINKTYPE_GSMTAP (155) was wrong — 155 is DLT_USER8, so
Wireshark showed 'USER 8 / DLT=155'. Now write Ethernet-encapsulated
PCAP (LINKTYPE 1): each GSMTAP message wrapped in Ethernet + IPv4 +
UDP (port 4729), with correct checksums — matching a real tcpdump
capture of GSMTAP-over-UDP and how Wireshark's gsmtap dissector is keyed.

gsmtap.py: GSMTAP_TYPE_SIM was 0x07 (WiMAX burst) — corrected to 0x04;
GSMTAP_SIM_APDU was 0x02 (PPS req) — corrected to 0x00; added PPS_REQ/
PPS_RSP. These match libosmocore/Wireshark packet-gsmtap.h.

Verified with tshark: PCAP now decodes as GSM SIM (SELECT/STATUS/ATR)
instead of 'USER 8'.

tests: pcap tests assert Ethernet linktype + framing + GSMTAP payload.
This commit is contained in:
catarrh committed 2026-08-12 21:32:21 +03:00
1 parent e09aac4cb7
commit cb4c07f3c2
3 files changed
+76 -20

No files matched your search

+44 -9
View File
@@ -1,7 +1,9 @@
"""PCAP file writer for Wireshark analysis.
Writes a standard PCAP file with LINKTYPE_GSMTAP (155, 0x009b).
Each packet is a GSMTAP header + raw APDU/ATR data.
Writes a standard PCAP file with Ethernet (LINKTYPE 1, DLT_EN10MB)
encapsulation. Each packet is Ethernet + IPv4 + UDP (port 4729) + GSMTAP,
matching a real ``tcpdump -i lo udp port 4729`` capture of GSMTAP-over-UDP
(simtrace2-sniff / simtrace2-pysniff output).
"""
import io
@@ -9,7 +11,11 @@ import struct
import time
PCAP_MAGIC = 0xa1b2c3d4
LINKTYPE_GSMTAP = 155 # 0x009b
LINKTYPE_ETHERNET = 1 # DLT_EN10MB
GSMTAP_UDP_PORT = 4729
_LOOPBACK = b'\x7f\x00\x00\x01'
_PCAP_GLOBAL_HDR_FMT = '<IHHiIII'
_PCAP_GLOBAL_HDR_SIZE = struct.calcsize(_PCAP_GLOBAL_HDR_FMT)
@@ -18,6 +24,35 @@ _PCAP_PKT_HDR_FMT = '<IIII'
_PCAP_PKT_HDR_SIZE = struct.calcsize(_PCAP_PKT_HDR_FMT)
def _checksum(data):
if len(data) % 2:
data += b'\x00'
s = sum(struct.unpack('!%dH' % (len(data) // 2), data))
s = (s >> 16) + (s & 0xffff)
s = (s >> 16) + (s & 0xffff)
return ~s & 0xffff
def wrap_gsmtap(gsmtap_payload):
"""Wrap a GSMTAP message in Ethernet + IPv4 + UDP (port 4729) headers."""
udp_len = 8 + len(gsmtap_payload)
udp_nocs = struct.pack('!HHHH', GSMTAP_UDP_PORT, GSMTAP_UDP_PORT, udp_len, 0)
pseudo = _LOOPBACK + _LOOPBACK + struct.pack('!BBH', 0, 17, udp_len)
udp_csum = _checksum(pseudo + udp_nocs + gsmtap_payload)
udp = struct.pack('!HHHH', GSMTAP_UDP_PORT, GSMTAP_UDP_PORT, udp_len, udp_csum) + gsmtap_payload
ip_len = 20 + len(udp)
ip_nocs = struct.pack('!BBHHHBBH4s4s', 0x45, 0x00, ip_len, 0, 0x4000, 64, 17, 0,
_LOOPBACK, _LOOPBACK)
ip_csum = _checksum(ip_nocs)
ip = struct.pack('!BBHHHBBH4s4s', 0x45, 0x00, ip_len, 0, 0x4000, 64, 17, ip_csum,
_LOOPBACK, _LOOPBACK) + udp
eth = struct.pack('!6s6sH', b'\x00' * 6, b'\x00' * 6, 0x0800)
return eth + ip
def build_pcap(packets):
"""Build a PCAP file in memory.
@@ -34,10 +69,10 @@ def build_pcap(packets):
0,
0,
65535,
LINKTYPE_GSMTAP,
LINKTYPE_ETHERNET,
))
for gsmtap_hdr, data, ts in packets:
packet = gsmtap_hdr + data
packet = wrap_gsmtap(gsmtap_hdr + data)
ts_sec = int(ts)
ts_usec = int((ts - ts_sec) * 1_000_000)
buf.write(struct.pack(
@@ -52,7 +87,7 @@ def build_pcap(packets):
class PcapWriter:
"""Write sniffed messages as GSMTAP-encapsulated PCAP packets."""
"""Write sniffed messages as Ethernet/IP/UDP/GSMTAP PCAP packets."""
def __init__(self, path):
self._f = open(path, 'wb')
@@ -67,12 +102,12 @@ class PcapWriter:
0, # thiszone (GMT)
0, # sigfigs
65535, # snaplen
LINKTYPE_GSMTAP, # network (LINKTYPE_GSMTAP)
LINKTYPE_ETHERNET, # network (Ethernet)
)
self._f.write(hdr)
def write_packet(self, gsmtap_hdr, data, ts=None):
"""Write a single GSMTAP-encapsulated packet.
"""Write a single GSMTAP-encapsulated packet (wrapped in IP/UDP/Eth).
*gsmtap_hdr* is the 16-byte GSMTAP header (already packed).
*data* is the raw APDU/ATR bytes.
@@ -80,7 +115,7 @@ class PcapWriter:
if ts is None:
ts = time.time()
packet = gsmtap_hdr + data
packet = wrap_gsmtap(gsmtap_hdr + data)
ts_sec = int(ts)
ts_usec = int((ts - ts_sec) * 1_000_000)