atomicshop 2.11.47__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 (268) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/addons/process_list/compile.cmd +7 -0
  4. atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
  5. atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
  6. atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
  7. atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +8 -1
  8. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  9. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  10. atomicshop/a_mains/github_wrapper.py +11 -0
  11. atomicshop/a_mains/install_ca_certificate.py +172 -0
  12. atomicshop/{addons/mains → a_mains}/msi_unpacker.py +3 -1
  13. atomicshop/a_mains/process_from_port.py +119 -0
  14. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  15. atomicshop/a_mains/update_config_toml.py +38 -0
  16. atomicshop/appointment_management.py +5 -3
  17. atomicshop/basics/ansi_escape_codes.py +3 -1
  18. atomicshop/basics/argparse_template.py +2 -0
  19. atomicshop/basics/booleans.py +27 -30
  20. atomicshop/basics/bytes_arrays.py +43 -0
  21. atomicshop/basics/classes.py +149 -1
  22. atomicshop/basics/dicts.py +12 -0
  23. atomicshop/basics/enums.py +2 -2
  24. atomicshop/basics/exceptions.py +5 -1
  25. atomicshop/basics/list_of_classes.py +29 -0
  26. atomicshop/basics/list_of_dicts.py +69 -5
  27. atomicshop/basics/lists.py +14 -0
  28. atomicshop/basics/multiprocesses.py +374 -50
  29. atomicshop/basics/package_module.py +10 -0
  30. atomicshop/basics/strings.py +160 -7
  31. atomicshop/basics/threads.py +14 -0
  32. atomicshop/basics/tracebacks.py +13 -4
  33. atomicshop/certificates.py +153 -52
  34. atomicshop/config_init.py +12 -7
  35. atomicshop/console_user_response.py +7 -14
  36. atomicshop/consoles.py +9 -0
  37. atomicshop/datetimes.py +98 -0
  38. atomicshop/diff_check.py +340 -40
  39. atomicshop/dns.py +128 -12
  40. atomicshop/etws/_pywintrace_fix.py +17 -0
  41. atomicshop/etws/const.py +38 -0
  42. atomicshop/etws/providers.py +21 -0
  43. atomicshop/etws/sessions.py +43 -0
  44. atomicshop/etws/trace.py +168 -0
  45. atomicshop/etws/traces/trace_dns.py +162 -0
  46. atomicshop/etws/traces/trace_sysmon_process_creation.py +126 -0
  47. atomicshop/etws/traces/trace_tcp.py +130 -0
  48. atomicshop/file_io/csvs.py +222 -24
  49. atomicshop/file_io/docxs.py +35 -18
  50. atomicshop/file_io/file_io.py +35 -19
  51. atomicshop/file_io/jsons.py +49 -0
  52. atomicshop/file_io/tomls.py +139 -0
  53. atomicshop/filesystem.py +864 -293
  54. atomicshop/get_process_list.py +133 -0
  55. atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +52 -19
  56. atomicshop/http_parse.py +149 -93
  57. atomicshop/ip_addresses.py +6 -1
  58. atomicshop/mitm/centered_settings.py +132 -0
  59. atomicshop/mitm/config_static.py +207 -0
  60. atomicshop/mitm/config_toml_editor.py +55 -0
  61. atomicshop/mitm/connection_thread_worker.py +875 -357
  62. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  63. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  64. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  65. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  66. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  67. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  68. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  69. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  70. atomicshop/mitm/engines/create_module_template.py +58 -14
  71. atomicshop/mitm/import_config.py +359 -139
  72. atomicshop/mitm/initialize_engines.py +160 -74
  73. atomicshop/mitm/message.py +64 -23
  74. atomicshop/mitm/mitm_main.py +892 -0
  75. atomicshop/mitm/recs_files.py +183 -0
  76. atomicshop/mitm/shared_functions.py +4 -10
  77. atomicshop/mitm/ssh_tester.py +82 -0
  78. atomicshop/mitm/statistic_analyzer.py +257 -166
  79. atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py +136 -0
  80. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +525 -0
  81. atomicshop/monitor/change_monitor.py +96 -120
  82. atomicshop/monitor/checks/dns.py +139 -70
  83. atomicshop/monitor/checks/file.py +77 -0
  84. atomicshop/monitor/checks/network.py +81 -77
  85. atomicshop/monitor/checks/process_running.py +33 -34
  86. atomicshop/monitor/checks/url.py +94 -0
  87. atomicshop/networks.py +671 -0
  88. atomicshop/on_exit.py +205 -0
  89. atomicshop/package_mains_processor.py +84 -0
  90. atomicshop/permissions/permissions.py +22 -0
  91. atomicshop/permissions/ubuntu_permissions.py +239 -0
  92. atomicshop/permissions/win_permissions.py +33 -0
  93. atomicshop/print_api.py +24 -41
  94. atomicshop/process.py +63 -17
  95. atomicshop/process_poller/__init__.py +0 -0
  96. atomicshop/process_poller/pollers/__init__.py +0 -0
  97. atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
  98. atomicshop/process_poller/process_pool.py +207 -0
  99. atomicshop/process_poller/simple_process_pool.py +311 -0
  100. atomicshop/process_poller/tracer_base.py +45 -0
  101. atomicshop/process_poller/tracers/__init__.py +0 -0
  102. atomicshop/process_poller/tracers/event_log.py +46 -0
  103. atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
  104. atomicshop/python_file_patcher.py +1 -1
  105. atomicshop/python_functions.py +27 -75
  106. atomicshop/question_answer_engine.py +2 -2
  107. atomicshop/scheduling.py +24 -5
  108. atomicshop/sound.py +4 -2
  109. atomicshop/speech_recognize.py +8 -0
  110. atomicshop/ssh_remote.py +158 -172
  111. atomicshop/startup/__init__.py +0 -0
  112. atomicshop/startup/win/__init__.py +0 -0
  113. atomicshop/startup/win/startup_folder.py +53 -0
  114. atomicshop/startup/win/task_scheduler.py +119 -0
  115. atomicshop/system_resource_monitor.py +61 -46
  116. atomicshop/system_resources.py +8 -8
  117. atomicshop/tempfiles.py +1 -2
  118. atomicshop/timer.py +30 -11
  119. atomicshop/urls.py +41 -0
  120. atomicshop/venvs.py +28 -0
  121. atomicshop/versioning.py +27 -0
  122. atomicshop/web.py +110 -25
  123. atomicshop/web_apis/__init__.py +0 -0
  124. atomicshop/web_apis/google_custom_search.py +44 -0
  125. atomicshop/web_apis/google_llm.py +188 -0
  126. atomicshop/websocket_parse.py +450 -0
  127. atomicshop/wrappers/certauthw/certauth.py +1 -0
  128. atomicshop/wrappers/cryptographyw.py +29 -8
  129. atomicshop/wrappers/ctyping/etw_winapi/__init__.py +0 -0
  130. atomicshop/wrappers/ctyping/etw_winapi/const.py +335 -0
  131. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +393 -0
  132. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  133. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  134. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +13 -9
  135. atomicshop/wrappers/ctyping/msi_windows_installer/tables.py +35 -0
  136. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  137. atomicshop/wrappers/ctyping/win_console.py +39 -0
  138. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  139. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  140. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  141. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  142. atomicshop/wrappers/factw/get_file_data.py +12 -5
  143. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  144. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  145. atomicshop/wrappers/factw/postgresql/firmware.py +4 -6
  146. atomicshop/wrappers/githubw.py +583 -51
  147. atomicshop/wrappers/loggingw/consts.py +49 -0
  148. atomicshop/wrappers/loggingw/filters.py +102 -0
  149. atomicshop/wrappers/loggingw/formatters.py +58 -71
  150. atomicshop/wrappers/loggingw/handlers.py +459 -40
  151. atomicshop/wrappers/loggingw/loggers.py +19 -0
  152. atomicshop/wrappers/loggingw/loggingw.py +1010 -178
  153. atomicshop/wrappers/loggingw/reading.py +344 -19
  154. atomicshop/wrappers/mongodbw/__init__.py +0 -0
  155. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  156. atomicshop/wrappers/mongodbw/mongodbw.py +1432 -0
  157. atomicshop/wrappers/netshw.py +271 -0
  158. atomicshop/wrappers/playwrightw/engine.py +34 -19
  159. atomicshop/wrappers/playwrightw/infra.py +5 -0
  160. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  161. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  162. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  163. atomicshop/wrappers/playwrightw/waits.py +9 -7
  164. atomicshop/wrappers/powershell_networking.py +80 -0
  165. atomicshop/wrappers/psutilw/processes.py +81 -0
  166. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  167. atomicshop/wrappers/psutilw/psutilw.py +9 -0
  168. atomicshop/wrappers/pyopensslw.py +9 -2
  169. atomicshop/wrappers/pywin32w/__init__.py +0 -0
  170. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  171. atomicshop/wrappers/pywin32w/console.py +34 -0
  172. atomicshop/wrappers/pywin32w/win_event_log/__init__.py +0 -0
  173. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  174. atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +212 -0
  175. atomicshop/wrappers/pywin32w/win_event_log/subscribes/__init__.py +0 -0
  176. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +57 -0
  177. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +49 -0
  178. atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
  179. atomicshop/wrappers/pywin32w/winshell.py +19 -0
  180. atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
  181. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  182. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  183. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  184. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  185. atomicshop/wrappers/socketw/accepter.py +21 -7
  186. atomicshop/wrappers/socketw/certificator.py +216 -150
  187. atomicshop/wrappers/socketw/creator.py +190 -50
  188. atomicshop/wrappers/socketw/dns_server.py +500 -173
  189. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  190. atomicshop/wrappers/socketw/process_getter.py +86 -0
  191. atomicshop/wrappers/socketw/receiver.py +144 -102
  192. atomicshop/wrappers/socketw/sender.py +65 -35
  193. atomicshop/wrappers/socketw/sni.py +334 -165
  194. atomicshop/wrappers/socketw/socket_base.py +134 -0
  195. atomicshop/wrappers/socketw/socket_client.py +137 -95
  196. atomicshop/wrappers/socketw/socket_server_tester.py +14 -9
  197. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  198. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  199. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  200. atomicshop/wrappers/sysmonw.py +157 -0
  201. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  202. atomicshop/wrappers/win_auditw.py +189 -0
  203. atomicshop/wrappers/winregw/__init__.py +0 -0
  204. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  205. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  206. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -49
  207. atomicshop-3.10.5.dist-info/RECORD +306 -0
  208. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  209. atomicshop/_basics_temp.py +0 -101
  210. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  211. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  212. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  213. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  214. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  215. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  216. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  217. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  218. atomicshop/addons/package_setup/Setup.cmd +0 -7
  219. atomicshop/addons/process_list/compile.cmd +0 -2
  220. atomicshop/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
  221. atomicshop/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
  222. atomicshop/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
  223. atomicshop/archiver/_search_in_zip.py +0 -189
  224. atomicshop/archiver/archiver.py +0 -34
  225. atomicshop/archiver/search_in_archive.py +0 -250
  226. atomicshop/archiver/sevenz_app_w.py +0 -86
  227. atomicshop/archiver/sevenzs.py +0 -44
  228. atomicshop/archiver/zips.py +0 -293
  229. atomicshop/etw/dns_trace.py +0 -118
  230. atomicshop/etw/etw.py +0 -61
  231. atomicshop/file_types.py +0 -24
  232. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  233. atomicshop/mitm/initialize_mitm_server.py +0 -240
  234. atomicshop/monitor/checks/hash.py +0 -44
  235. atomicshop/monitor/checks/hash_checks/file.py +0 -55
  236. atomicshop/monitor/checks/hash_checks/url.py +0 -62
  237. atomicshop/pbtkmultifile_argparse.py +0 -88
  238. atomicshop/permissions.py +0 -110
  239. atomicshop/process_poller.py +0 -237
  240. atomicshop/script_as_string_processor.py +0 -38
  241. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  242. atomicshop/ssh_scripts/process_from_port.py +0 -27
  243. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  244. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  245. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  246. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  247. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  248. atomicshop/wrappers/ffmpegw.py +0 -125
  249. atomicshop/wrappers/loggingw/checks.py +0 -20
  250. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  251. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  252. atomicshop/wrappers/socketw/base.py +0 -59
  253. atomicshop/wrappers/socketw/get_process.py +0 -107
  254. atomicshop/wrappers/wslw.py +0 -191
  255. atomicshop-2.11.47.dist-info/RECORD +0 -251
  256. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  257. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  258. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  259. /atomicshop/{addons/mains → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  260. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  261. /atomicshop/{addons/mains → a_mains}/search_for_hyperlinks_in_docx.py +0 -0
  262. /atomicshop/{archiver → etws}/__init__.py +0 -0
  263. /atomicshop/{etw → etws/traces}/__init__.py +0 -0
  264. /atomicshop/{monitor/checks/hash_checks → mitm/statistic_analyzer_helper}/__init__.py +0 -0
  265. /atomicshop/{wrappers/nodejsw → permissions}/__init__.py +0 -0
  266. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  267. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  268. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
@@ -3,47 +3,251 @@ 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
13
+ from ..psutilw import psutil_networks
14
+ from ...basics import booleans, tracebacks
15
+ from ...file_io import csvs
16
+ from ... import networks
9
17
 
18
+ # noinspection PyPackageRequirements
10
19
  import dnslib
20
+ # noinspection PyPackageRequirements
11
21
  from dnslib import DNSRecord, DNSHeader, RR, A
12
22
 
13
23
 
24
+ class DnsPortInUseError(Exception):
25
+ pass
26
+
27
+
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
+ )
139
+
140
+
14
141
  class DnsServer:
