atomicshop 3.3.28__py3-none-any.whl → 3.10.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 (99) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  3. atomicshop/a_mains/install_ca_certificate.py +172 -0
  4. atomicshop/a_mains/process_from_port.py +119 -0
  5. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  6. atomicshop/basics/strings.py +1 -1
  7. atomicshop/certificates.py +2 -2
  8. atomicshop/dns.py +26 -28
  9. atomicshop/etws/traces/trace_tcp.py +1 -2
  10. atomicshop/mitm/centered_settings.py +133 -0
  11. atomicshop/mitm/config_static.py +18 -43
  12. atomicshop/mitm/connection_thread_worker.py +376 -162
  13. atomicshop/mitm/engines/__parent/recorder___parent.py +1 -1
  14. atomicshop/mitm/engines/__parent/requester___parent.py +1 -1
  15. atomicshop/mitm/engines/__parent/responder___parent.py +15 -2
  16. atomicshop/mitm/engines/create_module_template.py +1 -2
  17. atomicshop/mitm/import_config.py +79 -88
  18. atomicshop/mitm/initialize_engines.py +1 -2
  19. atomicshop/mitm/message.py +5 -4
  20. atomicshop/mitm/mitm_main.py +222 -121
  21. atomicshop/mitm/recs_files.py +61 -5
  22. atomicshop/mitm/ssh_tester.py +82 -0
  23. atomicshop/networks.py +108 -93
  24. atomicshop/package_mains_processor.py +84 -0
  25. atomicshop/permissions/ubuntu_permissions.py +47 -0
  26. atomicshop/print_api.py +3 -5
  27. atomicshop/python_functions.py +23 -108
  28. atomicshop/speech_recognize.py +8 -0
  29. atomicshop/ssh_remote.py +115 -51
  30. atomicshop/web.py +20 -7
  31. atomicshop/web_apis/google_llm.py +22 -14
  32. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  33. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -1
  34. atomicshop/wrappers/dockerw/dockerw.py +2 -2
  35. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +5 -5
  36. atomicshop/wrappers/githubw.py +175 -63
  37. atomicshop/wrappers/loggingw/handlers.py +1 -1
  38. atomicshop/wrappers/loggingw/loggingw.py +17 -1
  39. atomicshop/wrappers/netshw.py +124 -3
  40. atomicshop/wrappers/playwrightw/scenarios.py +1 -1
  41. atomicshop/wrappers/powershell_networking.py +80 -0
  42. atomicshop/wrappers/psutilw/psutil_networks.py +9 -0
  43. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  44. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  45. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  46. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +12 -27
  47. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +15 -9
  48. atomicshop/wrappers/socketw/certificator.py +19 -9
  49. atomicshop/wrappers/socketw/creator.py +30 -7
  50. atomicshop/wrappers/socketw/dns_server.py +6 -6
  51. atomicshop/wrappers/socketw/exception_wrapper.py +3 -3
  52. atomicshop/wrappers/socketw/process_getter.py +86 -0
  53. atomicshop/wrappers/socketw/receiver.py +29 -9
  54. atomicshop/wrappers/socketw/sender.py +10 -9
  55. atomicshop/wrappers/socketw/sni.py +23 -6
  56. atomicshop/wrappers/socketw/{base.py → socket_base.py} +33 -1
  57. atomicshop/wrappers/socketw/socket_client.py +6 -8
  58. atomicshop/wrappers/socketw/socket_wrapper.py +82 -21
  59. atomicshop/wrappers/socketw/ssl_base.py +6 -2
  60. atomicshop/wrappers/win_auditw.py +189 -0
  61. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/METADATA +25 -30
  62. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/RECORD +74 -88
  63. atomicshop/_basics_temp.py +0 -101
  64. atomicshop/a_installs/ubuntu/docker_rootless.py +0 -11
  65. atomicshop/a_installs/ubuntu/docker_sudo.py +0 -11
  66. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  67. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  68. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  69. atomicshop/addons/package_setup/Setup.cmd +0 -7
  70. atomicshop/archiver/__init__.py +0 -0
  71. atomicshop/archiver/_search_in_zip.py +0 -189
  72. atomicshop/archiver/search_in_archive.py +0 -284
  73. atomicshop/archiver/sevenz_app_w.py +0 -86
  74. atomicshop/archiver/sevenzs.py +0 -73
  75. atomicshop/archiver/shutils.py +0 -34
  76. atomicshop/archiver/zips.py +0 -353
  77. atomicshop/file_types.py +0 -24
  78. atomicshop/pbtkmultifile_argparse.py +0 -88
  79. atomicshop/script_as_string_processor.py +0 -42
  80. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  81. atomicshop/ssh_scripts/process_from_port.py +0 -27
  82. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  83. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  84. atomicshop/wrappers/dockerw/install_docker.py +0 -449
  85. atomicshop/wrappers/ffmpegw.py +0 -125
  86. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  87. atomicshop/wrappers/socketw/get_process.py +0 -123
  88. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  89. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  90. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  91. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  92. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  93. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  94. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  95. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  96. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  97. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/WHEEL +0 -0
  98. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/licenses/LICENSE.txt +0 -0
  99. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/top_level.txt +0 -0
