summaryrefslogtreecommitdiff
path: root/contrib/scripts/spandspflow2pcap.py
diff options
context:
space:
mode:
authorWalter Doekes <walter+asterisk@wjd.nu>2016-01-06 14:12:40 +0100
committerWalter Doekes <walter+asterisk@wjd.nu>2016-01-06 14:12:40 +0100
commit64b2046f3de199954e23118ddc3a2e25d465b23c (patch)
tree6f4396221b97bcfa93f5b35ae1ec1357cf2cf835 /contrib/scripts/spandspflow2pcap.py
parent0c62e0a55cc5480b370ea7a4ab291b0842f3a133 (diff)
Add sipp-sendfax.xml and spandspflow2pcap.py to contrib/scripts.
The spandspflow2pcap.py creates pcap files from fax.log files, generated through 'fax set debug on' when receiving a fax. An example fax.log is included as spandspflow2pcap.log. The sipp-sendfax.xml SIPp scenario can be used to replay that fax with a recent version of SIPp. ASTERISK-25660 #close Change-Id: I4de8f28b084055b482ab8a5b28d28b605b0ed526
Diffstat (limited to 'contrib/scripts/spandspflow2pcap.py')
-rwxr-xr-xcontrib/scripts/spandspflow2pcap.py197
1 files changed, 197 insertions, 0 deletions
diff --git a/contrib/scripts/spandspflow2pcap.py b/contrib/scripts/spandspflow2pcap.py
new file mode 100755
index 000000000..a6546b693
--- /dev/null
+++ b/contrib/scripts/spandspflow2pcap.py
@@ -0,0 +1,197 @@
+#!/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('<IIII', sec, msec, datalen, datalen)
+ packet = '%(pre)s%(data)s' % kwargs
+
+ return (packet, new_prev)
+
+ def add(self, date, seqno, data):
+ if self.seqno is None:
+ self.seqno = 0
+ for i in range(seqno):
+ # In case the first zeroes were dropped, add them.
+ self.add(date, i, '\x00')
+ assert seqno == self.seqno, '%s != %s' % (seqno, self.seqno)
+
+ # Data is prepended by len(data).
+ data = chr(len(data)) + data
+
+ # Auto-increasing dates
+ if self.date is None or date > 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)