15
142
  """
16
143
  DnsServer class is responsible to handle DNS Requests on port 53 based on configuration and send DNS Response back.
17
144
  """
18
- 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)
19
237
 
20
- def __init__(self, config):
21
238
  # Settings for static DNS Responses in offline mode.
22
- self.offline_route_ipv4 = '10.10.10.10'
23
- self.offline_route_ipv6 = 'fe80::3c09:df29:d52b:af39'
24
- self.offline_route_domain = 'domain.com'
25
- 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 = \
26
243
  '. 86391 IN SOA domain.com. domain.com. 2022012500 1800 900 604800 86400'
244
+ # self.offline_https_answer: str = str()
27
245
 
28
- # Other settings.
29
- # Full domain list to pass to TCP Server module.
30
- self.domain_list: list = list()
31
-
32
- # Set Buffer size of the connection while receiving messages. The function uses this variable right away.
33
- self.buffer_size_receive: int = 8192
34
- # TTL variable that is going to be returned in DNS response.
35
- self.response_ttl: int = 60
36
- # How many times the DNS Service will retry on errors (socket connect / request send / response receive)
37
- self.dns_service_retries: int = 5
38
246
  # If forwarding to Live DNS Service fails. Currently, we didn't send anything, so it's 'False'.
39
247
  self.retried: bool = False
