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
atomicshop/networks.py ADDED
@@ -0,0 +1,671 @@
1
+ import socket
2
+ import time
3
+ from typing import Union, Literal
4
+ import os
5
+ import psutil
6
+ import ctypes
7
+ from logging import Logger
8
+ import subprocess
9
+
10
+ from icmplib import ping
11
+ from icmplib.models import Host
12
+ from win32com.client import CDispatch
13
+
14
+ from .print_api import print_api
15
+ from .wrappers.pywin32w.wmis import win32networkadapter, win32_networkadapterconfiguration, wmi_helpers
16
+ from .wrappers.ctyping import setup_device
17
+ from .wrappers.winregw import winreg_network
18
+ from .wrappers.psutilw import psutil_networks
19
+ from .wrappers import powershell_networking, netshw
20
+ from .wrappers.socketw import socket_base
21
+
22
+
23
+ MICROSOFT_LOOPBACK_DEVICE_NAME: str = 'Microsoft KM-TEST Loopback Adapter'
24
+ MICROSOFT_LOOPBACK_DEVICE_INF_PATH = os.path.join(os.environ["WINDIR"], "INF", "netloop.inf")
25
+ MICROSOFT_LOOPBACK_DEVICE_HARDWARE_ID = "*MSLOOP"
26
+ GUID_DEVCLASS_NET: str = '{4d36e972-e325-11ce-bfc1-08002be10318}'
27
+
28
+
29
+ def is_ip_in_use_ping(ip_address: str, timeout: int = 1) -> bool:
30
+ """
31
+ Returns True if the IP address is pingable, False otherwise.
32
+ :param ip_address: string, IP address to check.
33
+ :param timeout: int, timeout in seconds. Default is 1 second.
34
+ :return: bool, True if the IP address is pingable, False otherwise.
35
+ """
36
+
37
+ host_object: Host = ping(ip_address, count=1, timeout=timeout)
38
+
39
+ return host_object.is_alive
40
+
41
+
42
+ def is_ip_in_use_arp(
43
+ ipv4: str,
44
+ gateway_ip: str = None
45
+ ) -> tuple[
46
+ Union[str, None],
47
+ Union[bool, None]
48
+ ]:
49
+ """
50
+ Windows only.
51
+ Check if an IPv4 address is in use on the local network using ARP.
52
+ :param ipv4: string, IPv4 address to check.
53
+ :param gateway_ip: string, IPv4 address of the default gateway.
54
+ How it works: If you provide the gateway_ip, the function will get yje MAC of the gateway,
55
+ then it will get the MAC of the target IP address. If the MACs are the same, it means that the target IP's
56
+ ARP reply is an ARP proxy reply from the gateway.
57
+ :return: tuple (mac_address: str | None, via_gateway: bool | None)
58
+ If the IP address is in use, mac_address will be the MAC address of the device using the IP address,
59
+ else None. If gateway_ip is provided, via_gateway will be True if the MAC address is the same as the gateway's MAC address,
60
+ False if it's different, and None if gateway_ip is not provided.
61
+ """
62
+
63
+ iphlpapi = ctypes.windll.iphlpapi
64
+ ws2_32 = ctypes.windll.ws2_32
65
+
66
+ def _send_arp(ip: str) -> str | None:
67
+ """Return MAC string like 'aa:bb:cc:dd:ee:ff' if IP is claimed on the LAN, else None."""
68
+ # inet_addr returns DWORD in network byte order
69
+ # noinspection PyUnresolvedReferences
70
+ dest_ip = ws2_32.inet_addr(ip.encode('ascii'))
71
+ if dest_ip == 0xFFFFFFFF: # INVALID
72
+ raise ValueError(f"Bad IPv4 address: {ip}")
73
+
74
+ mac_buf = ctypes.c_uint64(0) # storage for up to 8 bytes
75
+ mac_len = ctypes.c_ulong(ctypes.sizeof(mac_buf)) # in/out len
76
+ # SrcIP=0 lets Windows pick the right interface
77
+ # noinspection PyUnresolvedReferences
78
+ rc = iphlpapi.SendARP(dest_ip, 0, ctypes.byref(mac_buf), ctypes.byref(mac_len))
79
+ if rc != 0: # Non-zero means no ARP reply / not on-link / other error
80
+ return None
81
+
82
+ # Extract the first 6 bytes from little-endian integer
83
+ mac_int = mac_buf.value
84
+ mac_bytes = mac_int.to_bytes(8, 'little')[:6]
85
+ return ':'.join(f'{b:02x}' for b in mac_bytes)
86
+
87
+ mac = _send_arp(ipv4)
88
+ if mac is None:
89
+ return None, None
90
+ via_gateway = None
91
+ if gateway_ip:
92
+ gw_mac = _send_arp(gateway_ip)
93
+ via_gateway = (gw_mac is not None and gw_mac.lower() == mac.lower())
94
+ return mac, via_gateway
95
+
96
+
97
+ def __get_default_internet_ipv4() -> str:
98
+ """
99
+ FOR REFERENCE ONLY, DO NOT USE.
100
+ DOESN'T WORK UNDER ALL CIRCUMSTANCES, CAN'T PINPOINT THE REASON.
101
+
102
+ Get the default IPv4 address of the interface that is being used for internet.
103
+ :return: string, default IPv4 address.
104
+ """
105
+
106
+ return socket.gethostbyname(socket.gethostname())
107
+
108
+
109
+ def get_default_internet_ipv4(target: str = "8.8.8.8") -> str:
110
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
111
+ s.connect((target, 80)) # no packet sent; OS just chooses a route
112
+ return s.getsockname()[0] # local address of that route
113
+
114
+
115
+ def get_default_interface_name() -> str:
116
+ default_connection_name_dict: dict = psutil_networks.get_default_connection_name()
117
+ if not default_connection_name_dict:
118
+ return ""
119
+ # Get the first key from the dictionary.
120
+ connection_name: str = list(default_connection_name_dict.keys())[0]
121
+ return connection_name
122
+
123
+
124
+ def list_network_interfaces() -> list[str]:
125
+ """
126
+ List all network interfaces on the system.
127
+ :return: list of strings, network interface names.
128
+ """
129
+
130
+ return psutil_networks.list_network_interfaces()
131
+
132
+
133
+ def get_hostname() -> str:
134
+ """
135
+ Get the default network interface name that is being used for internet.
136
+ :return: string, default network interface name.
137
+ """
138
+
139
+ return socket.gethostname()
140
+
141
+
142
+ def get_interface_ips_psutil(
143
+ interface_name: str = None,
144
+ ipv4: bool = True,
145
+ ipv6: bool = True,
146
+ localhost: bool = True,
147
+ default_interface: bool = False
148
+ ):
149
+ if not ipv4 and not ipv6:
150
+ raise ValueError("At least one of ipv4 or ipv6 must be True.")
151
+ if default_interface and interface_name:
152
+ raise ValueError("You can't specify both default_interface and interface_name.")
153
+
154
+ if default_interface:
155
+ # Get the default interface name.
156
+ interface_name = get_default_interface_name()
157
+
158
+ physical_ip_types: list[str] = []
159
+ if ipv4:
160
+ physical_ip_types.append("AF_INET") # IPv4
161
+ if ipv6:
162
+ physical_ip_types.append("AF_INET6") # IPv6
163
+
164
+ interfaces: dict = psutil.net_if_addrs()
165
+
166
+ ips = []
167
+ for name, addresses in interfaces.items():
168
+ if interface_name and interface_name != name:
169
+ continue
170
+
171
+ for address in addresses:
172
+ if address.family.name in physical_ip_types:
173
+ if not localhost and (address.address.startswith("127.") or address.address.startswith("::1")):
174
+ # Skip localhost addresses if localhost is True.
175
+ continue
176
+
177
+ ips.append(address.address)
178
+ return ips
179
+
180
+
181
+ def get_host_ips_psutil(
182
+ localhost: bool = True,
183
+ ipv4: bool = True,
184
+ ipv6: bool = True
185
+ ) -> list[str]:
186
+ """
187
+ Yield (ifname, family, ip) for all UP interfaces that have bindable addresses.
188
+
189
+ Args:
190
+ localhost: include 127.0.0.0/8 and ::1 if True.
191
+ ipv4: include IPv4 addresses if True.
192
+ ipv6: include IPv6 addresses if True.
193
+ """
194
+ stats = psutil.net_if_stats()
195
+
196
+ ip_list: list[str] = []
197
+ for ifname, addrs in psutil.net_if_addrs().items():
198
+ st = stats.get(ifname)
199
+ if not st or not st.isup:
200
+ continue # interface is down or unknown
201
+
202
+ for a in addrs:
203
+ fam = a.family
204
+ if fam not in (socket.AF_INET, socket.AF_INET6):
205
+ continue
206
+
207
+ # Family filters
208
+ if fam == socket.AF_INET and not ipv4:
209
+ continue
210
+ if fam == socket.AF_INET6 and not ipv6:
211
+ continue
212
+
213
+ ip = a.address
214
+
215
+ # Skip placeholders/wildcards
216
+ if fam == socket.AF_INET and ip == "0.0.0.0":
217
+ continue
218
+ if fam == socket.AF_INET6 and ip in ("::",):
219
+ continue
220
+
221
+ # Optionally skip loopback
222
+ if not localhost:
223
+ if fam == socket.AF_INET and ip.startswith("127."):
224
+ continue
225
+ if fam == socket.AF_INET6 and (ip == "::1" or ip.startswith("::1%")):
226
+ continue
227
+
228
+ # yield ifname, fam, ip
229
+ ip_list.append(ip)
230
+
231
+ return ip_list
232
+
233
+
234
+ def get_interface_ips_powershell(
235
+ interface_name: str = None,
236
+ ip_type: Literal["virtual", "dynamic", "all"] = "virtual"
237
+ ) -> list[str]:
238
+ """
239
+ Get the IP addresses of a network interface using PowerShell.
240
+
241
+ :param interface_name: string, name of the network interface.
242
+ If None, all interfaces will be queried.
243
+ :param ip_type: string, type of IP addresses to retrieve.
244
+ :return: list of strings, IP addresses of the network interface.
245
+ """
246
+
247
+ return powershell_networking.get_interface_ips(interface_name=interface_name, ip_type=ip_type)
248
+
249
+
250
+ def get_microsoft_loopback_device_network_configuration(
251
+ wmi_instance: CDispatch = None,
252
+ timeout: int = 1,
253
+ ) -> Union[
254
+ tuple[CDispatch, str],
255
+ tuple[None, None]
256
+ ]:
257
+ """
258
+ Get the WMI Win32_NetworkAdapterConfiguration object of the Microsoft Loopback device.
259
+
260
+ :param wmi_instance: WMI instance. You can get it from:
261
+ wrappers.pywin32s.wmis.wmi_helpers.get_wmi_instance()
262
+ If not specified the default WMI instance will be used '.'.
263
+ :param timeout: int, timeout in seconds. Default is 1 second.
264
+ :return: tuple(Win32_NetworkAdapterConfiguration, Win32_NetworkAdapter.PNPDeviceID)
265
+ If the adapter is not found, it will return (None, None).
266
+ """
267
+
268
+ if not wmi_instance:
269
+ wmi_instance, _ = wmi_helpers.get_wmi_instance()
270
+
271
+ for _ in range(timeout):
272
+ adapter: CDispatch = win32networkadapter.get_network_adapter_by_device_name(
273
+ MICROSOFT_LOOPBACK_DEVICE_NAME, wmi_instance)
274
+ if not adapter:
275
+ # noinspection PyTypeChecker
276
+ network_config = None
277
+ else:
278
+ network_config: CDispatch = win32_networkadapterconfiguration.get_network_configuration_by_adapter(
279
+ adapter, wmi_instance=wmi_instance)
280
+
281
+ if network_config:
282
+ return network_config, adapter.PNPDeviceID
283
+ time.sleep(1)
284
+
285
+ return None, None
286
+
287
+
288
+ def create_microsoft_loopback_device():
289
+ """
290
+ Create a Microsoft Loopback device using the setupapi.dll.
291
+ """
292
+ setup_device.add_device(
293
+ class_guid=GUID_DEVCLASS_NET,
294
+ friendly_name=MICROSOFT_LOOPBACK_DEVICE_NAME,
295
+ hardware_ids=MICROSOFT_LOOPBACK_DEVICE_HARDWARE_ID,
296
+ inf_path=MICROSOFT_LOOPBACK_DEVICE_INF_PATH,
297
+ force_install=True,
298
+ quiet=True,
299
+ existing_ok=True
300
+ )
301
+
302
+
303
+ def get_create_microsoft_loopback_device_network_configuration(
304
+ wmi_instance: CDispatch = None
305
+ ) -> Union[
306
+ tuple[CDispatch, str],
307
+ tuple[None, None]
308
+ ]:
309
+ """
310
+ Get the WMI Win32_NetworkAdapterConfiguration object of the Microsoft Loopback device.
311
+ If it does not exist, create it.
312
+
313
+ :param wmi_instance: WMI instance. You can get it from:
314
+ wrappers.pywin32s.wmis.wmi_helpers.get_wmi_instance()
315
+ If not specified the default WMI instance will be used '.'.
316
+ :return: tuple(Win32_NetworkAdapterConfiguration, Win32_NetworkAdapter.PNPDeviceID)
317
+ If the adapter is not found, it will return (None, None).
318
+ """
319
+
320
+ if not wmi_instance:
321
+ wmi_instance, _ = wmi_helpers.get_wmi_instance()
322
+
323
+ network_config, pnp_device_id = get_microsoft_loopback_device_network_configuration(wmi_instance=wmi_instance)
324
+
325
+ if network_config:
326
+ return network_config, pnp_device_id
327
+
328
+ create_microsoft_loopback_device()
329
+
330
+ network_config, pnp_device_id = get_microsoft_loopback_device_network_configuration(
331
+ wmi_instance=wmi_instance, timeout=20)
332
+
333
+ return network_config, pnp_device_id
334
+
335
+
336
+ def remove_microsoft_loopback_device(
337
+ pnp_device_id: str
338
+ ) -> bool:
339
+ """
340
+ Remove the Microsoft Loopback device using the setupapi.dll.
341
+
342
+ :param pnp_device_id: string, PNPDeviceID of the device to remove.
343
+ :return: bool, True if the device was removed successfully.
344
+ """
345
+ return setup_device.remove_device(
346
+ pnp_device_id=pnp_device_id,
347
+ class_guid=GUID_DEVCLASS_NET
348
+ )
349
+
350
+
351
+ def change_interface_metric_restart_device(
352
+ network_config: CDispatch,
353
+ metric: int = 9999,
354
+ wmi_instance: CDispatch = None
355
+ ):
356
+ """
357
+ Change the interface metric and restart the device.
358
+ You can check the metric in CMD with:
359
+ route print
360
+
361
+ :param network_config: CDispatch, Win32_NetworkAdapterConfiguration object.
362
+ :param metric: int, new metric value.
363
+ :param wmi_instance: WMI instance. You can get it from:
364
+ wrappers.pywin32s.wmis.wmi_helpers.get_wmi_instance()
365
+ """
366
+
367
+ if not wmi_instance:
368
+ wmi_instance, _ = wmi_helpers.get_wmi_instance()
369
+
370
+ # 1) Registry tweak
371
+ guid = network_config.SettingID
372
+ winreg_network.change_metric_of_network_adapter(adapter_guid=guid, metric=metric)
373
+
374
+ # 2) Bounce the NIC so TCP/IP re‑reads the new metric
375
+ idx = network_config.Index
376
+ adapter = wmi_instance.ExecQuery(f"SELECT * FROM Win32_NetworkAdapter WHERE Index={idx}")[0]
377
+
378
+ adapter.ExecMethod_("Disable")
379
+ time.sleep(1.0)
380
+ adapter.ExecMethod_("Enable")
381
+
382
+
383
+ def get_wmi_network_adapter_configuration(
384
+ interface_name: str = None,
385
+ mac_address: str = None,
386
+ wmi_instance: CDispatch = None,
387
+ get_info_from_network_config: bool = True
388
+ ) -> tuple:
389
+ """
390
+ Get the WMI network configuration for a network adapter.
391
+ :param interface_name: string, adapter name as shown in the network settings.
392
+ :param mac_address: string, MAC address of the adapter. Format: '00:00:00:00:00:00'.
393
+ :param wmi_instance: WMI instance. You can get it from:
394
+ wrappers.pywin32s.wmis.wmi_helpers.get_wmi_instance()
395
+ or default will be used.
396
+ :param get_info_from_network_config: bool, if True, the function will return the network configuration info
397
+ on the third position of the tuple. On False, it will return an empty dictionary.
398
+ :return: tuple(Win32_NetworkAdapterConfiguration, Win32_NetworkAdapter, dict)
399
+ """
400
+
401
+ wmi_network_config, wmi_network_adapter = win32_networkadapterconfiguration.get_adapter_network_configuration(
402
+ interface_name=interface_name,
403
+ mac_address=mac_address,
404
+ wmi_instance=wmi_instance
405
+ )
406
+
407
+ if get_info_from_network_config:
408
+ adapter_info: dict = win32_networkadapterconfiguration.get_info_from_network_config(wmi_network_config)
409
+ adapter_info['name'] = wmi_network_adapter.NetConnectionID
410
+ else:
411
+ adapter_info: dict = {}
412
+
413
+ return wmi_network_config, wmi_network_adapter, adapter_info
414
+
415
+
416
+ def generate_unused_ipv4_addresses_by_vlan(
417
+ vlan: str,
418
+ number_of_ips: int,
419
+ skip_ips: list[str] = None
420
+ ) -> list[str]:
421
+ """
422
+ Generate a list of unused IPv4 addresses in the given VLAN.
423
+
424
+ :param vlan: string, VLAN in the format '192.168.0'.
425
+ :param number_of_ips: int, number of IPs to generate.
426
+ :param skip_ips: list of strings, IPs to skip.
427
+ :return: list of strings, free IPv4 addresses.
428
+ """
429
+
430
+ generated_ips: list[str] = []
431
+ counter: int = 1
432
+ for i in range(number_of_ips):
433
+ # Create the IP address.
434
+ while True:
435
+ ip_address: str = f"{vlan}.{counter}"
436
+ counter += 1
437
+ is_ip_in_use, _ = is_ip_in_use_arp(ip_address)
438
+ if not is_ip_in_use and not ip_address in skip_ips:
439
+ # print("[+] Found IP to assign: ", ip_address)
440
+ generated_ips.append(ip_address)
441
+ break
442
+ else:
443
+ # print(f"[!] IP {ip_address} is already in use or assigned to the adapter.")
444
+ continue
445
+
446
+ return generated_ips
447
+
448
+
449
+ def generate_unused_ipv4_addresses_from_ip(
450
+ ip_address: str,
451
+ mask: str,
452
+ number_of_ips: int,
453
+ skip_ips: list[str] = None
454
+ ) -> tuple[list[str], list[str]]:
455
+ """
456
+ Generate a list of unused IPv4 addresses in the given VLAN.
457
+
458
+ :param ip_address: string, IP address, example: '192.168.0.1'.
459
+ This address will be a part of skip_ips list, even if an empty list is passed.
460
+ :param mask: string, subnet mask, example: '255.255.255.0'.
461
+ :param number_of_ips: int, number of IPs to generate.
462
+ :param skip_ips: list of strings, IPs to skip.
463
+ :return: list of strings, unused IPv4 addresses.
464
+ """
465
+
466
+ if not skip_ips:
467
+ skip_ips = []
468
+
469
+ # Remove duplicate IPs from the skip_ips list, loses order.
470
+ skip_ips = list(set(skip_ips))
471
+
472
+ # Add the IP address to the list of IPs to skip.
473
+ if ip_address not in skip_ips:
474
+ skip_ips = [ip_address] + skip_ips
475
+
476
+ # Get the VLAN of the default IPv4 address.
477
+ default_vlan: str = ip_address.rsplit(".", 1)[0]
478
+
479
+
480
+ # Find IPs to assign.
481
+ generated_ips: list[str] = generate_unused_ipv4_addresses_by_vlan(
482
+ vlan=default_vlan, number_of_ips=number_of_ips, skip_ips=skip_ips)
483
+
484
+ # Add subnet masks to the IPs to assign.
485
+ masks_for_ips: list[str] = []
486
+ for ip_address in generated_ips:
487
+ print(f"[+] Found IP to assign: {ip_address}")
488
+ masks_for_ips.append(mask)
489
+
490
+ return generated_ips, masks_for_ips
491
+
492
+
493
+ def set_dynamic_ip_for_adapter_wmi(
494
+ network_config: CDispatch,
495
+ reset_dns: bool = True,
496
+ reset_wins: bool = True
497
+ ):
498
+ """
499
+ Set the IP address of the network adapter to dynamic from DHCP.
500
+ :param network_config: CDispatch, Win32_NetworkAdapterConfiguration object.
501
+ :param reset_dns: bool, if True, the DNS servers will be reset to automatic.
502
+ :param reset_wins: bool, if True, the WINS servers will be reset to automatic.
503
+ """
504
+
505
+ win32_networkadapterconfiguration.set_dynamic_ip(
506
+ nic_cfg=network_config, reset_dns=reset_dns, reset_wins=reset_wins)
507
+
508
+
509
+ def set_static_ip_for_adapter_wmi(
510
+ network_config: CDispatch,
511
+ ips: list[str],
512
+ masks: list[str],
513
+ gateways: list[str] = None,
514
+ dns_gateways: list[str] = None,
515
+ availability_wait_seconds: int = 0
516
+ ):
517
+ """
518
+ Set the IP address of the network adapter to static.
519
+ :param network_config: CDispatch, Win32_NetworkAdapterConfiguration object.
520
+ :param ips: list of strings, IP addresses to assign.
521
+ :param masks: list of strings, subnet masks to assign.
522
+ :param gateways: list of strings, default gateways to assign.
523
+ :param dns_gateways: list of strings, DNS servers to assign.
524
+ :param availability_wait_seconds: int, seconds to wait for the adapter to be available after setting the IP address.
525
+ """
526
+
527
+ win32_networkadapterconfiguration.set_static_ips(
528
+ network_config=network_config,
529
+ ips=ips,
530
+ masks=masks,
531
+ gateways=gateways,
532
+ dns_gateways=dns_gateways,
533
+ availability_wait_seconds=availability_wait_seconds
534
+ )
535
+
536
+
537
+ def add_virtual_ips_to_network_interface(
538
+ interface_name: str,
539
+ number_of_ips: int = 0,
540
+ virtual_ipv4s_to_add: list[str] = None,
541
+ virtual_ipv4_masks_to_add: list[str] = None,
542
+ set_virtual_ips_skip_as_source: bool = True,
543
+ simulate_only: bool = False,
544
+ locator: CDispatch = None,
545
+ wait_until_applied: bool = True,
546
+ wait_until_applied_seconds: int = 15,
547
+ verbose: bool = False,
548
+ logger: Logger = None,
549
+ ) -> tuple[list[str], list[str]] | None:
550
+ """
551
+ Add virtual IP addresses to the default network adapter.
552
+ The adapter will set to static IP and DNS gateway, instead of dynamic DHCP.
553
+ The first IPv4 address of the network_config will be used as VLAN and the unused IP addresses
554
+ will be generated from it. Unused addresses decided by pinging them.
555
+ Same for the subnet mask.
556
+
557
+ While generating the IPs, the function will skip the already existing IPs in the adapter, like default gateway
558
+ and DNS servers.
559
+
560
+ :param interface_name: string, adapter name as shown in the network settings.
561
+
562
+ :param number_of_ips: int, number of IPs to generate in addition to the IPv4s that already exist in the adapter.
563
+ Or you add the IPs and masks to the adapter with the parameters virtual_ipv4s_to_add and virtual_ipv4_masks_to_add.
564
+
565
+ :param virtual_ipv4s_to_add: list of strings, Add this IPv4 addresses to the current IPs of the adapter.
566
+ :param virtual_ipv4_masks_to_add: list of strings, Add this subnet masks to the current subnet masks of the adapter.
567
+ Or you generate the IPs and masks by specifying the number_of_ips parameter.
568
+
569
+ :param set_virtual_ips_skip_as_source: bool, if True, the SkipAsSource flag will be set for the virtual IPs.
570
+ This is needed to avoid the endless accept() loop.
571
+
572
+ :param simulate_only: bool, if True, the function will only simulate the addition of the IP addresses.
573
+ No changes will be made to the system.
574
+ :param locator: CDispatch, WMI locator object. You can get it from:
575
+ wrappers.pywin32s.wmis.wmi_helpers.get_wmi_instance()
576
+
577
+ :param wait_until_applied: bool, if True, the function will wait until the IP addresses are applied.
578
+ By default, while WMI command is executed, there is no indication if the addresses were finished applying or not.
579
+ If you have 15+ addresses, it can take a while to apply them.
580
+ :param wait_until_applied_seconds: int, seconds to wait for the IP addresses to be applied.
581
+ This is different from availability_wait_seconds, which is the time to wait for the adapter to be available
582
+ after setting the IP addresses. This is the time to wait for the IP addresses to be
583
+ applied after setting them. If the IP addresses are not applied in this time, a TimeoutError will be raised.
584
+
585
+ :param verbose: bool, if True, the function will print verbose output.
586
+ :param logger: Logger, if provided, the function will log messages to this logger.
587
+
588
+ :return: tuple of lists, (ips_to_assign, masks_to_assign)
589
+ """
590
+
591
+ if virtual_ipv4s_to_add and not virtual_ipv4_masks_to_add:
592
+ raise ValueError("If you specify virtual_ipv4s_to_add, you must also specify virtual_ipv4_masks_to_add.")
593
+ if virtual_ipv4_masks_to_add and not virtual_ipv4s_to_add:
594
+ raise ValueError("If you specify virtual_ipv4_masks_to_add, you must also specify virtual_ipv4s_to_add.")
595
+
596
+ if virtual_ipv4s_to_add and len(virtual_ipv4s_to_add) != len(virtual_ipv4_masks_to_add):
597
+ raise ValueError("If you specify virtual_ipv4s_to_add, the number of IPs must be equal to the number of masks.")
598
+
599
+ if number_of_ips > 0 and (virtual_ipv4s_to_add or virtual_ipv4_masks_to_add):
600
+ raise ValueError("If you specify number_of_ips, you cannot specify virtual_ipv4s_to_add or virtual_ipv4_masks_to_add.")
601
+
602
+ # Connect to WMi.
603
+ wmi_civ2_instance, locator = wmi_helpers.get_wmi_instance(locator=locator)
604
+
605
+ # Get the network adapter configuration.
606
+ network_adapter_config, network_adapter, adapter_info = get_wmi_network_adapter_configuration(
607
+ interface_name=interface_name, wmi_instance=wmi_civ2_instance, get_info_from_network_config=True)
608
+
609
+ current_ipv4s: list[str] = adapter_info['ipv4s']
610
+ current_ipv4_masks: list[str] = adapter_info['ipv4_subnet_masks']
611
+
612
+ if number_of_ips > 0:
613
+ ips_to_assign, masks_to_assign = generate_unused_ipv4_addresses_from_ip(
614
+ ip_address=current_ipv4s[0],
615
+ mask=current_ipv4_masks[0],
616
+ number_of_ips=number_of_ips,
617
+ skip_ips=current_ipv4s + adapter_info['default_gateways'] + adapter_info['dns_gateways']
618
+ )
619
+ elif virtual_ipv4s_to_add and virtual_ipv4_masks_to_add:
620
+ ips_to_assign = virtual_ipv4s_to_add
621
+ masks_to_assign = virtual_ipv4_masks_to_add
622
+ else:
623
+ ips_to_assign = []
624
+ masks_to_assign = []
625
+
626
+ if not simulate_only:
627
+ # Enable DHCP + static IP coexistence on the interface.
628
+ process_complete: subprocess.CompletedProcess = netshw.enable_dhcp_static_coexistence(interface_name=interface_name)
629
+ if process_complete.returncode != 0:
630
+ print_api(f"[!] Failed to enable DHCP + static IP coexistence on interface {interface_name}.\n"
631
+ f" stdout: {process_complete.stdout}\n"
632
+ f" stderr: {process_complete.stderr}", color="red", logger=logger)
633
+ return None
634
+
635
+ for ip, mask in zip(ips_to_assign, masks_to_assign):
636
+ if verbose:
637
+ print_api(f"[+] Adding virtual IP {ip} with mask {mask} to interface {interface_name}.", logger=logger)
638
+
639
+ netshw.add_virtual_ip(
640
+ interface_name=interface_name,
641
+ ip=ip,
642
+ mask=mask,
643
+ skip_as_source=set_virtual_ips_skip_as_source
644
+ )
645
+
646
+ if wait_until_applied:
647
+ # Wait until the IP addresses are applied.
648
+ for _ in range(wait_until_applied_seconds):
649
+ current_virtual_ips = get_interface_ips_powershell(interface_name=interface_name, ip_type="virtual")
650
+ if set(current_virtual_ips) == set(ips_to_assign):
651
+ break
652
+ time.sleep(1)
653
+ else:
654
+ raise TimeoutError("Timeout while waiting for the IP addresses to be applied.")
655
+
656
+ return ips_to_assign, masks_to_assign
657
+
658
+
659
+ def wait_for_ip_bindable_socket(
660
+ ip: str,
661
+ port: int = 0,
662
+ timeout: float = 15.0,
663
+ interval: float = 0.5,
664
+ ) -> None:
665
+ """
666
+ Wait until a single IP is bindable (or timeout).
667
+
668
+ Raises TimeoutError if the IP cannot be bound within 'timeout' seconds.
669
+ """
670
+
671
+ socket_base.wait_for_ip_bindable(ip=ip, port=port, timeout=timeout, interval=interval)