#!/usr/bin/env python # vim: set ts=8 sw=4 sts=4 et ai tw=79: ''' Usage: ./spandspflow2pcap.py SPANDSP_LOG SENDFAX_PCAP Takes a log from Asterisk with SpanDSP, extracts the "received" data and puts it in a pcap file. Use 'fax set debug on' and configure logger.conf to get fax logs. Input data should look something like this:: [2013-08-07 15:17:34] FAX[23479] res_fax.c: FLOW T.38 Rx 5: IFP c0 01 ... Output data will look like a valid pcap file ;-) This allows you to reconstruct received faxes into replayable pcaps. Replaying is expected to be done by SIPp with sipp-sendfax.xml. The SIPp binary used for replaying must have image (fax) support. This means you'll need a version higher than 3.5.0 (unreleased when writing this), or the git master branch: https://github.com/SIPp/sipp Author: Walter Doekes, OSSO B.V. (2013,2015,2016) License: Public Domain ''' from base64 import b16decode from datetime import datetime, timedelta from re import search from time import mktime from struct import pack import sys LOSSY = False EMPTY_RECOVERY = False def n2b(text): return b16decode(text.replace(' ', '').replace('\n', '').upper()) class FaxPcap(object): PCAP_PREAMBLE = n2b('d4 c3 b2 a1 02 00 04 00' '00 00 00 00 00 00 00 00' 'ff ff 00 00 71 00 00 00') def __init__(self, outfile): self.outfile = outfile self.date = None self.dateoff = timedelta(seconds=0) self.seqno = None self.udpseqno = 128 self.prev_data = None # Only do this if at pos 0? self.outfile.write(self.PCAP_PREAMBLE) def data2packet(self, date, udpseqno, seqno, data, prev_data): sum16 = '\x43\x21' # checksum is irrelevant for sipp sending new_prev = data # without seqno.. data = '%s%s' % (pack('>H', seqno), data) if prev_data: if LOSSY and (seqno % 3) == 2: return '', new_prev if EMPTY_RECOVERY: # struct ast_frame f[16], we have room for a few # packets. packets = 14 data += '\x00%c%s%s' % ( chr(packets + 1), '\x00' * packets, prev_data) else: # Add 1 previous packet, without the seqno. data += '\x00\x01' + prev_data kwargs = {'udpseqno': pack('>H', udpseqno), 'sum16': sum16} kwargs['data'] = data kwargs['lenb16'] = pack('>H', len(kwargs['data']) + 8) udp = '\x00\x01\x00\x02%(lenb16)s%(sum16)s%(data)s' % kwargs kwargs['data'] = udp kwargs['lenb16'] = pack('>H', len(kwargs['data']) + 20) ip = ('\x45\xb8%(lenb16)s%(udpseqno)s\x00\x00\xf9\x11%(sum16)s\x01' '\x01\x01\x01\x02\x02\x02\x02%(data)s') % kwargs kwargs['data'] = ip frame = ('\x00\x00\x00\x01\x00\x06\x00\x30\x48\xb1\x1c\x34\x00\x00' '\x08\x00%(data)s') % kwargs kwargs['data'] = frame sec = mktime(date.timetuple()) msec = date.microsecond datalen = len(kwargs['data']) kwargs['pre'] = pack(' self.date: # print 'date is larger', date, self.date self.date = date elif (date < self.date.replace(microsecond=0)): assert False, ('We increased too fast.. decrease delta: %r/%r' % (date, self.date)) else: self.date += timedelta(microseconds=9000) print seqno, '\t', self.date + self.dateoff # Make packet. packet, prev_data = self.data2packet(self.date + self.dateoff, self.udpseqno, self.seqno, data, self.prev_data) self.outfile.write(packet) # Increase values. self.udpseqno += 1 self.seqno += 1 self.prev_data = prev_data def add_garbage(self, date): if self.date is None or date > self.date: self.date = date packet, ignored = self.data2packet(self.date, self.udpseqno, 0xffff, 'GARBAGE', '') self.udpseqno += 1 self.outfile.write(packet) with open(sys.argv[1], 'r') as infile: with open(sys.argv[2], 'wb') as outfile: first = True p = FaxPcap(outfile) # p.add(datetime.now(), 0, n2b('06')) # p.add(datetime.now(), 1, n2b('c0 01 80 00 00 ff')) for lineno, line in enumerate(infile): # Look for lines like: # [2013-08-07 15:17:34] FAX[23479] res_fax.c: \ # FLOW T.38 Rx 5: IFP c0 01 80 00 00 ff if 'FLOW T.38 Rx' not in line: continue if 'IFP' not in line: continue match = search(r'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)', line) assert match date = datetime(*[int(i) for i in match.groups()]) match = search(r'Rx\s*(\d+):', line) assert match seqno = int(match.groups()[0]) match = search(r': IFP ([0-9a-f ]+)', line) assert match data = n2b(match.groups()[0]) # Have the file start a second early. if first: p.add_garbage(date) first = False # Add the packets. # # T.38 basic format of UDPTL payload section with redundancy: # # UDPTL_SEQNO # - 2 sequence number (big endian) # UDPTL_PRIMARY_PAYLOAD (T30?) # - 1 subpacket length (excluding this byte) # - 1 type of message (e.g. 0xd0 for data(?)) # - 1 items in data field (e.g. 0x01) # - 2 length of data (big endian) # - N data # RECOVERY (optional) # - 2 count of previous seqno packets (big endian) # - N UDPTL_PRIMARY_PAYLOAD of (seqno-1) # - N UDPTL_PRIMARY_PAYLOAD of (seqno-2) # - ... # p.add(date, seqno, data)