40
248
  # Defining cache dictionary for assigning DNS Questions to DNS Answers
41
249
  self.dns_questions_to_answers_cache: dict = dict()
42
250
 
43
- # Queue for all the requested domains that hit the dns server.
44
- # self.request_domain_queue: queue.Queue = queue.Queue()
45
- self.request_domain_queue = None
46
-
47
251
  # Filename to save all the known domains and their relative IPv4 addresses.
48
252
  self.known_domains_filename: str = 'dns_known_domains.txt'
49
253
  # Filename to save all the known IPv4 addresses and their relative domains.
@@ -51,13 +255,77 @@ class DnsServer:
51
255
  # Filename to save domains and their IPv4 addresses by time they hit the DNS server.
52
256
  self.known_dns_ipv4_by_time_filename: str = 'dns_ipv4_by_time.txt'
53
257
 
54
- # Configuration object with all the settings.
55
- self.config = config
56
-
57
258
  # Logger that logs all the DNS Requests and responses in DNS format. These entries will not present in
58
259
  # network log of TCP Server module.
59
- self.dns_full_logger = loggingw.get_logger_with_timedfilehandler(
60
- logger_name="dns", directory_path=self.config['log']['logs_path'], disable_duplicate_ms=True)
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)
61
329
 
62
330
  def thread_worker_empty_dns_cache(self, function_sleep_time: int):
