diff --git a/simtrace2_pysniff/gsmtap.py b/simtrace2_pysniff/gsmtap.py index 8ff0688..b217c9d 100644 --- a/simtrace2_pysniff/gsmtap.py +++ b/simtrace2_pysniff/gsmtap.py @@ -8,10 +8,12 @@ import socket GSMTAP_VERSION = 0x02 GSMTAP_HDR_LEN = 4 # in 32-bit words (16 bytes) -GSMTAP_TYPE_SIM = 0x07 +GSMTAP_TYPE_SIM = 0x04 +GSMTAP_SIM_APDU = 0x00 GSMTAP_SIM_ATR = 0x01 -GSMTAP_SIM_APDU = 0x02 +GSMTAP_SIM_PPS_REQ = 0x02 +GSMTAP_SIM_PPS_RSP = 0x03 GSMTAP_UDP_PORT = 4729 diff --git a/simtrace2_pysniff/pcap.py b/simtrace2_pysniff/pcap.py index e79f070..e3b82a9 100644 --- a/simtrace2_pysniff/pcap.py +++ b/simtrace2_pysniff/pcap.py @@ -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 = '> 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) diff --git a/tests/test_pcap.py b/tests/test_pcap.py index f3d72fd..4d169ba 100644 --- a/tests/test_pcap.py +++ b/tests/test_pcap.py @@ -3,13 +3,14 @@ import struct import unittest -from simtrace2_pysniff.pcap import build_pcap, PCAP_MAGIC, LINKTYPE_GSMTAP +from simtrace2_pysniff.pcap import build_pcap, PCAP_MAGIC, LINKTYPE_ETHERNET from simtrace2_pysniff.server.server import _content_disposition +FRAMING = 14 + 20 + 8 # Ethernet + IPv4 + UDP + class TestBuildPcap(unittest.TestCase): def _gsmtap_hdr(self, sub_type): - import struct return struct.pack('!BBBBHBBIBBBB', 2, 4, 7, 0, 0, 0, 0, 0, sub_type, 0, 0, 0) def test_empty(self): @@ -19,16 +20,34 @@ class TestBuildPcap(unittest.TestCase): self.assertEqual(magic, PCAP_MAGIC) self.assertEqual((vmaj, vmin), (2, 4)) self.assertEqual(snaplen, 65535) - self.assertEqual(network, LINKTYPE_GSMTAP) + self.assertEqual(network, LINKTYPE_ETHERNET) def test_one_packet(self): pkt = build_pcap([(self._gsmtap_hdr(2), b'\xa0\xa4\x00\x00\x02\x3f\x00', 1234567890.5)]) - self.assertEqual(len(pkt), 24 + 16 + (16 + 7)) # global + pkt hdr + gsmtap hdr + data - ts_sec, ts_usec, incl_len, orig_len = struct.unpack('