[Asterisk-code-review] contrib/scripts: Make spandspflow2pcap.py Python 2.7+/3.3+ compatible (...asterisk[master])
Walter Doekes
asteriskteam at digium.com
Mon Jul 22 10:43:44 CDT 2019
Walter Doekes has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/11602
Change subject: contrib/scripts: Make spandspflow2pcap.py Python 2.7+/3.3+ compatible
......................................................................
contrib/scripts: Make spandspflow2pcap.py Python 2.7+/3.3+ compatible
Change-Id: Ica182a891743017ff3cda16de3d95335fffd9a91
---
M contrib/scripts/spandspflow2pcap.py
1 file changed, 173 insertions(+), 109 deletions(-)
git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/02/11602/1
diff --git a/contrib/scripts/spandspflow2pcap.py b/contrib/scripts/spandspflow2pcap.py
old mode 100755
new mode 100644
index 7c403f1..442e15f
--- a/contrib/scripts/spandspflow2pcap.py
+++ b/contrib/scripts/spandspflow2pcap.py
@@ -9,7 +9,7 @@
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 ...
+ [2013-08-07 15:17:34] FAX[23479] res_fax.c: FLOW T.38 Rx 5: IFP c0 ...
Output data will look like a valid pcap file ;-)
@@ -21,14 +21,16 @@
or the git master branch: https://github.com/SIPp/sipp
-Author: Walter Doekes, OSSO B.V. (2013,2015,2016)
+Author: Walter Doekes, OSSO B.V. (2013,2015,2016,2019)
License: Public Domain
'''
from base64 import b16decode
+from collections import namedtuple
from datetime import datetime, timedelta
from re import search
from time import mktime
from struct import pack
+import os
import sys
@@ -36,124 +38,176 @@
EMPTY_RECOVERY = False
+IFP = namedtuple('IFP', 'date seqno data') # datetime, int, bytearray
+
+
def n2b(text):
- return b16decode(text.replace(' ', '').replace('\n', '').upper())
+ """
+ Convert "aa bb cc" to bytearray('\xaa\xbb\xcc').
+ """
+ return bytearray(
+ b16decode(text.replace(' ', '').replace('\n', '').upper()))
+
+
+class SkipPacket(Exception):
+ pass
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')
+ 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?
+
+ def add(self, ifp):
+ """
+ Add the IFP packet.
+
+ 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)
+ - ...
+ """
+ # First packet?
+ if self.seqno is None:
+ # Add preamble.
+ self._add_preamble()
+ # Start a second late (optional).
+ self._add_garbage(ifp.date)
+
+ # Set sequence, and fill with missing leading zeroes.
+ self.seqno = 0
+ for i in range(ifp.seqno):
+ self.add(IFP(date=ifp.date, seqno=i, data=bytearray([0])))
+
+ # Auto-increasing dates
+ if self.date is None or ifp.date > self.date:
+ self.date = ifp.date
+ elif ifp.date < self.date.replace(microsecond=0):
+ assert False, 'More packets than expected in 1s? {!r}/{!r}'.format(
+ ifp.date, self.date)
+ else:
+ self.date += timedelta(microseconds=9000)
+
+ # Add packet.
+ self.seqno = ifp.seqno
+ try:
+ self.outfile.write(self._make_packet(ifp.data))
+ except SkipPacket:
+ pass
+
+ def _add_preamble(self):
self.outfile.write(self.PCAP_PREAMBLE)
- def data2packet(self, date, udpseqno, seqno, data, prev_data):
- sum16 = '\x43\x21' # checksum is irrelevant for sipp sending
+ def _add_garbage(self, date):
+ if self.date is None or date > self.date:
+ self.date = date
- new_prev = data # without seqno..
- data = '%s%s' % (pack('>H', seqno), data)
+ self.seqno = 0xffff
+ self.outfile.write(self._make_packet(
+ bytearray(b'GARBAGE'), is_ifp=False))
+
+ def _make_packet(self, ifp_data, is_ifp=True):
+ sum16 = bytearray(b'\x43\x21') # the OS fixes the checksums for us
+
+ data = bytearray()
+ if is_ifp:
+ data.append(len(ifp_data)) # length
+ data.extend(ifp_data) # data
+ self.prev_data, prev_data = data[:], self.prev_data
+ else:
+ data.extend(ifp_data)
+ prev_data = None
+
if prev_data:
- if LOSSY and (seqno % 3) == 2:
- return '', new_prev
+ if LOSSY and (self.seqno % 3) == 2:
+ self.udpseqno += 1
+ raise SkipPacket()
+
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)
+ data.extend([0, packets + 1] + [0] * packets)
+ data.extend(prev_data)
else:
# Add 1 previous packet, without the seqno.
- data += '\x00\x01' + prev_data
+ data.extend([0, 1])
+ data.extend(prev_data)
- kwargs = {'udpseqno': pack('>H', udpseqno), 'sum16': sum16}
+ # Wrap it in UDP
+ udp = bytearray(
+ b'\x00\x01\x00\x02%(len)s%(sum16)s%(seqno)s%(data)s' % {
+ b'len': pack('>H', len(data) + 10),
+ b'sum16': sum16,
+ b'seqno': pack('>H', self.seqno),
+ b'data': data})
- kwargs['data'] = data
- kwargs['lenb16'] = pack('>H', len(kwargs['data']) + 8)
- udp = '\x00\x01\x00\x02%(lenb16)s%(sum16)s%(data)s' % kwargs
+ # Wrap it in IP
+ ip = bytearray(
+ b'\x45\xb8%(len)s%(udpseqno)s\x00\x00\xf9\x11%(sum16)s'
+ b'\x01\x01\x01\x01\x02\x02\x02\x02%(udp)s' % {
+ b'len': pack('>H', len(udp) + 20),
+ b'udpseqno': pack('>H', self.udpseqno),
+ b'sum16': sum16,
+ b'udp': udp})
- 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
+ # Wrap it in Ethernet
+ ethernet = bytearray(
+ b'\x00\x00\x00\x01\x00\x06\x00\x30\x48\xb1\x1c\x34\x00\x00'
+ b'\x08\x00%(ip)s' % {b'ip': ip})
- 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)
+ # Wrap it in a pcap packet
+ packet = bytearray(b'%(prelude)s%(ethernet)s' % {
+ b'prelude': pack(
+ '<IIII', int(mktime(self.date.timetuple())),
+ self.date.microsecond, len(ethernet), len(ethernet)),
+ b'ethernet': ethernet})
# 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)
+ return 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'))
+class SpandspLog:
+ def __init__(self, fp):
+ self._fp = fp
- 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
+ def __iter__(self):
+ r"""
+ Looks for lines line:
+
+ [2013-08-07 15:17:34] FAX[23479] res_fax.c: \
+ FLOW T.38 Rx 5: IFP c0 01 80 00 00 ff
+
+ And yields:
+
+ IFP(date=..., seqno=..., data=...)
+ """
+ prev_seqno = None
+
+ for lineno, line in enumerate(self._fp):
if 'FLOW T.38 Rx' not in line:
continue
if 'IFP' not in line:
@@ -171,27 +225,37 @@
assert match
data = n2b(match.groups()[0])
- # Have the file start a second early.
- if first:
- p.add_garbage(date)
- first = False
+ if prev_seqno is not None:
+ # Expected all sequence numbers. But you can safely disable
+ # this check.
+ assert seqno == prev_seqno + 1, '%s+1 != %s' % (
+ seqno, prev_seqno)
+ pass
+ prev_seqno = seqno
- # 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)
+ yield IFP(date=date, seqno=seqno, data=data)
+
+
+def main(logname, pcapname):
+ with open(sys.argv[1], 'r') as infile:
+ log = SpandspLog(infile)
+
+ # with open(sys.argv[2], 'xb') as outfile: # py3 exclusive write, bin
+ create_or_fail = os.O_CREAT | os.O_EXCL | os.O_WRONLY
+ try:
+ fd = os.open(sys.argv[2], create_or_fail, 0o600)
+ except Exception:
+ raise
+ else:
+ with os.fdopen(fd, 'wb') as outfile:
+ pcap = FaxPcap(outfile)
+ for data in log:
+ pcap.add(data)
+
+
+if __name__ == '__main__':
+ if len(sys.argv) != 3:
+ sys.stderr.write('Usage: {} LOGFILE PCAP\n'.format(sys.argv[0]))
+ sys.exit(1)
+
+ main(sys.argv[1], sys.argv[2])
--
To view, visit https://gerrit.asterisk.org/c/asterisk/+/11602
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: Ica182a891743017ff3cda16de3d95335fffd9a91
Gerrit-Change-Number: 11602
Gerrit-PatchSet: 1
Gerrit-Owner: Walter Doekes <walter+asterisk at wjd.nu>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20190722/2ece5e62/attachment-0001.html>
More information about the asterisk-code-review
mailing list