traffic-taffy 0.3.6__py3-none-any.whl → 0.4.1__py3-none-any.whl

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.
Files changed (36) hide show
  1. traffic_taffy/cache_info.py +0 -6
  2. traffic_taffy/compare.py +154 -250
  3. traffic_taffy/comparison.py +26 -0
  4. traffic_taffy/dissection.py +383 -0
  5. traffic_taffy/dissectmany.py +20 -18
  6. traffic_taffy/dissector.py +128 -476
  7. traffic_taffy/dissector_engine/__init__.py +35 -0
  8. traffic_taffy/dissector_engine/dpkt.py +98 -0
  9. traffic_taffy/dissector_engine/scapy.py +98 -0
  10. traffic_taffy/graph.py +23 -90
  11. traffic_taffy/graphdata.py +35 -20
  12. traffic_taffy/output/__init__.py +118 -0
  13. traffic_taffy/output/console.py +72 -0
  14. traffic_taffy/output/fsdb.py +50 -0
  15. traffic_taffy/output/memory.py +51 -0
  16. traffic_taffy/pcap_splitter.py +17 -36
  17. traffic_taffy/tools/cache_info.py +65 -0
  18. traffic_taffy/tools/compare.py +110 -0
  19. traffic_taffy/tools/dissect.py +77 -0
  20. traffic_taffy/tools/explore.py +686 -0
  21. traffic_taffy/tools/graph.py +85 -0
  22. {traffic_taffy-0.3.6.dist-info → traffic_taffy-0.4.1.dist-info}/METADATA +1 -1
  23. traffic_taffy-0.4.1.dist-info/RECORD +29 -0
  24. traffic_taffy-0.4.1.dist-info/entry_points.txt +6 -0
  25. pcap_compare/cache_info.py +0 -46
  26. pcap_compare/compare.py +0 -288
  27. pcap_compare/dissectmany.py +0 -21
  28. pcap_compare/dissector.py +0 -512
  29. pcap_compare/dissectorresults.py +0 -21
  30. pcap_compare/graph.py +0 -210
  31. traffic_taffy/explore.py +0 -221
  32. traffic_taffy-0.3.6.dist-info/RECORD +0 -22
  33. traffic_taffy-0.3.6.dist-info/entry_points.txt +0 -5
  34. {pcap_compare → traffic_taffy/tools}/__init__.py +0 -0
  35. {traffic_taffy-0.3.6.dist-info → traffic_taffy-0.4.1.dist-info}/WHEEL +0 -0
  36. {traffic_taffy-0.3.6.dist-info → traffic_taffy-0.4.1.dist-info}/top_level.txt +0 -0
@@ -47,12 +47,6 @@ def main():
47
47
  for key in contents["parameters"]:
48
48
  print(f" {key:<16} {contents['parameters'][key]}")
49
49
 
50
- print("data info:")
51
- timestamps = list(contents["dissection"].keys())
52
- print(f" timestamps: {len(timestamps)}")
53
- print(f" first: {timestamps[1]}") # skips 0 = global
54
- print(f" last: {timestamps[-1]}")
55
-
56
50
 
57
51
  if __name__ == "__main__":
58
52
  main()
traffic_taffy/compare.py CHANGED
@@ -1,18 +1,12 @@
1
- """Takes a set of pcap files to compare and creates a report"""
2
-
3
- import logging
4
- from logging import info, debug
5
- from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
1
+ from logging import debug
6
2
  from typing import List
7
- from rich.console import Console
3
+ import datetime as dt
4
+ from datetime import datetime
5
+
6
+ from traffic_taffy.comparison import Comparison
8
7
  from traffic_taffy.dissectmany import PCAPDissectMany
9
- from traffic_taffy.dissector import (
10
- PCAPDissectorType,
11
- dissector_add_parseargs,
12
- limitor_add_parseargs,
13
- PCAPDissector,
14
- check_dissector_level,
15
- )
8
+ from traffic_taffy.dissector import PCAPDissectorLevel
9
+ from traffic_taffy.dissection import Dissection
16
10
 
17
11
 
18
12
  class PcapCompare:
@@ -22,34 +16,35 @@ class PcapCompare:
22
16
 
