traffic-taffy 0.8.5__py3-none-any.whl → 0.9__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 (47) hide show
  1. traffic_taffy/__init__.py +1 -1
  2. traffic_taffy/algorithms/__init__.py +14 -7
  3. traffic_taffy/algorithms/comparecorrelation.py +164 -0
  4. traffic_taffy/algorithms/comparecorrelationchanges.py +210 -0
  5. traffic_taffy/algorithms/compareseries.py +117 -0
  6. traffic_taffy/algorithms/compareslices.py +116 -0
  7. traffic_taffy/algorithms/statistical.py +9 -9
  8. traffic_taffy/compare.py +149 -159
  9. traffic_taffy/comparison.py +18 -4
  10. traffic_taffy/config.py +133 -0
  11. traffic_taffy/dissection.py +78 -6
  12. traffic_taffy/dissectmany.py +26 -16
  13. traffic_taffy/dissector.py +189 -77
  14. traffic_taffy/dissector_engine/scapy.py +41 -8
  15. traffic_taffy/graph.py +54 -53
  16. traffic_taffy/graphdata.py +13 -2
  17. traffic_taffy/hooks/ip2asn.py +20 -7
  18. traffic_taffy/hooks/labels.py +45 -0
  19. traffic_taffy/hooks/psl.py +21 -3
  20. traffic_taffy/output/__init__.py +8 -48
  21. traffic_taffy/output/console.py +37 -25
  22. traffic_taffy/output/fsdb.py +24 -18
  23. traffic_taffy/reports/__init__.py +5 -0
  24. traffic_taffy/reports/compareslicesreport.py +85 -0
  25. traffic_taffy/reports/correlationchangereport.py +54 -0
  26. traffic_taffy/reports/correlationreport.py +42 -0
  27. traffic_taffy/taffy_config.py +44 -0
  28. traffic_taffy/tests/test_compare_results.py +22 -7
  29. traffic_taffy/tests/test_config.py +149 -0
  30. traffic_taffy/tests/test_global_config.py +33 -0
  31. traffic_taffy/tests/test_normalize.py +1 -0
  32. traffic_taffy/tests/test_pcap_dissector.py +12 -2
  33. traffic_taffy/tests/test_pcap_splitter.py +21 -10
  34. traffic_taffy/tools/cache_info.py +3 -2
  35. traffic_taffy/tools/compare.py +32 -24
  36. traffic_taffy/tools/config.py +83 -0
  37. traffic_taffy/tools/dissect.py +51 -59
  38. traffic_taffy/tools/explore.py +5 -4
  39. traffic_taffy/tools/export.py +28 -17
  40. traffic_taffy/tools/graph.py +25 -27
  41. {traffic_taffy-0.8.5.dist-info → traffic_taffy-0.9.dist-info}/METADATA +4 -1
  42. traffic_taffy-0.9.dist-info/RECORD +56 -0
  43. {traffic_taffy-0.8.5.dist-info → traffic_taffy-0.9.dist-info}/entry_points.txt +1 -0
  44. traffic_taffy/report.py +0 -12
  45. traffic_taffy-0.8.5.dist-info/RECORD +0 -43
  46. {traffic_taffy-0.8.5.dist-info → traffic_taffy-0.9.dist-info}/WHEEL +0 -0
  47. {traffic_taffy-0.8.5.dist-info → traffic_taffy-0.9.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  from collections import Counter
2
- from traffic_taffy.report import Report
2
+ from traffic_taffy.reports.compareslicesreport import CompareSlicesReport
3
3
  from traffic_taffy.algorithms.statistical import ComparisonStatistical
4
4
 
5
5
 
@@ -10,7 +10,7 @@ def test_compare_statistical_algorithm():
10
10
  # this should be positive when right_data is larger
11
11
  expected = {
12
12
  "src": {
13
- "a": Report(
13
+ "a": CompareSlicesReport(
14
14
  total=20,
15
15
  left_count=5,
16
16
  right_count=15,
@@ -19,7 +19,7 @@ def test_compare_statistical_algorithm():
19
19
  right_percentage=15.0 / 30.0,
20
20
  delta_percentage=15.0 / 30.0 - 5.0 / 15.0,
21
21
  ),
22
- "b": Report(
22
+ "b": CompareSlicesReport(
23
23
  total=-10, # only in 1
24
24
  left_count=10,
25
25
  right_count=0,
@@ -28,7 +28,7 @@ def test_compare_statistical_algorithm():
28
28
  right_percentage=0.0,
29
29
  delta_percentage=-1.0,
30
30
  ),
31
- "c": Report(
31
+ "c": CompareSlicesReport(
32
32
  total=15, # only in 2
33
33
  left_count=0,
34
34
  right_count=15,
@@ -37,7 +37,7 @@ def test_compare_statistical_algorithm():
37
37
  right_percentage=15.0 / 30.0,
38
38
  delta_percentage=1.0,
39
39
  ),
40
- "__NEW_VALUES__": Report(
40
+ "__NEW_VALUES__": CompareSlicesReport(
41
41
  total=2, # 1 on each side
42
42
  left_count=1, # b
43
43
  right_count=1, # c
@@ -50,6 +50,21 @@ def test_compare_statistical_algorithm():
50
50
  }
51
51
 
52
52
  algorithm = ComparisonStatistical() # bogus file names
53
- report = algorithm.compare_dissections(left_data[0], right_data[0])
54
53
 
55
- assert report.contents == expected
54
+ class FakeDissection:
55
+ def __init__(self, data):
56
+ self._data = data
57
+
58
+ @property
59
+ def data(self):
60
+ return self._data
61
+
62
+ @property
63
+ def pcap_file(self):
64
+ return "bogus"
65
+
66
+ left = FakeDissection(left_data)
67
+ right = FakeDissection(right_data)
68
+
69
+ report = algorithm.compare_dissections(iter([left, right]))
70
+ assert report[0].contents == expected
@@ -0,0 +1,149 @@
1
+ from io import StringIO
2
+ from traffic_taffy.config import Config
3
+ from argparse import Namespace
4
+ from tempfile import NamedTemporaryFile
5
+
6
+ from argparse import ArgumentParser
7
+
8
+ TESTCONFIG: str = """
9
+ name: foo
10
+ value: bar
11
+ arry:
12
+ - 1
13
+ - 2
14
+ """
15
+
16
+
17
+ def test_loading():
18
+ contents = StringIO(TESTCONFIG)
19
+
20
+ cfg = Config()
21
+ cfg.load_stream(contents)
22
+ assert cfg["name"] == "foo"
23
+ assert cfg["arry"][0] == 1 # truly sic!
24
+
25
+
26
+ def test_namespace_loading():
27
+ cfg = Config()
28
+
29
+ arguments: Namespace = Namespace()
30
+ arguments.test_arg_one = 12
31
+ arguments.test_arg_two = {"a": "hello", "b": "world"}
32
+
33
+ cfg.load_namespace(arguments)
34
+
35
+ assert cfg["test_arg_one"] == 12
36
+ assert cfg["test_arg_two"]["b"] == "world"
37
+
38
+
39
+ def test_as_namespace():
40
+ contents = StringIO(TESTCONFIG)
41
+
42
+ cfg = Config()
43
+ cfg.load_stream(contents)
44
+ assert cfg["name"] == "foo"
45
+ assert cfg["arry"][0] == 1 # truly sic!
46
+
47
+ args = cfg.as_namespace()
48
+
49
+ assert args.name == "foo"
50
+ assert args.arry[0] == 1
51
+
52
+
53
+ def test_namespace_loading_and_mapping():
54
+ cfg = Config()
55
+
56
+ arguments: Namespace = Namespace()
57
+ arguments.test_arg_one = 12
58
+ arguments.test_arg_two = {"a": "hello", "b": "world"}
59
+
60
+ remap: dict = {"test_arg_one": "new_arg_one"}
61
+
62
+ cfg.load_namespace(arguments, mapping=remap)
63
+
64
+ assert cfg["new_arg_one"] == 12
65
+ assert cfg["test_arg_two"]["b"] == "world"
66
+
67
+ assert "test_arg_one" not in cfg
68
+
69
+
70
+ def test_config_commandline_option():
71
+ cfg = Config()
72
+
73
+ with NamedTemporaryFile("w", suffix="yml") as fileh:
74
+ fileh.write(TESTCONFIG)
75
+ fileh.flush()
76
+
77
+ cfg.read_configfile_from_arguments(
78
+ ["foo", "bar", "-in-the-way", "--config", fileh.name, "--other", "-arg"]
79
+ )
80
+
81
+ assert cfg["name"] == "foo"
82
+ assert cfg["arry"][0] == 1
83
+
84
+
85
+ def test_expected_full_usage():
86
+ # Create configuration in a yaml file
87
+ with NamedTemporaryFile("w", suffix="yml") as fileh:
88
+ fileh.write("question: 'how many roads must a man walk down?'\n")
89
+ fileh.write("reference: hitchhikers\n")
90
+ fileh.write("options:\n - 1\n - 2\n - 3\n")
91
+ fileh.flush()
92
+
93
+ # set some application hard-code defaults
94
+ cfg = Config()
95
+ cfg["answer"] = 42
96
+ cfg["options"] = ["a", "b", "c"]
97
+
98
+ assert cfg == {"answer": 42, "options": ["a", "b", "c"]}
99
+
100
+ # define the arguments we want to pass (potentially overriding other variables)
101
+ passed_arguments = [
102
+ "--question",
103
+ "What do you get when you multiply six by seven?",
104
+ "--config",
105
+ fileh.name,
106
+ "-r",
107
+ "The guide",
108
+ ]
109
+
110
+ # now parse these to just read the config file
111
+ cfg.read_configfile_from_arguments(passed_arguments)
112
+
113
+ # ensure the configuration has been updated from the file contents, but not CLI args
114
+
115
+ assert cfg == {
116
+ "answer": 42, # note: same
117
+ "options": [1, 2, 3], # note: overwritten
118
+ "question": "how many roads must a man walk down?", # note: same
119
+ "reference": "hitchhikers", # note: same
120
+ }
121
+
122
+ # set up the command line options
123
+ parser = ArgumentParser()
124
+
125
+ parser.add_argument("-q", "--question", default=cfg["question"], type=str)
126
+ parser.add_argument("-a", "--answer", default=cfg["answer"], type=int)
127
+ parser.add_argument(
128
+ "-o", "--options", default=cfg["options"], nargs="+", type=int
129
+ )
130
+ parser.add_argument("-r", "--reference", default=cfg["reference"], type=str)
131
+ parser.add_argument("-c", "--config", type=str)
132
+ parser.add_argument("--only-unused-argument", "--", type=str)
133
+
134
+ args = parser.parse_args(passed_arguments)
135
+ cfg.load_namespace(args)
136
+
137
+ del cfg[
138
+ "config"
139
+ ] # this will always be random tmp file and we don't need to check it
140
+ assert (
141
+ cfg
142
+ == {
143
+ "answer": 42, # note: still a default
144
+ "options": [1, 2, 3], # note: from config
145
+ "question": "What do you get when you multiply six by seven?", # note: from cli
146
+ "reference": "The guide", # note: from cli
147
+ "only_unused_argument": None,
148
+ }
149
+ )
@@ -0,0 +1,33 @@
1
+ from traffic_taffy.config import Config
2
+ from traffic_taffy.taffy_config import TaffyConfig, taffy_default
3
+
4
+
5
+ def test_multi_config():
6
+ c1 = Config()
7
+ c2 = Config()
8
+
9
+ c1["foo"] = 2
10
+ c2["foo"] = 3
11
+ assert c1["foo"] == 2
12
+
13
+
14
+ def test_global_config():
15
+ c1 = TaffyConfig()
16
+ c2 = TaffyConfig()
17
+
18
+ c1["foo"] = 2
19
+ c2["foo"] = 3
20
+ assert c1["foo"] == 3
21
+
22
+
23
+ def test_defaults():
24
+ taffy_default("a", "b")
25
+
26
+ c = TaffyConfig()
27
+ assert c["a"] == "b"
28
+
29
+ c["a"] = "c" # override
30
+ assert c["a"] == "c"
31
+
32
+ taffy_default("a", "d") # ignore overrides
33
+ assert c["a"] == "c"
@@ -9,6 +9,7 @@ class ParentFaker(PcapGraphData):
9
9
  self.match_value = None
10
10
  self.minimum_count = 0
11
11
  self.bin_size = 1
12
+ self.match_expression = None
12
13
 
13
14
  super().__init__()
14
15
 
@@ -50,10 +50,20 @@ def test_dissector_simple_callback() -> None:
50
50
  dpkt_engine.dissection.save(save_file)
51
51
 
52
52
  # create a new one to make sure it's blank
53
+ from traffic_taffy.taffy_config import TaffyConfig
54
+
55
+ config = TaffyConfig(
56
+ {
57
+ "dissect": {
58
+ "dissection_level": PCAPDissectorLevel.COUNT_ONLY.value,
59
+ "cache_results": True,
60
+ }
61
+ }
62
+ )
63
+
53
64
  pd = PCAPDissector(
54
65
  base_pcap,
55
- dissector_level=PCAPDissectorLevel.COUNT_ONLY.value,
56
- cache_results=True,
66
+ config,
57
67
  )
58
68
 
59
69
  pd.load()
@@ -4,17 +4,33 @@ import logging
4
4
  from logging import debug
5
5
  from traffic_taffy.dissector import PCAPDissector, pcap_data_merge
6
6
  from traffic_taffy.dissection import Dissection
7
+ from traffic_taffy.taffy_config import TaffyConfig
7
8
  from pcap_parallel import PCAPParallel
8
9
 
9
10
  test_pkl = "/tmp/test.pcap.pkl"
10
11
 
12
+ default_config = TaffyConfig(
13
+ {
14
+ "dissect": {
15
+ "dissection_level": 10,
16
+ "filter": None,
17
+ "packet_count": 0,
18
+ "cache_pcap_results": False,
19
+ "bin_size": 1,
20
+ "cache_file_suffix": "taffy",
21
+ "ignore_list": [],
22
+ "layers": [],
23
+ "force_overwrite": False,
24
+ "force_load": False,
25
+ }
26
+ }
27
+ )
28
+
11
29
 
12
30
  def buffer_callback(pcap_io_buffer):
13
31
  pd = PCAPDissector(
14
32
  pcap_io_buffer,
15
- bin_size=0,
16
- dissector_level=10,
17
- cache_results=False,
33
+ default_config,
18
34
  )
19
35
  pd.load()
20
36
  return pd.dissection.data
@@ -62,9 +78,7 @@ def test_pcap_splitter():
62
78
  # create a bogus dissector
63
79
  pd = PCAPDissector(
64
80
  None,
65
- bin_size=0,
66
- dissector_level=10,
67
- cache_results=False,
81
+ default_config,
68
82
  )
69
83
  pd.dissection = dissection
70
84
  dissection.save(test_pkl)
@@ -75,10 +89,7 @@ def test_pcap_splitter():
75
89
  normal_start_time = time.time()
76
90
  pd = PCAPDissector(
77
91
  test_pcap,
78
- bin_size=0,
79
- dissector_level=10,
80
- cache_results=False,
81
- maximum_count=maximum_count,
92
+ default_config,
82
93
  )
83
94
  pd.load()
84
95
  data2 = pd.dissection.data
@@ -1,6 +1,7 @@
1
1
  """Loads the cached data for a file to display the results about it."""
2
2
 
3
- from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace
3
+ from argparse import ArgumentParser, Namespace
4
+ from rich_argparse import RichHelpFormatter
4
5
  from pathlib import Path
5
6
  from rich import print
6
7
  import logging
@@ -10,7 +11,7 @@ import msgpack
10
11
  def parse_args() -> Namespace:
11
12
  """Parse the command line arguments."""
12
13
  parser = ArgumentParser(
13
- formatter_class=ArgumentDefaultsHelpFormatter,
14
+ formatter_class=RichHelpFormatter,
14
15
  description=__doc__,
15
16
  epilog="Example Usage: taffy-cache-info something.taffy",
16
17
  )
@@ -1,27 +1,37 @@
1
1
  """Takes a set of pcap files to compare and creates a report."""
2
2
 
3
3
  import sys
4
- from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace
4
+ from argparse import Namespace
5
+ from argparse_with_config import ArgumentParserWithConfig
6
+ from rich_argparse import RichHelpFormatter
5
7
  import logging
6
8
  from logging import error
7
9
  from traffic_taffy.output.console import Console
8
10
  from traffic_taffy.output.fsdb import Fsdb
11
+ from traffic_taffy.taffy_config import TaffyConfig, taffy_default
9
12
 
10
13
  from traffic_taffy.compare import compare_add_parseargs, get_comparison_args
11
14
  from traffic_taffy.dissector import (
12
15
  dissector_add_parseargs,
13
16
  limitor_add_parseargs,
14
17
  dissector_handle_arguments,
18
+ TTD_CFG,
15
19
  )
16
20
  from traffic_taffy.compare import PcapCompare
17
21
 
22
+ taffy_default("compare.fsdb", False)
18
23
 
19
- def parse_args() -> Namespace:
24
+
25
+ def compare_parse_args() -> Namespace:
20
26
  """Parse the command line arguments."""
21
- parser = ArgumentParser(
22
- formatter_class=ArgumentDefaultsHelpFormatter,
27
+
28
+ config: TaffyConfig = TaffyConfig()
29
+
30
+ parser = ArgumentParserWithConfig(
31
+ formatter_class=RichHelpFormatter,
23
32
  description=__doc__,
24
33
  epilog="Example Usage: taffy-compare -C file1.pcap file2.pcap",
34
+ default_config=config,
25
35
  )
26
36
 
27
37
  output_options = parser.add_argument_group("Output format")
@@ -29,12 +39,13 @@ def parse_args() -> Namespace:
29
39
  "-f",
30
40
  "--fsdb",
31
41
  action="store_true",
42
+ config_path="compare.output_fsdb",
32
43
  help="Print results in an FSDB formatted output",
33
44
  )
34
45
 
35
- limitor_parser = limitor_add_parseargs(parser)
36
- compare_add_parseargs(limitor_parser, add_subgroup=False)
37
- dissector_add_parseargs(parser)
46
+ limitor_add_parseargs(parser, config)
47
+ compare_add_parseargs(parser, config)
48
+ dissector_add_parseargs(parser, config)
38
49
 
39
50
  debugging_group = parser.add_argument_group("Debugging options")
40
51
 
@@ -42,6 +53,7 @@ def parse_args() -> Namespace:
42
53
  "--log-level",
43
54
  "--ll",
44
55
  default="info",
56
+ config_path="log_level",
45
57
  help="Define the logging verbosity level (debug, info, warning, error, ...).",
46
58
  )
47
59
 
@@ -53,15 +65,17 @@ def parse_args() -> Namespace:
53
65
 
54
66
  dissector_handle_arguments(args)
55
67
 
56
- return args
68
+ return parser.config, args
57
69
 
58
70
 
59
71
  def main() -> None:
60
72
  """Run taffy-compare."""
61
- args = parse_args()
73
+ config, args = compare_parse_args()
62
74
 
63
75
  # setup output options
64
- printing_arguments = get_comparison_args(args)
76
+ config[TTD_CFG.KEY_DISSECTOR][TTD_CFG.FILTER_ARGUMENTS] = get_comparison_args(
77
+ config
78
+ )
65
79
 
66
80
  # get our files to compare (maybe just one)
67
81
  left = args.pcap_files.pop(0)
@@ -79,18 +93,7 @@ def main() -> None:
79
93
 
80
94
  pc = PcapCompare(
81
95
  files,
82
- cache_results=args.cache_pcap_results,
83
- cache_file_suffix=args.cache_file_suffix,
84
- maximum_count=printing_arguments["maximum_count"],
85
- dissection_level=args.dissection_level,
86
- # between_times=args.between_times, # TODO(hardaker): TBD
87
- bin_size=args.bin_size,
88
- ignore_list=args.ignore_list,
89
- pcap_filter=args.filter,
90
- layers=args.layers,
91
- force_load=args.force_load,
92
- force_overwrite=args.force_overwrite,
93
- merge_files=args.merge,
96
+ config,
94
97
  )
95
98
 
96
99
  # compare the pcaps
@@ -101,9 +104,11 @@ def main() -> None:
101
104
  sys.exit()
102
105
 
103
106
  if args.fsdb:
104
- output = Fsdb(None, printing_arguments)
107
+ output = Fsdb(None, config[TTD_CFG.KEY_DISSECTOR][TTD_CFG.FILTER_ARGUMENTS])
105
108
  else:
106
- output = Console(None, printing_arguments)
109
+ output = Console(
110
+ None, config[TTD_CFG.KEY_DISSECTOR][TTD_CFG.FILTER_ARGUMENTS]
111
+ )
107
112
 
108
113
  for report in reports:
109
114
  # output results to the console
@@ -120,3 +125,6 @@ def main() -> None:
120
125
 
121
126
  if __name__ == "__main__":
122
127
  main()
128
+ config = TaffyConfig()
129
+ if config.get("dump", False):
130
+ config.dump()
@@ -0,0 +1,83 @@
1
+ """Performs generic dissection of a PCAP file."""
2
+ import sys
3
+ import logging
4
+ import yaml
5
+ from traffic_taffy.taffy_config import TaffyConfig, TT_CFG
6
+ from rich_argparse import RichHelpFormatter
7
+ from argparse import ArgumentParser, Namespace
8
+
9
+ # these force configuration token loading in a way ruff won't "fix"
10
+ from traffic_taffy.dissector import TTD_CFG as TTD_CFG
11
+ from traffic_taffy.compare import TTC_CFG as TTC_CFG
12
+ from traffic_taffy.graph import TTG_CFG as TTG_CFG
13
+ from traffic_taffy.tools.compare import compare_parse_args as compare_parse_args
14
+
15
+
16
+ # we try to load a number of modules, but if the missing requirements aren't available
17
+ # we don't fail here
18
+ try:
19
+ from traffic_taffy.dissector_engine.scapy import (
20
+ DissectionEngineScapy as DissectionEngineScapy,
21
+ )
22
+ except ModuleNotFoundError:
23
+ logging.debug("scapy module not loadable")
24
+
25
+ try:
26
+ from traffic_taffy.hooks.ip2asn import ip_to_asn as ip_to_asn
27
+ except ModuleNotFoundError:
28
+ logging.debug("ip2asn module not loadable")
29
+
30
+ try:
31
+ from traffic_taffy.hooks.psl import split_dns_names as split_dns_names
32
+ except ModuleNotFoundError:
33
+ logging.debug("psl module not loadable")
34
+
35
+
36
+ def main() -> None:
37
+ """Dissect a pcap file and report contents."""
38
+
39
+ def parse_args() -> Namespace:
40
+ """Parse the command line arguments."""
41
+
42
+ config: TaffyConfig = TaffyConfig()
43
+ config.config_option_names = ["-y", "--config"]
44
+ config[TT_CFG.LOG_LEVEL] = "info"
45
+
46
+ config.read_configfile_from_arguments(sys.argv)
47
+
48
+ parser = ArgumentParser(
49
+ formatter_class=RichHelpFormatter,
50
+ description=__doc__,
51
+ epilog="Example Usage: taffy-config > defaults.yml",
52
+ )
53
+
54
+ parser.add_argument(
55
+ "-y",
56
+ "--config",
57
+ default=None,
58
+ type=str,
59
+ help="Configuration file (YAML) to load.",
60
+ )
61
+
62
+ parser.add_argument(
63
+ "--log-level",
64
+ "--ll",
65
+ default="info",
66
+ help="Define the logging verbosity level (debug, info, warning, error, fotal, critical).",
67
+ )
68
+
69
+ args = parser.parse_args()
70
+ log_level = args.log_level.upper()
71
+ logging.basicConfig(level=log_level, format="%(levelname)-10s:\t%(message)s")
72
+
73
+ config.load_namespace(args)
74
+ return config
75
+
76
+ config = parse_args()
77
+ config.as_namespace()
78
+
79
+ print(yaml.dump(dict(config)))
80
+
81
+
82
+ if __name__ == "__main__":
83
+ main()