atomicshop 2.15.12__py3-none-any.whl → 2.16.0__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.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

Files changed (42) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  3. atomicshop/basics/booleans.py +14 -5
  4. atomicshop/dns.py +104 -0
  5. atomicshop/file_io/docxs.py +8 -0
  6. atomicshop/file_io/tomls.py +133 -0
  7. atomicshop/filesystem.py +5 -4
  8. atomicshop/get_process_list.py +3 -3
  9. atomicshop/mitm/config_static.py +195 -0
  10. atomicshop/mitm/config_toml_editor.py +55 -0
  11. atomicshop/mitm/connection_thread_worker.py +54 -90
  12. atomicshop/mitm/import_config.py +147 -139
  13. atomicshop/mitm/initialize_engines.py +7 -2
  14. atomicshop/mitm/initialize_mitm_server.py +161 -107
  15. atomicshop/mitm/shared_functions.py +0 -1
  16. atomicshop/mitm/statistic_analyzer.py +13 -1
  17. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +54 -14
  18. atomicshop/script_as_string_processor.py +5 -1
  19. atomicshop/wrappers/cryptographyw.py +3 -3
  20. atomicshop/wrappers/psutilw/networks.py +25 -1
  21. atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
  22. atomicshop/wrappers/pywin32w/wmis/helpers.py +127 -0
  23. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +167 -0
  24. atomicshop/wrappers/socketw/accepter.py +8 -8
  25. atomicshop/wrappers/socketw/base.py +13 -0
  26. atomicshop/wrappers/socketw/certificator.py +202 -149
  27. atomicshop/wrappers/socketw/creator.py +15 -35
  28. atomicshop/wrappers/socketw/dns_server.py +157 -103
  29. atomicshop/wrappers/socketw/exception_wrapper.py +8 -27
  30. atomicshop/wrappers/socketw/get_process.py +115 -95
  31. atomicshop/wrappers/socketw/sni.py +298 -164
  32. atomicshop/wrappers/socketw/socket_client.py +5 -12
  33. atomicshop/wrappers/socketw/socket_server_tester.py +1 -1
  34. atomicshop/wrappers/socketw/socket_wrapper.py +328 -72
  35. atomicshop/wrappers/socketw/statistics_csv.py +94 -16
  36. {atomicshop-2.15.12.dist-info → atomicshop-2.16.0.dist-info}/METADATA +1 -1
  37. {atomicshop-2.15.12.dist-info → atomicshop-2.16.0.dist-info}/RECORD +41 -36
  38. atomicshop/mitm/config_editor.py +0 -37
  39. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  40. {atomicshop-2.15.12.dist-info → atomicshop-2.16.0.dist-info}/LICENSE.txt +0 -0
  41. {atomicshop-2.15.12.dist-info → atomicshop-2.16.0.dist-info}/WHEEL +0 -0
  42. {atomicshop-2.15.12.dist-info → atomicshop-2.16.0.dist-info}/top_level.txt +0 -0
@@ -1,53 +1,56 @@
1
1
  import os
2
- import sys
3
2
  import threading
4
3
  import time
5
4
 
6
- # Importing atomicshop package to get the version of the package.
7
- import atomicshop
5
+ import atomicshop # Importing atomicshop package to get the version of the package.
8
6
 
9
- from .import_config import ImportConfig
10
- from .initialize_engines import ModuleCategory
11
- from .connection_thread_worker import thread_worker_main
12
- from .. import filesystem, queues
7
+ from .. import filesystem, queues, dns, on_exit, permissions
13
8
  from ..python_functions import get_current_python_version_string, check_python_version_compliance
14
- from ..wrappers.socketw.socket_wrapper import SocketWrapper
15
- from ..wrappers.socketw import dns_server
16
- from ..wrappers.psutilw import networks
17
- from ..basics import dicts_nested
9
+ from ..wrappers.socketw import socket_wrapper, dns_server, base
18
10
  from ..wrappers.loggingw import loggingw
19
11
  from ..print_api import print_api
20
12
 
13
+ from .initialize_engines import ModuleCategory
14
+ from .connection_thread_worker import thread_worker_main
15
+ from . import config_static
16
+
17
+
18
+ def exit_cleanup():
19
+ if config_static.DNSServer.set_default_dns_gateway:
20
+ is_dns_dynamic, current_dns_gateway = dns.get_default_dns_gateway()
21
+ print_api(f'Current DNS Gateway: {current_dns_gateway}')
21
22
 
22
- STATISTICS_HEADER: str = \
23
- 'request_time_sent,host,path,command,status_code,request_size_bytes,response_size_bytes,file_path,process_cmd,error'
23
+ if current_dns_gateway == config_static.DNSServer.set_default_dns_gateway and not is_dns_dynamic:
24
+ if permissions.is_admin():
25
+ dns.set_connection_dns_gateway_dynamic(use_default_connection=True)
26
+ print_api("Returned default DNS gateway...", color='blue')
24
27
 
