atomicshop 2.15.11__py3-none-any.whl → 3.10.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/strings.py +72 -3
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -3
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +11 -6
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +1 -1
- atomicshop/diff_check.py +3 -3
- atomicshop/dns.py +128 -3
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/trace.py +40 -42
- atomicshop/etws/traces/trace_dns.py +56 -44
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +27 -5
- atomicshop/file_io/docxs.py +34 -17
- atomicshop/file_io/file_io.py +31 -17
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +616 -291
- atomicshop/get_process_list.py +3 -3
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -80
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +136 -40
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
- atomicshop/monitor/checks/dns.py +1 -1
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +39 -9
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -42
- atomicshop/process.py +24 -6
- atomicshop/process_poller/process_pool.py +0 -1
- atomicshop/process_poller/simple_process_pool.py +204 -5
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/system_resource_monitor.py +61 -47
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/urls.py +6 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +98 -27
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/githubw.py +537 -54
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/filters.py +23 -0
- atomicshop/wrappers/loggingw/formatters.py +12 -0
- atomicshop/wrappers/loggingw/handlers.py +214 -107
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +860 -22
- atomicshop/wrappers/loggingw/reading.py +134 -112
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +37 -1
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +491 -182
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +1 -1
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/pycharm.py +0 -9
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
- atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/config_editor.py +0 -37
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -268
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -151
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/install.py +0 -81
- atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
- atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
- atomicshop/wrappers/msiw.py +0 -149
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/psutilw/networks.py +0 -45
- atomicshop/wrappers/pycharmw.py +0 -81
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.15.11.dist-info/RECORD +0 -302
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- /atomicshop/{archiver → permissions}/__init__.py +0 -0
- /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
- /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
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)
|