@@ -305,7 +305,7 @@ class DnsServer:
305
305
 
306
306
  # If the listening interface is not localhost, check if the interface can be bound to.
307
307
  if not self.listening_interface.startswith('127.'):
308
- host_ips: list[str] = networks.get_host_ips(ipv6=False)
308
+ host_ips: list[str] = networks.get_host_ips_psutil(ipv6=False)
309
309
  if self.listening_interface not in host_ips:
310
310
  message = (f"Listening interface [{self.listening_interface}] is not assigned to any of the host "
311
311
  f"network interfaces. Current host IPv4 addresses: {host_ips}")
@@ -478,7 +478,7 @@ class DnsServer:
478
478
  dns_cached_request = False
479
479
  # Check if the received data request from client is already in the cache
480
480
  if client_data in self.dns_questions_to_answers_cache:
481
- # message = "!!! Question / Answer is already in the dictionary..."
481
+ # message = "!!! Request / Response is already in the dictionary..."
482
482
  # self.logger.info(message)
483
483
 
484
484
  # Get the response from the cached answers list
@@ -561,7 +561,7 @@ class DnsServer:
561
561
  f'{self.offline_route_ipv6}')
562
562
  )
563
563
 
564
- message = f"!!! Question / Answer is in offline mode returning " \
564
+ message = f"!!! Request / Response is in offline mode returning " \
565
565
  f"{self.offline_route_ipv6}."
566
566
  self.logger.info(message)
567
567
 
@@ -577,7 +577,7 @@ class DnsServer:
577
577
  elif qtype_string == "SRV" or qtype_string == "SOA" or qtype_string == "HTTPS":
578
578
  dns_built_response.add_answer(*RR.fromZone(self.offline_srv_answer))
579
579
 
580
- message = f"!!! Question / Answer is in offline mode returning: " \
580
+ message = f"!!! Request / Response is in offline mode returning: " \
581
581
  f"{self.offline_srv_answer}."
582
582
  self.logger.info(message)
583
583
  elif qtype_string == "ANY":
@@ -586,7 +586,7 @@ class DnsServer:
586
586
  self.offline_route_domain)
587
587
  )
588
588
 
589
- message = f"!!! Question / Answer is in offline mode returning " \
589
+ message = f"!!! Request / Response is in offline mode returning " \
590
590
  f"{self.offline_route_domain}."
591
591
  self.logger.info(message)
592
592
  else:
@@ -596,7 +596,7 @@ class DnsServer:
596
596
  " " + self.offline_route_ipv4)
597
597
  )
598
598
 
599
- message = f"!!! Question / Answer is in offline mode returning " \
599
+ message = f"!!! Request / Response is in offline mode returning " \
600
600
  f"{self.offline_route_ipv4}."
601
601
  self.logger.info(message)
602
602
  # Values error means in most cases that you create wrong response