23
17
  def __init__(
24
18
  self,
25
- pcaps: List[str],
26
- maximum_count: int | None = None,
19
+ pcap_files: List[str],
20
+ maximum_count: int = 0, # where 0 == all
27
21
  deep: bool = True,
28
- print_threshold: float = 0.0,
29
- print_minimum_count: int | None = None,
30
- print_match_string: str | None = None,
31
22
  pkt_filter: str | None = None,
32
- only_positive: bool = False,
33
- only_negative: bool = False,
34
23
  cache_results: bool = False,
35
- bin_size: int | None = 3600,
36
- dissection_level: PCAPDissectorType = PCAPDissectorType.COUNT_ONLY,
24
+ cache_file_suffix: str = "taffy",
25
+ bin_size: int | None = None,
26
+ dissection_level: PCAPDissectorLevel = PCAPDissectorLevel.COUNT_ONLY,
37
27
  between_times: List[int] | None = None,
28
+ ignore_list: List[str] = [],
38
29
  ) -> None:
39
- self.pcaps = pcaps
30
+ self.pcap_files = pcap_files
40
31
  self.deep = deep
41
32
  self.maximum_count = maximum_count
42
- self.print_threshold = print_threshold
43
- self.print_minimum_count = print_minimum_count
44
- self.print_match_string = print_match_string
45
33
  self.pkt_filter = pkt_filter
46
- self.only_positive = only_positive
47
- self.only_negative = only_negative
48
34
  self.cache_results = cache_results
49
35
  self.dissection_level = dissection_level
50
36
  self.between_times = between_times
51
37
  self.bin_size = bin_size
52
- self.console = None
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
53
48
 
54
49
  @property
55
50
  def reports(self):
@@ -59,210 +54,135 @@ class PcapCompare:
59
54
  def reports(self, newvalue):
60
55
  self._reports = newvalue
61
56
 
62
- def compare_dissections(self, dissection1: dict, dissection2: dict) -> dict:
57
+ def compare_dissections(self, left_side: dict, right_side: dict) -> dict:
63
58
  "compares the results from two reports"
64
59
 
65
60
  report = {}
66
61
 
67
- # TODO: missing key in dissection2 (major items added)
68
- keys = set(dissection1.keys())
69
- keys = keys.union(dissection2.keys())
62
+ # TODO: missing key in right_side (major items added)
63
+ keys = set(left_side.keys())
64
+ keys = keys.union(right_side.keys())
70
65
  for key in keys:
71
- dissection1_total = dissection1[key].total()
72
- dissection2_total = dissection2[key].total()
73
66
  report[key] = {}
74
67
 
75
- for subkey in dissection1[key].keys():
76
- delta = 0.0
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
77
79
  total = 0
78
- if subkey in dissection1[key] and subkey in dissection2[key]:
79
- delta = (
80
- dissection2[key][subkey] / dissection2_total
81
- - dissection1[key][subkey] / dissection1_total
82
- )
83
- total = dissection2[key][subkey] + dissection1[key][subkey]
84
- ref_count = dissection1[key][subkey]
85
- comp_count = dissection2[key][subkey]
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]
86
87
  else:
87
- delta = -1.0
88
- total = dissection1[key][subkey]
89
- ref_count = dissection1[key][subkey]
90
- comp_count = 0
91
-
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
92
97
  report[key][subkey] = {
93
- "delta": delta,
98
+ "delta_percentage": delta_percentage,
99
+ "delta_absolute": delta_absolute,
94
100
  "total": total,
95
- "ref_count": ref_count,
96
- "comp_count": comp_count,
101
+ "left_count": left_count,
102
+ "right_count": right_count,
103
+ "left_percentage": left_percentage,
104
+ "right_percentage": right_percentage,
97
105
  }
98
106
 
99
- for subkey in dissection2[key].keys():
107
+ new_right_count = 0
108
+ for subkey in right_side[key].keys():
100
109
  if subkey not in report[key]:
101
- delta = 1.0
102
- total = dissection2[key][subkey]
103
- ref_count = 0
104
- comp_count = dissection2[key][subkey]
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
105
117
 
106
118
  report[key][subkey] = {
107
- "delta": delta,
119
+ "delta_percentage": delta_percentage,
120
+ "delta_absolute": right_count,
108
121
  "total": total,
109
- "ref_count": ref_count,
110
- "comp_count": comp_count,
122
+ "left_count": left_count,
123
+ "right_count": right_count,
124
+ "left_percentage": left_percentage,
125
+ "right_percentage": right_percentage,
111
126
  }
112
127
 