25
28
 
26
- def initialize_mitm_server(config_static):
29
+ def initialize_mitm_server(config_file_path: str):
30
+ on_exit.register_exit_handler(exit_cleanup)
31
+
27
32
  # Main function should return integer with error code, 0 is successful.
28
33
  # Since listening server is infinite, this will not be reached.
29
34
  # After modules import - we check for python version.
30
35
  check_python_version_compliance(minimum_version='3.11')
31
36
 
32
- # Preparing everything for the logging module.
33
- # Log folder path is in the "config.ini" file, so we need to read it before setting loggers.
34
- config_importer = ImportConfig(
35
- file_name=config_static.CONFIG_INI_SERVER_FILE_NAME, directory_path=config_static.WORKING_DIRECTORY)
36
- config_importer.open()
37
- config = config_importer.config
37
+ # Import the configuration file.
38
+ result = config_static.load_config(config_file_path)
39
+ if result != 0:
40
+ return result
38
41
 
39
42
  # Create folders.
40
- filesystem.create_directory(config['log']['logs_path'])
41
- filesystem.create_directory(config['recorder']['recordings_path'])
42
- if config['certificates']['sni_get_server_certificate_from_server_socket']:
43
+ filesystem.create_directory(config_static.Log.logs_path)
44
+ filesystem.create_directory(config_static.Recorder.recordings_path)
45
+ if config_static.Certificates.sni_get_server_certificate_from_server_socket:
43
46
  filesystem.create_directory(
44
- config['certificates']['sni_server_certificate_from_server_socket_download_directory'])
47
+ config_static.Certificates.sni_server_certificate_from_server_socket_download_directory)
45
48
 
46
49
  # Create a logger that will log messages to file, Initiate System logger.
47
50
  logger_name = "system"
48
51
  system_logger = loggingw.create_logger(
49
52
  logger_name=logger_name,
50
- file_path=f"{config['log']['logs_path']}{os.sep}{logger_name}.txt",
53
+ file_path=f"{config_static.Log.logs_path}{os.sep}{logger_name}.txt",
51
54
  add_stream=True,
52
55
  add_timedfile=True,
53
56
  formatter_streamhandler='DEFAULT',
@@ -56,44 +59,37 @@ def initialize_mitm_server(config_static):
56
59
 
57
60
  # Writing first log.
58
61
  system_logger.info("======================================")
59
-
60
- if config_importer.admin_rights is not None:
61
- if not config_importer.admin_rights:
62
- system_logger.error("User continued with errors on Command Line harvesting for system processes.")
63
-
64
62
  system_logger.info("Server Started.")
65
63
  system_logger.info(f"Python Version: {get_current_python_version_string()}")
66
64
  system_logger.info(f"Script Version: {config_static.SCRIPT_VERSION}")
67
65
  system_logger.info(f"Atomic Workshop Version: {atomicshop.__version__}")
68
- system_logger.info(f"Loaded config.ini: {config_importer.config_parser.file_path}")
69
- system_logger.info(f"Log folder: {config['log']['logs_path']}")
70
- system_logger.info(f"Recordings folder for Requests/Responses: {config['recorder']['recordings_path']}")
66
+ system_logger.info(f"Log folder: {config_static.Log.logs_path}")
67
+ system_logger.info(f"Recordings folder for Requests/Responses: {config_static.Recorder.recordings_path}")
71
68
  system_logger.info(f"Loaded system logger: {system_logger}")
72
69
 
73
- system_logger.info(f"TCP Server Target IP: {config['dns']['target_tcp_server_ipv4']}")
70
+ system_logger.info(f"TCP Server Target IP: {config_static.DNSServer.target_tcp_server_ipv4}")
74
71
 
75
72
  # Some 'config.ini' settings logging ===========================================================================
76
- if config['certificates']['default_server_certificate_usage']:
73
+ if config_static.Certificates.default_server_certificate_usage:
77
74
  system_logger.info(
78
75
  f"Default server certificate usage enabled, if no SNI available: "
79
- f"{config_static.CONFIG_EXTENDED['certificates']['default_server_certificate_directory']}"
80
- f"{os.sep}{config_static.CONFIG_EXTENDED['certificates']['default_server_certificate_name']}.pem")
76
+ f"{config_static.MainConfig.default_server_certificate_filepath}")
81
77
 
82
- if config['certificates']['sni_server_certificates_cache_directory']:
78
+ if config_static.Certificates.sni_server_certificates_cache_directory:
83
79
  system_logger.info(
84
80
  f"SNI function certificates creation enabled. Certificates cache: "
85
- f"{config['certificates']['sni_server_certificates_cache_directory']}")
81
+ f"{config_static.Certificates.sni_server_certificates_cache_directory}")
86
82
  else:
87
83
  system_logger.info(f"SNI function certificates creation disabled.")
88
84
 
89
- if config['certificates']['custom_server_certificate_usage']:
85
+ if config_static.Certificates.custom_server_certificate_usage:
90
86
  system_logger.info(f"Custom server certificate usage is enabled.")
