traffic-taffy 0.2__py3-none-any.whl → 0.3.5__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.
File without changes
@@ -0,0 +1,46 @@
1
+ """Loads the cached data for a file to display the results about it"""
2
+
3
+ from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, FileType
4
+ from logging import debug, info, warning, error, critical
5
+ from rich import print
6
+ import logging
7
+ import sys
8
+ import pickle
9
+
10
+ def parse_args():
11
+ "Parse the command line arguments."
12
+ parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter,
13
+ description=__doc__,
14
+ epilog="Exmaple Usage: ")
15
+
16
+ parser.add_argument("--log-level", "--ll", default="info",
17
+ help="Define the logging verbosity level (debug, info, warning, error, fotal, critical).")
18
+
19
+ parser.add_argument("cache_file", type=str,
20
+ help="The cache file to load and display information about")
21
+
22
+ args = parser.parse_args()
23
+ log_level = args.log_level.upper()
24
+ logging.basicConfig(level=log_level,
25
+ format="%(levelname)-10s:\t%(message)s")
26
+ return args
27
+
28
+ def main():
29
+ args = parse_args()
30
+ contents = pickle.load(open(args.cache_file, "rb"))
31
+
32
+ # play the major keys
33
+ for key in contents.keys():
34
+ if key != 'dissection' and key != 'parameters':
35
+ print(f"{key:<20} {contents[key]}")
36
+
37
+ # then the minors
38
+ print("parameters:")
39
+ for key in contents['parameters']:
40
+ print(f" {key:<16} {contents['parameters'][key]}")
41
+
42
+
43
+
44
+
45
+ if __name__ == "__main__":
46
+ main()
@@ -0,0 +1,288 @@
1
+ """Takes a set of pcap files to compare and creates a report"""
2
+
3
+ import logging
4
+ from logging import info
5
+ from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
6
+ from typing import List
7
+ from rich.console import Console
8
+ from pcap_compare.dissectmany import PCAPDissectMany
9
+ from pcap_compare.dissector import (
10
+ PCAPDissectorType,
11
+ dissector_add_parseargs,
12
+ limitor_add_parseargs,
13
+ PCAPDissector,
14
+ check_dissector_level,
15
+ )
16
+
17
+
18
+ class PcapCompare:
19
+ "Takes a set of PCAPs to then perform various comparisons upon"
20
+
21
+ REPORT_VERSION: int = 2
22
+
23
+ def __init__(
24
+ self,
25
+ pcaps: List[str],
26
+ maximum_count: int | None = None,
27
+ deep: bool = True,
28
+ print_threshold: float = 0.0,
29
+ print_minimum_count: int | None = None,
30
+ print_match_string: str | None = None,
31
+ pkt_filter: str | None = None,
32
+ only_positive: bool = False,
33
+ only_negative: bool = False,
34
+ cache_results: bool = False,
35
+ dissection_level: PCAPDissectorType = PCAPDissectorType.COUNT_ONLY,
36
+ ) -> None:
37
+
38
+ self.pcaps = pcaps
39
+ self.deep = deep
40
+ self.maximum_count = maximum_count
41
+ self.print_threshold = print_threshold
42
+ self.print_minimum_count = print_minimum_count
43
+ self.print_match_string = print_match_string
44
+ self.pkt_filter = pkt_filter
45
+ self.only_positive = only_positive
46
+ self.only_negative = only_negative
47
+ self.cache_results = cache_results
48
+ self.dissection_level = dissection_level
49
+
50
+ def compare_results(self, report1: dict, report2: dict) -> dict:
51
+ "compares the results from two reports"
52
+
53
+ # TODO: handle recursive depths, where items are subtrees rather than Counters
54
+
55
+ report = {}
56
+
57
+ # TODO: we're only (currently) doing full pcap compares
58
+ report1 = report1[0]
59
+ report2 = report2[0]
60
+
61
+ for key in report1:
62
+ # TODO: deal with missing keys from one set
63
+ report1_total = report1[key].total()
64
+ report2_total = report2[key].total()
65
+ report[key] = {}
66
+
67
+ for subkey in report1[key].keys():
68
+ delta = 0.0
69
+ total = 0
70
+ if subkey in report1[key] and subkey in report2[key]:
71
+ delta = (
72
+ report2[key][subkey] / report2_total
73
+ - report1[key][subkey] / report1_total
74
+ )
75
+ total = report2[key][subkey] + report1[key][subkey]
76
+ ref_count = report1[key][subkey]
77
+ comp_count = report2[key][subkey]
78
+ else:
79
+ delta = -1.0
80
+ total = report1[key][subkey]
81
+ ref_count = report1[key][subkey]
82
+ comp_count = 0
83
+
84
+ report[key][subkey] = {
85
+ "delta": delta,
86
+ "total": total,
87
+ "ref_count": ref_count,
88
+ "comp_count": comp_count,
89
+ }
90
+
91
+ for subkey in report2[key].keys():
92
+ if subkey not in report[key]:
93
+ delta = 1.0
94
+ total = report2[key][subkey]
95
+ ref_count = 0
96
+ comp_count = report2[key][subkey]
97
+
98
+ report[key][subkey] = {
99
+ "delta": delta,
100
+ "total": total,
101
+ "ref_count": ref_count,
102
+ "comp_count": comp_count,
103
+ }
104
+
105
+ return report
106
+
107
+ def filter_check(self, data: dict) -> bool:
108
+ "Returns true if we should include it"
109
+ delta: float = data["delta"]
110
+ total: int = data["total"]
111
+
112
+ if self.only_positive and delta <= 0:
113
+ return False
114
+
115
+ if self.only_negative and delta >= 0:
116
+ return False
117
+
118
+ if not self.print_threshold and not self.print_minimum_count:
119
+ # always print
120
+ return True
121
+
122
+ if self.print_threshold and not self.print_minimum_count:
123
+ # check print_threshold as a fraction
124
+ if abs(delta) > self.print_threshold:
125
+ return True
126
+ elif not self.print_threshold and self.print_minimum_count:
127
+ # just check print_minimum_count
128
+ if total > self.print_minimum_count:
129
+ return True
130
+ else:
131
+ # require both
132
+ if total > self.print_minimum_count and abs(delta) > self.print_threshold:
133
+ return True
134
+
135
+ return False
136
+
137
+ def print_report(self, report: dict) -> None:
138
+ "prints a report to the console"
139
+ console = Console()
140
+ for key in sorted(report):
141
+ reported: bool = False
142
+
143
+ if self.print_match_string and self.print_match_string not in key:
144
+ continue
145
+
146
+ for subkey, data in sorted(
147
+ report[key].items(), key=lambda x: x[1]["delta"], reverse=True
148
+ ):
149
+ if not self.filter_check(data):
150
+ continue
151
+
152
+ # print the header
153
+ if not reported:
154
+ print(f"====== {key}")
155
+ reported = True
156
+
157
+ delta: float = data["delta"]
158
+
159
+ # apply some fancy styling
160
+ style = ""
161
+ if delta < -0.5:
162
+ style = "[bold red]"
163
+ elif delta < 0.0:
164
+ style = "[red]"
165
+ elif delta > 0.5:
166
+ style = "[bold green]"
167
+ elif delta > 0.0:
168
+ style = "[green]"
169
+ endstyle = style.replace("[", "[/")
170
+
171
+ # construct the output line with styling
172
+ subkey = PCAPDissector.make_printable(subkey)
173
+ line = f" {style}{subkey:<50}{endstyle}"
174
+ line += f"{100*delta:>6.2f} {data['total']:>8} "
175
+ line += f"{data['ref_count']:>8} {data['comp_count']:>8}"
176
+
177
+ # print it to the rich console
178
+ console.print(line)
179
+
180
+ def print(self) -> None:
181
+ "outputs the results"
182
+ for n, report in enumerate(self.reports):
183
+ print(f"************ report #{n}")
184
+ self.print_report(report)
185
+
186
+ def compare(self) -> None:
187
+ "Compares each pcap against the original source"
188
+
189
+ reports = []
190
+
191
+ # TODO: use parallel processes to load multiple at a time
192
+
193
+ # load the first as a reference pcap
194
+ info(f"reading pcap files using level={self.dissection_level}")
195
+ pdm = PCAPDissectMany(
196
+ self.pcaps,
197
+ bin_size=None,
198
+ maximum_count=self.maximum_count,
199
+ pcap_filter=self.pkt_filter,
200
+ cache_results=self.cache_results,
201
+ dissector_level=self.dissection_level,
202
+ )
203
+ results = pdm.load_all()
204
+
205
+ reference = next(results)
206
+ for other in results:
207
+ # compare the two
208
+ reports.append(self.compare_results(reference["data"], other["data"]))
209
+
210
+ self.reports = reports
211
+
212
+
213
+ def parse_args():
214
+ "Parse the command line arguments."
215
+ parser = ArgumentParser(
216
+ formatter_class=ArgumentDefaultsHelpFormatter,
217
+ description=__doc__,
218
+ epilog="Exmaple Usage: ",
219
+ )
220
+
221
+ limiting_parser = limitor_add_parseargs(parser)
222
+
223
+ limiting_parser.add_argument(
224
+ "-t",
225
+ "--print-threshold",
226
+ default=0.0,
227
+ type=float,
228
+ help="Don't print results with abs(percent) less than this threshold",
229
+ )
230
+
231
+ limiting_parser.add_argument(
232
+ "-P", "--only-positive", action="store_true", help="Only show positive entries"
233
+ )
234
+
235
+ limiting_parser.add_argument(
236
+ "-N", "--only-negative", action="store_true", help="Only show negative entries"
237
+ )
238
+
239
+ dissector_add_parseargs(parser)
240
+
241
+ debugging_group = parser.add_argument_group("Debugging options")
242
+
243
+ debugging_group.add_argument(
244
+ "--log-level",
245
+ "--ll",
246
+ default="info",
247
+ help="Define the logging verbosity level (debug, info, warning, error, ...).",
248
+ )
249
+
250
+ parser.add_argument("pcap_files", type=str, nargs="*", help="PCAP files to analyze")
251
+
252
+ args = parser.parse_args()
253
+ log_level = args.log_level.upper()
254
+ logging.basicConfig(level=log_level, format="%(levelname)-10s:\t%(message)s")
255
+
256
+ check_dissector_level(args.dissection_level)
257
+
258
+ return args
259
+
260
+
261
+ def main():
262
+ args = parse_args()
263
+ pc = PcapCompare(
264
+ args.pcap_files,
265
+ maximum_count=args.packet_count,
266
+ print_threshold=float(args.print_threshold) / 100.0,
267
+ print_minimum_count=args.minimum_count,
268
+ print_match_string=args.match_string,
269
+ only_positive=args.only_positive,
270
+ only_negative=args.only_negative,
271
+ cache_results=args.cache_pcap_results,
272
+ dissection_level=args.dissection_level,
273
+ )
274
+
275
+ # compare the pcaps
276
+ pc.compare()
277
+
278
+ # print the results
279
+ pc.print()
280
+
281
+ # maybe save them
282
+ # TODO: loading and saving both makes more sense, throw error
283
+ if args.save_report:
284
+ pc.save_report(args.save_report)
285
+
286
+
287
+ if __name__ == "__main__":
288
+ main()
@@ -0,0 +1,21 @@
1
+ from pcap_compare.dissector import PCAPDissector
2
+ from concurrent.futures import ProcessPoolExecutor
3
+ from logging import info
4
+
5
+
6
+ class PCAPDissectMany:
7
+ def __init__(self, pcap_files, *args, **kwargs):
8
+ self.pcap_files = pcap_files
9
+ self.args = args
10
+ self.kwargs = kwargs
11
+ self.futures = {}
12
+
13
+ def load_pcap(self, pcap_file):
14
+ pd = PCAPDissector(pcap_file, *self.args, **self.kwargs)
15
+ info(f"reading {pcap_file}")
16
+ return {"file": pcap_file, "data": pd.load()}
17
+
18
+ def load_all(self):
19
+ with ProcessPoolExecutor() as executor:
20
+ results = executor.map(self.load_pcap, self.pcap_files)
21
+ return results