113
- return report
114
-
115
- def filter_check(self, data: dict) -> bool:
116
- "Returns true if we should include it"
117
- delta: float = data["delta"]
118
- total: int = data["total"]
119
-
120
- if self.only_positive and delta <= 0:
121
- return False
122
-
123
- if self.only_negative and delta >= 0:
124
- return False
125
-
126
- if not self.print_threshold and not self.print_minimum_count:
127
- # always print
128
- return True
129
-
130
- if self.print_threshold and not self.print_minimum_count:
131
- # check print_threshold as a fraction
132
- if abs(delta) > self.print_threshold:
133
- return True
134
- elif not self.print_threshold and self.print_minimum_count:
135
- # just check print_minimum_count
136
- if total > self.print_minimum_count:
137
- return True
138
- else:
139
- # require both
140
- if total > self.print_minimum_count and abs(delta) > self.print_threshold:
141
- return True
142
-
143
- return False
144
-
145
- def init_console(self):
146
- if not self.console:
147
- self.console = Console()
148
-
149
- def print_report(self, report: dict) -> None:
150
- "prints a report to the console"
151
-
152
- self.init_console()
153
- for key in sorted(report):
154
- reported: bool = False
155
-
156
- if self.print_match_string and self.print_match_string not in key:
157
- continue
158
-
159
- for subkey, data in sorted(
160
- report[key].items(), key=lambda x: x[1]["delta"], reverse=True
161
- ):
162
- if not self.filter_check(data):
163
- continue
164
-
165
- # print the header
166
- if not reported:
167
- print(f"====== {key}")
168
- reported = True
169
-
170
- delta: float = data["delta"]
171
-
172
- # apply some fancy styling
173
- style = ""
174
- if delta < -0.5:
175
- style = "[bold red]"
176
- elif delta < 0.0:
177
- style = "[red]"
178
- elif delta > 0.5:
179
- style = "[bold green]"
180
- elif delta > 0.0:
181
- style = "[green]"
182
- endstyle = style.replace("[", "[/")
183
-
184
- # construct the output line with styling
185
- subkey = PCAPDissector.make_printable(key, subkey)
186
- line = f" {style}{subkey:<50}{endstyle}"
187
- line += f"{100*delta:>7.2f} {data['total']:>8} "
188
- line += f"{data['ref_count']:>8} {data['comp_count']:>8}"
189
-
190
- # print it to the rich console
191
- self.console.print(line)
192
-
193
- def print_header(self):
194
- # This should match the spacing in print_report()
195
- self.init_console()
196
-
197
- style = ""
198
- subkey = "Value"
199
- endstyle = ""
200
- delta = "Delta %"
201
- total = "Total"
202
- ref_count = "Left"
203
- comp_count = "Right"
204
-
205
- line = f" {style}{subkey:<50}{endstyle}"
206
- line += f"{delta:>7} {total:>8} "
207
- line += f"{ref_count:>8} {comp_count:>8}"
208
-
209
- self.console.print(line)
210
-
211
- def print(self) -> None:
212
- "outputs the results"
213
- self.print_header()
214
- for n, report in enumerate(self.reports):
215
- title = report.get("title", f"report #{n}")
216
- print(f"************ {title}")
217
- self.print_report(report["report"])
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)
218
143
 
219
144
  def load_pcaps(self) -> None:
220
145
  # load the first as a reference pcap
221
- info(f"reading pcap files using level={self.dissection_level}")
222
146
  pdm = PCAPDissectMany(
223
- self.pcaps,
147
+ self.pcap_files,
224
148
  bin_size=self.bin_size,
225
149
  maximum_count=self.maximum_count,
226
150
  pcap_filter=self.pkt_filter,
227
151
  cache_results=self.cache_results,
152
+ cache_file_suffix=self.cache_file_suffix,
228
153
  dissector_level=self.dissection_level,
154
+ ignore_list=self.ignore_list,
229
155
  )
230
156
  results = pdm.load_all()
231
157
  return results
232
158
 
233
- def compare(self) -> None:
159
+ def compare(self) -> List[Comparison]:
234
160
  "Compares each pcap against the original source"
235
161
 
236
- results = self.load_pcaps()
237
- self.compare_all(results)
162
+ dissections = self.load_pcaps()
163
+ self.compare_all(dissections)
164
+ return self.reports
238
165
 
239
- def compare_all(self, results):
166
+ def compare_all(self, dissections) -> List[Comparison]:
240
167
  reports = []
241
- if len(self.pcaps) > 1:
168
+ if len(self.pcap_files) > 1:
242
169
  # multiple file comparison
243
- reference = next(results)
244
- for other in results:
170
+ reference = next(dissections)
171
+ for other in dissections:
245
172
  # compare the two global summaries
246
- reports.append(
247
- {
248
- "report": self.compare_dissections(
249
- reference["data"][0], other["data"][0]
250
- ),
251
- "title": f"{reference['file']} vs {other['file']}",
252
- }
253
- )
254
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)
255
178
  else:
