pcap.py: build_pcap(packets) — build PCAP bytes in memory (same format as PcapWriter) database.py: get_messages_raw() — raw elapsed/type/data without decode server.py: GET /api/sessions/<id>/pcap generates a GSMTAP-encapsulated PCAP (ATR/TPDU) with real timestamps and downloads it as <session-name>.pcap (ASCII filename fallback + RFC 5987 UTF-8 for spaces/unicode via _content_disposition) tests: build_pcap + _content_disposition (67 total)
98 lines
2.6 KiB
Python
98 lines
2.6 KiB
Python
"""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.
|
|
"""
|
|
|
|
import io
|
|
import struct
|
|
import time
|
|
|
|
PCAP_MAGIC = 0xa1b2c3d4
|
|
LINKTYPE_GSMTAP = 155 # 0x009b
|
|
|
|
_PCAP_GLOBAL_HDR_FMT = '<IHHiIII'
|
|
_PCAP_GLOBAL_HDR_SIZE = struct.calcsize(_PCAP_GLOBAL_HDR_FMT)
|
|
|
|
_PCAP_PKT_HDR_FMT = '<IIII'
|
|
_PCAP_PKT_HDR_SIZE = struct.calcsize(_PCAP_PKT_HDR_FMT)
|
|
|
|
|
|
def build_pcap(packets):
|
|
"""Build a PCAP file in memory.
|
|
|
|
*packets* is a list of ``(gsmtap_hdr, data, ts)`` tuples, where
|
|
gsmtap_hdr is the packed 16-byte GSMTAP header and ts is a
|
|
seconds-since-epoch float. Returns the PCAP file contents as bytes.
|
|
"""
|
|
buf = io.BytesIO()
|
|
buf.write(struct.pack(
|
|
_PCAP_GLOBAL_HDR_FMT,
|
|
PCAP_MAGIC,
|
|
2,
|
|
4,
|
|
0,
|
|
0,
|
|
65535,
|
|
LINKTYPE_GSMTAP,
|
|
))
|
|
for gsmtap_hdr, data, ts in packets:
|
|
packet = gsmtap_hdr + data
|
|
ts_sec = int(ts)
|
|
ts_usec = int((ts - ts_sec) * 1_000_000)
|
|
buf.write(struct.pack(
|
|
_PCAP_PKT_HDR_FMT,
|
|
ts_sec,
|
|
ts_usec,
|
|
len(packet),
|
|
len(packet),
|
|
))
|
|
buf.write(packet)
|
|
return buf.getvalue()
|
|
|
|
|
|
class PcapWriter:
|
|
"""Write sniffed messages as GSMTAP-encapsulated PCAP packets."""
|
|
|
|
def __init__(self, path):
|
|
self._f = open(path, 'wb')
|
|
self._write_global_header()
|
|
|
|
def _write_global_header(self):
|
|
hdr = struct.pack(
|
|
_PCAP_GLOBAL_HDR_FMT,
|
|
PCAP_MAGIC, # magic_number
|
|
2, # version_major
|
|
4, # version_minor
|
|
0, # thiszone (GMT)
|
|
0, # sigfigs
|
|
65535, # snaplen
|
|
LINKTYPE_GSMTAP, # network (LINKTYPE_GSMTAP)
|
|
)
|
|
self._f.write(hdr)
|
|
|
|
def write_packet(self, gsmtap_hdr, data, ts=None):
|
|
"""Write a single GSMTAP-encapsulated packet.
|
|
|
|
*gsmtap_hdr* is the 16-byte GSMTAP header (already packed).
|
|
*data* is the raw APDU/ATR bytes.
|
|
"""
|
|
if ts is None:
|
|
ts = time.time()
|
|
|
|
packet = gsmtap_hdr + data
|
|
ts_sec = int(ts)
|
|
ts_usec = int((ts - ts_sec) * 1_000_000)
|
|
|
|
pkt_hdr = struct.pack(
|
|
_PCAP_PKT_HDR_FMT,
|
|
ts_sec, # ts_sec
|
|
ts_usec, # ts_usec
|
|
len(packet), # incl_len
|
|
len(packet), # orig_len
|
|
)
|
|
self._f.write(pkt_hdr + packet)
|
|
|
|
def close(self):
|
|
self._f.close()
|