@@ -1,7 +1,7 @@
1
1
  import ssl
2
2
  import functools
3
3
 
4
- from . import base
4
+ from . import socket_base
5
5
  from ...print_api import print_api
6
6
  from ...inspect_wrapper import get_target_function_default_args_and_combine_with_current
7
7
 
@@ -80,14 +80,14 @@ def connection_exception_decorator(function_name):
80
80
  # Getting the exact reason of "ssl.SSLError"
81
81
  if exception_object.reason == "HTTP_REQUEST":
82
82
  message = f"Socket Accept: HTTP Request on SSL Socket: " \
83
- f"{base.get_source_destination(kwargs['socket_object'])}"
83
+ f"{socket_base.get_source_destination(kwargs['socket_object'])}"
84
84
  wrapper_handle_connection_exceptions.message = message
85
85
  print_api(message, logger_method='error', traceback_string=True, oneline=True, **kwargs['print_kwargs'])
86
86
 
87
87
  excepted = True
88
88
  elif exception_object.reason == "TSV1_ALERT_UNKNOWN_CA":
89
89
  message = f"Socket Accept: Check CA certificate on the client " \
90
- f"{base.get_source_destination(kwargs['socket_object'])}"
90
+ f"{socket_base.get_source_destination(kwargs['socket_object'])}"
91
91
  wrapper_handle_connection_exceptions.message = message
92
92
  print_api(message, logger_method='error', traceback_string=True, oneline=True, **kwargs['print_kwargs'])
93
93
 
@@ -0,0 +1,86 @@
1
+ import logging
2
+
3
+ from . import socket_base
4
+ from ...print_api import print_api
5
+ from ...ssh_remote import SSHRemote
6
+ from ... import package_mains_processor
7
+
8
+
9
+ GET_LOCALHOST_FUNCTION_NAME = 'find_cmdline_by_port'
10
+
11
+
12
+ class GetCommandLine:
13
+ def __init__(
14
+ self,
15
+ client_ip: str,
16
+ client_port: int = None,
17
+ package_processor: package_mains_processor.PackageMainsProcessor = None,
18
+ ssh_client: SSHRemote = None,
19
+ logger: logging.Logger = None
20
+ ):
21
+ self.client_ip: str = client_ip
22
+ self.client_port: int = client_port
23
+ self.package_processor: package_mains_processor.PackageMainsProcessor = package_processor
24
+ self.ssh_client: SSHRemote = ssh_client
25
+ self.logger: logging.Logger = logger
26
+
27
+ def get_process_name(self, print_kwargs: dict = None):
28
+ if print_kwargs is None:
29
+ print_kwargs = {}
30
+
31
+ # Checking if we're on localhost. If not, we'll execute SSH connection to get calling process name.
32
+ if self.client_ip not in socket_base.THIS_DEVICE_IP_LIST:
33
+ # Tried using paramiko SSH concurrently within threads, but with bigger loads it just breaks.
34
+ # So, better using it separately for each thread.
35
+
36
+ print_api(f"Initializing SSH connection to [{self.client_ip}]", **print_kwargs)
37
+
38
+ script_string: str = self.package_processor.read_script_file_to_string()
39
+
40
+ execution_output, execution_error = self.ssh_client.connect_get_client_commandline(port=self.client_port, script_string=script_string)
41
+ # Else, if we're on localhost, then execute the script directly without SSH.
42
+ else:
43
+ print_api(f"Executing LOCALHOST command to get the calling process.", **print_kwargs)
44
+ # execution_output, execution_error, rc = self.package_processor.execute_script_with_subprocess(arguments=[str(client_port)])
45
+ execution_output = self.package_processor.execute_script_file(
46
+ function_name=GET_LOCALHOST_FUNCTION_NAME, args=(self.client_port,))
47
+ execution_error = None
48
+
49
+ # This section is generic for both remote SSH and localhost executions of the script.
50
+ process_name = self.get_commandline_and_error(execution_output, execution_error, print_kwargs=print_kwargs)
51
+
52
+ return process_name
53
+
54
+ @staticmethod
55
+ def get_commandline_and_error(
56
+ execution_output,
57
+ execution_error,
58
+ print_kwargs: dict = None
59
+ ):
60
+ # If there was known error on localhost / known error on remote or any kind of error on remote, it was
61
+ # already logged, so we'll just put the error into 'process_name'.
62
+ if execution_error:
63
+ process_name = execution_error
64
+ print_api(
65
+ f"Error During Command Execution: {process_name}", error_type=True,
66
+ logger_method='error', **(print_kwargs or {}))
67
+ # If there wasn't any error of above types, then we can put the output from either local or remote script
68
+ # execution into 'process_name' and log it / output to console.
69
+ else:
70
+ # If the output that was returned is not empty.
71
+ if execution_output:
72
+ # Replacing '\r\n' escape lines with string, so that the line will not be escaped in logs.
73
+ if '\r\n' in execution_output:
74
+ execution_output = execution_output.replace('\r\n', '')
75
+ elif '\n' in execution_output:
76
+ execution_output = execution_output.replace('\n', '')
77
+
78
+ process_name = execution_output
79
+ print_api(f"Client Process Command Line: {process_name}", **(print_kwargs or {}))
80
+ # Else if the script output came back empty.
81
+ else:
82
+ process_name = ''
83
+ message = "Client Process Command Line came back empty after script execution."
84
+ print_api(message, error_type=True, logger_method='error', **(print_kwargs or {}))
85
+
86
+ return process_name
@@ -1,26 +1,44 @@
1
1
  import logging