256
179
  # deal with timestamps within a single file
257
- results = list(results)
258
- reference = results[0]
259
- timestamps = list(reference["data"].keys())
180
+ reference = list(dissections)[0].data
181
+ timestamps = list(reference.keys())
260
182
  debug(
261
183
  f"found {len(timestamps)} timestamps from {timestamps[2]} to {timestamps[-1]}"
262
184
  )
263
185
 
264
- self.print_header()
265
-
266
186
  for timestamp in range(
267
187
  2, len(timestamps)
268
188
  ): # second real non-zero timestamp to last
@@ -280,12 +200,19 @@ class PcapCompare:
280
200
  debug(f"comparing timestamps {time_left} and {time_right}")
281
201
 
282
202
  report = self.compare_dissections(
283
- reference["data"][time_left], reference["data"][time_right]
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"
284
212
  )
285
213
 
286
- title = f"time {time_left} vs time {time_right}"
287
- print(f"************ {title}")
288
- self.print_report(report)
214
+ report.title = f"time {title_left} vs time {title_right}"
215
+ reports.append(report)
289
216
 
290
217
  continue
291
218
 
@@ -298,19 +225,14 @@ class PcapCompare:
298
225
  # )
299
226
 
300
227
  self.reports = reports
228
+ return reports
301
229
 
302
230
 
303
- def parse_args():
304
- "Parse the command line arguments."
305
- parser = ArgumentParser(
306
- formatter_class=ArgumentDefaultsHelpFormatter,
307
- description=__doc__,
308
- epilog="Exmaple Usage: ",
309
- )
310
-
311
- limiting_parser = limitor_add_parseargs(parser)
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")
312
234
 
313
- limiting_parser.add_argument(
235
+ compare_parser.add_argument(
314
236
  "-t",
315
237
  "--print-threshold",
316
238
  default=0.0,
@@ -318,66 +240,48 @@ def parse_args():
318
240
  help="Don't print results with abs(percent) less than this threshold",
319
241
  )
320
242
 
321
- limiting_parser.add_argument(
243
+ compare_parser.add_argument(
322
244
  "-P", "--only-positive", action="store_true", help="Only show positive entries"
323
245
  )
324
246
 
325
- limiting_parser.add_argument(
247
+ compare_parser.add_argument(
326
248
  "-N", "--only-negative", action="store_true", help="Only show negative entries"
327
249
  )
328
250
 
329
- limiting_parser.add_argument(
330
- "-T",
331
- "--between-times",
332
- nargs=2,
251
+ compare_parser.add_argument(
252
+ "-x",
253
+ "--top-records",
254
+ default=None,
333
255
  type=int,
334
- help="For single files, only display results between these timestamps",
256
+ help="Show the top N records from each section.",
335
257
  )
336
258
 
337
- dissector_add_parseargs(parser)
338
-
339
- debugging_group = parser.add_argument_group("Debugging options")
340
-
341
- debugging_group.add_argument(
342
- "--log-level",
343
- "--ll",
344
- default="info",
345
- help="Define the logging verbosity level (debug, info, warning, error, ...).",
259
+ compare_parser.add_argument(
260
+ "-r",
261
+ "--reverse_sort",
262
+ action="store_true",
263
+ help="Reverse the sort order of reports",
346
264
  )
347
265
 
348
- parser.add_argument("pcap_files", type=str, nargs="*", help="PCAP files to analyze")
349
-
350
- args = parser.parse_args()
351
- log_level = args.log_level.upper()
352
- logging.basicConfig(level=log_level, format="%(levelname)-10s:\t%(message)s")
353
-
354
- check_dissector_level(args.dissection_level)
355
-
356
- return args
357
-
358
-
359
- def main():
360
- args = parse_args()
361
- pc = PcapCompare(
362
- args.pcap_files,
363
- maximum_count=args.packet_count,
364
- print_threshold=float(args.print_threshold) / 100.0,
365
- print_minimum_count=args.minimum_count,
366
- print_match_string=args.match_string,
367
- only_positive=args.only_positive,
368
- only_negative=args.only_negative,
369
- cache_results=args.cache_pcap_results,
370
- dissection_level=args.dissection_level,
371
- between_times=args.between_times,
372
- bin_size=args.bin_size,
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",
373
272
  )
374
273
 
375
- # compare the pcaps
376
- pc.compare()
377
-
378
- # print the results
379
- pc.print()
274
+ return compare_parser
380
275
 
381
276
 
382
- if __name__ == "__main__":
383
- main()
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