63
331
  """
@@ -71,10 +339,15 @@ class DnsServer:
71
339
  self.dns_questions_to_answers_cache = dict()
72
340
  self.logger.info("*** DNS cache cleared")
73
341
 
74
- def start(self):
342
+ def start(
343
+ self,
344
+ is_ready_multiprocessing: multiprocessing.Event = None
345
+ ):
75
346
  """
76
347
  Main DNS Server function to start it.
77
348
 
349
+ :param is_ready_multiprocessing: multiprocessing.Event: Event to signal that the DNS Server is ready.
350
+
78
351
  :return: None.
79
352
  """
80
353
 
@@ -90,31 +363,32 @@ class DnsServer:
90
363
  known_a_records_ipv4_dict: dict = dict()
91
364
 
92
365
  # Check if 'route_to_tcp_server_only_engine_domains' was set to 'True' and output message accordingly.
93
- if self.config['dns']['route_to_tcp_server_only_engine_domains']:
94
- 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."
95
368
  print_api(message, logger=self.logger)
96
369
 
97
- message = f"Current engine domains: {self.domain_list}"
98
- 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')
99
372
 
100
- if self.config['dns']['route_to_tcp_server_all_domains']:
101
- message = "Routing all domains to Built-in TCP Server."
102
- 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')
103
376
 
104
- if self.config['dns']['regular_resolving']:
105
- message = f"Routing all domains to Live DNS Service: {self.config['dns']['forwarding_dns_service_ipv4']}"
106
- 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')
107
380
 
108
381
  # The list that will hold all the threads that can be joined later
109
382
  threads_list: list = list()
110
383
 
111
384
  # Starting a thread that will empty the DNS Cache lists
112
385
  thread_current = threading.Thread(target=self.thread_worker_empty_dns_cache,
113
- args=(self.config['dns']['cache_timeout_minutes'],))
114
- # Append to list of threads, so they can be "joined" later
115
- threads_list.append(thread_current)
386
+ args=(self.cache_timeout_minutes,))
387
+ thread_current.daemon = True
116
388
  # Start the thread
117
389
  thread_current.start()
390
+ # Append to list of threads, so they can be "joined" later
391
+ threads_list.append(thread_current)
118
392
 
119
393
  # To handle DNS requests UDP socket is needed.
120
394
  # AF_INET - Socket family of IPv4
@@ -124,9 +398,15 @@ class DnsServer:
124
398
 
125
399
  # Binding / assigning the port to the server / this script, that is going to be used for
126
400
  # receiving connections.
127
- 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()
128
406
 
129
407
  while True:
408
+ forward_to_tcp_server = False # reset every request
409
+
130
410
  # Needed this logging line when DNS was separate process.
131
411
  # self.logger.info("Waiting to receive new requests...")
132
412
 
@@ -135,24 +415,30 @@ class DnsServer:
135
415
  client_data: bytes
136
416
  client_address: tuple
137
417
  except ConnectionResetError:
418
+ client_address = (str(), int())
419
+ traceback_string = tracebacks.get_as_string(one_line=True)
138
420
  # This error happens when the client closes the connection before the server.
139
421
  # This is not an error for a DNS Server, but we'll log it anyway only with the full DNS logger.
140
- message = "Error: to receive DNS request, An existing connection was forcibly closed"
141
- # print_api(message, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
142
- print_api(
143
- message, logger=self.dns_full_logger, logger_method='error', traceback_string=True,
144
- oneline=True)
145
- self.dns_full_logger.info("==========")
146
- 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)
147
427
  continue
148
- except Exception:
149
- message = "Unknown Exception: to receive DNS request"
150
- print_api(
151
- message, logger=self.logger, logger_method='critical', traceback_string=True, oneline=True)
152
- self.logger.info("==========")
153
- 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)
154
439
  continue
155
440
 
441
+ # noinspection PyBroadException
156
442
  try:
157
443
  # This is the real point when the request received was logged, but since it takes too much place
158
444
  # on the screen, moved it to full request logging position.
@@ -162,11 +448,11 @@ class DnsServer:
162
448
 
163
449
  # Received DNS request that needs to be parsed to readable format
164
450
  dns_object: dnslib.dns.DNSRecord = DNSRecord.parse(client_data)
165
- # "qtype" returns as numeric identification, we need to convert it to Readable QType (DNS Record Type)
166
- # 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
167
453
  # "dns_object.q" is the Question from the client that holds all the DNS question data,
168
- # like which domain was
169
- # 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.
170
456
  # "dns_object.q.qtype" returns only QType of the Question
171
457
  qtype_string: str = dnslib.QTYPE[dns_object.q.qtype]
172
458
  # "qclass" returns as numeric identification, we need to convert it
@@ -182,25 +468,18 @@ class DnsServer:
182
468
  # "dns_object.q.qname" returns only the questioned domain with "." (dot) in the end,
183
469
  # which needs to be removed.
184
470
  question_domain: str = str(dns_object.q.qname)[:-1]
185
- self.dns_full_logger.info(f"QCLASS: {qclass_string}")
186
- self.dns_full_logger.info(f"QTYPE: {qtype_string}")
187
- 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)
188
472
 
189
- 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}.")
190
475
  self.logger.info(message)
191
- self.dns_full_logger.info(message)
192
-
193
- self.dns_full_logger.info("--")
194
476
 
195
477
  # Nullifying the DNS cache for current request before check.
196
478
  dns_cached_request = False
197
479
  # Check if the received data request from client is already in the cache
198
480
  if client_data in self.dns_questions_to_answers_cache:
199
- message = "!!! Question / Answer is already in the dictionary..."
481
+ # message = "!!! Request / Response is already in the dictionary..."
200
482
  # self.logger.info(message)
201
- self.dns_full_logger.info(message)
202
-
203
- self.dns_full_logger.info("--")
204
483
 
205
484
  # Get the response from the cached answers list
206
485
  dns_response = self.dns_questions_to_answers_cache[client_data]
@@ -211,12 +490,12 @@ class DnsServer:
211
490
  else:
212
491
  # Check if the incoming Record is "A" record.
213
492
  if qtype_string == "A":
214
- # Check if 'route_to_tcp_server_only_engine_domains' is set to 'True' in 'config.ini'.
215
- # If so, we need to check if the incoming domain contain any of the 'engine_domains'.
216
- 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:
217
496
  # If current query domain (+ subdomains) CONTAIN any of the domains from modules config
218
497
  # files and current request contains "A" (IPv4) record.
219
- 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()):
220
499
  # If incoming domain contains any of the 'engine_domains' then domain will
221
500
  # be forwarded to our TCP Server.
222
501
  forward_to_tcp_server = True
@@ -225,12 +504,12 @@ class DnsServer:
225
504
 
226
505
  # If 'route_to_tcp_server_all_domains' was set to 'False' in 'config.ini' file then
227
506
  # we'll forward all 'A' records domains to the Built-in TCP Server.
228
- if self.config['dns']['route_to_tcp_server_all_domains']:
507
+ if self.resolve_all_domains_to_ipv4_enable:
229
508
  forward_to_tcp_server = True
230
509
 
231
510
  # If 'regular_resolving' was set to 'True' in 'config.ini' file then
232
511
  # we'll forward all 'A' records domains to the Live DNS Service.
233
- if self.config['dns']['regular_resolving']:
512
+ if self.resolve_regular_pass_thru:
234
513
  forward_to_tcp_server = False
235
514
 
236
515
  # If incoming record is not an "A" record, then it will not be forwarded to our TCP Server.
@@ -239,9 +518,15 @@ class DnsServer:
239
518
 
240
519
  # If 'forward_to_tcp_server' is 'True' we'll resolve the record with our TCP Server IP address.
241
520
  if forward_to_tcp_server:
242
- # If the request is forwarded to TCP server, then we'll put the domain in the domain queue.
243
- # self.request_domain_queue.put(question_domain)
244
- 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
245
530
 
246
531
  # Make DNS response that will refer TCP traffic to our server
247
532
  dns_built_response = DNSRecord(
@@ -250,7 +535,7 @@ class DnsServer:
250
535
  # q=DNSQuestion(question_domain),
251
536
  q=dns_object.q,
252
537
  a=RR(question_domain,
253
- rdata=A(self.config['dns']['target_tcp_server_ipv4']),
538
+ rdata=A(resolved_target_ipv4),
254
539
  ttl=self.response_ttl)
255
540
  )
256
541
  # Encode the response that was built above to legit DNS Response
@@ -261,7 +546,7 @@ class DnsServer:
261
546
  # any of the domains from modules config files
262
547
  else:
263
548
  # If we're in offline mode
264
- if self.config['dns']['offline_mode']:
549
+ if self.offline_mode:
265
550
  # Make DNS response that will refer TCP traffic to our server
266
551
  # dns_question = DNSRecord.question(question_domain)
267
552
  dns_built_response = dns_object.reply()
@@ -272,14 +557,13 @@ class DnsServer:
272
557
  if qtype_string == "AAAA":
273
558
  dns_built_response.add_answer(
274
559
  *RR.fromZone(
275
- question_domain + " " + str(self.response_ttl) + " " + qtype_string + " " +
276
- self.offline_route_ipv6)
560
+ f'{question_domain} {str(self.response_ttl)} {qtype_string} '
561
+ f'{self.offline_route_ipv6}')
277
562
  )
278
563
 
279
- message = f"!!! Question / Answer is in offline mode returning " \
564
+ message = f"!!! Request / Response is in offline mode returning " \
280
565
  f"{self.offline_route_ipv6}."
281
566
  self.logger.info(message)
282
- self.dns_full_logger.info(message)
283
567
 
284
568
  # SRV Record type explanation:
285
569
  # https://www.cloudflare.com/learning/dns/dns-records/dns-srv-record/
@@ -290,44 +574,39 @@ class DnsServer:
290
574
  # com. domain.com. 2022012500 1800 900 604800 86400
291
575
  # Basically SOA is the same, but can be with additional fields.
292
576
  # Since, it's offline and not online - we don't really care.
293
- elif qtype_string == "SRV" or qtype_string == "SOA":
577
+ elif qtype_string == "SRV" or qtype_string == "SOA" or qtype_string == "HTTPS":
294
578
  dns_built_response.add_answer(*RR.fromZone(self.offline_srv_answer))
295
579
 
296
- message = f"!!! Question / Answer is in offline mode returning: " \
580
+ message = f"!!! Request / Response is in offline mode returning: " \
297
581
  f"{self.offline_srv_answer}."
298
582
  self.logger.info(message)
299
- self.dns_full_logger.info(message)
300
583
  elif qtype_string == "ANY":
301
584
  dns_built_response.add_answer(
302
- # *RR.fromZone(question_domain + " " + str(response_ttl) + " " + qclass_string +
303
- # " CNAME " + dns_server_offline_route_domain)
304
585
  *RR.fromZone(question_domain + " " + str(self.response_ttl) + " CNAME " +
305
586
  self.offline_route_domain)
306
587
  )
307
588
 
308
- message = f"!!! Question / Answer is in offline mode returning " \
589
+ message = f"!!! Request / Response is in offline mode returning " \
309
590
  f"{self.offline_route_domain}."
310
591
  self.logger.info(message)
311
- self.dns_full_logger.info(message)
312
592
  else:
313
593
  dns_built_response.add_answer(
314
594
  *RR.fromZone(
315
- question_domain + " " + str(self.response_ttl) + " " + qtype_string + " " +
316
- self.offline_route_ipv4)
595
+ question_domain + " " + str(self.response_ttl) + " " + qtype_string +
596
+ " " + self.offline_route_ipv4)
317
597
  )
318
598
 
319
- message = f"!!! Question / Answer is in offline mode returning " \
599
+ message = f"!!! Request / Response is in offline mode returning " \
320
600
  f"{self.offline_route_ipv4}."
321
601
  self.logger.info(message)
322
- self.dns_full_logger.info(message)
323
602
  # Values error means in most cases that you create wrong response
324
603
  # for specific type of request.
325
604
  except ValueError:
326
605
  message = f"Looks like wrong type of response for QTYPE: {qtype_string}. Response: "
327
606
  print_api(message, logger=self.logger, logger_method='critical',
328
- traceback_string=True, oneline=True)
607
+ traceback_string=True)
329
608
  print_api(f"{dns_built_response}", logger=self.logger, logger_method='critical',
330
- traceback_string=True, oneline=True)
609
+ traceback_string=True)
331
610
  # Pass the exception.
332
611
  pass
333
612
  # Continue to the next DNS request, since there's nothing to do here right now.
@@ -335,18 +614,15 @@ class DnsServer:
335
614
  # General exception in response creation.
336
615
  except Exception:
337
616
  message = \
338
- 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}")
339
619
  print_api(message, logger=self.logger, logger_method='critical',
340
- traceback_string=True, oneline=True)
341
- print_api(f"{dns_built_response}", logger=self.logger, logger_method='critical',
342
- traceback_string=True, oneline=True)
620
+ traceback_string=True)
343
621
  # Pass the exception.
344
622
  pass
345
623
  # Continue to the next DNS request, since there's nothing to do here right now.
346
624
  continue
347
625
 
348
- self.dns_full_logger.info("--")
349
-
350
626
  # Encode the response that was built above to legit DNS Response
351
627
  dns_response = dns_built_response.pack()
352
628
  # If we're in online mode
@@ -361,26 +637,26 @@ class DnsServer:
361
637
  # Since, it's probably going to succeed.
362
638
  if counter > 0:
363
639
  self.logger.info(f"Retry #: {counter}/{self.dns_service_retries}")
364
- self.dns_full_logger.info(
640
+ self.logger.info(
365
641
  f"Forwarding request. Creating UDP socket to: "
366
- f"{self.config['dns']['forwarding_dns_service_ipv4']}:"
367
- f"{self.config['dns']['listening_port']}")
642
+ f"{self.forwarding_dns_service_ipv4}:"
643
+ f"{self.forwarding_dns_service_port}")
368
644
  try:
369
645
  google_dns_ipv4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
370
646
  google_dns_ipv4_socket.settimeout(5)
371
647
 
372
648
  message = "Socket created, Forwarding..."
373
649
  # self.logger.info(message)
374
- self.dns_full_logger.info(message)
650
+ self.logger.info(message)
375
651
 
376
652
  google_dns_ipv4_socket.sendto(client_data, (
377
- self.config['dns']['forwarding_dns_service_ipv4'],
378
- self.config['dns']['listening_port']
653
+ self.forwarding_dns_service_ipv4,
654
+ self.forwarding_dns_service_port
379
655
  ))
380
656
  # The script needs to wait a second or receive can hang
381
657
  message = "Request sent to the forwarding DNS, Receiving the answer..."
382
658
  # self.logger.info(message)
383
- self.dns_full_logger.info(message)
659
+ self.logger.info(message)
384
660
 
385
661
  dns_response, google_address = \
386
662
  google_dns_ipv4_socket.recvfrom(self.buffer_size_receive)
@@ -399,9 +675,8 @@ class DnsServer:
399
675
  self.logger.info(
400
676
  f"Retried {self.dns_service_retries} times. "
401
677
  f"Couldn't forward DNS request to: "
402
- f"[{self.config['dns']['forwarding_dns_service_ipv4']}]. "
678
+ f"[{self.forwarding_dns_service_ipv4}]. "
403
679
  f"Continuing to next request.")
404
- self.dns_full_logger.info("==========")
405
680
 
406
681
  # From here continue to the next iteration of While loop.
407
682
  continue
@@ -414,36 +689,28 @@ class DnsServer:
414
689
  if retried:
415
690
  continue
416
691
 
417
- self.dns_full_logger.info(
418
- 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}")
419
694
 
420
695
  # Closing the socket to forwarding service
421
696
  google_dns_ipv4_socket.close()
422
- self.dns_full_logger.info("Closed socket to forwarding service")
697
+ self.logger.info("Closed socket to forwarding service")
423
698
 
424
699
  # Appending current DNS Request and DNS Answer to the Cache
425
700
  self.dns_questions_to_answers_cache.update({client_data: dns_response})
426
701
 
427
- # if dns_object.q.qtype == dnslib.QTYPE.AAAA:
428
-
429
- # dns_response = dns_object.reply()
430
- # dns_response.add_answer(*RR.fromZone(f"{question_domain} 60 {qtype_object} 8.8.8.8"))
431
-
432
- # dns_built_response = \
433
- # DNSRecord(
434
- # DNSHeader(id=dns_object.header.id, qr=1, aa=1, ra=1), q=DNSQuestion(question_domain),
435
- # a=RR.fromZone(question_domain + " 60 " + qtype_object + " " + dns_server_offline_ipv4))
436
-
437
- # If 'forward_to_tcp_server' it means that we built the response, and we don't need to reparse it, since
438
- # 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.
439
704
  if forward_to_tcp_server:
440
- 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)
441
708
 
442
709
  message = f"Response Details: {dns_built_response.rr}"
443
- 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)
444
711
 
445
- message = f"Response Full Details: {dns_built_response.format(prefix='', sort=True)}"
446
- 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)
447
714
 
448
715
  # Now we can turn it to false, so it won't trigger this
449
716
  # condition next time if the response was not built
@@ -459,30 +726,34 @@ class DnsServer:
459
726
  # Reinitializing the ipv4 addresses list.
460
727
  ipv4_addresses = list()
461
728
 
462
- 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:
463
731
  for rr in dns_response_parsed.rr:
464
732
  if isinstance(rr.rdata, A):
465
- 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}")
466
737
 
467
738
  # Adding the address to the list as 'str' object and not 'dnslib.dns.A'.
468
739
  ipv4_addresses.append(str(rr.rdata))
469
740
 
470
- message = f"Response Details: {dns_response_parsed.rr}"
471
- print_api(message, logger=self.dns_full_logger, logger_method='info', oneline=True)
472
-
473
- message = f"Response Full Details: {dns_response_parsed}"
474
- 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)
475
746
 
476
- self.dns_full_logger.info("Sending DNS response back to client...")
747
+ self.logger.info("Sending DNS response back to client...")
477
748
  main_socket_object.sendto(dns_response, client_address)
478
- self.dns_full_logger.info("DNS Response sent...")
749
+ self.logger.info("DNS Response sent...")
479
750
 
480
751
  # 'ipv4_addresses' list contains entries of type 'dnslib.dns.A' and not string.
481
752
  # We'll convert each entry to string, so strings can be searched in this list.
482
753
  # for index, ip_address in enumerate(ipv4_addresses):
483
754
  # ipv4_addresses[index] = str(ip_address)
484
755
 
485
- # ==============================================================================================================
756
+ # ==================================================================================================
486
757
  # # Known domain dictionary of last 2 A records' management.
487
758
  #
488
759
  # # Sorting the addresses, so it will be easier to compare dictionaries in the list.
@@ -504,7 +775,7 @@ class DnsServer:
504
775
  #
505
776
  # dns.logger.info(f"Latest known list: {known_a_records_domains_list_last_entries}")
506
777
 
507
- # ==============================================================================================================
778
+ # ==================================================================================================
508
779
  # Known domain list management (A Records only)
509
780
 
510
781
  # If current request is in the cache,
@@ -550,20 +821,20 @@ class DnsServer:
550
821
 
551
822
  # Save this string object as log file.
552
823
  with open(
553
- self.config['log']['logs_path'] + os.sep + self.known_domains_filename, 'w'
824
+ self.log_directory_path + os.sep + self.known_domains_filename, 'w'
554
825
  ) as output_file:
555
826
  output_file.write(record_string_line)
556
827
 
557
- self.dns_full_logger.info(
558
- f"Saved new known domains file: "
559
- 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}")
560
831
 
561
832
  # Known domain list managements EOF
562
- # ==============================================================================================================
833
+ # ==================================================================================================
563
834
  # Known IPv4 address to domains list management (A Records only)
564
835
 
565
836
  # If DNS Server 'offline_mode' was set to 'False'.
566
- if not self.config['dns']['offline_mode']:
837
+ if not self.offline_mode:
567
838
  dump_ipv4_dictionary_to_file = False
568
839
  # If IPv4 address list is not empty, meaning this DNS request was A type.
569
840
  if ipv4_addresses:
@@ -576,9 +847,9 @@ class DnsServer:
576
847
  # If so, get the list of current domains for current ipv4 address.
577
848
  current_domains_list = known_a_records_ipv4_dict[current_ip_address]
578
849
 
579
- # If current question domain is not in the known domains that we had from previous DNS
580
- # requests for the same IPv4 address, then we'll add this domain to the known domains
581
- # 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.
582
853
  # And update the dictionary of known IPv4 addresses and their domains.
583
854
  if question_domain not in current_domains_list:
584
855
  current_domains_list.append(question_domain)
@@ -609,16 +880,16 @@ class DnsServer:
609
880
 
610
881
  # Save this string object as log file.
611
882
  with open(
612
- self.config['log']['logs_path'] + os.sep + self.known_ipv4_filename, 'w'
883
+ self.log_directory_path + os.sep + self.known_ipv4_filename, 'w'
613
884
  ) as output_file:
614
885
  output_file.write(record_string_line)
615
886
 
616
- self.dns_full_logger.info(
617
- f"Saved new known IPv4 addresses file: "
618
- 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}")
619
890
 
620
891
  # Known IPv4 address to domains list management EOF
621
- # ==============================================================================================================
892
+ # ==================================================================================================
622
893
  # Writing IPs by time.
623
894
 
624
895
  # If IPv4 address list is not empty, meaning this DNS request was A type.
@@ -629,31 +900,87 @@ class DnsServer:
629
900
 
630
901
  # Save this string object as log file.
631
902
  with open(
632
- 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'
633
904
  ) as output_file:
634
905
  output_file.write(record_string_line + '\n')
635
906
 
636
907
  # EOF Writing IPs by time.
637
- # ==============================================================================================================
638
- # SSH Remote / LOCALHOST script execution to identify process section
639
-
640
- # Starting a thread that will query IPs of the last DNS request.
641
- # thread_current = \
642
- # threading.Thread(
643
- # target=thread_worker_check_process_by_ip, args=(ipv4_addresses, client_address[0],))
644
- # # Append to list of threads, so they can be "joined" later
645
- # threads_list.append(thread_current)
646
- # # Start the thread
647
- # thread_current.start()
648
-
649
- # EOF SSH / LOCALHOST executing process command line harvesting.
650
- # ==================================================================================================================
651
-
652
- self.dns_full_logger.info("==========")
908
+ # ==================================================================================================
909
+
910
+ # self.logger.info("==========")
653
911
  except Exception:
654
912
  message = "Unknown Exception: to parse DNS request"
655
913
  print_api(
656
- message, logger=self.logger, logger_method='critical', traceback_string=True, oneline=True)
914
+ message, logger=self.logger, logger_method='critical', traceback_string=True)
657
915
  self.logger.info("==========")
658
916
  pass
659
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)