mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 18:55:19 +00:00 
			
		
		
		
	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
		
			
				
	
	
		
			198 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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)
 |