2
2
  import socket
3
3
  import ssl
4
- import time
5
4
 
6
5
  import select
7
6
  from pathlib import Path
8
7
 
9
8
  from ...print_api import print_api
9
+ from ...basics import tracebacks
10
10
  from ..loggingw import loggingw
11
11
 
12
12
 
13
- def peek_first_bytes(client_socket, bytes_amount: int = 1) -> bytes:
13
+ def peek_first_bytes(
14
+ client_socket,
15
+ bytes_amount: int = 1,
16
+ timeout: float = None
17
+ ) -> bytes:
14
18
  """
15
19
  Peek first byte from the socket without removing it from the buffer.
16
20
 
17
21
  :param client_socket: Socket object.
18
22
  :param bytes_amount: Amount of bytes to peek.
23
+ :param timeout: float, Timeout in seconds.
19
24
 
20
25
  :return: the first X bytes from the socket buffer.
21
26
  """
22
27
 
23
- return client_socket.recv(bytes_amount, socket.MSG_PEEK)
28
+ error: bool = False
29
+ client_socket.settimeout(timeout)
30
+
31
+ try:
32
+ peek_a_bytes: bytes = client_socket.recv(bytes_amount, socket.MSG_PEEK)
33
+ except socket.timeout:
34
+ error = True
35
+ finally:
36
+ client_socket.settimeout(None)
37
+
38
+ if error:
39
+ raise TimeoutError
40
+
41
+ return peek_a_bytes
24
42
 
25
43
 
26
44
  def is_socket_ready_for_read(socket_instance, timeout: float = 0) -> bool:
@@ -93,14 +111,16 @@ class Receiver:
93
111
  # A signal to close connection will be empty bytes string: b''.
94
112
  received_data = self.ssl_socket.recv(self.buffer_size_receive)
95
113
  except ConnectionAbortedError:
96
- error_message = "ConnectionAbortedError: * Connection was aborted by the other side..."
97
- print_api(error_message, logger=self.logger, logger_method='critical', traceback_string=False)
114
+ error_message = "ConnectionAbortedError: Connection was aborted by local TCP stack (not remote close)..."
98
115
  except ConnectionResetError:
99
- error_message = "ConnectionResetError: * Connection was forcibly closed by the other side..."
100
- print_api(error_message, logger=self.logger, logger_method='critical', traceback_string=False)
116
+ error_message = "ConnectionResetError: Connection was forcibly closed by the other side..."
117
+ except TimeoutError as e:
118
+ if e.errno == 10060:
119
+ error_message = "TimeoutError: [WinError 10060] Socket receive operation timed out..."
120
+ else:
121
+ raise e
101
122
  except ssl.SSLError:
