atomicshop 2.15.11__py3-none-any.whl → 3.10.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/strings.py +72 -3
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -3
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +11 -6
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +1 -1
- atomicshop/diff_check.py +3 -3
- atomicshop/dns.py +128 -3
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/trace.py +40 -42
- atomicshop/etws/traces/trace_dns.py +56 -44
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +27 -5
- atomicshop/file_io/docxs.py +34 -17
- atomicshop/file_io/file_io.py +31 -17
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +616 -291
- atomicshop/get_process_list.py +3 -3
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -80
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +136 -40
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
- atomicshop/monitor/checks/dns.py +1 -1
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +39 -9
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -42
- atomicshop/process.py +24 -6
- atomicshop/process_poller/process_pool.py +0 -1
- atomicshop/process_poller/simple_process_pool.py +204 -5
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/system_resource_monitor.py +61 -47
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/urls.py +6 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +98 -27
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/githubw.py +537 -54
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/filters.py +23 -0
- atomicshop/wrappers/loggingw/formatters.py +12 -0
- atomicshop/wrappers/loggingw/handlers.py +214 -107
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +860 -22
- atomicshop/wrappers/loggingw/reading.py +134 -112
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +37 -1
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +491 -182
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +1 -1
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/pycharm.py +0 -9
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
- atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/config_editor.py +0 -37
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -268
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -151
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/install.py +0 -81
- atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
- atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
- atomicshop/wrappers/msiw.py +0 -149
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/psutilw/networks.py +0 -45
- atomicshop/wrappers/pycharmw.py +0 -81
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.15.11.dist-info/RECORD +0 -302
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- /atomicshop/{archiver → permissions}/__init__.py +0 -0
- /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
- /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
|
@@ -3,12 +3,21 @@ import datetime
|
|
|
3
3
|
import time
|
|
4
4
|
import threading
|
|
5
5
|
import socket
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Literal, Optional
|
|
9
|
+
import multiprocessing
|
|
6
10
|
|
|
7
11
|
from ...print_api import print_api
|
|
8
12
|
from ..loggingw import loggingw
|
|
9
|
-
from ..psutilw import
|
|
13
|
+
from ..psutilw import psutil_networks
|
|
14
|
+
from ...basics import booleans, tracebacks
|
|
15
|
+
from ...file_io import csvs
|
|
16
|
+
from ... import networks
|
|
10
17
|
|
|
18
|
+
# noinspection PyPackageRequirements
|
|
11
19
|
import dnslib
|
|
20
|
+
# noinspection PyPackageRequirements
|
|
12
21
|
from dnslib import DNSRecord, DNSHeader, RR, A
|
|
13
22
|
|
|
14
23
|
|
|
@@ -16,42 +25,229 @@ class DnsPortInUseError(Exception):
|
|
|
16
25
|
pass
|
|
17
26
|
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
class DnsConfigurationValuesError(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
LOGGER_NAME: str = 'dns_traffic'
|
|
33
|
+
DNS_STATISTICS_HEADER: str = (
|
|
34
|
+
'timestamp,dns_type,client_ipv4,client_port,qname,qtype,qclass,header,error')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DnsStatisticsCSVWriter:
|
|
38
|
+
"""
|
|
39
|
+
Class to write statistics to CSV file.
|
|
40
|
+
This can be initiated at the main, and then passed to the thread worker function.
|
|
41
|
+
"""
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
statistics_directory_path: str
|
|
45
|
+
):
|
|
46
|
+
self.csv_logger = loggingw.create_logger(
|
|
47
|
+
logger_name=LOGGER_NAME,
|
|
48
|
+
directory_path=statistics_directory_path,
|
|
49
|
+
add_timedfile_with_internal_queue=True,
|
|
50
|
+
formatter_filehandler='MESSAGE',
|
|
51
|
+
file_type='csv',
|
|
52
|
+
header=DNS_STATISTICS_HEADER
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def write_row(
|
|
56
|
+
self,
|
|
57
|
+
client_address: tuple,
|
|
58
|
+
timestamp=None,
|
|
59
|
+
dns_type: Literal['request', 'response'] = None,
|
|
60
|
+
dns_request = None,
|
|
61
|
+
dns_response = None,
|
|
62
|
+
error: str = None,
|
|
63
|
+
):
|
|
64
|
+
if not timestamp:
|
|
65
|
+
timestamp = datetime.datetime.now()
|
|
66
|
+
|
|
67
|
+
if not dns_type:
|
|
68
|
+
if not dns_request and not dns_response:
|
|
69
|
+
raise ValueError("Either DNS Request or DNS Response must be provided.")
|
|
70
|
+
elif dns_request and dns_response:
|
|
71
|
+
raise ValueError("Either DNS Request or DNS Response must be provided. Not both.")
|
|
72
|
+
|
|
73
|
+
if dns_request:
|
|
74
|
+
dns_type = 'request'
|
|
75
|
+
elif dns_response:
|
|
76
|
+
dns_type = 'response'
|
|
77
|
+
|
|
78
|
+
if dns_type not in ['request', 'response']:
|
|
79
|
+
raise ValueError(f"DNS Type can be only 'request' or 'response'. Provided: {dns_type}")
|
|
80
|
+
|
|
81
|
+
client_ipv4, client_port = client_address
|
|
82
|
+
client_ipv4: str
|
|
83
|
+
client_port: str = str(client_port)
|
|
84
|
+
|
|
85
|
+
qname: str = str()
|
|
86
|
+
qtype: str = str()
|
|
87
|
+
qclass: str = str()
|
|
88
|
+
rr: str = str()
|
|
89
|
+
header: str = str()
|
|
90
|
+
|
|
91
|
+
if dns_request:
|
|
92
|
+
qname = str(dns_request.q.qname)[:-1]
|
|
93
|
+
qtype = dnslib.QTYPE[dns_request.q.qtype]
|
|
94
|
+
qclass = dnslib.CLASS[dns_request.q.qclass]
|
|
95
|
+
|
|
96
|
+
if dns_response:
|
|
97
|
+
qname = str(dns_response.q.qname)[:-1]
|
|
98
|
+
qtype = dnslib.QTYPE[dns_response.q.qtype]
|
|
99
|
+
qclass = dnslib.CLASS[dns_response.q.qclass]
|
|
100
|
+
rr: str = str(dns_response.rr)
|
|
101
|
+
header = str(dns_response.header)
|
|
102
|
+
|
|
103
|
+
escaped_line_string: str = csvs.escape_csv_line_to_string([
|
|
104
|
+
timestamp,
|
|
105
|
+
dns_type,
|
|
106
|
+
client_ipv4,
|
|
107
|
+
client_port,
|
|
108
|
+
qname,
|
|
109
|
+
qtype,
|
|
110
|
+
qclass,
|
|
111
|
+
rr,
|
|
112
|
+
header,
|
|
113
|
+
error
|
|
114
|
+
])
|
|
115
|
+
|
|
116
|
+
self.csv_logger.info(escaped_line_string)
|
|
117
|
+
|
|
118
|
+
def write_error(
|
|
119
|
+
self,
|
|
120
|
+
dns_type: Literal['request', 'response'],
|
|
121
|
+
error_message: str,
|
|
122
|
+
client_address: tuple
|
|
123
|
+
):
|
|
124
|
+
"""
|
|
125
|
+
Write the error message to the statistics CSV file.
|
|
126
|
+
This is used for easier execution, since most of the parameters will be empty on accept.
|
|
127
|
+
|
|
128
|
+
:param dns_type: Literal['request', 'response'], DNS request or response.
|
|
129
|
+
:param error_message: string, error message.
|
|
130
|
+
:param client_address: tuple, client address (IPv4, Port).
|
|
131
|
+
:return:
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
self.write_row(
|
|
135
|
+
dns_type=dns_type,
|
|
136
|
+
client_address=client_address,
|
|
137
|
+
error=error_message
|
|
138
|
+
)
|
|
20
139
|
|
|
21
140
|
|
|
22
141
|
class DnsServer:
|
|
23
142
|
"""
|
|
24
143
|
DnsServer class is responsible to handle DNS Requests on port 53 based on configuration and send DNS Response back.
|
|
25
144
|
"""
|
|
26
|
-
|
|
145
|
+
# noinspection PyPep8Naming
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
listening_address: str,
|
|
149
|
+
log_directory_path: str,
|
|
150
|
+
backupCount_log_files_x_days: int = 0,
|
|
151
|
+
forwarding_dns_service_ipv4: str = '8.8.8.8',
|
|
152
|
+
forwarding_dns_service_port: int = 53,
|
|
153
|
+
resolve_by_engine: tuple[bool, list] = (False, None),
|
|
154
|
+
resolve_regular_pass_thru: bool = False,
|
|
155
|
+
resolve_all_domains_to_ipv4: tuple[bool, str] = (False, '127.0.0.1'),
|
|
156
|
+
offline_mode: bool = False,
|
|
157
|
+
buffer_size_receive: int = 8192,
|
|
158
|
+
response_ttl: int = 60,
|
|
159
|
+
dns_service_retries: int = 5,
|
|
160
|
+
cache_timeout_minutes: int = 60,
|
|
161
|
+
logger: logging.Logger = None,
|
|
162
|
+
logging_queue: multiprocessing.Queue = None,
|
|
163
|
+
logger_name: str = None
|
|
164
|
+
):
|
|
165
|
+
"""
|
|
166
|
+
Initialize the DNS Server object with all the necessary settings.
|
|
167
|
+
|
|
168
|
+
:param listening_address: str: Interface and a port that the DNS Server will listen on.
|
|
169
|
+
Example: '0.0.0.0:53'. For all interfaces on port 53.
|
|
170
|
+
:param log_directory_path: str: Path to the directory where the logs will be saved.
|
|
171
|
+
:param backupCount_log_files_x_days: int: How many days the log files will be kept.
|
|
172
|
+
Default is 0, which means that the log files will be kept indefinitely.
|
|
173
|
+
More than 0 means that the log files will be deleted after the specified days.
|
|
174
|
+
:param forwarding_dns_service_ipv4: str: IPv4 address of the DNS Service that will be used for resolving.
|
|
175
|
+
Example: '8.8.8.8'. For Google DNS Service.
|
|
176
|
+
:param forwarding_dns_service_port: int: Port number of the DNS Service that will be used for resolving.
|
|
177
|
+
Default is 53.
|
|
178
|
+
:param resolve_by_engine: tuple(boolean to enable the feature, list of engines).
|
|
179
|
+
True, The list of predefined engines will be used to resolve the domains.
|
|
180
|
+
Each list has a list of specific domains that will be routed to specified destination IPv4 address.
|
|
181
|
+
:param resolve_all_domains_to_ipv4: bool: If the DNS Server should resolve all the domains
|
|
182
|
+
to the provided origin DNS Service without altering the DNS request/response.
|
|
183
|
+
:param resolve_all_domains_to_ipv4: tuple(boolean to enable the feature, string IPv4 of the target).
|
|
184
|
+
True, the DNS Server will route all domains to the specified IPv4.
|
|
185
|
+
:param offline_mode: bool: If the DNS Server should work in offline mode.
|
|
186
|
+
:param buffer_size_receive: int: Buffer size of the connection while receiving messages.
|
|
187
|
+
:param response_ttl: int, Time to live of the DNS Response that will be returned. Default is 60 seconds.
|
|
188
|
+
:param dns_service_retries: int, How many times the request will be sent to forwarded DNS Service on errors:
|
|
189
|
+
(socket connect / request send / response receive).
|
|
190
|
+
:param cache_timeout_minutes: int: Timeout in minutes to clear the DNS Cache.
|
|
191
|
+
server. Each domain will be pass in the queue as a string.
|
|
192
|
+
|
|
193
|
+
:param logger: logging.Logger: Logger object to use for logging. If not provided, a new logger will be created.
|
|
194
|
+
:param logging_queue: multiprocessing.Queue: Queue to pass the logs to the QueueListener.
|
|
195
|
+
You will use this in case you run the DNS Server in a separate process.
|
|
196
|
+
Of course, you need to have a QueueListener to listen to this queue.
|
|
197
|
+
|
|
198
|
+
You can pass only one of the following: 'logger', 'logging_queue'.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
self.listening_address: str = listening_address
|
|
202
|
+
self.log_directory_path: str = log_directory_path
|
|
203
|
+
self.backupCount_log_files_x_days: int = backupCount_log_files_x_days
|
|
204
|
+
self.forwarding_dns_service_ipv4: str = forwarding_dns_service_ipv4
|
|
205
|
+
self.forwarding_dns_service_port: int = forwarding_dns_service_port
|
|
206
|
+
self.resolve_by_engine: tuple[bool, list] = resolve_by_engine
|
|
207
|
+
self.resolve_regular_pass_thru: bool = resolve_regular_pass_thru
|
|
208
|
+
self.resolve_all_domains_to_ipv4: tuple[bool, str] = resolve_all_domains_to_ipv4
|
|
209
|
+
self.offline_mode: bool = offline_mode
|
|
210
|
+
self.buffer_size_receive: int = buffer_size_receive
|
|
211
|
+
self.response_ttl: int = response_ttl
|
|
212
|
+
self.dns_service_retries: int = dns_service_retries
|
|
213
|
+
self.cache_timeout_minutes: int = cache_timeout_minutes
|
|
214
|
+
self.logging_queue: multiprocessing.Queue = logging_queue
|
|
215
|
+
self.logging_name: str = logger_name
|
|
216
|
+
|
|
217
|
+
if logger and logging_queue:
|
|
218
|
+
raise ValueError("You can pass only one of the following: 'logger', 'logging_queue'.")
|
|
219
|
+
|
|
220
|
+
self.listening_interface, listening_port = self.listening_address.split(':')
|
|
221
|
+
self.listening_interface: str
|
|
222
|
+
self.listening_port: int = int(listening_port)
|
|
223
|
+
self.resolve_by_engine_enable, self.engine_list = self.resolve_by_engine
|
|
224
|
+
self.resolve_by_engine_enable: bool
|
|
225
|
+
self.engine_list: list
|
|
226
|
+
self.resolve_all_domains_to_ipv4_enable, self.resolve_all_domains_target = self.resolve_all_domains_to_ipv4
|
|
227
|
+
self.resolve_all_domains_to_ipv4_enable: bool
|
|
228
|
+
self.resolve_all_domains_target: str
|
|
229
|
+
|
|
230
|
+
self.intercept_domain_dict: dict = dict()
|
|
231
|
+
for engine in self.engine_list:
|
|
232
|
+
# If the engine is not a reference engine.
|
|
233
|
+
if engine.engine_name != '__reference_general':
|
|
234
|
+
# Get the domains from the engine.
|
|
235
|
+
|
|
236
|
+
self.intercept_domain_dict.update(engine.domain_target_dict)
|
|
27
237
|
|
|
28
|
-
def __init__(self, config):
|
|
29
238
|
# Settings for static DNS Responses in offline mode.
|
|
30
|
-
self.offline_route_ipv4 = '10.10.10.10'
|
|
31
|
-
self.offline_route_ipv6 = 'fe80::3c09:df29:d52b:af39'
|
|
32
|
-
self.offline_route_domain = 'domain.com'
|
|
33
|
-
self.offline_srv_answer = \
|
|
239
|
+
self.offline_route_ipv4: str = '10.10.10.10'
|
|
240
|
+
self.offline_route_ipv6: str = 'fe80::3c09:df29:d52b:af39'
|
|
241
|
+
self.offline_route_domain: str = 'domain.com'
|
|
242
|
+
self.offline_srv_answer: str = \
|
|
34
243
|
'. 86391 IN SOA domain.com. domain.com. 2022012500 1800 900 604800 86400'
|
|
244
|
+
# self.offline_https_answer: str = str()
|
|
35
245
|
|
|
36
|
-
# Other settings.
|
|
37
|
-
# Full domain list to pass to TCP Server module.
|
|
38
|
-
self.domain_list: list = list()
|
|
39
|
-
|
|
40
|
-
# Set Buffer size of the connection while receiving messages. The function uses this variable right away.
|
|
41
|
-
self.buffer_size_receive: int = 8192
|
|
42
|
-
# TTL variable that is going to be returned in DNS response.
|
|
43
|
-
self.response_ttl: int = 60
|
|
44
|
-
# How many times the DNS Service will retry on errors (socket connect / request send / response receive)
|
|
45
|
-
self.dns_service_retries: int = 5
|
|
46
246
|
# If forwarding to Live DNS Service fails. Currently, we didn't send anything, so it's 'False'.
|
|
47
247
|
self.retried: bool = False
|
|
48
248
|
# Defining cache dictionary for assigning DNS Questions to DNS Answers
|
|
49
249
|
self.dns_questions_to_answers_cache: dict = dict()
|
|
50
250
|
|
|
51
|
-
# Queue for all the requested domains that hit the dns server.
|
|
52
|
-
# self.request_domain_queue: queue.Queue = queue.Queue()
|
|
53
|
-
self.request_domain_queue = None
|
|
54
|
-
|
|
55
251
|
# Filename to save all the known domains and their relative IPv4 addresses.
|
|
56
252
|
self.known_domains_filename: str = 'dns_known_domains.txt'
|
|
57
253
|
# Filename to save all the known IPv4 addresses and their relative domains.
|
|
@@ -59,17 +255,77 @@ class DnsServer:
|
|
|
59
255
|
# Filename to save domains and their IPv4 addresses by time they hit the DNS server.
|
|
60
256
|
self.known_dns_ipv4_by_time_filename: str = 'dns_ipv4_by_time.txt'
|
|
61
257
|
|
|
62
|
-
# Configuration object with all the settings.
|
|
63
|
-
self.config = config
|
|
64
|
-
|
|
65
258
|
# Logger that logs all the DNS Requests and responses in DNS format. These entries will not present in
|
|
66
259
|
# network log of TCP Server module.
|
|
67
|
-
self.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
260
|
+
self.dns_statistics_csv_writer = DnsStatisticsCSVWriter(statistics_directory_path=log_directory_path)
|
|
261
|
+
|
|
262
|
+
if not logger_name and not logger and not logging_queue:
|
|
263
|
+
self.logger_name = Path(__file__).stem
|
|
264
|
+
elif logger_name and (logger or logging_queue):
|
|
265
|
+
self.logger_name = f'{logger_name}.{Path(__file__).stem}'
|
|
266
|
+
|
|
267
|
+
# Check if the logger was provided, if not, create a new logger.
|
|
268
|
+
if not logger and not logging_queue:
|
|
269
|
+
self.logger: logging.Logger = loggingw.create_logger(
|
|
270
|
+
logger_name=Path(__file__).stem,
|
|
271
|
+
directory_path=self.log_directory_path,
|
|
272
|
+
add_stream=True,
|
|
273
|
+
add_timedfile_with_internal_queue=True,
|
|
274
|
+
formatter_streamhandler='DEFAULT',
|
|
275
|
+
formatter_filehandler='DEFAULT',
|
|
276
|
+
backupCount=self.backupCount_log_files_x_days
|
|
277
|
+
)
|
|
278
|
+
elif logger:
|
|
279
|
+
# Create child logger for the provided logger with the module's name.
|
|
280
|
+
self.logger: logging.Logger = loggingw.get_logger_with_level(self.logger_name)
|
|
281
|
+
elif logging_queue:
|
|
282
|
+
self.logger: logging.Logger = loggingw.create_logger(
|
|
283
|
+
logger_name=self.logger_name,
|
|
284
|
+
add_queue_handler=True,
|
|
285
|
+
log_queue=self.logging_queue
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
self.test_config()
|
|
289
|
+
|
|
290
|
+
def test_config(self):
|
|
291
|
+
try:
|
|
292
|
+
booleans.is_only_1_true_in_list(
|
|
293
|
+
booleans_list_of_tuples=[
|
|
294
|
+
(self.resolve_by_engine_enable, 'resolve_by_engine_enable'),
|
|
295
|
+
(self.resolve_regular_pass_thru, 'resolve_regular_pass_thru'),
|
|
296
|
+
(self.resolve_all_domains_to_ipv4_enable, 'resolve_all_domains_to_ipv4_enable')
|
|
297
|
+
],
|
|
298
|
+
raise_if_all_false=True
|
|
299
|
+
)
|
|
300
|
+
except ValueError as e:
|
|
301
|
+
print_api(f'DnsConfigurationValuesError: {str(e)}', error_type=True, color="red", logger=self.logger)
|
|
302
|
+
# Wait for the message to be printed and saved to file.
|
|
303
|
+
time.sleep(1)
|
|
304
|
+
raise DnsConfigurationValuesError(e)
|
|
305
|
+
|
|
306
|
+
# If the listening interface is not localhost, check if the interface can be bound to.
|
|
307
|
+
if not self.listening_interface.startswith('127.'):
|
|
308
|
+
host_ips: list[str] = networks.get_host_ips_psutil(ipv6=False)
|
|
309
|
+
if self.listening_interface not in host_ips:
|
|
310
|
+
message = (f"Listening interface [{self.listening_interface}] is not assigned to any of the host "
|
|
311
|
+
f"network interfaces. Current host IPv4 addresses: {host_ips}")
|
|
312
|
+
print_api(f'DnsConfigurationValuesError: {str(message)}', error_type=True, color="red", logger=self.logger)
|
|
313
|
+
# Wait for the message to be printed and saved to file.
|
|
314
|
+
time.sleep(1)
|
|
315
|
+
raise DnsConfigurationValuesError(message)
|
|
316
|
+
|
|
317
|
+
ips_ports: list[str] = [f'{self.listening_interface}:{self.listening_port}']
|
|
318
|
+
port_in_use = psutil_networks.get_processes_using_port_list(ips_ports)
|
|
319
|
+
if port_in_use:
|
|
320
|
+
error_messages: list = list()
|
|
321
|
+
for port, process_info in port_in_use.items():
|
|
322
|
+
error_messages.append(f"Port [{port}] is already in use by process: {process_info}")
|
|
323
|
+
|
|
324
|
+
message = "\n".join(error_messages)
|
|
325
|
+
print_api(f'DnsPortInUseError: {str(message)}', error_type=True, color="red", logger=self.logger)
|
|
326
|
+
# Wait for the message to be printed and saved to file.
|
|
327
|
+
time.sleep(1)
|
|
328
|
+
raise DnsPortInUseError(message)
|
|
73
329
|
|
|
74
330
|
def thread_worker_empty_dns_cache(self, function_sleep_time: int):
|
|
75
331
|
"""
|
|
@@ -83,18 +339,18 @@ class DnsServer:
|
|
|
83
339
|
self.dns_questions_to_answers_cache = dict()
|
|
84
340
|
self.logger.info("*** DNS cache cleared")
|
|
85
341
|
|
|
86
|
-
def start(
|
|
342
|
+
def start(
|
|
343
|
+
self,
|
|
344
|
+
is_ready_multiprocessing: multiprocessing.Event = None
|
|
345
|
+
):
|
|
87
346
|
"""
|
|
88
347
|
Main DNS Server function to start it.
|
|
89
348
|
|
|
349
|
+
:param is_ready_multiprocessing: multiprocessing.Event: Event to signal that the DNS Server is ready.
|
|
350
|
+
|
|
90
351
|
:return: None.
|
|
91
352
|
"""
|
|
92
353
|
|
|
93
|
-
port_in_use = networks.get_processes_using_port_list([self.config['dns']['listening_port']])
|
|
94
|
-
if port_in_use:
|
|
95
|
-
for port, process_info in port_in_use.items():
|
|
96
|
-
raise DnsPortInUseError(f"Port [{port}] is already in use by process: {process_info}")
|
|
97
|
-
|
|
98
354
|
self.logger.info("DNS Server Module Started.")
|
|
99
355
|
|
|
100
356
|
# Define objects for global usage
|
|
@@ -107,27 +363,27 @@ class DnsServer:
|
|
|
107
363
|
known_a_records_ipv4_dict: dict = dict()
|
|
108
364
|
|
|
109
365
|
# Check if 'route_to_tcp_server_only_engine_domains' was set to 'True' and output message accordingly.
|
|
110
|
-
if self.
|
|
111
|
-
message = "Routing
|
|
366
|
+
if self.resolve_by_engine_enable:
|
|
367
|
+
message = "Routing engine domains to the specified IPv4 targets."
|
|
112
368
|
print_api(message, logger=self.logger)
|
|
113
369
|
|
|
114
|
-
message = f"Current
|
|
115
|
-
print_api(message, logger=self.logger, color='
|
|
370
|
+
message = f"Current all engines domains: {list(self.intercept_domain_dict.keys())}"
|
|
371
|
+
print_api(message, logger=self.logger, color='blue')
|
|
116
372
|
|
|
117
|
-
if self.
|
|
118
|
-
message = "Routing all domains to
|
|
119
|
-
print_api(message, logger=self.logger, color='
|
|
373
|
+
if self.resolve_all_domains_to_ipv4_enable:
|
|
374
|
+
message = f"Routing all domains to the specified target: [{self.resolve_all_domains_target}]"
|
|
375
|
+
print_api(message, logger=self.logger, color='blue')
|
|
120
376
|
|
|
121
|
-
if self.
|
|
122
|
-
message = f"Routing all domains to
|
|
123
|
-
print_api(message, logger=self.logger, color='
|
|
377
|
+
if self.resolve_regular_pass_thru:
|
|
378
|
+
message = f"Routing all domains to the specified Origin DNS Service: {self.forwarding_dns_service_ipv4}:{self.forwarding_dns_service_port}"
|
|
379
|
+
print_api(message, logger=self.logger, color='blue')
|
|
124
380
|
|
|
125
381
|
# The list that will hold all the threads that can be joined later
|
|
126
382
|
threads_list: list = list()
|
|
127
383
|
|
|
128
384
|
# Starting a thread that will empty the DNS Cache lists
|
|
129
385
|
thread_current = threading.Thread(target=self.thread_worker_empty_dns_cache,
|
|
130
|
-
args=(self.
|
|
386
|
+
args=(self.cache_timeout_minutes,))
|
|
131
387
|
thread_current.daemon = True
|
|
132
388
|
# Start the thread
|
|
133
389
|
thread_current.start()
|
|
@@ -142,9 +398,15 @@ class DnsServer:
|
|
|
142
398
|
|
|
143
399
|
# Binding / assigning the port to the server / this script, that is going to be used for
|
|
144
400
|
# receiving connections.
|
|
145
|
-
main_socket_object.bind((self.
|
|
401
|
+
main_socket_object.bind((self.listening_interface, self.listening_port))
|
|
402
|
+
|
|
403
|
+
if is_ready_multiprocessing:
|
|
404
|
+
# If the DNS Server is running in a separate process, signal that the DNS Server is ready.
|
|
405
|
+
is_ready_multiprocessing.set()
|
|
146
406
|
|
|
147
407
|
while True:
|
|
408
|
+
forward_to_tcp_server = False # reset every request
|
|
409
|
+
|
|
148
410
|
# Needed this logging line when DNS was separate process.
|
|
149
411
|
# self.logger.info("Waiting to receive new requests...")
|
|
150
412
|
|
|
@@ -153,24 +415,30 @@ class DnsServer:
|
|
|
153
415
|
client_data: bytes
|
|
154
416
|
client_address: tuple
|
|
155
417
|
except ConnectionResetError:
|
|
418
|
+
client_address = (str(), int())
|
|
419
|
+
traceback_string = tracebacks.get_as_string(one_line=True)
|
|
156
420
|
# This error happens when the client closes the connection before the server.
|
|
157
421
|
# This is not an error for a DNS Server, but we'll log it anyway only with the full DNS logger.
|
|
158
|
-
message = "Error: to receive DNS request, An existing connection was forcibly closed"
|
|
159
|
-
|
|
160
|
-
print_api(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
self.dns_full_logger.info("==========")
|
|
164
|
-
pass
|
|
422
|
+
message = (f"Error: to receive DNS request, An existing connection was forcibly closed | "
|
|
423
|
+
f"{traceback_string}")
|
|
424
|
+
# print_api(message, logger=self.logger, logger_method='critical', traceback_string=True)
|
|
425
|
+
self.dns_statistics_csv_writer.write_error(
|
|
426
|
+
dns_type='request', client_address=client_address, error_message=message)
|
|
165
427
|
continue
|
|
166
|
-
except
|
|
167
|
-
message = "
|
|
168
|
-
print_api(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
428
|
+
except KeyboardInterrupt:
|
|
429
|
+
# message = "KeyboardInterrupt: Stopping DNS Server..."
|
|
430
|
+
# print_api(message, logger=self.logger, logger_method='info')
|
|
431
|
+
# self.logger.info(message)
|
|
432
|
+
# Stop the server
|
|
433
|
+
break
|
|
434
|
+
except Exception as e:
|
|
435
|
+
message = f"Unknown Exception to receive DNS request: {str(e)}"
|
|
436
|
+
print_api(message, logger=self.logger, logger_method='critical', traceback_string=True)
|
|
437
|
+
self.dns_statistics_csv_writer.write_error(
|
|
438
|
+
dns_type='request', client_address=client_address, error_message=message)
|
|
172
439
|
continue
|
|
173
440
|
|
|
441
|
+
# noinspection PyBroadException
|
|
174
442
|
try:
|
|
175
443
|
# This is the real point when the request received was logged, but since it takes too much place
|
|
176
444
|
# on the screen, moved it to full request logging position.
|
|
@@ -180,11 +448,11 @@ class DnsServer:
|
|
|
180
448
|
|
|
181
449
|
# Received DNS request that needs to be parsed to readable format
|
|
182
450
|
dns_object: dnslib.dns.DNSRecord = DNSRecord.parse(client_data)
|
|
183
|
-
# "qtype" returns as numeric identification, we need to convert it to
|
|
184
|
-
# provided by the dnslib
|
|
451
|
+
# "qtype" returns as numeric identification, we need to convert it to
|
|
452
|
+
# Readable QType (DNS Record Type) provided by the dnslib
|
|
185
453
|
# "dns_object.q" is the Question from the client that holds all the DNS question data,
|
|
186
|
-
# like which domain was
|
|
187
|
-
#
|
|
454
|
+
# like which domain was questioned for resolving,
|
|
455
|
+
# the class (example: IN), DNS Record Type that was questioned and a header.
|
|
188
456
|
# "dns_object.q.qtype" returns only QType of the Question
|
|
189
457
|
qtype_string: str = dnslib.QTYPE[dns_object.q.qtype]
|
|
190
458
|
# "qclass" returns as numeric identification, we need to convert it
|
|
@@ -200,25 +468,18 @@ class DnsServer:
|
|
|
200
468
|
# "dns_object.q.qname" returns only the questioned domain with "." (dot) in the end,
|
|
201
469
|
# which needs to be removed.
|
|
202
470
|
question_domain: str = str(dns_object.q.qname)[:-1]
|
|
203
|
-
self.
|
|
204
|
-
self.dns_full_logger.info(f"QTYPE: {qtype_string}")
|
|
205
|
-
self.dns_full_logger.info(f"Question Domain: {question_domain}")
|
|
471
|
+
self.dns_statistics_csv_writer.write_row(client_address=client_address, dns_request=dns_object)
|
|
206
472
|
|
|
207
|
-
message = f"Received request
|
|
473
|
+
message = (f"Received DNS request: {question_domain} | {qclass_string} | {qtype_string} | "
|
|
474
|
+
f"From: {client_address}.")
|
|
208
475
|
self.logger.info(message)
|
|
209
|
-
self.dns_full_logger.info(message)
|
|
210
|
-
|
|
211
|
-
self.dns_full_logger.info("--")
|
|
212
476
|
|
|
213
477
|
# Nullifying the DNS cache for current request before check.
|
|
214
478
|
dns_cached_request = False
|
|
215
479
|
# Check if the received data request from client is already in the cache
|
|
216
480
|
if client_data in self.dns_questions_to_answers_cache:
|
|
217
|
-
message = "!!!
|
|
481
|
+
# message = "!!! Request / Response is already in the dictionary..."
|
|
218
482
|
# self.logger.info(message)
|
|
219
|
-
self.dns_full_logger.info(message)
|
|
220
|
-
|
|
221
|
-
self.dns_full_logger.info("--")
|
|
222
483
|
|
|
223
484
|
# Get the response from the cached answers list
|
|
224
485
|
dns_response = self.dns_questions_to_answers_cache[client_data]
|
|
@@ -229,12 +490,12 @@ class DnsServer:
|
|
|
229
490
|
else:
|
|
230
491
|
# Check if the incoming Record is "A" record.
|
|
231
492
|
if qtype_string == "A":
|
|
232
|
-
# Check if '
|
|
233
|
-
# If so, we need to check if the incoming domain contain any of the
|
|
234
|
-
if self.
|
|
493
|
+
# Check if 'resolve_to_tcp_server_only_tcp_resolve_domains' is set to 'True'.
|
|
494
|
+
# If so, we need to check if the incoming domain contain any of the domains in the list.
|
|
495
|
+
if self.resolve_by_engine_enable:
|
|
235
496
|
# If current query domain (+ subdomains) CONTAIN any of the domains from modules config
|
|
236
497
|
# files and current request contains "A" (IPv4) record.
|
|
237
|
-
if any(x in question_domain for x in self.
|
|
498
|
+
if any(x in question_domain for x in self.intercept_domain_dict.keys()):
|
|
238
499
|
# If incoming domain contains any of the 'engine_domains' then domain will
|
|
239
500
|
# be forwarded to our TCP Server.
|
|
240
501
|
forward_to_tcp_server = True
|
|
@@ -243,12 +504,12 @@ class DnsServer:
|
|
|
243
504
|
|
|
244
505
|
# If 'route_to_tcp_server_all_domains' was set to 'False' in 'config.ini' file then
|
|
245
506
|
# we'll forward all 'A' records domains to the Built-in TCP Server.
|
|
246
|
-
if self.
|
|
507
|
+
if self.resolve_all_domains_to_ipv4_enable:
|
|
247
508
|
forward_to_tcp_server = True
|
|
248
509
|
|
|
249
510
|
# If 'regular_resolving' was set to 'True' in 'config.ini' file then
|
|
250
511
|
# we'll forward all 'A' records domains to the Live DNS Service.
|
|
251
|
-
if self.
|
|
512
|
+
if self.resolve_regular_pass_thru:
|
|
252
513
|
forward_to_tcp_server = False
|
|
253
514
|
|
|
254
515
|
# If incoming record is not an "A" record, then it will not be forwarded to our TCP Server.
|
|
@@ -257,9 +518,15 @@ class DnsServer:
|
|
|
257
518
|
|
|
258
519
|
# If 'forward_to_tcp_server' is 'True' we'll resolve the record with our TCP Server IP address.
|
|
259
520
|
if forward_to_tcp_server:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
521
|
+
if self.resolve_by_engine_enable:
|
|
522
|
+
for engine in self.engine_list:
|
|
523
|
+
resolved_target_ipv4 = get_target_ip_from_engine(question_domain, engine.domain_target_dict)
|
|
524
|
+
# If the domain was found in the current engine's domain list, we can stop the loop.
|
|
525
|
+
if resolved_target_ipv4:
|
|
526
|
+
break
|
|
527
|
+
elif self.resolve_all_domains_to_ipv4_enable:
|
|
528
|
+
# Assign the target IPv4 address to the resolved target IPv4 variable.
|
|
529
|
+
resolved_target_ipv4 = self.resolve_all_domains_target
|
|
263
530
|
|
|
264
531
|
# Make DNS response that will refer TCP traffic to our server
|
|
265
532
|
dns_built_response = DNSRecord(
|
|
@@ -268,7 +535,7 @@ class DnsServer:
|
|
|
268
535
|
# q=DNSQuestion(question_domain),
|
|
269
536
|
q=dns_object.q,
|
|
270
537
|
a=RR(question_domain,
|
|
271
|
-
rdata=A(
|
|
538
|
+
rdata=A(resolved_target_ipv4),
|
|
272
539
|
ttl=self.response_ttl)
|
|
273
540
|
)
|
|
274
541
|
# Encode the response that was built above to legit DNS Response
|
|
@@ -279,7 +546,7 @@ class DnsServer:
|
|
|
279
546
|
# any of the domains from modules config files
|
|
280
547
|
else:
|
|
281
548
|
# If we're in offline mode
|
|
282
|
-
if self.
|
|
549
|
+
if self.offline_mode:
|
|
283
550
|
# Make DNS response that will refer TCP traffic to our server
|
|
284
551
|
# dns_question = DNSRecord.question(question_domain)
|
|
285
552
|
dns_built_response = dns_object.reply()
|
|
@@ -290,14 +557,13 @@ class DnsServer:
|
|
|
290
557
|
if qtype_string == "AAAA":
|
|
291
558
|
dns_built_response.add_answer(
|
|
292
559
|
*RR.fromZone(
|
|
293
|
-
question_domain
|
|
294
|
-
self.offline_route_ipv6)
|
|
560
|
+
f'{question_domain} {str(self.response_ttl)} {qtype_string} '
|
|
561
|
+
f'{self.offline_route_ipv6}')
|
|
295
562
|
)
|
|
296
563
|
|
|
297
|
-
message = f"!!!
|
|
564
|
+
message = f"!!! Request / Response is in offline mode returning " \
|
|
298
565
|
f"{self.offline_route_ipv6}."
|
|
299
566
|
self.logger.info(message)
|
|
300
|
-
self.dns_full_logger.info(message)
|
|
301
567
|
|
|
302
568
|
# SRV Record type explanation:
|
|
303
569
|
# https://www.cloudflare.com/learning/dns/dns-records/dns-srv-record/
|
|
@@ -308,44 +574,39 @@ class DnsServer:
|
|
|
308
574
|
# com. domain.com. 2022012500 1800 900 604800 86400
|
|
309
575
|
# Basically SOA is the same, but can be with additional fields.
|
|
310
576
|
# Since, it's offline and not online - we don't really care.
|
|
311
|
-
elif qtype_string == "SRV" or qtype_string == "SOA":
|
|
577
|
+
elif qtype_string == "SRV" or qtype_string == "SOA" or qtype_string == "HTTPS":
|
|
312
578
|
dns_built_response.add_answer(*RR.fromZone(self.offline_srv_answer))
|
|
313
579
|
|
|
314
|
-
message = f"!!!
|
|
580
|
+
message = f"!!! Request / Response is in offline mode returning: " \
|
|
315
581
|
f"{self.offline_srv_answer}."
|
|
316
582
|
self.logger.info(message)
|
|
317
|
-
self.dns_full_logger.info(message)
|
|
318
583
|
elif qtype_string == "ANY":
|
|
319
584
|
dns_built_response.add_answer(
|
|
320
|
-
# *RR.fromZone(question_domain + " " + str(response_ttl) + " " + qclass_string +
|
|
321
|
-
# " CNAME " + dns_server_offline_route_domain)
|
|
322
585
|
*RR.fromZone(question_domain + " " + str(self.response_ttl) + " CNAME " +
|
|
323
586
|
self.offline_route_domain)
|
|
324
587
|
)
|
|
325
588
|
|
|
326
|
-
message = f"!!!
|
|
589
|
+
message = f"!!! Request / Response is in offline mode returning " \
|
|
327
590
|
f"{self.offline_route_domain}."
|
|
328
591
|
self.logger.info(message)
|
|
329
|
-
self.dns_full_logger.info(message)
|
|
330
592
|
else:
|
|
331
593
|
dns_built_response.add_answer(
|
|
332
594
|
*RR.fromZone(
|
|
333
|
-
question_domain + " " + str(self.response_ttl) + " " + qtype_string +
|
|
334
|
-
self.offline_route_ipv4)
|
|
595
|
+
question_domain + " " + str(self.response_ttl) + " " + qtype_string +
|
|
596
|
+
" " + self.offline_route_ipv4)
|
|
335
597
|
)
|
|
336
598
|
|
|
337
|
-
message = f"!!!
|
|
599
|
+
message = f"!!! Request / Response is in offline mode returning " \
|
|
338
600
|
f"{self.offline_route_ipv4}."
|
|
339
601
|
self.logger.info(message)
|
|
340
|
-
self.dns_full_logger.info(message)
|
|
341
602
|
# Values error means in most cases that you create wrong response
|
|
342
603
|
# for specific type of request.
|
|
343
604
|
except ValueError:
|
|
344
605
|
message = f"Looks like wrong type of response for QTYPE: {qtype_string}. Response: "
|
|
345
606
|
print_api(message, logger=self.logger, logger_method='critical',
|
|
346
|
-
traceback_string=True
|
|
607
|
+
traceback_string=True)
|
|
347
608
|
print_api(f"{dns_built_response}", logger=self.logger, logger_method='critical',
|
|
348
|
-
traceback_string=True
|
|
609
|
+
traceback_string=True)
|
|
349
610
|
# Pass the exception.
|
|
350
611
|
pass
|
|
351
612
|
# Continue to the next DNS request, since there's nothing to do here right now.
|
|
@@ -353,18 +614,15 @@ class DnsServer:
|
|
|
353
614
|
# General exception in response creation.
|
|
354
615
|
except Exception:
|
|
355
616
|
message = \
|
|
356
|
-
f"Unknown exception while creating response for QTYPE: {qtype_string}.
|
|
617
|
+
(f"Unknown exception while creating response for QTYPE: {qtype_string}. "
|
|
618
|
+
f"Response: \n{dns_built_response}")
|
|
357
619
|
print_api(message, logger=self.logger, logger_method='critical',
|
|
358
|
-
traceback_string=True
|
|
359
|
-
print_api(f"{dns_built_response}", logger=self.logger, logger_method='critical',
|
|
360
|
-
traceback_string=True, oneline=True)
|
|
620
|
+
traceback_string=True)
|
|
361
621
|
# Pass the exception.
|
|
362
622
|
pass
|
|
363
623
|
# Continue to the next DNS request, since there's nothing to do here right now.
|
|
364
624
|
continue
|
|
365
625
|
|
|
366
|
-
self.dns_full_logger.info("--")
|
|
367
|
-
|
|
368
626
|
# Encode the response that was built above to legit DNS Response
|
|
369
627
|
dns_response = dns_built_response.pack()
|
|
370
628
|
# If we're in online mode
|
|
@@ -379,26 +637,26 @@ class DnsServer:
|
|
|
379
637
|
# Since, it's probably going to succeed.
|
|
380
638
|
if counter > 0:
|
|
381
639
|
self.logger.info(f"Retry #: {counter}/{self.dns_service_retries}")
|
|
382
|
-
self.
|
|
640
|
+
self.logger.info(
|
|
383
641
|
f"Forwarding request. Creating UDP socket to: "
|
|
384
|
-
f"{self.
|
|
385
|
-
f"{
|
|
642
|
+
f"{self.forwarding_dns_service_ipv4}:"
|
|
643
|
+
f"{self.forwarding_dns_service_port}")
|
|
386
644
|
try:
|
|
387
645
|
google_dns_ipv4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
388
646
|
google_dns_ipv4_socket.settimeout(5)
|
|
389
647
|
|
|
390
648
|
message = "Socket created, Forwarding..."
|
|
391
649
|
# self.logger.info(message)
|
|
392
|
-
self.
|
|
650
|
+
self.logger.info(message)
|
|
393
651
|
|
|
394
652
|
google_dns_ipv4_socket.sendto(client_data, (
|
|
395
|
-
self.
|
|
396
|
-
|
|
653
|
+
self.forwarding_dns_service_ipv4,
|
|
654
|
+
self.forwarding_dns_service_port
|
|
397
655
|
))
|
|
398
656
|
# The script needs to wait a second or receive can hang
|
|
399
657
|
message = "Request sent to the forwarding DNS, Receiving the answer..."
|
|
400
658
|
# self.logger.info(message)
|
|
401
|
-
self.
|
|
659
|
+
self.logger.info(message)
|
|
402
660
|
|
|
403
661
|
dns_response, google_address = \
|
|
404
662
|
google_dns_ipv4_socket.recvfrom(self.buffer_size_receive)
|
|
@@ -417,9 +675,8 @@ class DnsServer:
|
|
|
417
675
|
self.logger.info(
|
|
418
676
|
f"Retried {self.dns_service_retries} times. "
|
|
419
677
|
f"Couldn't forward DNS request to: "
|
|
420
|
-
f"[{self.
|
|
678
|
+
f"[{self.forwarding_dns_service_ipv4}]. "
|
|
421
679
|
f"Continuing to next request.")
|
|
422
|
-
self.dns_full_logger.info("==========")
|
|
423
680
|
|
|
424
681
|
# From here continue to the next iteration of While loop.
|
|
425
682
|
continue
|
|
@@ -432,36 +689,28 @@ class DnsServer:
|
|
|
432
689
|
if retried:
|
|
433
690
|
continue
|
|
434
691
|
|
|
435
|
-
self.
|
|
436
|
-
f"Answer received from: {self.
|
|
692
|
+
self.logger.info(
|
|
693
|
+
f"Answer received from: {self.forwarding_dns_service_ipv4}")
|
|
437
694
|
|
|
438
695
|
# Closing the socket to forwarding service
|
|
439
696
|
google_dns_ipv4_socket.close()
|
|
440
|
-
self.
|
|
697
|
+
self.logger.info("Closed socket to forwarding service")
|
|
441
698
|
|
|
442
699
|
# Appending current DNS Request and DNS Answer to the Cache
|
|
443
700
|
self.dns_questions_to_answers_cache.update({client_data: dns_response})
|
|
444
701
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
# dns_response = dns_object.reply()
|
|
448
|
-
# dns_response.add_answer(*RR.fromZone(f"{question_domain} 60 {qtype_object} 8.8.8.8"))
|
|
449
|
-
|
|
450
|
-
# dns_built_response = \
|
|
451
|
-
# DNSRecord(
|
|
452
|
-
# DNSHeader(id=dns_object.header.id, qr=1, aa=1, ra=1), q=DNSQuestion(question_domain),
|
|
453
|
-
# a=RR.fromZone(question_domain + " 60 " + qtype_object + " " + dns_server_offline_ipv4))
|
|
454
|
-
|
|
455
|
-
# If 'forward_to_tcp_server' it means that we built the response, and we don't need to reparse it, since
|
|
456
|
-
# we already have all the data.
|
|
702
|
+
# If 'forward_to_tcp_server' it means that we built the response, and we don't need to reparse it,
|
|
703
|
+
# since we already have all the data.
|
|
457
704
|
if forward_to_tcp_server:
|
|
458
|
-
self.
|
|
705
|
+
# self.logger.info(f"Response {dns_built_response.short()}")
|
|
706
|
+
self.dns_statistics_csv_writer.write_row(
|
|
707
|
+
client_address=client_address, dns_response=dns_built_response)
|
|
459
708
|
|
|
460
709
|
message = f"Response Details: {dns_built_response.rr}"
|
|
461
|
-
print_api(message, logger=self.
|
|
710
|
+
print_api(message, logger=self.logger, logger_method='info', oneline=True)
|
|
462
711
|
|
|
463
|
-
message = f"Response Full Details: {dns_built_response.format(prefix='', sort=True)}"
|
|
464
|
-
print_api(message, logger=self.
|
|
712
|
+
# message = f"Response Full Details: {dns_built_response.format(prefix='', sort=True)}"
|
|
713
|
+
# print_api(message, logger=self.logger, logger_method='info', oneline=True)
|
|
465
714
|
|
|
466
715
|
# Now we can turn it to false, so it won't trigger this
|
|
467
716
|
# condition next time if the response was not built
|
|
@@ -477,30 +726,34 @@ class DnsServer:
|
|
|
477
726
|
# Reinitializing the ipv4 addresses list.
|
|
478
727
|
ipv4_addresses = list()
|
|
479
728
|
|
|
480
|
-
|
|
729
|
+
# If the DNS answer section isn't empty, and log the returned IPv4 addresses.
|
|
730
|
+
if dns_response_parsed.rr:
|
|
481
731
|
for rr in dns_response_parsed.rr:
|
|
482
732
|
if isinstance(rr.rdata, A):
|
|
483
|
-
self.
|
|
733
|
+
self.dns_statistics_csv_writer.write_row(
|
|
734
|
+
client_address=client_address, dns_response=dns_response_parsed)
|
|
735
|
+
|
|
736
|
+
self.logger.info(f"Response IP: {rr.rdata}")
|
|
484
737
|
|
|
485
738
|
# Adding the address to the list as 'str' object and not 'dnslib.dns.A'.
|
|
486
739
|
ipv4_addresses.append(str(rr.rdata))
|
|
487
740
|
|
|
488
|
-
message = f"Response Details: {dns_response_parsed.rr}"
|
|
489
|
-
print_api(message, logger=self.
|
|
741
|
+
# message = f"Response Details: {dns_response_parsed.rr}"
|
|
742
|
+
# print_api(message, logger=self.dns_statistics_csv_writer, logger_method='info', oneline=True)
|
|
743
|
+
#
|
|
744
|
+
# message = f"Response Full Details: {dns_response_parsed}"
|
|
745
|
+
# print_api(message, logger=self.dns_statistics_csv_writer, logger_method='info', oneline=True)
|
|
490
746
|
|
|
491
|
-
|
|
492
|
-
print_api(message, logger=self.dns_full_logger, logger_method='info', oneline=True)
|
|
493
|
-
|
|
494
|
-
self.dns_full_logger.info("Sending DNS response back to client...")
|
|
747
|
+
self.logger.info("Sending DNS response back to client...")
|
|
495
748
|
main_socket_object.sendto(dns_response, client_address)
|
|
496
|
-
self.
|
|
749
|
+
self.logger.info("DNS Response sent...")
|
|
497
750
|
|
|
498
751
|
# 'ipv4_addresses' list contains entries of type 'dnslib.dns.A' and not string.
|
|
499
752
|
# We'll convert each entry to string, so strings can be searched in this list.
|
|
500
753
|
# for index, ip_address in enumerate(ipv4_addresses):
|
|
501
754
|
# ipv4_addresses[index] = str(ip_address)
|
|
502
755
|
|
|
503
|
-
#
|
|
756
|
+
# ==================================================================================================
|
|
504
757
|
# # Known domain dictionary of last 2 A records' management.
|
|
505
758
|
#
|
|
506
759
|
# # Sorting the addresses, so it will be easier to compare dictionaries in the list.
|
|
@@ -522,7 +775,7 @@ class DnsServer:
|
|
|
522
775
|
#
|
|
523
776
|
# dns.logger.info(f"Latest known list: {known_a_records_domains_list_last_entries}")
|
|
524
777
|
|
|
525
|
-
#
|
|
778
|
+
# ==================================================================================================
|
|
526
779
|
# Known domain list management (A Records only)
|
|
527
780
|
|
|
528
781
|
# If current request is in the cache,
|
|
@@ -568,20 +821,20 @@ class DnsServer:
|
|
|
568
821
|
|
|
569
822
|
# Save this string object as log file.
|
|
570
823
|
with open(
|
|
571
|
-
self.
|
|
824
|
+
self.log_directory_path + os.sep + self.known_domains_filename, 'w'
|
|
572
825
|
) as output_file:
|
|
573
826
|
output_file.write(record_string_line)
|
|
574
827
|
|
|
575
|
-
self.
|
|
576
|
-
|
|
577
|
-
|
|
828
|
+
# self.logger.info(
|
|
829
|
+
# f"Saved new known domains file: "
|
|
830
|
+
# f"{self.log_directory_path}{os.sep}{self.known_domains_filename}")
|
|
578
831
|
|
|
579
832
|
# Known domain list managements EOF
|
|
580
|
-
#
|
|
833
|
+
# ==================================================================================================
|
|
581
834
|
# Known IPv4 address to domains list management (A Records only)
|
|
582
835
|
|
|
583
836
|
# If DNS Server 'offline_mode' was set to 'False'.
|
|
584
|
-
if not self.
|
|
837
|
+
if not self.offline_mode:
|
|
585
838
|
dump_ipv4_dictionary_to_file = False
|
|
586
839
|
# If IPv4 address list is not empty, meaning this DNS request was A type.
|
|
587
840
|
if ipv4_addresses:
|
|
@@ -594,9 +847,9 @@ class DnsServer:
|
|
|
594
847
|
# If so, get the list of current domains for current ipv4 address.
|
|
595
848
|
current_domains_list = known_a_records_ipv4_dict[current_ip_address]
|
|
596
849
|
|
|
597
|
-
# If current question domain is not in the known domains that we had from
|
|
598
|
-
# requests for the same IPv4 address, then we'll add this domain
|
|
599
|
-
# list for this IPv4.
|
|
850
|
+
# If current question domain is not in the known domains that we had from
|
|
851
|
+
# previous DNS requests for the same IPv4 address, then we'll add this domain
|
|
852
|
+
# to the known domains list for this IPv4.
|
|
600
853
|
# And update the dictionary of known IPv4 addresses and their domains.
|
|
601
854
|
if question_domain not in current_domains_list:
|
|
602
855
|
current_domains_list.append(question_domain)
|
|
@@ -627,16 +880,16 @@ class DnsServer:
|
|
|
627
880
|
|
|
628
881
|
# Save this string object as log file.
|
|
629
882
|
with open(
|
|
630
|
-
self.
|
|
883
|
+
self.log_directory_path + os.sep + self.known_ipv4_filename, 'w'
|
|
631
884
|
) as output_file:
|
|
632
885
|
output_file.write(record_string_line)
|
|
633
886
|
|
|
634
|
-
self.
|
|
635
|
-
|
|
636
|
-
|
|
887
|
+
# self.logger.info(
|
|
888
|
+
# f"Saved new known IPv4 addresses file: "
|
|
889
|
+
# f"{self.log_directory_path}{os.sep}{self.known_ipv4_filename}")
|
|
637
890
|
|
|
638
891
|
# Known IPv4 address to domains list management EOF
|
|
639
|
-
#
|
|
892
|
+
# ==================================================================================================
|
|
640
893
|
# Writing IPs by time.
|
|
641
894
|
|
|
642
895
|
# If IPv4 address list is not empty, meaning this DNS request was A type.
|
|
@@ -647,31 +900,87 @@ class DnsServer:
|
|
|
647
900
|
|
|
648
901
|
# Save this string object as log file.
|
|
649
902
|
with open(
|
|
650
|
-
self.
|
|
903
|
+
self.log_directory_path + os.sep + self.known_dns_ipv4_by_time_filename, 'a'
|
|
651
904
|
) as output_file:
|
|
652
905
|
output_file.write(record_string_line + '\n')
|
|
653
906
|
|
|
654
907
|
# EOF Writing IPs by time.
|
|
655
|
-
#
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
# Starting a thread that will query IPs of the last DNS request.
|
|
659
|
-
# thread_current = \
|
|
660
|
-
# threading.Thread(
|
|
661
|
-
# target=thread_worker_check_process_by_ip, args=(ipv4_addresses, client_address[0],))
|
|
662
|
-
# # Append to list of threads, so they can be "joined" later
|
|
663
|
-
# threads_list.append(thread_current)
|
|
664
|
-
# # Start the thread
|
|
665
|
-
# thread_current.start()
|
|
666
|
-
|
|
667
|
-
# EOF SSH / LOCALHOST executing process command line harvesting.
|
|
668
|
-
# ==================================================================================================================
|
|
669
|
-
|
|
670
|
-
self.dns_full_logger.info("==========")
|
|
908
|
+
# ==================================================================================================
|
|
909
|
+
|
|
910
|
+
# self.logger.info("==========")
|
|
671
911
|
except Exception:
|
|
672
912
|
message = "Unknown Exception: to parse DNS request"
|
|
673
913
|
print_api(
|
|
674
|
-
message, logger=self.logger, logger_method='critical', traceback_string=True
|
|
914
|
+
message, logger=self.logger, logger_method='critical', traceback_string=True)
|
|
675
915
|
self.logger.info("==========")
|
|
676
916
|
pass
|
|
677
917
|
continue
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def get_target_ip_from_engine(
|
|
921
|
+
target_domain: str,
|
|
922
|
+
engine_domain_target_dict: dict
|
|
923
|
+
) -> Optional[str]:
|
|
924
|
+
"""
|
|
925
|
+
Get the target IP address from the engine.
|
|
926
|
+
|
|
927
|
+
:param target_domain: str: The domain to return the target IP address for.
|
|
928
|
+
:param engine_domain_target_dict: dict: The dictionary of domains and their target IPs.
|
|
929
|
+
|
|
930
|
+
:return: str: The target IP address.
|
|
931
|
+
"""
|
|
932
|
+
# Iterate through the list of engines.
|
|
933
|
+
for domain, target_ip_port in engine_domain_target_dict.items():
|
|
934
|
+
# If the domain is exactly the same as the target domain,
|
|
935
|
+
if domain == target_domain:
|
|
936
|
+
# Get the target IP address from the engine.
|
|
937
|
+
return target_ip_port['ip']
|
|
938
|
+
elif domain in target_domain:
|
|
939
|
+
# Get the target IP address from the engine.
|
|
940
|
+
return target_ip_port['ip']
|
|
941
|
+
|
|
942
|
+
return None
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
# noinspection PyPep8Naming
|
|
946
|
+
def start_dns_server_multiprocessing_worker(
|
|
947
|
+
listening_address: str,
|
|
948
|
+
log_directory_path: str,
|
|
949
|
+
backupCount_log_files_x_days: int,
|
|
950
|
+
forwarding_dns_service_ipv4: str,
|
|
951
|
+
forwarding_dns_service_port: int,
|
|
952
|
+
resolve_by_engine: tuple[bool, list],
|
|
953
|
+
resolve_regular_pass_thru: bool,
|
|
954
|
+
resolve_all_domains_to_ipv4: tuple[bool, str],
|
|
955
|
+
offline_mode: bool,
|
|
956
|
+
cache_timeout_minutes: int,
|
|
957
|
+
logging_queue: multiprocessing.Queue,
|
|
958
|
+
logger_name: str,
|
|
959
|
+
is_ready_multiprocessing: multiprocessing.Event=None
|
|
960
|
+
):
|
|
961
|
+
# Setting the current thread name to the current process name.
|
|
962
|
+
current_process_name = multiprocessing.current_process().name
|
|
963
|
+
threading.current_thread().name = current_process_name
|
|
964
|
+
|
|
965
|
+
try:
|
|
966
|
+
dns_server_instance = DnsServer(
|
|
967
|
+
listening_address=listening_address,
|
|
968
|
+
log_directory_path=log_directory_path,
|
|
969
|
+
backupCount_log_files_x_days=backupCount_log_files_x_days,
|
|
970
|
+
forwarding_dns_service_ipv4=forwarding_dns_service_ipv4,
|
|
971
|
+
forwarding_dns_service_port=forwarding_dns_service_port,
|
|
972
|
+
resolve_by_engine=resolve_by_engine,
|
|
973
|
+
resolve_regular_pass_thru=resolve_regular_pass_thru,
|
|
974
|
+
resolve_all_domains_to_ipv4=resolve_all_domains_to_ipv4,
|
|
975
|
+
offline_mode=offline_mode,
|
|
976
|
+
cache_timeout_minutes=cache_timeout_minutes,
|
|
977
|
+
logging_queue=logging_queue,
|
|
978
|
+
logger_name=logger_name
|
|
979
|
+
)
|
|
980
|
+
except (DnsPortInUseError, DnsConfigurationValuesError) as e:
|
|
981
|
+
_ = e
|
|
982
|
+
# Wait for the message to be printed and saved to file.
|
|
983
|
+
time.sleep(1)
|
|
984
|
+
return 1
|
|
985
|
+
|
|
986
|
+
dns_server_instance.start(is_ready_multiprocessing=is_ready_multiprocessing)
|