Skip to content

Commit 2e21f0a

Browse files
committed
New distribution [0.15.0]
* bumped version to 0.15.0 * fixed issue #37 & #40 as we updated deps on DictDumper * merged protocol parsing & constructing logics * revised TCP flow tracing & several interfaces * added a full API doc as hosted on https://pypcapkit.jarryshaw.me * minor bugfix all over the whole project
1 parent bfa067a commit 2e21f0a

File tree

13 files changed

+121
-44
lines changed

13 files changed

+121
-44
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"Sval",
3232
"TLVs",
3333
"Topt",
34+
"Tpng",
3435
"Xcode",
3536
"ackn",
3637
"acnm",
@@ -116,6 +117,7 @@
116117
"gbhdr",
117118
"genc",
118119
"gpid",
120+
"gprof",
119121
"hdrs",
120122
"hend",
121123
"hexbuf",
@@ -300,6 +302,7 @@
300302
"usec",
301303
"utopt",
302304
"verf",
305+
"vfunc",
303306
"vihl",
304307
"vinfo",
305308
"vlen",

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ version = $(shell cat setup.py | grep "^__version__" | sed "s/__version__ = '\(
1010
# commit message
1111
message ?= ""
1212

13+
profile:
14+
pipenv run python -m cProfile -o /tmp/parse_pcap.pstats $@
15+
gprof2dot -f pstats /tmp/parse_pcap.pstats | dot -Tpng -o /tmp/parse_pcap.png
16+
open /tmp/parse_pcap.png
17+
1318
clean: clean-pyc clean-misc clean-pypi
1419
const: update-const
1520
#dist: dist-pypi dist-upload

Pipfile.lock

Lines changed: 19 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc/img/profile.png

913 KB
Loading

doc/sphinx/source/foundation/extraction.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,16 @@ extracts parametres from a PCAP file.
7070
TCP flow tracing flag (as the ``trace`` parameter).
7171

7272
.. attribute:: _flag_v
73-
:type: bool
73+
:type: Union[bool, Callable[[pcapkit.foundation.extraction.Extractor, pcapkit.protocols.pcap.frame.Frame]]]
74+
75+
A :obj:`bool` value or a function takes the :class:`Extract` instance and current parsed frame (depends on
76+
the engine selected) as parameters to print verbose output information (as the ``verbose`` parameter).
77+
78+
.. attribute:: _vfunc
79+
:type: Union[NotImplemented, Callable[[pcapkit.foundation.extraction.Extractor, pcapkit.protocols.pcap.frame.Frame]]]
7480

75-
Verbose output flag (as the ``verbose`` parameter).
81+
If the ``verbose`` parameter is a callable, then it will be assigned as :attr:`self._vfunc <Extractor._vfunc>`;
82+
otherwise, it keeps :obj:`NotImplemented` as a placeholder and has specific function for each engine.
7683

7784
.. attribute:: _frnum
7885
:type: int

pcapkit/dumpkit/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,13 @@ def __init__(self, fname, *, protocol, byteorder=sys.byteorder, nanosecond=False
102102
**kwargs: arbitrary keyword arguments
103103
104104
"""
105+
#: int: Frame counter.
106+
self._fnum = 1
107+
#: bool: Nanosecond-resolution file flag.
105108
self._nsec = nanosecond
109+
#: Union[pcapkit.const.reg.linktype.LinkType, enum.IntEnum, str, int]: Data link type.
110+
self._link = protocol
111+
106112
super().__init__(fname, protocol=protocol, byteorder=byteorder,
107113
nanosecond=nanosecond, **kwargs)
108114

@@ -155,6 +161,9 @@ def _append_value(self, value, file, name): # pylint: disable=unused-argument
155161
packet = Frame(
156162
packet=value.packet,
157163
nanosecond=self._nsec,
164+
num=self._fnum,
165+
proto=self._link,
158166
**value.get('frame_info', value),
159167
).data
160168
file.write(packet)
169+
self._fnum += 1

pcapkit/foundation/extraction.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,10 @@ def __init__(self,
558558
559559
files (bool): if split each frame into different files
560560
nofile (bool): if no output file is to be dumped
561-
verbose (bool): if print verbose output information
561+
verbose (Union[bool, Callable[[pcapkit.foundation.extraction.Extractor,
562+
pcapkit.protocol.pcap.frame.Frame]]]): a :obj:`bool` value or a function takes the :class:`Extract`
563+
instance and current parsed frame (depends on engine selected) as parameters to print verbose output
564+
information
562565
563566
engine (Optional[Literal['default', 'pcapkit', 'dpkt', 'scapy', 'pyshark', 'server', 'pipeline']]):
564567
extraction engine to be used
@@ -601,7 +604,13 @@ def __init__(self,
601604
self._flag_m = False # multiprocessing flag
602605
self._flag_q = nofile # no output flag
603606
self._flag_t = trace # trace flag
604-
self._flag_v = verbose # verbose output flag
607+
self._flag_v = bool(verbose) # verbose output flag
608+
609+
# verbose callback function
610+
if isinstance(verbose, bool):
611+
self._vfunc = NotImplemented
612+
else:
613+
self._vfunc = verbose
605614

606615
self._frnum = 0 # frame number
607616
self._frame = list() # frame record
@@ -881,7 +890,10 @@ def _default_read_frame(self, *, frame=None, mpkit=None):
881890

882891
# verbose output
883892
if self._flag_v:
884-
print(f' - Frame {self._frnum:>3d}: {frame.protochain}')
893+
if self._vfunc is NotImplemented:
894+
print(f' - Frame {self._frnum:>3d}: {frame.protochain}')
895+
else:
896+
self._vfunc(self, frame)
885897

886898
# write plist
887899
frnum = f'Frame {self._frnum}'
@@ -989,7 +1001,10 @@ def _scapy_read_frame(self):
9891001
self._frnum += 1
9901002
self._proto = packet2chain(packet)
9911003
if self._flag_v:
992-
print(f' - Frame {self._frnum:>3d}: {self._proto}')
1004+
if self._vfunc is NotImplemented:
1005+
print(f' - Frame {self._frnum:>3d}: {self._proto}')
1006+
else:
1007+
self._vfunc(self, packet)
9931008

9941009
# write plist
9951010
frnum = f'Frame {self._frnum}'
@@ -1102,7 +1117,10 @@ def _dpkt_read_frame(self):
11021117
self._frnum += 1
11031118
self._proto = packet2chain(packet)
11041119
if self._flag_v:
1105-
print(f' - Frame {self._frnum:>3d}: {self._proto}')
1120+
if self._vfunc is NotImplemented:
1121+
print(f' - Frame {self._frnum:>3d}: {self._proto}')
1122+
else:
1123+
self._vfunc(self, packet)
11061124

11071125
# write plist
11081126
frnum = f'Frame {self._frnum}'
@@ -1213,7 +1231,10 @@ def _pyshark_read_frame(self):
12131231
self._frnum = int(packet.number)
12141232
self._proto = packet.frame_info.protocols
12151233
if self._flag_v:
1216-
print(f' - Frame {self._frnum:>3d}: {self._proto}')
1234+
if self._vfunc is NotImplemented:
1235+
print(f' - Frame {self._frnum:>3d}: {self._proto}')
1236+
else:
1237+
self._vfunc(self, packet)
12171238

12181239
# write plist
12191240
frnum = f'Frame {self._frnum}'

pcapkit/foundation/traceflow.py

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
|--> (Info) data ...
6767
6868
"""
69-
import copy
69+
import ipaddress
7070
import pathlib
7171
import sys
7272
import warnings
@@ -155,7 +155,36 @@ def make_fout(fout='./tmp', fmt='pcap'):
155155
else:
156156
raise FileExists(*error.args).with_traceback(error.__traceback__) from None
157157

158-
return output, fmt
158+
class DictDumper(output):
159+
"""Customised :class:`~dictdumper.dumper.Dumper` object."""
160+
161+
def object_hook(self, o):
162+
"""Convert content for function call.
163+
164+
Args:
165+
o (:obj:`Any`): object to convert
166+
167+
Returns:
168+
:obj:`Any`: the converted object
169+
170+
"""
171+
import enum
172+
import aenum
173+
174+
if isinstance(o, (enum.IntEnum, aenum.IntEnum)):
175+
return dict(
176+
enum=type(o).__name__,
177+
desc=o.__doc__,
178+
name=o.name,
179+
value=o.value,
180+
)
181+
if isinstance(o, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
182+
return str(o)
183+
if isinstance(o, Info):
184+
return o.info2dict()
185+
return super().object_hook(o)
186+
187+
return DictDumper, fmt
159188

160189
def dump(self, packet):
161190
"""Dump frame to output files.
@@ -168,8 +197,7 @@ def dump(self, packet):
168197
output = self.trace(packet, check=False, output=True)
169198

170199
# dump files
171-
output(packet['frame'], name=f"Frame {packet['index']}",
172-
byteorder=self._endian, nanosecond=self._nnsecd)
200+
output(packet['frame'], name=f"Frame {packet['index']}")
173201

174202
def trace(self, packet, *, check=True, output=False):
175203
"""Trace packets.
@@ -216,8 +244,8 @@ def trace(self, packet, *, check=True, output=False):
216244
if BUFID not in self._buffer:
217245
label = f'{info.src}_{info.srcport}-{info.dst}_{info.dstport}-{info.timestamp}' # pylint: disable=E1101
218246
self._buffer[BUFID] = dict(
219-
fpout=self._foutio(f'{self._fproot}/{label}.{self._fdpext}',
220-
protocol=info.protocol), # pylint: disable=E1101
247+
fpout=self._foutio(fname=f'{self._fproot}/{label}.{self._fdpext}', protocol=info.protocol, # pylint: disable=E1101
248+
byteorder=self._endian, nanosecond=self._nnsecd),
221249
index=list(),
222250
label=label,
223251
)
@@ -251,13 +279,10 @@ def submit(self):
251279
self._newflg = False
252280
ret = list()
253281
for buf in self._buffer.values():
254-
buf = copy.deepcopy(buf)
255-
if self._fdpext:
256-
buf['fpout'] = f"{self._fproot}/{buf['label']}.{self._fdpext}"
257-
else:
258-
del buf['fpout']
259-
buf['index'] = tuple(buf['index'])
260-
ret.append(Info(buf))
282+
lbl = buf['label']
283+
ret.append(Info(fpout=f"{self._fproot}/{lbl}.{self._fdpext}" if self._fdpext else NotImplemented,
284+
index=tuple(buf['index']),
285+
label=lbl,))
261286
ret += self._stream
262287
return tuple(ret)
263288

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import sys
55

66
# version string
7-
__version__ = '0.15.0rc1'
7+
__version__ = '0.15.0'
88

99
# README
1010
with open('README.md', encoding='utf-8') as file:

test/test_api.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pcapkit
4+
5+
json = pcapkit.extract(fin='../sample/http.pcap', fout='../sample/http', format='json', files=True,
6+
store=True, verbose=True, ip=True, tcp=True, strict=False, trace=True,
7+
trace_format='json', trace_fout='../sample/trace')

test/test_profile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test():
1111
store=False, nofile=True, engine=engine)
1212

1313

14-
for engine in {'default', 'pyshark', 'scapy', 'dpkt', 'pipline', 'server'}:
14+
for engine in ['default', 'dpkt', 'scapy', 'pyshark', 'pipline', 'server']:
1515
profiler = cProfile.Profile()
1616
profiler.runcall(test)
1717

test/test_reassembly.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
# -*- coding: utf-8 -*-
22

33
import os
4-
import pprint
4+
#import pprint
55
import textwrap
6-
import time
76

8-
import chardet
97
import pcapkit
108

119
os.system('> ../sample/out')

test/test_trace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
import pcapkit
66

77
trace = pcapkit.extract(fin='../sample/http.pcap', nofile=True, verbose=True,
8-
trace=True, trace_format='pcap', trace_fout='../sample/trace')
8+
trace=True, trace_format='json', trace_fout='../sample/trace')
99
pprint.pprint(trace.trace)

0 commit comments

Comments
 (0)