102
- error_message = "ssl.SSLError: * Encountered SSL error on receive..."
103
- print_api(error_message, logger=self.logger, logger_method='critical', traceback_string=True)
123
+ error_message = f"ssl.SSLError: Encountered SSL error on receive...\n{tracebacks.get_as_string()}"
104
124
 
105
125
  if received_data == b'':
106
126
  self.logger.info("Empty message received, socket closed on the other side.")
@@ -1,3 +1,4 @@
1
+ import socket
1
2
  import ssl
2
3
  import logging
3
4
  from pathlib import Path
@@ -6,18 +7,18 @@ from ...print_api import print_api
6
7
  from ..loggingw import loggingw
7
8
  from ...basics import tracebacks
8
9
 
9
- from . import base
10
+ from . import socket_base
10
11
 
11
12
 
12
13
  class Sender:
13
14
  def __init__(
14
15
  self,
15
- ssl_socket: ssl.SSLSocket,
16
- class_message: bytes,
16
+ ssl_socket: ssl.SSLSocket | socket.socket,
17
+ bytes_to_send: bytes,
17
18
  logger: logging.Logger = None
18
19
  ):
19
- self.class_message: bytes = class_message
20
- self.ssl_socket: ssl.SSLSocket = ssl_socket
20
+ self.bytes_to_send: bytes = bytes_to_send
21
+ self.ssl_socket: ssl.SSLSocket | socket.socket = ssl_socket
21
22
 
22
23
  if logger:
23
24
  # Create child logger for the provided logger with the module's name.
@@ -42,7 +43,7 @@ class Sender:
42
43
 
43
44
  try:
44
45
  # Getting byte length of current message
45
- current_message_length = len(self.class_message)
46
+ current_message_length = len(self.bytes_to_send)
46
47
 
