4 Usage: ./spandspflow2pcap.py SPANDSP_LOG SENDFAX_PCAP 6 Takes a log from Asterisk with SpanDSP, extracts the "received" data 7 and puts it in a pcap file. Use 'fax set debug on' and configure 8 logger.conf to get fax logs. 10 Input data should look something like this:: 12 [2013-08-07 15:17:34] FAX[23479] res_fax.c: FLOW T.38 Rx 5: IFP c0 ... 14 Output data will look like a valid pcap file ;-) 16 This allows you to reconstruct received faxes into replayable pcaps. 18 Replaying is expected to be done by SIPp with sipp-sendfax.xml. The 19 SIPp binary used for replaying must have image (fax) support. This means 20 you'll need a version higher than 3.5.0 (unreleased when writing this), 21 or the git master branch: https://github.com/SIPp/sipp 24 Author: Walter Doekes, OSSO B.V. (2013,2015,2016,2019) 25 License: Public Domain 27 from base64
import b16decode
28 from collections
import namedtuple
29 from datetime
import datetime, timedelta
31 from time
import mktime
32 from struct
import pack
38 EMPTY_RECOVERY =
False 41 IFP = namedtuple(
'IFP',
'date seqno data')
46 Convert "aa bb cc" to bytearray('\xaa\xbb\xcc'). 49 b16decode(text.replace(
' ',
'').
replace(
'\n',
'').upper()))
56 class FaxPcap(object):
58 'd4 c3 b2 a1 02 00 04 00' 59 '00 00 00 00 00 00 00 00' 60 'ff ff 00 00 71 00 00 00')
75 T.38 basic format of UDPTL payload section with redundancy: 78 - 2 sequence number (big endian) 79 UDPTL_PRIMARY_PAYLOAD (T30?) 80 - 1 subpacket length (excluding this byte) 81 - 1 type of message (e.g. 0xd0 for data(?)) 82 - 1 items in data field (e.g. 0x01) 83 - 2 length of data (big endian) 86 - 2 count of previous seqno packets (big endian) 87 - N UDPTL_PRIMARY_PAYLOAD of (seqno-1) 88 - N UDPTL_PRIMARY_PAYLOAD of (seqno-2) 92 if self.
seqno is None:
100 for i
in range(ifp.seqno):
101 self.
add(
IFP(date=ifp.date, seqno=i, data=bytearray([0])))
104 if self.
date is None or ifp.date > self.
date:
107 assert False,
'More packets than expected in 1s? {!r}/{!r}'.
format(
110 self.
date += timedelta(microseconds=9000)
113 self.
seqno = ifp.seqno
119 def _add_preamble(self):
122 def _add_garbage(self, date):
123 if self.
date is None or date > self.
date:
128 bytearray(b
'GARBAGE'), is_ifp=
False))
130 def _make_packet(self, ifp_data, is_ifp=True):
131 sum16 = bytearray(b
'\x43\x21')
135 data.append(
len(ifp_data))
136 data.extend(ifp_data)
139 data.extend(ifp_data)
143 if LOSSY
and (self.
seqno % 3) == 2:
151 data.extend([0, packets + 1] + [0] * packets)
152 data.extend(prev_data)
156 data.extend(prev_data)
160 b
'\x00\x01\x00\x02%(len)s%(sum16)s%(seqno)s%(data)s' % {
161 b
'len': pack(
'>H',
len(data) + 10),
163 b
'seqno': pack(
'>H', self.
seqno),
168 b
'\x45\xb8%(len)s%(udpseqno)s\x00\x00\xf9\x11%(sum16)s' 169 b
'\x01\x01\x01\x01\x02\x02\x02\x02%(udp)s' % {
170 b
'len': pack(
'>H',
len(udp) + 20),
171 b
'udpseqno': pack(
'>H', self.
udpseqno),
176 ethernet = bytearray(
177 b
'\x00\x00\x00\x01\x00\x06\x00\x30\x48\xb1\x1c\x34\x00\x00' 178 b
'\x08\x00%(ip)s' % {b
'ip': ip})
181 packet = bytearray(b
'%(prelude)s%(ethernet)s' % {
183 '<IIII', int(mktime(self.
date.timetuple())),
184 self.
date.microsecond,
len(ethernet),
len(ethernet)),
185 b
'ethernet': ethernet})
199 Looks for lines line: 201 [2013-08-07 15:17:34] FAX[23479] res_fax.c: \ 202 FLOW T.38 Rx 5: IFP c0 01 80 00 00 ff 206 IFP(date=..., seqno=..., data=...) 210 for lineno, line
in enumerate(self.
_fp):
211 if 'FLOW T.38 Rx' not in line:
213 if 'IFP' not in line:
216 match = search(
r'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)', line)
218 date = datetime(*[int(i)
for i
in match.groups()])
220 match = search(
r'Rx\s*(\d+):', line)
222 seqno = int(match.groups()[0])
224 match = search(
r': IFP ([0-9a-f ]+)', line)
226 data =
n2b(match.groups()[0])
228 if prev_seqno
is not None:
231 assert seqno == prev_seqno + 1,
'%s+1 != %s' % (
236 yield IFP(date=date, seqno=seqno, data=data)
240 with open(sys.argv[1],
'r') as infile: 244 create_or_fail = os.O_CREAT | os.O_EXCL | os.O_WRONLY
246 fd = os.open(sys.argv[2], create_or_fail, 0o600)
250 with os.fdopen(fd,
'wb')
as outfile:
256 if __name__ ==
'__main__':
257 if len(sys.argv) != 3:
258 sys.stderr.write(
'Usage: {} LOGFILE PCAP\n'.
format(sys.argv[0]))
261 main(sys.argv[1], sys.argv[2])
def main(logname, pcapname)
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
static int replace(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
def __init__(self, outfile)
def _add_garbage(self, date)
static snd_pcm_format_t format
def _make_packet(self, ifp_data, is_ifp=True)