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.
Files changed (221) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  4. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  5. atomicshop/a_mains/github_wrapper.py +11 -0
  6. atomicshop/a_mains/install_ca_certificate.py +172 -0
  7. atomicshop/a_mains/process_from_port.py +119 -0
  8. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  9. atomicshop/a_mains/update_config_toml.py +38 -0
  10. atomicshop/basics/ansi_escape_codes.py +3 -1
  11. atomicshop/basics/argparse_template.py +2 -0
  12. atomicshop/basics/booleans.py +27 -30
  13. atomicshop/basics/bytes_arrays.py +43 -0
  14. atomicshop/basics/classes.py +149 -1
  15. atomicshop/basics/enums.py +2 -2
  16. atomicshop/basics/exceptions.py +5 -1
  17. atomicshop/basics/list_of_classes.py +29 -0
  18. atomicshop/basics/multiprocesses.py +374 -50
  19. atomicshop/basics/strings.py +72 -3
  20. atomicshop/basics/threads.py +14 -0
  21. atomicshop/basics/tracebacks.py +13 -3
  22. atomicshop/certificates.py +153 -52
  23. atomicshop/config_init.py +11 -6
  24. atomicshop/console_user_response.py +7 -14
  25. atomicshop/consoles.py +9 -0
  26. atomicshop/datetimes.py +1 -1
  27. atomicshop/diff_check.py +3 -3
  28. atomicshop/dns.py +128 -3
  29. atomicshop/etws/_pywintrace_fix.py +17 -0
  30. atomicshop/etws/trace.py +40 -42
  31. atomicshop/etws/traces/trace_dns.py +56 -44
  32. atomicshop/etws/traces/trace_tcp.py +130 -0
  33. atomicshop/file_io/csvs.py +27 -5
  34. atomicshop/file_io/docxs.py +34 -17
  35. atomicshop/file_io/file_io.py +31 -17
  36. atomicshop/file_io/jsons.py +49 -0
  37. atomicshop/file_io/tomls.py +139 -0
  38. atomicshop/filesystem.py +616 -291
  39. atomicshop/get_process_list.py +3 -3
  40. atomicshop/http_parse.py +149 -93
  41. atomicshop/ip_addresses.py +6 -1
  42. atomicshop/mitm/centered_settings.py +132 -0
  43. atomicshop/mitm/config_static.py +207 -0
  44. atomicshop/mitm/config_toml_editor.py +55 -0
  45. atomicshop/mitm/connection_thread_worker.py +875 -357
  46. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  47. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  48. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  49. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  50. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  51. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  52. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  53. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  54. atomicshop/mitm/engines/create_module_template.py +58 -14
  55. atomicshop/mitm/import_config.py +359 -139
  56. atomicshop/mitm/initialize_engines.py +160 -80
  57. atomicshop/mitm/message.py +64 -23
  58. atomicshop/mitm/mitm_main.py +892 -0
  59. atomicshop/mitm/recs_files.py +183 -0
  60. atomicshop/mitm/shared_functions.py +4 -10
  61. atomicshop/mitm/ssh_tester.py +82 -0
  62. atomicshop/mitm/statistic_analyzer.py +136 -40
  63. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
  64. atomicshop/monitor/checks/dns.py +1 -1
  65. atomicshop/networks.py +671 -0
  66. atomicshop/on_exit.py +39 -9
  67. atomicshop/package_mains_processor.py +84 -0
  68. atomicshop/permissions/permissions.py +22 -0
  69. atomicshop/permissions/ubuntu_permissions.py +239 -0
  70. atomicshop/permissions/win_permissions.py +33 -0
  71. atomicshop/print_api.py +24 -42
  72. atomicshop/process.py +24 -6
  73. atomicshop/process_poller/process_pool.py +0 -1
  74. atomicshop/process_poller/simple_process_pool.py +204 -5
  75. atomicshop/python_file_patcher.py +1 -1
  76. atomicshop/python_functions.py +27 -75
  77. atomicshop/speech_recognize.py +8 -0
  78. atomicshop/ssh_remote.py +158 -172
  79. atomicshop/system_resource_monitor.py +61 -47
  80. atomicshop/system_resources.py +8 -8
  81. atomicshop/tempfiles.py +1 -2
  82. atomicshop/urls.py +6 -0
  83. atomicshop/venvs.py +28 -0
  84. atomicshop/versioning.py +27 -0
  85. atomicshop/web.py +98 -27
  86. atomicshop/web_apis/google_custom_search.py +44 -0
  87. atomicshop/web_apis/google_llm.py +188 -0
  88. atomicshop/websocket_parse.py +450 -0
  89. atomicshop/wrappers/certauthw/certauth.py +1 -0
  90. atomicshop/wrappers/cryptographyw.py +29 -8
  91. atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
  92. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
  93. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  94. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  95. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
  96. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  97. atomicshop/wrappers/ctyping/win_console.py +39 -0
  98. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  99. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  100. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  101. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  102. atomicshop/wrappers/factw/get_file_data.py +12 -5
  103. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  104. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  105. atomicshop/wrappers/githubw.py +537 -54
  106. atomicshop/wrappers/loggingw/consts.py +1 -1
  107. atomicshop/wrappers/loggingw/filters.py +23 -0
  108. atomicshop/wrappers/loggingw/formatters.py +12 -0
  109. atomicshop/wrappers/loggingw/handlers.py +214 -107
  110. atomicshop/wrappers/loggingw/loggers.py +19 -0
  111. atomicshop/wrappers/loggingw/loggingw.py +860 -22
  112. atomicshop/wrappers/loggingw/reading.py +134 -112
  113. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  114. atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
  115. atomicshop/wrappers/netshw.py +271 -0
  116. atomicshop/wrappers/playwrightw/engine.py +34 -19
  117. atomicshop/wrappers/playwrightw/infra.py +5 -0
  118. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  119. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  120. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  121. atomicshop/wrappers/playwrightw/waits.py +9 -7
  122. atomicshop/wrappers/powershell_networking.py +80 -0
  123. atomicshop/wrappers/psutilw/processes.py +37 -1
  124. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  125. atomicshop/wrappers/pyopensslw.py +9 -2
  126. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  127. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  128. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  129. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  130. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  131. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  132. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  133. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  134. atomicshop/wrappers/socketw/accepter.py +21 -7
  135. atomicshop/wrappers/socketw/certificator.py +216 -150
  136. atomicshop/wrappers/socketw/creator.py +190 -50
  137. atomicshop/wrappers/socketw/dns_server.py +491 -182
  138. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  139. atomicshop/wrappers/socketw/process_getter.py +86 -0
  140. atomicshop/wrappers/socketw/receiver.py +144 -102
  141. atomicshop/wrappers/socketw/sender.py +65 -35
  142. atomicshop/wrappers/socketw/sni.py +334 -165
  143. atomicshop/wrappers/socketw/socket_base.py +134 -0
  144. atomicshop/wrappers/socketw/socket_client.py +137 -95
  145. atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
  146. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  147. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  148. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  149. atomicshop/wrappers/sysmonw.py +1 -1
  150. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  151. atomicshop/wrappers/win_auditw.py +189 -0
  152. atomicshop/wrappers/winregw/__init__.py +0 -0
  153. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  154. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  155. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
  156. atomicshop-3.10.5.dist-info/RECORD +306 -0
  157. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  158. atomicshop/_basics_temp.py +0 -101
  159. atomicshop/a_installs/win/fibratus.py +0 -9
  160. atomicshop/a_installs/win/mongodb.py +0 -9
  161. atomicshop/a_installs/win/pycharm.py +0 -9
  162. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  163. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  164. atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
  165. atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
  166. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  167. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  168. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  169. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  170. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  171. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  172. atomicshop/addons/package_setup/Setup.cmd +0 -7
  173. atomicshop/archiver/_search_in_zip.py +0 -189
  174. atomicshop/archiver/archiver.py +0 -34
  175. atomicshop/archiver/search_in_archive.py +0 -250
  176. atomicshop/archiver/sevenz_app_w.py +0 -86
  177. atomicshop/archiver/sevenzs.py +0 -44
  178. atomicshop/archiver/zips.py +0 -293
  179. atomicshop/file_types.py +0 -24
  180. atomicshop/mitm/config_editor.py +0 -37
  181. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  182. atomicshop/mitm/initialize_mitm_server.py +0 -268
  183. atomicshop/pbtkmultifile_argparse.py +0 -88
  184. atomicshop/permissions.py +0 -151
  185. atomicshop/script_as_string_processor.py +0 -38
  186. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  187. atomicshop/ssh_scripts/process_from_port.py +0 -27
  188. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  189. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  190. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  191. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  192. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  193. atomicshop/wrappers/ffmpegw.py +0 -125
  194. atomicshop/wrappers/fibratusw/install.py +0 -81
  195. atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
  196. atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
  197. atomicshop/wrappers/msiw.py +0 -149
  198. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  199. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  200. atomicshop/wrappers/psutilw/networks.py +0 -45
  201. atomicshop/wrappers/pycharmw.py +0 -81
  202. atomicshop/wrappers/socketw/base.py +0 -59
  203. atomicshop/wrappers/socketw/get_process.py +0 -107
  204. atomicshop/wrappers/wslw.py +0 -191
  205. atomicshop-2.15.11.dist-info/RECORD +0 -302
  206. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  207. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  208. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  209. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  210. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  211. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  212. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  213. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  214. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  215. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  216. /atomicshop/{archiver → permissions}/__init__.py +0 -0
  217. /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
  218. /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
  219. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  220. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  221. {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 networks
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
- OUTBOUND_DNS_PORT: int = 53
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
- logger = loggingw.get_logger_with_level("network." + __name__.rpartition('.')[2])
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.dns_full_logger = loggingw.create_logger(
68
- logger_name="dns",
69
- directory_path=self.config['log']['logs_path'],
70
- add_timedfile=True,
71
- formatter_filehandler='DEFAULT'
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(self):
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.config['dns']['route_to_tcp_server_only_engine_domains']:
111
- message = "Routing only engine domains to Built-in TCP Server."
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 engine domains: {self.domain_list}"
115
- print_api(message, logger=self.logger, color='green')
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.config['dns']['route_to_tcp_server_all_domains']:
118
- message = "Routing all domains to Built-in TCP Server."
119
- print_api(message, logger=self.logger, color='green')
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.config['dns']['regular_resolving']:
122
- message = f"Routing all domains to Live DNS Service: {self.config['dns']['forwarding_dns_service_ipv4']}"
123
- print_api(message, logger=self.logger, color='green')
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.config['dns']['cache_timeout_minutes'],))
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.config['dns']['listening_interface'], self.config['dns']['listening_port']))
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
- # print_api(message, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
160
- print_api(
161
- message, logger=self.dns_full_logger, logger_method='error', traceback_string=True,
162
- oneline=True)
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 Exception:
167
- message = "Unknown Exception: to receive DNS request"
168
- print_api(
169
- message, logger=self.logger, logger_method='critical', traceback_string=True, oneline=True)
170
- self.logger.info("==========")
171
- pass
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 Readable QType (DNS Record Type)
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
- # questioned for resolving, the class (example: IN), DNS Record Type that was questioned and a header.
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.dns_full_logger.info(f"QCLASS: {qclass_string}")
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 from: {client_address}. Full Request: {dns_object.q}"
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 = "!!! Question / Answer is already in the dictionary..."
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 'route_to_tcp_server_only_engine_domains' is set to 'True' in 'config.ini'.
233
- # If so, we need to check if the incoming domain contain any of the 'engine_domains'.
234
- if self.config['dns']['route_to_tcp_server_only_engine_domains']:
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.domain_list):
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.config['dns']['route_to_tcp_server_all_domains']:
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.config['dns']['regular_resolving']:
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
- # If the request is forwarded to TCP server, then we'll put the domain in the domain queue.
261
- # self.request_domain_queue.put(question_domain)
262
- self.request_domain_queue.queue = question_domain
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(self.config['dns']['target_tcp_server_ipv4']),
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.config['dns']['offline_mode']:
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 + " " + str(self.response_ttl) + " " + qtype_string + " " +
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"!!! Question / Answer is in offline mode returning " \
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"!!! Question / Answer is in offline mode returning: " \
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"!!! Question / Answer is in offline mode returning " \
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"!!! Question / Answer is in offline mode returning " \
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, oneline=True)
607
+ traceback_string=True)
347
608
  print_api(f"{dns_built_response}", logger=self.logger, logger_method='critical',
348
- traceback_string=True, oneline=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}. Response: "
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, oneline=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.dns_full_logger.info(
640
+ self.logger.info(
383
641
  f"Forwarding request. Creating UDP socket to: "
384
- f"{self.config['dns']['forwarding_dns_service_ipv4']}:"
385
- f"{OUTBOUND_DNS_PORT}")
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.dns_full_logger.info(message)
650
+ self.logger.info(message)
393
651
 
394
652
  google_dns_ipv4_socket.sendto(client_data, (
395
- self.config['dns']['forwarding_dns_service_ipv4'],
396
- OUTBOUND_DNS_PORT
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.dns_full_logger.info(message)
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.config['dns']['forwarding_dns_service_ipv4']}]. "
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.dns_full_logger.info(
436
- f"Answer received from: {self.config['dns']['forwarding_dns_service_ipv4']}")
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.dns_full_logger.info("Closed socket to forwarding service")
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
- # if dns_object.q.qtype == dnslib.QTYPE.AAAA:
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.dns_full_logger.info(f"Response IP: {dns_built_response.short()}")
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.dns_full_logger, logger_method='info', oneline=True)
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.dns_full_logger, logger_method='info', oneline=True)
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
- if dns_response_parsed.a:
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.dns_full_logger.info(f"Response IP: {rr.rdata}")
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.dns_full_logger, logger_method='info', oneline=True)
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
- message = f"Response Full Details: {dns_response_parsed}"
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.dns_full_logger.info("DNS Response sent...")
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.config['log']['logs_path'] + os.sep + self.known_domains_filename, 'w'
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.dns_full_logger.info(
576
- f"Saved new known domains file: "
577
- f"{self.config['log']['logs_path'] + os.sep + self.known_domains_filename}")
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.config['dns']['offline_mode']:
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 previous DNS
598
- # requests for the same IPv4 address, then we'll add this domain to the known domains
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.config['log']['logs_path'] + os.sep + self.known_ipv4_filename, 'w'
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.dns_full_logger.info(
635
- f"Saved new known IPv4 addresses file: "
636
- f"{self.config['log']['logs_path'] + os.sep + self.known_ipv4_filename}")
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.config['log']['logs_path'] + os.sep + self.known_dns_ipv4_by_time_filename, 'a'
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
- # SSH Remote / LOCALHOST script execution to identify process section
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, oneline=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)