traffic-taffy 0.3.6__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/PKG-INFO +1 -7
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/pyproject.toml +1 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/setup.py +6 -5
- traffic-taffy-0.4.1/traffic_taffy/compare.py +287 -0
- traffic-taffy-0.4.1/traffic_taffy/comparison.py +26 -0
- traffic-taffy-0.4.1/traffic_taffy/dissection.py +383 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/traffic_taffy/dissectmany.py +20 -18
- traffic-taffy-0.4.1/traffic_taffy/dissector.py +260 -0
- traffic-taffy-0.4.1/traffic_taffy/dissector_engine/__init__.py +35 -0
- traffic-taffy-0.4.1/traffic_taffy/dissector_engine/dpkt.py +98 -0
- traffic-taffy-0.4.1/traffic_taffy/dissector_engine/scapy.py +98 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/traffic_taffy/graph.py +23 -90
- traffic-taffy-0.4.1/traffic_taffy/graphdata.py +68 -0
- traffic-taffy-0.4.1/traffic_taffy/output/__init__.py +118 -0
- traffic-taffy-0.4.1/traffic_taffy/output/console.py +72 -0
- traffic-taffy-0.4.1/traffic_taffy/output/fsdb.py +50 -0
- traffic-taffy-0.4.1/traffic_taffy/output/memory.py +51 -0
- traffic-taffy-0.4.1/traffic_taffy/tools/__init__.py +0 -0
- {traffic-taffy-0.3.6/traffic_taffy → traffic-taffy-0.4.1/traffic_taffy/tools}/cache_info.py +25 -18
- traffic-taffy-0.4.1/traffic_taffy/tools/compare.py +110 -0
- traffic-taffy-0.4.1/traffic_taffy/tools/dissect.py +77 -0
- traffic-taffy-0.4.1/traffic_taffy/tools/explore.py +686 -0
- traffic-taffy-0.4.1/traffic_taffy/tools/graph.py +85 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/traffic_taffy.egg-info/PKG-INFO +1 -7
- traffic-taffy-0.4.1/traffic_taffy.egg-info/SOURCES.txt +31 -0
- traffic-taffy-0.4.1/traffic_taffy.egg-info/entry_points.txt +6 -0
- traffic-taffy-0.3.6/traffic_taffy/compare.py +0 -383
- traffic-taffy-0.3.6/traffic_taffy/dissector.py +0 -608
- traffic-taffy-0.3.6/traffic_taffy/explore.py +0 -221
- traffic-taffy-0.3.6/traffic_taffy/graphdata.py +0 -53
- traffic-taffy-0.3.6/traffic_taffy.egg-info/SOURCES.txt +0 -18
- traffic-taffy-0.3.6/traffic_taffy.egg-info/entry_points.txt +0 -5
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/README.md +0 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/setup.cfg +0 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/traffic_taffy/__init__.py +0 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/traffic_taffy/dissectorresults.py +0 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/traffic_taffy.egg-info/dependency_links.txt +0 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/traffic_taffy.egg-info/requires.txt +0 -0
- {traffic-taffy-0.3.6 → traffic-taffy-0.4.1}/traffic_taffy.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: traffic-taffy
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.1
|
4
4
|
Summary: A tool for doing differential analysis of pcap files
|
5
5
|
Home-page: https://github.com/hardaker/traffic-taffy
|
6
6
|
Author: Wes Hardaker
|
@@ -9,12 +9,6 @@ Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
10
10
|
Requires-Python: >=3.7
|
11
11
|
Description-Content-Type: text/markdown
|
12
|
-
Requires-Dist: pandas
|
13
|
-
Requires-Dist: rich
|
14
|
-
Requires-Dist: seaborn
|
15
|
-
Requires-Dist: scapy
|
16
|
-
Requires-Dist: dpkt
|
17
|
-
Requires-Dist: pcap-parallel
|
18
12
|
|
19
13
|
# Traffic Analysis of Fluctuating Flows (TAFFy)
|
20
14
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
5
5
|
|
6
6
|
setuptools.setup(
|
7
7
|
name="traffic-taffy",
|
8
|
-
version="0.
|
8
|
+
version="0.4.1",
|
9
9
|
author="Wes Hardaker",
|
10
10
|
author_email="opensource@hardakers.net",
|
11
11
|
description="A tool for doing differential analysis of pcap files",
|
@@ -15,10 +15,11 @@ setuptools.setup(
|
|
15
15
|
packages=setuptools.find_packages(),
|
16
16
|
entry_points={
|
17
17
|
"console_scripts": [
|
18
|
-
"taffy-compare = traffic_taffy.compare:main",
|
19
|
-
"taffy-graph = traffic_taffy.graph:main",
|
20
|
-
"taffy-dissect = traffic_taffy.
|
21
|
-
"taffy-cache-info = traffic_taffy.cache_info:main",
|
18
|
+
"taffy-compare = traffic_taffy.tools.compare:main",
|
19
|
+
"taffy-graph = traffic_taffy.tools.graph:main",
|
20
|
+
"taffy-dissect = traffic_taffy.tools.dissect:main",
|
21
|
+
"taffy-cache-info = traffic_taffy.tools.cache_info:main",
|
22
|
+
"taffy-explorer = traffic_taffy.tools.explorer:main",
|
22
23
|
]
|
23
24
|
},
|
24
25
|
classifiers=[
|
@@ -0,0 +1,287 @@
|
|
1
|
+
from logging import debug
|
2
|
+
from typing import List
|
3
|
+
import datetime as dt
|
4
|
+
from datetime import datetime
|
5
|
+
|
6
|
+
from traffic_taffy.comparison import Comparison
|
7
|
+
from traffic_taffy.dissectmany import PCAPDissectMany
|
8
|
+
from traffic_taffy.dissector import PCAPDissectorLevel
|
9
|
+
from traffic_taffy.dissection import Dissection
|
10
|
+
|
11
|
+
|
12
|
+
class PcapCompare:
|
13
|
+
"Takes a set of PCAPs to then perform various comparisons upon"
|
14
|
+
|
15
|
+
REPORT_VERSION: int = 2
|
16
|
+
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
pcap_files: List[str],
|
20
|
+
maximum_count: int = 0, # where 0 == all
|
21
|
+
deep: bool = True,
|
22
|
+
pkt_filter: str | None = None,
|
23
|
+
cache_results: bool = False,
|
24
|
+
cache_file_suffix: str = "taffy",
|
25
|
+
bin_size: int | None = None,
|
26
|
+
dissection_level: PCAPDissectorLevel = PCAPDissectorLevel.COUNT_ONLY,
|
27
|
+
between_times: List[int] | None = None,
|
28
|
+
ignore_list: List[str] = [],
|
29
|
+
) -> None:
|
30
|
+
self.pcap_files = pcap_files
|
31
|
+
self.deep = deep
|
32
|
+
self.maximum_count = maximum_count
|
33
|
+
self.pkt_filter = pkt_filter
|
34
|
+
self.cache_results = cache_results
|
35
|
+
self.dissection_level = dissection_level
|
36
|
+
self.between_times = between_times
|
37
|
+
self.bin_size = bin_size
|
38
|
+
self.cache_file_suffix = cache_file_suffix
|
39
|
+
self.ignore_list = ignore_list
|
40
|
+
|
41
|
+
@property
|
42
|
+
def pcap_files(self):
|
43
|
+
return self._pcap_files
|
44
|
+
|
45
|
+
@pcap_files.setter
|
46
|
+
def pcap_files(self, new_pcap_files):
|
47
|
+
self._pcap_files = new_pcap_files
|
48
|
+
|
49
|
+
@property
|
50
|
+
def reports(self):
|
51
|
+
return self._reports
|
52
|
+
|
53
|
+
@reports.setter
|
54
|
+
def reports(self, newvalue):
|
55
|
+
self._reports = newvalue
|
56
|
+
|
57
|
+
def compare_dissections(self, left_side: dict, right_side: dict) -> dict:
|
58
|
+
"compares the results from two reports"
|
59
|
+
|
60
|
+
report = {}
|
61
|
+
|
62
|
+
# TODO: missing key in right_side (major items added)
|
63
|
+
keys = set(left_side.keys())
|
64
|
+
keys = keys.union(right_side.keys())
|
65
|
+
for key in keys:
|
66
|
+
report[key] = {}
|
67
|
+
|
68
|
+
if key not in left_side:
|
69
|
+
left_side[key] = {}
|
70
|
+
left_side_total = sum(left_side[key].values())
|
71
|
+
|
72
|
+
if key not in right_side:
|
73
|
+
right_side[key] = {}
|
74
|
+
right_side_total = sum(right_side[key].values())
|
75
|
+
|
76
|
+
new_left_count = 0
|
77
|
+
for subkey in left_side[key].keys():
|
78
|
+
delta_percentage = 0.0
|
79
|
+
total = 0
|
80
|
+
if subkey in right_side[key]:
|
81
|
+
left_percentage = left_side[key][subkey] / left_side_total
|
82
|
+
right_percentage = right_side[key][subkey] / right_side_total
|
83
|
+
delta_percentage = right_percentage - left_percentage
|
84
|
+
total = right_side[key][subkey] + left_side[key][subkey]
|
85
|
+
left_count = left_side[key][subkey]
|
86
|
+
right_count = right_side[key][subkey]
|
87
|
+
else:
|
88
|
+
delta_percentage = -1.0
|
89
|
+
left_percentage = left_side[key][subkey] / left_side_total
|
90
|
+
right_percentage = 0.0
|
91
|
+
total = -left_side[key][subkey]
|
92
|
+
left_count = left_side[key][subkey]
|
93
|
+
right_count = 0
|
94
|
+
new_left_count += 1
|
95
|
+
|
96
|
+
delta_absolute = right_count - left_count
|
97
|
+
report[key][subkey] = {
|
98
|
+
"delta_percentage": delta_percentage,
|
99
|
+
"delta_absolute": delta_absolute,
|
100
|
+
"total": total,
|
101
|
+
"left_count": left_count,
|
102
|
+
"right_count": right_count,
|
103
|
+
"left_percentage": left_percentage,
|
104
|
+
"right_percentage": right_percentage,
|
105
|
+
}
|
106
|
+
|
107
|
+
new_right_count = 0
|
108
|
+
for subkey in right_side[key].keys():
|
109
|
+
if subkey not in report[key]:
|
110
|
+
delta_percentage = 1.0
|
111
|
+
total = right_side[key][subkey]
|
112
|
+
left_count = 0
|
113
|
+
right_count = right_side[key][subkey]
|
114
|
+
left_percentage = 0.0
|
115
|
+
right_percentage = right_side[key][subkey] / right_side_total
|
116
|
+
new_right_count += 1 # this value wasn't in the left
|
117
|
+
|
118
|
+
report[key][subkey] = {
|
119
|
+
"delta_percentage": delta_percentage,
|
120
|
+
"delta_absolute": right_count,
|
121
|
+
"total": total,
|
122
|
+
"left_count": left_count,
|
123
|
+
"right_count": right_count,
|
124
|
+
"left_percentage": left_percentage,
|
125
|
+
"right_percentage": right_percentage,
|
126
|
+
}
|
127
|
+
|
128
|
+
if right_side_total == 0:
|
129
|
+
right_percent = 100
|
130
|
+
else:
|
131
|
+
right_percent = new_right_count / right_side_total
|
132
|
+
report[key][Dissection.NEW_RIGHT_SUBKEY] = {
|
133
|
+
"delta_absolute": new_right_count - new_left_count,
|
134
|
+
"total": new_left_count + new_right_count,
|
135
|
+
"left_count": new_left_count,
|
136
|
+
"right_count": new_right_count,
|
137
|
+
"left_percentage": new_left_count / left_side_total,
|
138
|
+
"right_percentage": right_percent,
|
139
|
+
"delta_percentage": (right_percent - new_left_count / left_side_total),
|
140
|
+
}
|
141
|
+
|
142
|
+
return Comparison(report)
|
143
|
+
|
144
|
+
def load_pcaps(self) -> None:
|
145
|
+
# load the first as a reference pcap
|
146
|
+
pdm = PCAPDissectMany(
|
147
|
+
self.pcap_files,
|
148
|
+
bin_size=self.bin_size,
|
149
|
+
maximum_count=self.maximum_count,
|
150
|
+
pcap_filter=self.pkt_filter,
|
151
|
+
cache_results=self.cache_results,
|
152
|
+
cache_file_suffix=self.cache_file_suffix,
|
153
|
+
dissector_level=self.dissection_level,
|
154
|
+
ignore_list=self.ignore_list,
|
155
|
+
)
|
156
|
+
results = pdm.load_all()
|
157
|
+
return results
|
158
|
+
|
159
|
+
def compare(self) -> List[Comparison]:
|
160
|
+
"Compares each pcap against the original source"
|
161
|
+
|
162
|
+
dissections = self.load_pcaps()
|
163
|
+
self.compare_all(dissections)
|
164
|
+
return self.reports
|
165
|
+
|
166
|
+
def compare_all(self, dissections) -> List[Comparison]:
|
167
|
+
reports = []
|
168
|
+
if len(self.pcap_files) > 1:
|
169
|
+
# multiple file comparison
|
170
|
+
reference = next(dissections)
|
171
|
+
for other in dissections:
|
172
|
+
# compare the two global summaries
|
173
|
+
|
174
|
+
report = self.compare_dissections(reference.data[0], other.data[0])
|
175
|
+
report.title = f"{reference.pcap_file} vs {other.pcap_file}"
|
176
|
+
|
177
|
+
reports.append(report)
|
178
|
+
else:
|
179
|
+
# deal with timestamps within a single file
|
180
|
+
reference = list(dissections)[0].data
|
181
|
+
timestamps = list(reference.keys())
|
182
|
+
debug(
|
183
|
+
f"found {len(timestamps)} timestamps from {timestamps[2]} to {timestamps[-1]}"
|
184
|
+
)
|
185
|
+
|
186
|
+
for timestamp in range(
|
187
|
+
2, len(timestamps)
|
188
|
+
): # second real non-zero timestamp to last
|
189
|
+
time_left = timestamps[timestamp - 1]
|
190
|
+
time_right = timestamps[timestamp]
|
191
|
+
|
192
|
+
# see if we were asked to only use particular time ranges
|
193
|
+
if self.between_times and (
|
194
|
+
time_left < self.between_times[0]
|
195
|
+
or time_right > self.between_times[1]
|
196
|
+
):
|
197
|
+
# debug(f"skipping timestamps {time_left} and {time_right}")
|
198
|
+
continue
|
199
|
+
|
200
|
+
debug(f"comparing timestamps {time_left} and {time_right}")
|
201
|
+
|
202
|
+
report = self.compare_dissections(
|
203
|
+
reference[time_left],
|
204
|
+
reference[time_right],
|
205
|
+
)
|
206
|
+
|
207
|
+
title_left = datetime.fromtimestamp(time_left, dt.UTC).strftime(
|
208
|
+
"%Y-%m-%d %H:%M:%S"
|
209
|
+
)
|
210
|
+
title_right = datetime.fromtimestamp(time_right, dt.UTC).strftime(
|
211
|
+
"%Y-%m-%d %H:%M:%S"
|
212
|
+
)
|
213
|
+
|
214
|
+
report.title = f"time {title_left} vs time {title_right}"
|
215
|
+
reports.append(report)
|
216
|
+
|
217
|
+
continue
|
218
|
+
|
219
|
+
# takes way too much memory to do it "right"
|
220
|
+
# reports.append(
|
221
|
+
# {
|
222
|
+
# "report": report,
|
223
|
+
# "title": f"time {time_left} vs time {time_right}",
|
224
|
+
# }
|
225
|
+
# )
|
226
|
+
|
227
|
+
self.reports = reports
|
228
|
+
return reports
|
229
|
+
|
230
|
+
|
231
|
+
def compare_add_parseargs(compare_parser, add_subgroup: bool = True):
|
232
|
+
if add_subgroup:
|
233
|
+
compare_parser = compare_parser.add_argument_group("Comparison result options")
|
234
|
+
|
235
|
+
compare_parser.add_argument(
|
236
|
+
"-t",
|
237
|
+
"--print-threshold",
|
238
|
+
default=0.0,
|
239
|
+
type=float,
|
240
|
+
help="Don't print results with abs(percent) less than this threshold",
|
241
|
+
)
|
242
|
+
|
243
|
+
compare_parser.add_argument(
|
244
|
+
"-P", "--only-positive", action="store_true", help="Only show positive entries"
|
245
|
+
)
|
246
|
+
|
247
|
+
compare_parser.add_argument(
|
248
|
+
"-N", "--only-negative", action="store_true", help="Only show negative entries"
|
249
|
+
)
|
250
|
+
|
251
|
+
compare_parser.add_argument(
|
252
|
+
"-x",
|
253
|
+
"--top-records",
|
254
|
+
default=None,
|
255
|
+
type=int,
|
256
|
+
help="Show the top N records from each section.",
|
257
|
+
)
|
258
|
+
|
259
|
+
compare_parser.add_argument(
|
260
|
+
"-r",
|
261
|
+
"--reverse_sort",
|
262
|
+
action="store_true",
|
263
|
+
help="Reverse the sort order of reports",
|
264
|
+
)
|
265
|
+
|
266
|
+
compare_parser.add_argument(
|
267
|
+
"-T",
|
268
|
+
"--between-times",
|
269
|
+
nargs=2,
|
270
|
+
type=int,
|
271
|
+
help="For single files, only display results between these timestamps",
|
272
|
+
)
|
273
|
+
|
274
|
+
return compare_parser
|
275
|
+
|
276
|
+
|
277
|
+
def get_comparison_args(args):
|
278
|
+
return {
|
279
|
+
"maximum_count": args.packet_count or 0,
|
280
|
+
"print_threshold": float(args.print_threshold) / 100.0,
|
281
|
+
"minimum_count": args.minimum_count,
|
282
|
+
"match_string": args.match_string,
|
283
|
+
"only_positive": args.only_positive,
|
284
|
+
"only_negative": args.only_negative,
|
285
|
+
"top_records": args.top_records,
|
286
|
+
"reverse_sort": args.reverse_sort,
|
287
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
|
3
|
+
|
4
|
+
class Comparison:
|
5
|
+
def __init__(self, contents: list, title: str = ""):
|
6
|
+
self.contents = contents
|
7
|
+
self.title: str = title
|
8
|
+
self.printing_arguments: Dict[str] = {}
|
9
|
+
|
10
|
+
# title
|
11
|
+
@property
|
12
|
+
def title(self) -> str:
|
13
|
+
return self._title
|
14
|
+
|
15
|
+
@title.setter
|
16
|
+
def title(self, new_title):
|
17
|
+
self._title = new_title
|
18
|
+
|
19
|
+
# report contents -- actual data
|
20
|
+
@property
|
21
|
+
def contents(self):
|
22
|
+
return self._contents
|
23
|
+
|
24
|
+
@contents.setter
|
25
|
+
def contents(self, new_contents):
|
26
|
+
self._contents = new_contents
|