atomicshop 2.20.8__py3-none-any.whl → 2.21.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of atomicshop might be problematic. Click here for more details.
- atomicshop/__init__.py +1 -1
- atomicshop/mitm/config_static.py +27 -20
- atomicshop/mitm/connection_thread_worker.py +136 -164
- atomicshop/mitm/engines/__parent/responder___parent.py +43 -43
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +43 -43
- atomicshop/mitm/engines/create_module_template.py +11 -5
- atomicshop/mitm/engines/create_module_template_main_example.py +13 -0
- atomicshop/mitm/import_config.py +174 -22
- atomicshop/mitm/initialize_engines.py +49 -68
- atomicshop/mitm/mitm_main.py +118 -152
- atomicshop/wrappers/socketw/dns_server.py +92 -68
- atomicshop/wrappers/socketw/socket_client.py +1 -1
- atomicshop/wrappers/socketw/socket_wrapper.py +136 -46
- {atomicshop-2.20.8.dist-info → atomicshop-2.21.1.dist-info}/METADATA +1 -1
- {atomicshop-2.20.8.dist-info → atomicshop-2.21.1.dist-info}/RECORD +18 -18
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- {atomicshop-2.20.8.dist-info → atomicshop-2.21.1.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.20.8.dist-info → atomicshop-2.21.1.dist-info}/WHEEL +0 -0
- {atomicshop-2.20.8.dist-info → atomicshop-2.21.1.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
atomicshop/mitm/config_static.py
CHANGED
|
@@ -6,16 +6,15 @@ from . import import_config
|
|
|
6
6
|
|
|
7
7
|
# CONFIG = None
|
|
8
8
|
LIST_OF_BOOLEANS: list = [
|
|
9
|
+
('dnstcp', 'offline'),
|
|
9
10
|
('dns', 'enable'),
|
|
10
|
-
('dns', '
|
|
11
|
-
('dns', '
|
|
12
|
-
('dns', '
|
|
13
|
-
('dns', 'resolve_regular'),
|
|
11
|
+
('dns', 'resolve_by_engine'),
|
|
12
|
+
('dns', 'resolve_regular_pass_thru'),
|
|
13
|
+
('dns', 'resolve_all_domains_to_ipv4'),
|
|
14
14
|
('dns', 'set_default_dns_gateway_to_localhost'),
|
|
15
15
|
('dns', 'set_default_dns_gateway_to_default_interface_ipv4'),
|
|
16
16
|
('tcp', 'enable'),
|
|
17
|
-
('tcp', '
|
|
18
|
-
('tcp', 'server_response_mode'),
|
|
17
|
+
('tcp', 'no_engines_usage_to_listen_addresses'),
|
|
19
18
|
('logrec', 'enable_request_response_recordings_in_logs'),
|
|
20
19
|
('certificates', 'install_ca_certificate_to_root_store'),
|
|
21
20
|
('certificates', 'uninstall_unused_ca_certificates_with_mitm_ca_name'),
|
|
@@ -32,6 +31,7 @@ LIST_OF_BOOLEANS: list = [
|
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
TOML_TO_STATIC_CATEGORIES: dict = {
|
|
34
|
+
'dnstcp': 'MainConfig',
|
|
35
35
|
'dns': 'DNSServer',
|
|
36
36
|
'tcp': 'TCPServer',
|
|
37
37
|
'logrec': 'LogRec',
|
|
@@ -40,6 +40,10 @@ TOML_TO_STATIC_CATEGORIES: dict = {
|
|
|
40
40
|
'process_name': 'ProcessName'
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
# noinspection PyTypeChecker
|
|
44
|
+
ENGINES_LIST: list = None # list[initialize_engines.ModuleCategory]
|
|
45
|
+
REFERENCE_MODULE = None # initialize_engines.ModuleCategory
|
|
46
|
+
|
|
43
47
|
|
|
44
48
|
class MainConfig:
|
|
45
49
|
LOGGER_NAME: str = 'network'
|
|
@@ -61,6 +65,7 @@ class MainConfig:
|
|
|
61
65
|
# Default server certificate file name and path.
|
|
62
66
|
default_server_certificate_filename = f'{default_server_certificate_name}.pem'
|
|
63
67
|
default_server_certificate_filepath: str = None
|
|
68
|
+
offline: bool = False
|
|
64
69
|
|
|
65
70
|
@classmethod
|
|
66
71
|
def update(cls):
|
|
@@ -77,32 +82,34 @@ class DNSServer:
|
|
|
77
82
|
enable: bool
|
|
78
83
|
offline_mode: bool
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
listening_port: int
|
|
85
|
+
listening_address: str
|
|
82
86
|
forwarding_dns_service_ipv4: str
|
|
83
87
|
cache_timeout_minutes: int
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
resolve_by_engine: bool
|
|
90
|
+
resolve_regular_pass_thru: bool
|
|
91
|
+
resolve_all_domains_to_ipv4_enable: bool
|
|
92
|
+
target_ipv4: str
|
|
89
93
|
|
|
90
94
|
set_default_dns_gateway: str
|
|
91
95
|
set_default_dns_gateway_to_localhost: bool
|
|
92
96
|
set_default_dns_gateway_to_default_interface_ipv4: bool
|
|
93
97
|
|
|
98
|
+
# Convertable variables.
|
|
99
|
+
resolve_all_domains_to_ipv4: dict
|
|
100
|
+
|
|
101
|
+
# Static variables.
|
|
102
|
+
forwarding_dns_service_port: int = 53
|
|
103
|
+
|
|
94
104
|
|
|
95
105
|
@dataclass
|
|
96
106
|
class TCPServer:
|
|
97
107
|
enable: bool
|
|
108
|
+
no_engines_usage_to_listen_addresses_enable: bool
|
|
109
|
+
no_engines_listening_address_list: list[str]
|
|
98
110
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
listening_address_list: list[str]
|
|
103
|
-
|
|
104
|
-
forwarding_dns_service_ipv4_list___only_for_localhost: list[str]
|
|
105
|
-
|
|
111
|
+
# Convertable variables.
|
|
112
|
+
no_engines_usage_to_listen_addresses: dict
|
|
106
113
|
|
|
107
114
|
@dataclass
|
|
108
115
|
class LogRec:
|
|
@@ -161,7 +168,7 @@ def load_config(config_toml_file_path: str):
|
|
|
161
168
|
MainConfig.update()
|
|
162
169
|
|
|
163
170
|
# Load the configuration file.
|
|
164
|
-
result = import_config.
|
|
171
|
+
result = import_config.import_config_files(config_toml_file_path)
|
|
165
172
|
return result
|
|
166
173
|
|
|
167
174
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
import threading
|
|
3
3
|
import queue
|
|
4
|
-
import copy
|
|
5
4
|
import socket
|
|
6
5
|
|
|
7
6
|
from ..wrappers.socketw import receiver, sender, socket_client, base
|
|
@@ -182,30 +181,33 @@ def thread_worker_main(
|
|
|
182
181
|
network_logger.info("Thread Finished. Will continue listening on the Main thread")
|
|
183
182
|
|
|
184
183
|
def create_responder_response(client_message: ClientMessage) -> list[bytes]:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
# If it's the first cycle and the protocol is Websocket, then we'll create the HTTP Handshake
|
|
189
|
-
# response automatically.
|
|
190
|
-
if protocol == 'Websocket' and client_receive_count == 1:
|
|
191
|
-
responses: list = list()
|
|
192
|
-
responses.append(
|
|
193
|
-
websocket_parse.create_byte_http_response(client_message.request_raw_bytes))
|
|
184
|
+
if client_message.action == 'service_connect':
|
|
185
|
+
return responder.create_connect_response(client_message)
|
|
194
186
|
else:
|
|
195
|
-
#
|
|
196
|
-
|
|
187
|
+
# Since we're in response mode, we'll record the request anyway, after the responder did its job.
|
|
188
|
+
# client_message.info = "In Server Response Mode"
|
|
189
|
+
|
|
190
|
+
# If it's the first cycle and the protocol is Websocket, then we'll create the HTTP Handshake
|
|
191
|
+
# response automatically.
|
|
192
|
+
if protocol == 'Websocket' and client_receive_count == 1:
|
|
193
|
+
responses: list = list()
|
|
194
|
+
responses.append(
|
|
195
|
+
websocket_parse.create_byte_http_response(client_message.request_raw_bytes))
|
|
196
|
+
else:
|
|
197
|
+
# Creating response for parsed message and printing
|
|
198
|
+
responses: list = responder.create_response(client_message)
|
|
197
199
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
# Output first 100 characters of all the responses in the list.
|
|
201
|
+
for response_raw_bytes_single in responses:
|
|
202
|
+
responder.logger.info(f"{response_raw_bytes_single[0: 100]}...")
|
|
201
203
|
|
|
202
|
-
|
|
204
|
+
return responses
|
|
203
205
|
|
|
204
206
|
def create_client_socket(client_message: ClientMessage):
|
|
205
207
|
# If there is a custom certificate for the client for this domain, then we'll use it.
|
|
206
208
|
# noinspection PyTypeChecker
|
|
207
209
|
custom_client_pem_certificate_path: str = None
|
|
208
|
-
for subdomain, pem_file_path in
|
|
210
|
+
for subdomain, pem_file_path in found_domain_module.mtls.items():
|
|
209
211
|
if subdomain == client_message.server_name:
|
|
210
212
|
custom_client_pem_certificate_path = pem_file_path
|
|
211
213
|
break
|
|
@@ -217,8 +219,7 @@ def thread_worker_main(
|
|
|
217
219
|
service_name=client_message.server_name,
|
|
218
220
|
service_port=client_message.destination_port,
|
|
219
221
|
tls=is_tls,
|
|
220
|
-
dns_servers_list=
|
|
221
|
-
config_static.TCPServer.forwarding_dns_service_ipv4_list___only_for_localhost),
|
|
222
|
+
dns_servers_list=[config_static.DNSServer.forwarding_dns_service_ipv4],
|
|
222
223
|
logger=network_logger,
|
|
223
224
|
custom_pem_client_certificate_file_path=custom_client_pem_certificate_path,
|
|
224
225
|
enable_sslkeylogfile_env_to_client_ssl_context=(
|
|
@@ -304,53 +305,11 @@ def thread_worker_main(
|
|
|
304
305
|
|
|
305
306
|
return client_message
|
|
306
307
|
|
|
307
|
-
def responder_thread_worker():
|
|
308
|
-
nonlocal exception_or_close_in_receiving_thread
|
|
309
|
-
|
|
310
|
-
client_message: ClientMessage = client_message_first_start()
|
|
311
|
-
try:
|
|
312
|
-
while True:
|
|
313
|
-
client_message = responder_queue.get()
|
|
314
|
-
print_api(f"Got message from queue, action: [{client_message.action}]", logger=network_logger, logger_method='info')
|
|
315
|
-
|
|
316
|
-
# If the message is not a ClientMessage object, then we'll break the loop, since it is the exit signal.
|
|
317
|
-
if not isinstance(client_message, ClientMessage):
|
|
318
|
-
return
|
|
319
|
-
|
|
320
|
-
if client_message.action == 'service_connect':
|
|
321
|
-
raw_responses: list[bytes] = responder.create_connect_response(client_message)
|
|
322
|
-
else:
|
|
323
|
-
raw_responses: list[bytes] = create_responder_response(client_message)
|
|
324
|
-
print_api(f"Got responses from responder, count: [{len(raw_responses)}]", logger=network_logger, logger_method='info')
|
|
325
|
-
|
|
326
|
-
is_socket_closed: bool = False
|
|
327
|
-
for response_raw_bytes in raw_responses:
|
|
328
|
-
client_message.reinitialize_dynamic_vars()
|
|
329
|
-
client_message.timestamp = datetime.now()
|
|
330
|
-
client_message.response_raw_bytes = response_raw_bytes
|
|
331
|
-
|
|
332
|
-
record_and_statistics_write(client_message)
|
|
333
|
-
|
|
334
|
-
error_on_send: str = sender.Sender(
|
|
335
|
-
ssl_socket=client_socket, class_message=client_message.response_raw_bytes,
|
|
336
|
-
logger=network_logger).send()
|
|
337
|
-
|
|
338
|
-
# If there was problem with sending data, we'll break the main while loop.
|
|
339
|
-
if error_on_send:
|
|
340
|
-
client_message.errors.append(error_on_send)
|
|
341
|
-
record_and_statistics_write(client_message)
|
|
342
|
-
is_socket_closed = True
|
|
343
|
-
|
|
344
|
-
if is_socket_closed:
|
|
345
|
-
exception_or_close_in_receiving_thread = True
|
|
346
|
-
return
|
|
347
|
-
except Exception as exc:
|
|
348
|
-
handle_exceptions_on_sub_connection_thread(client_message, client_exception_queue, exc)
|
|
349
|
-
|
|
350
308
|
def receive_send_start(
|
|
351
309
|
receiving_socket,
|
|
352
310
|
sending_socket = None,
|
|
353
|
-
exception_queue: queue.Queue = None
|
|
311
|
+
exception_queue: queue.Queue = None,
|
|
312
|
+
client_connection_message: ClientMessage = None
|
|
354
313
|
):
|
|
355
314
|
nonlocal client_receive_count
|
|
356
315
|
nonlocal server_receive_count
|
|
@@ -371,59 +330,87 @@ def thread_worker_main(
|
|
|
371
330
|
raise ValueError(f"Unknown side of the socket: {receiving_socket}")
|
|
372
331
|
|
|
373
332
|
while True:
|
|
374
|
-
|
|
333
|
+
# pass the socket connect to responder.
|
|
334
|
+
if side == 'Service' and client_connection_message:
|
|
335
|
+
client_message = client_connection_message
|
|
375
336
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
337
|
+
bytes_to_send_list: list[bytes] = create_responder_response(client_message)
|
|
338
|
+
print_api(f"Got responses from responder, count: [{len(bytes_to_send_list)}]", logger=network_logger,
|
|
339
|
+
logger_method='info')
|
|
379
340
|
else:
|
|
380
|
-
|
|
381
|
-
current_count = server_receive_count
|
|
341
|
+
client_message.reinitialize_dynamic_vars()
|
|
382
342
|
|
|
383
|
-
|
|
384
|
-
|
|
343
|
+
if side == 'Client':
|
|
344
|
+
client_receive_count += 1
|
|
345
|
+
current_count = client_receive_count
|
|
346
|
+
else:
|
|
347
|
+
server_receive_count += 1
|
|
348
|
+
current_count = server_receive_count
|
|
385
349
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
ssl_socket=receiving_socket, logger=network_logger).receive()
|
|
350
|
+
network_logger.info(
|
|
351
|
+
f"Initializing Receiver for {side} cycle: {str(current_count)}")
|
|
389
352
|
|
|
390
|
-
|
|
391
|
-
|
|
353
|
+
# Getting message from the client over the socket using specific class.
|
|
354
|
+
received_raw_data, is_socket_closed, error_message = receiver.Receiver(
|
|
355
|
+
ssl_socket=receiving_socket, logger=network_logger).receive()
|
|
392
356
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
process_client_raw_data(received_raw_data, error_message, client_message)
|
|
396
|
-
client_message.action = 'client_receive'
|
|
397
|
-
# In case of service socket, we'll process the raw data specifically for the service.
|
|
398
|
-
else:
|
|
399
|
-
process_server_raw_data(received_raw_data, error_message, client_message)
|
|
400
|
-
client_message.action = 'service_receive'
|
|
357
|
+
# Getting current time of message received, either from client or service.
|
|
358
|
+
client_message.timestamp = datetime.now()
|
|
401
359
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
360
|
+
# In case of client socket, we'll process the raw data specifically for the client.
|
|
361
|
+
if side == 'Client':
|
|
362
|
+
process_client_raw_data(received_raw_data, error_message, client_message)
|
|
363
|
+
client_message.action = 'client_receive'
|
|
364
|
+
# In case of service socket, we'll process the raw data specifically for the service.
|
|
365
|
+
else:
|
|
366
|
+
process_server_raw_data(received_raw_data, error_message, client_message)
|
|
367
|
+
client_message.action = 'service_receive'
|
|
368
|
+
|
|
369
|
+
# If there was an exception in the service thread, then receiving empty bytes doesn't mean that
|
|
370
|
+
# the socket was closed by the other side, it means that the service thread closed the socket.
|
|
371
|
+
if (received_raw_data == b'' or error_message) and exception_or_close_in_receiving_thread:
|
|
372
|
+
print_api("Both sockets are closed, breaking the loop", logger=network_logger,
|
|
373
|
+
logger_method='info')
|
|
374
|
+
return
|
|
408
375
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
376
|
+
# We will record only if there was no closing signal, because if there was, it means that we initiated
|
|
377
|
+
# the close on the opposite socket.
|
|
378
|
+
record_and_statistics_write(client_message)
|
|
412
379
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
380
|
+
# Now send it to requester/responder.
|
|
381
|
+
if side == 'Client':
|
|
382
|
+
# Send to requester.
|
|
383
|
+
bytes_to_send_list: list[bytes] = [client_message.request_raw_bytes]
|
|
384
|
+
else:
|
|
385
|
+
bytes_to_send_list: list[bytes] = create_responder_response(client_message)
|
|
386
|
+
print_api(f"Got responses from responder, count: [{len(bytes_to_send_list)}]",
|
|
387
|
+
logger=network_logger,
|
|
388
|
+
logger_method='info')
|
|
389
|
+
|
|
390
|
+
if is_socket_closed:
|
|
391
|
+
exception_or_close_in_receiving_thread = True
|
|
392
|
+
finish_thread()
|
|
393
|
+
return
|
|
417
394
|
|
|
418
|
-
# If we'
|
|
419
|
-
if
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
395
|
+
# If nothing was passed from the responder, and the client message is the connection message, then we'll skip to the next iteration.
|
|
396
|
+
if not bytes_to_send_list and client_connection_message:
|
|
397
|
+
client_connection_message = None
|
|
398
|
+
continue
|
|
399
|
+
|
|
400
|
+
is_socket_closed: bool = False
|
|
401
|
+
for bytes_to_send_single in bytes_to_send_list:
|
|
424
402
|
client_message.reinitialize_dynamic_vars()
|
|
403
|
+
client_message.timestamp = datetime.now()
|
|
404
|
+
|
|
405
|
+
if side == 'Client':
|
|
406
|
+
client_message.request_raw_bytes = bytes_to_send_single
|
|
407
|
+
else:
|
|
408
|
+
client_message.response_raw_bytes = bytes_to_send_single
|
|
409
|
+
|
|
410
|
+
record_and_statistics_write(client_message)
|
|
411
|
+
|
|
425
412
|
error_on_send: str = sender.Sender(
|
|
426
|
-
ssl_socket=sending_socket, class_message=
|
|
413
|
+
ssl_socket=sending_socket, class_message=bytes_to_send_single,
|
|
427
414
|
logger=network_logger).send()
|
|
428
415
|
|
|
429
416
|
if error_on_send:
|
|
@@ -440,9 +427,11 @@ def thread_worker_main(
|
|
|
440
427
|
# If the socket was closed on message receive, then we'll break the loop only after send.
|
|
441
428
|
if is_socket_closed or error_on_send:
|
|
442
429
|
exception_or_close_in_receiving_thread = True
|
|
443
|
-
responder_queue.put('exit')
|
|
444
430
|
finish_thread()
|
|
445
431
|
return
|
|
432
|
+
|
|
433
|
+
# For next iteration to start in case this iteration was responsible to process connection message, we need to set it to None.
|
|
434
|
+
client_connection_message = None
|
|
446
435
|
except Exception as exc:
|
|
447
436
|
# If the sockets were already closed, then there is nothing to do here besides log.
|
|
448
437
|
# if (isinstance(exc, OSError) and exc.errno == 10038 and
|
|
@@ -471,7 +460,6 @@ def thread_worker_main(
|
|
|
471
460
|
# record_and_statistics_write(client_message)
|
|
472
461
|
|
|
473
462
|
finish_thread()
|
|
474
|
-
responder_queue.put('exit')
|
|
475
463
|
exception_queue.put(exc)
|
|
476
464
|
|
|
477
465
|
def handle_exceptions_on_main_connection_thread(
|
|
@@ -517,23 +505,26 @@ def thread_worker_main(
|
|
|
517
505
|
|
|
518
506
|
# Loading parser by domain, if there is no parser for current domain - general reference parser is loaded.
|
|
519
507
|
# These should be outside any loop and initialized only once entering the thread.
|
|
520
|
-
|
|
521
|
-
engines_usage=config_static.TCPServer.engines_usage,
|
|
508
|
+
found_domain_module = assign_class_by_domain(
|
|
522
509
|
engines_list=engines_list,
|
|
523
510
|
message_domain_name=server_name,
|
|
524
|
-
reference_module=reference_module
|
|
525
|
-
logger=network_logger
|
|
511
|
+
reference_module=reference_module
|
|
526
512
|
)
|
|
513
|
+
parser = found_domain_module.parser_class_object
|
|
514
|
+
responder = found_domain_module.responder_class_object()
|
|
515
|
+
recorder = found_domain_module.recorder_class_object(record_path=config_static.LogRec.recordings_path)
|
|
516
|
+
|
|
517
|
+
network_logger.info(f"Assigned Modules for [{server_name}]: "
|
|
518
|
+
f"{parser.__name__}, "
|
|
519
|
+
f"{responder.__class__.__name__}, "
|
|
520
|
+
f"{recorder.__class__.__name__}")
|
|
527
521
|
|
|
528
|
-
recorder = recorder_no_init(record_path=config_static.LogRec.recordings_path)
|
|
529
522
|
|
|
530
523
|
# Initializing the client message object with current thread's data.
|
|
531
524
|
# This is needed only to skip error alerts after 'try'.
|
|
532
525
|
client_message_connection: ClientMessage = ClientMessage()
|
|
533
526
|
# This is needed to indicate if there was an exception or socket was closed in any of the receiving thread.
|
|
534
527
|
exception_or_close_in_receiving_thread: bool = False
|
|
535
|
-
# Responder queue for ClientMessage objects.
|
|
536
|
-
responder_queue: queue.Queue = queue.Queue()
|
|
537
528
|
# Queue for http request URI paths.
|
|
538
529
|
http_path_queue: queue.Queue = queue.Queue()
|
|
539
530
|
|
|
@@ -543,86 +534,67 @@ def thread_worker_main(
|
|
|
543
534
|
client_name = socket.gethostbyaddr(client_ip)[0]
|
|
544
535
|
destination_port = client_socket.getsockname()[1]
|
|
545
536
|
|
|
546
|
-
if config_static.
|
|
547
|
-
# If in
|
|
537
|
+
if config_static.MainConfig.offline:
|
|
538
|
+
# If in offline mode, then we'll get the TCP server's input address.
|
|
548
539
|
server_ip = client_socket.getsockname()[0]
|
|
549
540
|
else:
|
|
550
|
-
# If not in
|
|
541
|
+
# If not in offline mode, we will get the ip from the socket that will connect later to the service.
|
|
551
542
|
server_ip = ""
|
|
552
543
|
|
|
553
544
|
network_logger.info(f"Thread Created - Client [{client_ip}:{source_port}] | "
|
|
554
545
|
f"Destination service: [{server_name}:{destination_port}]")
|
|
555
546
|
|
|
556
|
-
# If we're in response mode, we'll start the responder thread.
|
|
557
|
-
if config_static.TCPServer.server_response_mode:
|
|
558
|
-
responder_thread: threading.Thread = threading.Thread(
|
|
559
|
-
target=responder_thread_worker, name=f"Thread-{thread_id}-Responder", daemon=True)
|
|
560
|
-
responder_thread.start()
|
|
561
|
-
else:
|
|
562
|
-
# noinspection PyTypeChecker
|
|
563
|
-
responder_thread = None
|
|
564
|
-
|
|
565
547
|
service_client = None
|
|
566
548
|
client_receive_count: int = 0
|
|
567
549
|
server_receive_count: int = 0
|
|
568
550
|
client_message_connection = client_message_first_start()
|
|
569
551
|
|
|
570
|
-
# If we're not in
|
|
552
|
+
# If we're not in offline mode, then we'll create the client socket to the service.
|
|
571
553
|
# noinspection PyTypeChecker
|
|
572
554
|
connection_error: str = None
|
|
573
555
|
service_socket_instance = None
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
service_socket_instance, connection_error = service_client.service_connection()
|
|
580
|
-
# Now we'll update the server IP with the IP of the service.
|
|
581
|
-
server_ip = service_socket_instance.getpeername()[0]
|
|
582
|
-
client_message_connection.server_ip = server_ip
|
|
583
|
-
else:
|
|
584
|
-
client_message_connection.timestamp = datetime.now()
|
|
585
|
-
client_message_connection.action = 'service_connect'
|
|
586
|
-
client_message_connection.info = 'Server Response Mode'
|
|
587
|
-
responder_queue.put(copy.deepcopy(client_message_connection))
|
|
588
|
-
|
|
589
|
-
if connection_error:
|
|
590
|
-
client_message_connection.timestamp = datetime.now()
|
|
591
|
-
client_message_connection.errors.append(connection_error)
|
|
592
|
-
client_message_connection.action = 'service_connect'
|
|
593
|
-
record_and_statistics_write(client_message_connection)
|
|
556
|
+
client_message_connection.action = 'service_connect'
|
|
557
|
+
client_message_connection.timestamp = datetime.now()
|
|
558
|
+
|
|
559
|
+
if config_static.MainConfig.offline:
|
|
560
|
+
client_message_connection.info = 'Offline Mode'
|
|
594
561
|
else:
|
|
562
|
+
service_client = create_client_socket(client_message_connection)
|
|
563
|
+
service_socket_instance, connection_error = service_client.service_connection()
|
|
564
|
+
# Now we'll update the server IP with the IP of the service.
|
|
565
|
+
server_ip = service_socket_instance.getpeername()[0]
|
|
566
|
+
client_message_connection.server_ip = server_ip
|
|
567
|
+
|
|
568
|
+
if connection_error:
|
|
569
|
+
client_message_connection.errors.append(connection_error)
|
|
570
|
+
record_and_statistics_write(client_message_connection)
|
|
571
|
+
|
|
572
|
+
if not connection_error:
|
|
595
573
|
client_exception_queue: queue.Queue = queue.Queue()
|
|
596
574
|
client_thread = threading.Thread(
|
|
597
|
-
target=receive_send_start,
|
|
575
|
+
target=receive_send_start,
|
|
576
|
+
args=(client_socket, service_socket_instance, client_exception_queue, None),
|
|
598
577
|
name=f"Thread-{thread_id}-Client")
|
|
599
578
|
client_thread.daemon = True
|
|
600
579
|
client_thread.start()
|
|
601
580
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
581
|
+
service_exception_queue: queue.Queue = queue.Queue()
|
|
582
|
+
service_thread = threading.Thread(
|
|
583
|
+
target=receive_send_start,
|
|
584
|
+
args=(service_socket_instance, client_socket, service_exception_queue, client_message_connection),
|
|
585
|
+
name=f"Thread-{thread_id}-Service")
|
|
586
|
+
service_thread.daemon = True
|
|
587
|
+
service_thread.start()
|
|
609
588
|
|
|
610
589
|
client_thread.join()
|
|
611
|
-
|
|
612
|
-
responder_thread.join()
|
|
613
|
-
else:
|
|
614
|
-
service_thread.join()
|
|
590
|
+
service_thread.join()
|
|
615
591
|
|
|
616
592
|
# If there was an exception in any of the threads, then we'll raise it here.
|
|
617
593
|
if not client_exception_queue.empty():
|
|
618
594
|
raise client_exception_queue.get()
|
|
619
|
-
if not
|
|
620
|
-
|
|
621
|
-
raise service_exception_queue.get()
|
|
595
|
+
if not service_exception_queue.empty():
|
|
596
|
+
raise service_exception_queue.get()
|
|
622
597
|
|
|
623
598
|
finish_thread()
|
|
624
599
|
except Exception as e:
|
|
625
|
-
if not client_message_connection.timestamp:
|
|
626
|
-
client_message_connection.timestamp = datetime.now()
|
|
627
|
-
|
|
628
600
|
handle_exceptions_on_main_connection_thread(e, client_message_connection)
|
|
@@ -110,48 +110,49 @@ class ResponderParent:
|
|
|
110
110
|
headers: dict,
|
|
111
111
|
body: bytes
|
|
112
112
|
) -> bytes:
|
|
113
|
+
# noinspection GrazieInspection
|
|
113
114
|
"""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
115
|
+
Create genuine response from input parameters.
|
|
116
|
+
---------------
|
|
117
|
+
The response is built from:
|
|
118
|
+
HTTP-Version HTTP-Status HTTP-Status-String\r\n
|
|
119
|
+
Headers1: Value\r\n
|
|
120
|
+
Headers2: Value\r\n
|
|
121
|
+
\r\n # This is meant to end the headers' section
|
|
122
|
+
Body\r\n\r\n # In most cases Body is ended with '\r\n\r\n'
|
|
123
|
+
---------------
|
|
124
|
+
Example for 200 response:
|
|
125
|
+
HTTP/1.1 200 OK\r\n
|
|
126
|
+
Cache-Control: max-age=86400\r\n
|
|
127
|
+
Content-Type: application/json; charset=utf-8\r\n
|
|
128
|
+
\r\n
|
|
129
|
+
{"id":1,"name":"something"}
|
|
130
|
+
---------------
|
|
131
|
+
The final response will look like oneline string:
|
|
132
|
+
HTTP/1.1 200 OK\r\nCache-Control: max-age=86400\r\n
|
|
133
|
+
Content-Type: application/json; charset=utf-8\r\n\r\n{"id":1,"name":"something"}
|
|
134
|
+
---------------
|
|
135
|
+
You can create response as:
|
|
136
|
+
|
|
137
|
+
...HTTP/1.1 200 OK
|
|
138
|
+
header1: value
|
|
139
|
+
header2: value
|
|
140
|
+
|
|
141
|
+
{data: value}...
|
|
142
|
+
|
|
143
|
+
Change 3 dots ("...") to 3 double quotes before "HTTP" and after "value}".
|
|
144
|
+
This way there will be "\n" added automatically after each line.
|
|
145
|
+
While, the HTTP Client does the parsing of the text and not raw data, most probably it will be parsed well,
|
|
146
|
+
but genuine responses from HTTP sources come with "\r\n" at the end of the line, so better use these for
|
|
147
|
+
better compatibility.
|
|
148
|
+
---------------
|
|
149
|
+
|
|
150
|
+
:param http_version: HTTP Version of Response in HTTP Status line.
|
|
151
|
+
:param status_code: HTTP Status Code of Response in HTTP Status line.
|
|
152
|
+
:param headers: HTTP Headers of Response.
|
|
153
|
+
:param body: HTTP body data of Response, bytes.
|
|
154
|
+
:return: bytes of the response.
|
|
155
|
+
"""
|
|
155
156
|
|
|
156
157
|
try:
|
|
157
158
|
# Building full status string line and the "\r\n" to the end of the status line
|
|
@@ -201,6 +202,5 @@ class ResponderParent:
|
|
|
201
202
|
def create_response(self, class_client_message: ClientMessage):
|
|
202
203
|
""" This function should be overridden in the child class. """
|
|
203
204
|
|
|
204
|
-
|
|
205
|
-
response_bytes_list: list[bytes] = list()
|
|
205
|
+
response_bytes_list: list[bytes] = [class_client_message.response_raw_bytes]
|
|
206
206
|
return response_bytes_list
|