47
48
  self.logger.info(
48
49
  f"Sending message to "
@@ -51,7 +52,7 @@ class Sender:
51
52
  # Looping through "socket.send()" method while total sent bytes are less than message length
52
53
  while total_sent_bytes < current_message_length:
53
54
  # Sending the message and getting the amount of bytes sent
54
- sent_bytes = self.ssl_socket.send(self.class_message[total_sent_bytes:])
55
+ sent_bytes = self.ssl_socket.send(self.bytes_to_send[total_sent_bytes:])
55
56
  # If there were only "0" bytes sent, then connection on the other side was terminated
56
57
  if sent_bytes == 0:
57
58
  error_message = (
@@ -61,13 +62,13 @@ class Sender:
61
62
  break
62
63
 
63
64
  # Adding amount of currently sent bytes to the total amount of bytes sent
64
- total_sent_bytes = total_sent_bytes + sent_bytes
65
+ total_sent_bytes += sent_bytes
65
66
  self.logger.info(f"Sent {total_sent_bytes} bytes out of {current_message_length}")
66
67
 
67
68
  # At this point the sending loop finished successfully
68
69
  self.logger.info(f"Sent the message to destination.")
69
70
  except Exception as e:
70
- source_tuple, destination_tuple = base.get_source_destination(self.ssl_socket)
71
+ source_tuple, destination_tuple = socket_base.get_source_destination(self.ssl_socket)
71
72
  source_address, source_port = source_tuple
72
73
  destination_address, destination_port = destination_tuple
73
74
  if self.ssl_socket.server_hostname:
@@ -47,7 +47,9 @@ class SNISetup:
47
47
  tls: bool,
48
48
  domain_from_dns_server: str = None,
49
49
  skip_extension_id_list: list = None,
50
- exceptions_logger: loggingw.ExceptionCsvLogger = None
50
+ exceptions_logger: loggingw.ExceptionCsvLogger = None,
51
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
52
+ sslkeylog_file_path: str = None
51
53
  ):
52
54
  self.ca_certificate_name = ca_certificate_name
53
55
  self.ca_certificate_filepath = ca_certificate_filepath
@@ -74,6 +76,8 @@ class SNISetup:
74
76
  self.tls = tls
75
77
  self.exceptions_logger = exceptions_logger
76
78
  self.certificator_instance = None
79
+ self.enable_sslkeylogfile_env_to_client_ssl_context: bool = enable_sslkeylogfile_env_to_client_ssl_context
80
+ self.sslkeylog_file_path: str = sslkeylog_file_path
77
81
 
78
82
  def wrap_socket_with_ssl_context_server_sni_extended(
79
83
  self,
@@ -82,7 +86,9 @@ class SNISetup:
82
86
  ):
83
87
 
84
88
  # Create SSL Socket to wrap the raw socket with.
85
- ssl_context: ssl.SSLContext = creator.create_ssl_context_for_server(True)
89
+ ssl_context: ssl.SSLContext = creator.create_ssl_context_for_server(
90
+ allow_legacy=True, enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
91
+ sslkeylog_file_path=self.sslkeylog_file_path)
86
92
 
87
93
  self.certificator_instance = certificator.Certificator(
88
94
  ca_certificate_name=self.ca_certificate_name,
@@ -101,7 +107,9 @@ class SNISetup:
101
107
  forwarding_dns_service_ipv4_list___only_for_localhost=(
102
108
  self.forwarding_dns_service_ipv4_list___only_for_localhost),
103
109
  skip_extension_id_list=self.skip_extension_id_list,
104
- tls=self.tls
110
+ tls=self.tls,
111
+ enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
112
+ sslkeylog_file_path=self.sslkeylog_file_path
105
113
  )
106
114
 
107
115
  # Add SNI callback function to the SSL context.
@@ -160,7 +168,10 @@ class SNISetup:
160
168
  certificator_instance=self.certificator_instance,
161
169
  domain_from_dns_server=self.domain_from_dns_server,
162
170
  default_certificate_domain_list=self.default_certificate_domain_list,
163
- exceptions_logger=self.exceptions_logger )
171
+ exceptions_logger=self.exceptions_logger,
172
+ enable_sslkeylogfile_env_to_client_ssl_context=(
173
+ self.certificator_instance.enable_sslkeylogfile_env_to_client_ssl_context),
174
+ sslkeylog_file_path=self.certificator_instance.sslkeylog_file_path)
164
175
  ssl_context.set_servername_callback(
165
176
  sni_handler_instance.setup_sni_callback(print_kwargs=print_kwargs))
166
177
 
@@ -178,7 +189,9 @@ class SNIHandler:
178
189
  certificator_instance: certificator.Certificator,
179
190
  domain_from_dns_server: str,
180
191
  default_certificate_domain_list: list,
181
- exceptions_logger: loggingw.ExceptionCsvLogger
192
+ exceptions_logger: loggingw.ExceptionCsvLogger,
193
+ enable_sslkeylogfile_env_to_client_ssl_context: bool,
194
+ sslkeylog_file_path: str
182
195
  ):
183
196
  self.sni_use_default_callback_function_extended = sni_use_default_callback_function_extended
184
197
  self.sni_add_new_domains_to_default_server_certificate = sni_add_new_domains_to_default_server_certificate
@@ -187,6 +200,8 @@ class SNIHandler:
187
200
  self.domain_from_dns_server: str = domain_from_dns_server
188
201
  self.default_certificate_domain_list = default_certificate_domain_list
189
202
  self.exceptions_logger = exceptions_logger
203
+ self.enable_sslkeylogfile_env_to_client_ssl_context: bool = enable_sslkeylogfile_env_to_client_ssl_context
204
+ self.sslkeylog_file_path: str = sslkeylog_file_path
190
205
 
191
206
  # noinspection PyTypeChecker
192
207
  self.sni_received_parameters: SNIReceivedParameters = None