91
- system_logger.info(f"Custom Certificate Path: {config['certificates']['custom_server_certificate_path']}")
87
+ system_logger.info(f"Custom Certificate Path: {config_static.Certificates.custom_server_certificate_path}")
92
88
 
93
89
  # If 'custom_private_key_path' field was populated.
94
- if config['certificates']['custom_private_key_path']:
90
+ if config_static.Certificates.custom_private_key_path:
95
91
  system_logger.info(
96
- f"Custom Certificate Private Key Path: {config['certificates']['custom_private_key_path']}")
92
+ f"Custom Certificate Private Key Path: {config_static.Certificates.custom_private_key_path}")
97
93
  else:
98
94
  system_logger.info(f"Custom Certificate Private Key Path wasn't provided in [advanced] section. "
99
95
  f"Assuming the private key is inside the certificate file.")
@@ -103,17 +99,17 @@ def initialize_mitm_server(config_static):
103
99
 
104
100
  # Get full paths of all the 'engine_config.ini' files.
105
101
  engine_config_path_list = filesystem.get_file_paths_from_directory(
106
- directory_path=config_static.ENGINES_DIRECTORY_PATH,
107
- file_name_check_pattern=config_static.ENGINE_CONFIG_FILE_NAME)
102
+ directory_path=config_static.MainConfig.ENGINES_DIRECTORY_PATH,
103
+ file_name_check_pattern=config_static.MainConfig.ENGINE_CONFIG_FILE_NAME)
108
104
 
109
105
  # Iterate through all the 'engine_config.ini' file paths.
110
106
  domains_engine_list_full: list = list()
111
107
  engines_list: list = list()
112
108
  for engine_config_path in engine_config_path_list:
113
109
  # Initialize engine.
114
- current_module = ModuleCategory(config_static.WORKING_DIRECTORY)
110
+ current_module = ModuleCategory(config_static.MainConfig.SCRIPT_DIRECTORY)
115
111
  current_module.fill_engine_fields_from_config(engine_config_path)