@@ -325,7 +340,9 @@ class SNIHandler:
325
340
  creator.create_server_ssl_context___load_certificate_and_key(
326
341
  default_server_certificate_path,
327
342
  None,
328
- inherit_from=self.sni_received_parameters.ssl_socket.context
343
+ inherit_from=self.sni_received_parameters.ssl_socket.context,
344
+ enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
345
+ sslkeylog_file_path=self.sslkeylog_file_path
329
346
  )
330
347
  )
331
348
  else:
@@ -1,4 +1,5 @@
1
1
  import socket
2
+ import time
2
3
 
3
4
 
4
5
  LOCALHOST_IPV4: str = '127.0.0.1'
@@ -99,4 +100,35 @@ def get_host_name_from_ip_address(ip_address: str) -> str:
99
100
  host_name, alias_list, ipaddr_list = socket.gethostbyaddr(ip_address)
100
101
  _ = alias_list, ipaddr_list
101
102
 
102
- return host_name
103
+ return host_name
104
+
105
+
106
+ def wait_for_ip_bindable(
107
+ ip: str,
108
+ port: int = 0,
109
+ timeout: float = 15.0,
110
+ interval: float = 0.5,
111
+ ) -> None:
112
+ """
113
+ Wait until a single IP is bindable (or timeout).
114
+
115
+ Raises TimeoutError if the IP cannot be bound within 'timeout' seconds.
116
+ """
117
+ deadline = time.time() + timeout
118
+ last_err: OSError | None = None
119
+
120
+ while time.time() < deadline:
121
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
122
+ try:
123
+ s.bind((ip, port))
124
+ s.close()
125
+ return # success
126
+ except OSError as e:
127
+ last_err = e
128
+ s.close()
129
+ time.sleep(interval)
130
+
131
+ raise TimeoutError(
132
+ f"IP {ip} not bindable within {timeout} seconds; "
133
+ f"last error: {last_err}"
134
+ )
@@ -218,7 +218,6 @@ class SocketClient:
218
218
  self.socket_instance = None
219
219
  self.logger.info(f"Closed socket to service server [{self.service_name}:{self.service_port}]")
220
220
 
221
- # noinspection PyUnusedLocal
222
221
  def send_receive_to_service(
223
222
  self,
224
223
  request_bytes: Union[bytearray, bytes],
@@ -231,9 +230,8 @@ class SocketClient:
231
230
  :param skip_send: If True, the data will not be sent to the service server. After the connection is established,
232
231
  the function will wait for the response only.
233
232
  """
234
- # Define variables
235
- function_service_data = None
236
- error_message = None
233
+
234
+ origin_data: bytes | None = None
237
235
 
238
236
  service_socket, error_message = self.service_connection()
239
237
  # If connection to service server wasn't successful
@@ -257,7 +255,7 @@ class SocketClient:
257
255
  if not skip_send:
258
256
  # Send the data received from the client to the service over socket
259
257
  error_on_send = Sender(
260
- ssl_socket=self.socket_instance, class_message=request_bytes, logger=self.logger).send()
258
+ ssl_socket=self.socket_instance, bytes_to_send=request_bytes, logger=self.logger).send()
261
259
 
262
260
  # If the socket disconnected on data send
263
261
  if error_on_send:
@@ -268,17 +266,17 @@ class SocketClient:
268
266
 
269
267
  # Else if send was successful
270
268
  if not error_on_send:
271
- function_service_data = Receiver(
269
+ origin_data, is_socket_closed, error_message = Receiver(
272
270
  ssl_socket=self.socket_instance, logger=self.logger).receive()
273
271
 
274
272
  # If data received is empty meaning the socket was closed on the other side
275
- if not function_service_data:
273
+ if not origin_data:
276
274
  error_message = "Service server closed the connection on receive"
277
275
 
278
276
  # We'll close the socket and nullify the object
279
277
  self.close_socket()
280
278
 
281
- return function_service_data, error_message, self.connection_ip, self.socket_instance
279
+ return origin_data, error_message, self.connection_ip, self.socket_instance
282
280
 
283
281
  def send_receive_message_list_with_interval(
284
282
  self, requests_bytes_list: list, intervals_list: list, intervals_defaults: int, cycles: int = 1):