116
- current_module.initialize_engine(logs_path=config['log']['logs_path'],
112
+ current_module.initialize_engine(logs_path=config_static.Log.logs_path,
117
113
  logger=system_logger)
118
114
 
119
115
  # Extending the full engine domain list with this list.
@@ -122,9 +118,9 @@ def initialize_mitm_server(config_static):
122
118
  engines_list.append(current_module)
123
119
  # === EOF Importing engine modules =============================================================================
124
120
  # ==== Initialize Reference Module =============================================================================
125
- reference_module = ModuleCategory(config_static.WORKING_DIRECTORY)
126
- reference_module.fill_engine_fields_from_general_reference(config_static.ENGINES_DIRECTORY_PATH)
127
- reference_module.initialize_engine(logs_path=config['log']['logs_path'],
121
+ reference_module = ModuleCategory(config_static.MainConfig.SCRIPT_DIRECTORY)
122
+ reference_module.fill_engine_fields_from_general_reference(config_static.MainConfig.ENGINES_DIRECTORY_PATH)
123
+ reference_module.initialize_engine(logs_path=config_static.Log.logs_path,
128
124
  logger=system_logger, stdout=False, reference_general=True)
129
125
  # === EOF Initialize Reference Module ==========================================================================
130
126
  # === Engine logging ===========================================================================================
@@ -139,41 +135,41 @@ def initialize_mitm_server(config_static):
139
135
  f"{engine.recorder_class_object.__name__}")
140
136
  print_api(message, logger=system_logger)
141
137
 
142
- if config['dns']['enable_dns_server']:
138
+ if config_static.DNSServer.enable:
143
139
  print_api("DNS Server is enabled.", logger=system_logger)
144
140
 
145
141
  # If engines were found and dns is set to route by the engine domains.
146
- if engines_list and config['dns']['route_to_tcp_server_only_engine_domains']:
142
+ if engines_list and config_static.DNSServer.resolve_to_tcp_server_only_engine_domains:
147
143
  print_api("Engine domains will be routed by the DNS server to Built-in TCP Server.", logger=system_logger)
148
144
  # If engines were found, but the dns isn't set to route to engines.
149
- elif engines_list and not config['dns']['route_to_tcp_server_only_engine_domains']:
145
+ elif engines_list and not config_static.DNSServer.resolve_to_tcp_server_only_engine_domains:
150
146
  message = f"[*] Engine domains found, but the DNS routing is set not to use them for routing."
151
147
  print_api(message, color="yellow", logger=system_logger)
152
- elif not engines_list and config['dns']['route_to_tcp_server_only_engine_domains']:
148
+ elif not engines_list and config_static.DNSServer.resolve_to_tcp_server_only_engine_domains:
153
149
  raise ValueError("No engines were found, but the DNS routing is set to use them for routing.\n"
154
150
  "Please check your DNS configuration in the 'config.ini' file.")
155
151
 
156
- if config['dns']['route_to_tcp_server_all_domains']:
152
+ if config_static.DNSServer.resolve_to_tcp_server_all_domains:
157
153
  print_api("All domains will be routed by the DNS server to Built-in TCP Server.", logger=system_logger)
158
154
 
159
- if config['dns']['regular_resolving']:
155
+ if config_static.DNSServer.resolve_regular:
160
156
  print_api(
161
157
  "Regular DNS resolving is enabled. Built-in TCP server will not be routed to",
162
158
  logger=system_logger, color="yellow")
163
159
  else:
164
160
  print_api("DNS Server is disabled.", logger=system_logger, color="yellow")
165
161
 
166
- if config['tcp']['enable_tcp_server']:
162
+ if config_static.TCPServer.enable:
167
163
  print_api("TCP Server is enabled.", logger=system_logger)
168
164
 
169
- if engines_list and not config['tcp']['engines_usage']:
165
+ if engines_list and not config_static.TCPServer.engines_usage:
170
166
  message = \
171
167
  f"Engines found, but the TCP server is set not to use them for processing. General responses only."
172
168
  print_api(message, color="yellow", logger=system_logger)
173
- elif engines_list and config['tcp']['engines_usage']:
169
+ elif engines_list and config_static.TCPServer.engines_usage:
174
170
  message = f"Engines found, and the TCP server is set to use them for processing."
175
171
  print_api(message, logger=system_logger)
176
- elif not engines_list and config['tcp']['engines_usage']:
172
+ elif not engines_list and config_static.TCPServer.engines_usage:
177
173
  raise ValueError("No engines were found, but the TCP server is set to use them for processing.\n"
178
174
  "Please check your TCP configuration in the 'config.ini' file.")
179
175
  else:
@@ -182,22 +178,12 @@ def initialize_mitm_server(config_static):
182
178
  # === EOF Engine Logging =======================================================================================
183
179
 
184
180
  # Assigning all the engines domains to all time domains, that will be responsible for adding new domains.
185
- config_static.CONFIG_EXTENDED['certificates']['domains_all_times'] = list(domains_engine_list_full)
186
-
187
- # Creating Statistics logger.
188
- statistics_logger = loggingw.create_logger(
189
- logger_name="statistics",
190
- directory_path=config['log']['logs_path'],
191
- add_timedfile=True,
192
- formatter_filehandler='MESSAGE',
193
- file_type='csv',
194
- header=STATISTICS_HEADER
195
- )
181
+ config_static.Certificates.domains_all_times = list(domains_engine_list_full)
196
182
 
197
183
  network_logger_name = "network"
198
184
  network_logger = loggingw.create_logger(
199
185
  logger_name=network_logger_name,
200
- directory_path=config['log']['logs_path'],
186
+ directory_path=config_static.Log.logs_path,
201
187
  add_stream=True,
202
188
  add_timedfile=True,
203
189
  formatter_streamhandler='DEFAULT',
@@ -209,60 +195,128 @@ def initialize_mitm_server(config_static):
209
195
  listener_logger = loggingw.get_logger_with_level(f'{network_logger_name}.listener')
210
196
  system_logger.info(f"Loaded listener logger: {listener_logger}")
211
197
 
198
+ print_api("Press [Ctrl]+[C] to stop.", color='blue')
199
+
212
200
  # Create request domain queue.
213
201
  domain_queue = queues.NonBlockQueue()
214
202
 
215
203
  # === Initialize DNS module ====================================================================================
216
- if config['dns']['enable_dns_server']:
217
- # Check if the DNS server port is in use.
218
- port_in_use = networks.get_processes_using_port_list([config['dns']['listening_port']])
219
- if port_in_use:
220
- for port, process_info in port_in_use.items():
221
- message = f"Port [{port}] is already in use by process: {process_info}"
222
- print_api(message, error_type=True, logger_method='critical', logger=system_logger)
223
-
204
+ if config_static.DNSServer.enable:
205
+ try:
206
+ dns_server_instance = dns_server.DnsServer(
207
+ listening_interface=config_static.DNSServer.listening_interface,
208
+ listening_port=config_static.DNSServer.listening_port,
209
+ forwarding_dns_service_ipv4=config_static.DNSServer.forwarding_dns_service_ipv4,
210
+ tcp_target_server_ipv4=config_static.DNSServer.target_tcp_server_ipv4,
211
+ # Passing the engine domain list to DNS server to work with.
212
+ # 'list' function re-initializes the current list, or else it will be the same instance object.
213
+ tcp_resolve_domain_list=list(config_static.Certificates.domains_all_times),
214
+ log_directory_path=config_static.Log.logs_path,
215
+ offline_mode=config_static.DNSServer.offline_mode,
216
+ resolve_to_tcp_server_only_tcp_resolve_domains=(
217
+ config_static.DNSServer.resolve_to_tcp_server_only_engine_domains),
218
+ resolve_to_tcp_server_all_domains=config_static.DNSServer.resolve_to_tcp_server_all_domains,
219
+ resolve_regular=config_static.DNSServer.resolve_regular,
220
+ cache_timeout_minutes=config_static.DNSServer.cache_timeout_minutes,
221
+ request_domain_queue=domain_queue
222
+ )
223
+ except (dns_server.DnsPortInUseError, dns_server.DnsConfigurationValuesError) as e:
224
+ print_api(e, error_type=True, color="red", logger=system_logger)
224
225
  # Wait for the message to be printed and saved to file.
225
226
  time.sleep(1)
226
- sys.exit(1)
227
-
228
- # before executing TCP sockets and after executing 'network' logger.
229
- dns_server_instance = dns_server.DnsServer(config)
230
- # Passing the engine domain list to DNS server to work with.
231
- # 'list' function re-initializes the current list, or else it will be the same instance object.
232
- dns_server_instance.domain_list = list(domains_engine_list_full)
227
+ return 1
233
228
 
234
- dns_server_instance.request_domain_queue = domain_queue
235
- # Initiate the thread.
236
229
  dns_thread = threading.Thread(target=dns_server_instance.start)
237
230
  dns_thread.daemon = True
238
231
  dns_thread.start()
239
232
 
240
233
  # === EOF Initialize DNS module ================================================================================
241
234
  # === Initialize TCP Server ====================================================================================
242
- if config['tcp']['enable_tcp_server']:
243
- port_in_use = networks.get_processes_using_port_list(config['tcp']['listening_port_list'])
244
- if port_in_use:
245
- for port, process_info in port_in_use.items():
246
- print_api(f"Port [{port}] is already in use by process: {process_info}", logger=system_logger,
247
- error_type=True, logger_method='critical')
235
+ if config_static.TCPServer.enable:
236
+ try:
237
+ socket_wrapper_instance = socket_wrapper.SocketWrapper(
238
+ listening_interface=config_static.TCPServer.listening_interface,
239
+ listening_port_list=config_static.TCPServer.listening_port_list,
240
+ ca_certificate_name=config_static.MainConfig.ca_certificate_name,
241
+ ca_certificate_filepath=config_static.MainConfig.ca_certificate_filepath,
242
+ default_server_certificate_usage=config_static.Certificates.default_server_certificate_usage,
243
+ default_server_certificate_name=config_static.MainConfig.default_server_certificate_name,
244
+ default_certificate_domain_list=config_static.Certificates.domains_all_times,
245
+ default_server_certificate_directory=config_static.MainConfig.SCRIPT_DIRECTORY,
246
+ sni_use_default_callback_function=True,
247
+ sni_use_default_callback_function_extended=True,
248
+ sni_add_new_domains_to_default_server_certificate=(
249
+ config_static.Certificates.sni_add_new_domains_to_default_server_certificate),
250
+ sni_create_server_certificate_for_each_domain=(
251
+ config_static.Certificates.sni_create_server_certificate_for_each_domain),
252
+ sni_server_certificates_cache_directory=(
253
+ config_static.Certificates.sni_server_certificates_cache_directory),
254
+ sni_get_server_certificate_from_server_socket=(
255
+ config_static.Certificates.sni_get_server_certificate_from_server_socket),
256
+ sni_server_certificate_from_server_socket_download_directory=(
257
+ config_static.Certificates.sni_server_certificate_from_server_socket_download_directory),
258
+ custom_server_certificate_usage=config_static.Certificates.custom_server_certificate_usage,
259
+ custom_server_certificate_path=config_static.Certificates.custom_server_certificate_path,
260
+ custom_private_key_path=config_static.Certificates.custom_private_key_path,
261
+ get_process_name=config_static.ProcessName.get_process_name,
262
+ ssh_user=config_static.ProcessName.ssh_user,
263
+ ssh_pass=config_static.ProcessName.ssh_pass,
264
+ ssh_script_to_execute=config_static.ProcessName.ssh_script_to_execute,
265
+ logger=listener_logger,
266
+ statistics_logs_directory=config_static.Log.logs_path,
267
+ forwarding_dns_service_ipv4_list___only_for_localhost=(
268
+ config_static.TCPServer.forwarding_dns_service_ipv4_list___only_for_localhost),
269
+ skip_extension_id_list=config_static.SkipExtensions.SKIP_EXTENSION_ID_LIST
270
+ )
271
+ except socket_wrapper.SocketWrapperPortInUseError as e:
272
+ print_api(e, error_type=True, color="red", logger=system_logger)
248
273
  # Wait for the message to be printed and saved to file.
249
274
  time.sleep(1)
250
- sys.exit(1)
251
-
252
- socket_wrapper = SocketWrapper(
253
- config=dicts_nested.merge(config, config_static.CONFIG_EXTENDED), logger=listener_logger,
254
- statistics_logger=statistics_logger)
255
-
256
- socket_wrapper.create_tcp_listening_socket_list()
257
-
258
- socket_wrapper.requested_domain_from_dns_server = domain_queue
275
+ return 1
276
+
277
+ statistics_writer = socket_wrapper_instance.statistics_writer
278
+
279
+ socket_wrapper_instance.create_tcp_listening_socket_list()
280
+
281
+ socket_wrapper_instance.requested_domain_from_dns_server = domain_queue
282
+
283
+ # Before we start the loop. we can set the default gateway if specified.
284
+ set_dns_gateway = False
285
+ dns_gateway_server_list = list()
286
+ if config_static.DNSServer.set_default_dns_gateway:
287
+ dns_gateway_server_list = config_static.DNSServer.set_default_dns_gateway
288
+ set_dns_gateway = True
289
+ elif config_static.DNSServer.set_default_dns_gateway_to_localhost:
290
+ dns_gateway_server_list = [base.LOCALHOST_IPV4]
291
+ set_dns_gateway = True
292
+ elif config_static.DNSServer.set_default_dns_gateway_to_default_interface_ipv4:
293
+ dns_gateway_server_list = [base.DEFAULT_IPV4]
294
+ set_dns_gateway = True
295
+
296
+ if set_dns_gateway:
297
+ # noinspection PyTypeChecker
298
+ dns.set_connection_dns_gateway_static(
299
+ dns_servers=dns_gateway_server_list,
300
+ use_default_connection=True
301
+ )
259
302
 
260
303
  # General exception handler will catch all the exceptions that occurred in the threads and write it to the log.
304
+ # noinspection PyBroadException
261
305
  try:
262
- socket_wrapper.loop_for_incoming_sockets(function_reference=thread_worker_main, reference_args=(
263
- network_logger, statistics_logger, engines_list, reference_module, config,))
306
+ socket_thread = threading.Thread(
307
+ target=socket_wrapper_instance.loop_for_incoming_sockets,
308
+ kwargs={
309
+ 'reference_function_name': thread_worker_main,
310
+ 'reference_function_args': (network_logger, statistics_writer, engines_list, reference_module,)
311
+ }
312
+ )
313
+
314
+ socket_thread.daemon = True
315
+ socket_thread.start()
264
316
  except Exception:
265
317
  message = f"Unhandled Exception occurred in 'loop_for_incoming_sockets' function"
266
318
  print_api(message, error_type=True, color="red", logger=network_logger, traceback_string=True, oneline=True)
267
319
 
268
- # === EOF Initialize TCP Server ================================================================================
320
+ # This is needed for Keyboard Exception.
321
+ while True:
322
+ time.sleep(1)
@@ -1,5 +1,4 @@
1
1
  import json
2
- # Needed to get the function caller module.
3
2
  import inspect
4
3
 
5
4
  from ..wrappers.loggingw import loggingw
@@ -346,6 +346,7 @@ def analyze(main_file_path: str):
346
346
 
347
347
  def deviation_calculator_by_moving_average_main(
348
348
  statistics_file_directory: str,
349
+ by_type: Literal['host', 'url'],
349
350
  moving_average_window_days: int,
350
351
  top_bottom_deviation_percentage: float,
351
352
  get_deviation_for_last_day_only: bool = False,
@@ -359,6 +360,9 @@ def deviation_calculator_by_moving_average_main(
359
360
  :param statistics_file_directory: string, the directory where 'statistics.csv' file resides.
360
361
  Also, all the rotated files like: statistics_2021-01-01.csv, statistics_2021-01-02.csv, etc.
361
362
  These will be analyzed in the order of the date in the file name.
363
+ :param by_type: string, 'host' or 'url'. The type of the deviation calculation.
364
+ 'host' will calculate the deviation by the host name. Example: maps.google.com, yahoo.com, etc.
365
+ 'url' will calculate the deviation by the URL. Example: maps.google.com/maps, yahoo.com/news, etc.
362
366
  :param moving_average_window_days: integer, the moving average window days.
363
367
  :param top_bottom_deviation_percentage: float, the top bottom deviation percentage. Example: 0.1 for 10%.
364
368
  :param get_deviation_for_last_day_only: bool, if True, only the last day will be analyzed.
@@ -402,6 +406,9 @@ def deviation_calculator_by_moving_average_main(
402
406
  if output_file_type not in ['json', 'csv']:
403
407
  raise ValueError(f'output_file_type must be "json" or "csv", not [{output_file_type}]')
404
408
 
409
+ if by_type not in ['host', 'url']:
410
+ raise ValueError(f'by_type must be "host" or "url", not [{by_type}]')
411
+
405
412
  statistics_file_path: str = f'{statistics_file_directory}{os.sep}{STATISTICS_FILE_NAME}'
406
413
 
407
414
  def convert_data_value_to_string(value_key: str, list_index: int) -> None:
@@ -413,6 +420,7 @@ def deviation_calculator_by_moving_average_main(
413
420
 
414
421
  deviation_list = moving_average_helper.calculate_moving_average(
415
422
  statistics_file_path,
423
+ by_type,
416
424
  moving_average_window_days,
417
425
  top_bottom_deviation_percentage,
418
426
  get_deviation_for_last_day_only
@@ -436,7 +444,11 @@ def deviation_calculator_by_moving_average_main(
436
444
  'value': deviation.get('value', None),
437
445
  'ma_value': deviation.get('ma_value', None),
438
446
  'deviation_percentage': deviation.get('deviation_percentage', None),
439
- 'total_entries_averaged': total_entries_averaged
447
+ 'total_entries_averaged': total_entries_averaged,
448
+ 'median_request_size': deviation.get('median_request_size', None),
449
+ 'median_response_size': deviation.get('median_response_size', None),
450
+ 'mm_request_size': deviation.get('mm_request_size', None),
451
+ 'mm_response_size': deviation.get('mm_response_size', None),
440
452
  })
441
453
 
442
454
  deviation_list = summary_deviation_list
@@ -4,10 +4,12 @@ from typing import Literal
4
4
  from ...print_api import print_api
5
5
  from ...wrappers.loggingw import reading, consts
6
6
  from ...file_io import csvs
7
+ from ... import urls
7
8
 
8
9
 
9
10
  def calculate_moving_average(
10
11
  file_path: str,
12
+ by_type: Literal['host', 'url'],
11
13
  moving_average_window_days,
12
14
  top_bottom_deviation_percentage: float,
13
15
  get_deviation_for_last_day_only: bool = False,
@@ -17,6 +19,7 @@ def calculate_moving_average(
17
19
  This function calculates the moving average of the daily statistics.
18
20
 
19
21
  :param file_path: string, the path to the 'statistics.csv' file.
22
+ :param by_type: string, the type to calculate the moving average by. Can be 'host' or 'url'.
20
23
  :param moving_average_window_days: integer, the window size for the moving average.
21
24
  :param top_bottom_deviation_percentage: float, the percentage of deviation from the moving average to the top or
22
25
  bottom.
@@ -65,8 +68,7 @@ def calculate_moving_average(
65
68
 
66
69
  # Get the data dictionary from the statistics content.
67
70
  statistics_content[date_string]['statistics_daily'] = compute_statistics_from_content(
68
- statistics_content[date_string]['content_no_errors']
69
- )
71
+ statistics_content[date_string]['content_no_errors'], by_type)
70
72
 
71
73
  moving_average_dict: dict = compute_moving_averages_from_average_statistics(
72
74
  statistics_content,
@@ -106,19 +108,36 @@ def get_content_without_errors(content: list) -> list:
106
108
  return traffic_statistics_without_errors
107
109
 
108
110
 
109
- def get_data_dict_from_statistics_content(content: list) -> dict:
111
+ def get_data_dict_from_statistics_content(
112
+ content: list,
113
+ by_type: Literal['host', 'url']
114
+ ) -> dict:
110
115
  """
111
116
  This function gets the data dictionary from the 'statistics.csv' file content.
112
117
 
113
118
  :param content: list, the content list.
119
+ :param by_type: string, the type to calculate the moving average by. Can be 'host' or 'url'.
114
120
  :return: dict, the data dictionary.
115
121
  """
116
122
 
117
123
  hosts_requests_responses: dict = {}
118
124
  for line in content:
125
+ if by_type == 'host':
126
+ type_to_check: str = line['host']
127
+ elif by_type == 'url':
128
+ # Combine host and path to URL.
129
+ type_to_check: str = line['host'] + line['path']
130
+ # Remove the parameters from the URL.
131
+ url_parsed = urls.url_parser(type_to_check)
132
+ type_to_check = url_parsed['path']
133
+ # Remove the last slash from the URL.
134
+ type_to_check = type_to_check.removesuffix('/')
135
+ else:
136
+ raise ValueError(f'Invalid by_type: {by_type}')
137
+
119
138
  # If subdomain is not in the dictionary, add it.
120
- if line['host'] not in hosts_requests_responses:
121
- hosts_requests_responses[line['host']] = {
139
+ if type_to_check not in hosts_requests_responses:
140
+ hosts_requests_responses[type_to_check] = {
122
141
  'request_sizes': [],
123
142
  'response_sizes': []
124
143
  }
@@ -132,8 +151,8 @@ def get_data_dict_from_statistics_content(content: list) -> dict:
132
151
  if response_size_bytes == '':
133
152
  response_size_bytes = '0'
134
153
 
135
- hosts_requests_responses[line['host']]['request_sizes'].append(int(request_size_bytes))
136
- hosts_requests_responses[line['host']]['response_sizes'].append(int(response_size_bytes))
154
+ hosts_requests_responses[type_to_check]['request_sizes'].append(int(request_size_bytes))
155
+ hosts_requests_responses[type_to_check]['response_sizes'].append(int(response_size_bytes))
137
156
  except ValueError:
138
157
  print_api(line, color='yellow')
139
158
  raise
@@ -163,18 +182,22 @@ def compute_statistics_from_data_dict(data_dict: dict):
163
182
  data_dict[host]['median_response_size'] = median_response_size
164
183
 
165
184
 
166
- def compute_statistics_from_content(content: list):
185
+ def compute_statistics_from_content(
186
+ content: list,
187
+ by_type: Literal['host', 'url']
188
+ ):
167
189
  """
168
190
  This function computes the statistics from the 'statistics.csv' file content.
169
191
 
170
192
  :param content: list, the content list.
193
+ :param by_type: string, the type to calculate the moving average by. Can be 'host' or 'url'.
171
194
  :return: dict, the statistics dictionary.
172
195
  """
173
196
 
174
- hosts_requests_responses: dict = get_data_dict_from_statistics_content(content)
175
- compute_statistics_from_data_dict(hosts_requests_responses)
197
+ requests_responses: dict = get_data_dict_from_statistics_content(content, by_type)
198
+ compute_statistics_from_data_dict(requests_responses)
176
199
 
177
- return hosts_requests_responses
200
+ return requests_responses
178
201
 
179
202
 
180
203
  def compute_moving_averages_from_average_statistics(
@@ -200,12 +223,15 @@ def compute_moving_averages_from_average_statistics(
200
223
  list(average_statistics_dict.values()))[current_day-moving_average_window_days:current_day]
201
224
 
202
225
  # Compute the moving averages.
203
- moving_average[day] = compute_average_for_current_day_from_past_x_days(last_x_window_days_content_list)
226
+ moving_average[day] = compute_average_for_current_day_from_past_x_days(
227
+ last_x_window_days_content_list)
204
228
 
205
229
  return moving_average
206
230
 
207
231
 
208
- def compute_average_for_current_day_from_past_x_days(previous_days_content_list: list) -> dict:
232
+ def compute_average_for_current_day_from_past_x_days(
233
+ previous_days_content_list: list
234
+ ) -> dict:
209
235
  """
210
236
  This function computes the average for the current day from the past x days.
211
237
 
@@ -222,11 +248,15 @@ def compute_average_for_current_day_from_past_x_days(previous_days_content_list:
222
248
  'counts': [],
223
249
  'avg_request_sizes': [],
224
250
  'avg_response_sizes': [],
251
+ 'median_request_sizes': [],
252
+ 'median_response_sizes': []
225
253
  }
226
254
 
227
255
  moving_average[host]['counts'].append(int(host_dict['count']))
228
256
  moving_average[host]['avg_request_sizes'].append(float(host_dict['avg_request_size']))
229
257
  moving_average[host]['avg_response_sizes'].append(float(host_dict['avg_response_size']))
258
+ moving_average[host]['median_request_sizes'].append(float(host_dict['median_request_size']))
259
+ moving_average[host]['median_response_sizes'].append(float(host_dict['median_response_size']))
230
260
 
231
261
  # Compute the moving average.
232
262
  moving_average_results: dict = {}
@@ -234,14 +264,20 @@ def compute_average_for_current_day_from_past_x_days(previous_days_content_list:
234
264
  ma_count = statistics.mean(host_dict['counts'])
235
265
  ma_request_size = statistics.mean(host_dict['avg_request_sizes'])
236
266
  ma_response_size = statistics.mean(host_dict['avg_response_sizes'])
267
+ mm_request_size = statistics.median(host_dict['median_request_sizes'])
268
+ mm_response_size = statistics.median(host_dict['median_response_sizes'])
237
269
 
238
270
  moving_average_results[host] = {
239
271
  'ma_count': ma_count,
240
272
  'ma_request_size': ma_request_size,
241
273
  'ma_response_size': ma_response_size,
274
+ 'mm_request_size': mm_request_size,
275
+ 'mm_response_size': mm_response_size,
242
276
  'counts': host_dict['counts'],
243
277
  'avg_request_sizes': host_dict['avg_request_sizes'],
244
- 'avg_response_sizes': host_dict['avg_response_sizes']
278
+ 'avg_response_sizes': host_dict['avg_response_sizes'],
279
+ 'median_request_sizes': host_dict['median_request_sizes'],
280
+ 'median_response_sizes': host_dict['median_response_sizes']
245
281
  }
246
282
 
247
283
  return moving_average_results
@@ -303,6 +339,10 @@ def find_deviation_from_moving_average(
303
339
  'ma_value_checked': check_type_moving_above,
304
340
  'deviation_percentage': deviation_percentage,
305
341
  'deviation_type': deviation_type,
342
+ 'median_request_size': day_statistics_content_dict['median_request_size'],
343
+ 'median_response_size': day_statistics_content_dict['median_response_size'],
344
+ 'mm_request_size': moving_averages_dict[host]['mm_request_size'],
345
+ 'mm_response_size': moving_averages_dict[host]['mm_response_size'],
306
346
  'data': day_statistics_content_dict,
307
347
  'ma_data': moving_averages_dict[host]
308
348
  })