174 lines
6.6 KiB
Python
174 lines
6.6 KiB
Python
"""
|
|
Implementación de NetworkManager para Windows.
|
|
Usa psutil + netsh; no requiere pyroute2 ni nmcli.
|
|
"""
|
|
import socket
|
|
import subprocess
|
|
import re
|
|
from typing import Tuple, List, Dict, Any
|
|
|
|
import psutil
|
|
|
|
|
|
def _parse_netsh_config() -> Dict[str, Dict[str, str]]:
|
|
"""Ejecuta netsh interface ipv4 show config y devuelve dict por interfaz."""
|
|
out: Dict[str, Dict[str, str]] = {}
|
|
try:
|
|
r = subprocess.run(
|
|
["netsh", "interface", "ipv4", "show", "config"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=15,
|
|
creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0,
|
|
)
|
|
if r.returncode != 0:
|
|
return out
|
|
current = None
|
|
for line in r.stdout.splitlines():
|
|
# "Configuration for interface "Ethernet""
|
|
m = re.match(r'Configuration for interface "([^"]+)"', line)
|
|
if m:
|
|
current = m.group(1)
|
|
out[current] = {"method": "unknown", "ip": "", "prefix": "", "gateway": "", "dns": ""}
|
|
continue
|
|
if current is None:
|
|
continue
|
|
line = line.strip()
|
|
if line.startswith("DHCP enabled:"):
|
|
val = line.split(":", 1)[1].strip().lower()
|
|
out[current]["method"] = "auto" if val == "yes" else "manual"
|
|
elif line.startswith("IP Address:"):
|
|
out[current]["ip"] = line.split(":", 1)[1].strip()
|
|
elif line.startswith("Subnet Prefix:"):
|
|
val = line.split(":", 1)[1].strip()
|
|
if "/" in val:
|
|
ip_part, prefix_part = val.split("/", 1)
|
|
out[current]["ip"] = out[current]["ip"] or ip_part.strip()
|
|
out[current]["prefix"] = prefix_part.strip()
|
|
elif line.startswith("Default Gateway:"):
|
|
out[current]["gateway"] = line.split(":", 1)[1].strip()
|
|
elif line.startswith("DNS Servers:"):
|
|
out[current]["dns"] = line.split(":", 1)[1].strip()
|
|
except Exception:
|
|
pass
|
|
return out
|
|
|
|
|
|
def _is_mac_address(addr: str) -> bool:
|
|
if not addr or len(addr) != 17:
|
|
return False
|
|
parts = addr.split(":")
|
|
return len(parts) == 6 and all(len(p) == 2 and p.isalnum() for p in parts)
|
|
|
|
|
|
class NetworkManager:
|
|
"""NetworkManager para Windows: list_interfaces y get_interface_config con psutil + netsh."""
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def list_interfaces(self) -> List[Dict[str, Any]]:
|
|
interfaces = []
|
|
stats = psutil.net_if_stats()
|
|
addrs = psutil.net_if_addrs()
|
|
netsh_config = _parse_netsh_config()
|
|
|
|
for if_name, if_addrs in addrs.items():
|
|
if if_name == "lo" or "Loopback" in if_name:
|
|
continue
|
|
is_up = False
|
|
if if_name in stats:
|
|
is_up = stats[if_name].isup
|
|
ipv4 = []
|
|
ipv6 = []
|
|
mac = ""
|
|
for addr in if_addrs:
|
|
try:
|
|
af = addr.family
|
|
addr_str = addr.address
|
|
except AttributeError:
|
|
af = addr[0]
|
|
addr_str = addr[1]
|
|
if af == socket.AF_INET:
|
|
ipv4.append(addr_str)
|
|
elif af == socket.AF_INET6 and not addr_str.startswith("fe80") and addr_str != "::1":
|
|
ipv6.append(addr_str)
|
|
elif _is_mac_address(addr_str):
|
|
mac = addr_str
|
|
if not mac:
|
|
for addr in if_addrs:
|
|
try:
|
|
a = getattr(addr, "address", addr[1])
|
|
except (IndexError, TypeError):
|
|
continue
|
|
if _is_mac_address(a):
|
|
mac = a
|
|
break
|
|
config = netsh_config.get(if_name, {
|
|
"method": "unknown", "ip": "", "prefix": "", "gateway": "", "dns": ""
|
|
})
|
|
if ipv4 and not config.get("ip"):
|
|
config = dict(config)
|
|
config["ip"] = ipv4[0].split("/")[0] if "/" in str(ipv4[0]) else str(ipv4[0])
|
|
# Formato ip/prefix como en Linux cuando hay prefix en netsh
|
|
if config.get("prefix") and config.get("ip"):
|
|
ipv4_formatted = [f"{config['ip']}/{config['prefix']}"]
|
|
elif ipv4:
|
|
ipv4_formatted = [a if "/" in str(a) else str(a) for a in ipv4]
|
|
else:
|
|
ipv4_formatted = []
|
|
interfaces.append({
|
|
"name": if_name,
|
|
"mac": mac or "N/A",
|
|
"status": "UP" if is_up else "DOWN",
|
|
"ipv4": ipv4_formatted if ipv4 else [],
|
|
"ipv6": ipv6,
|
|
"type": "Wired",
|
|
"is_up": is_up,
|
|
"config": config,
|
|
})
|
|
return interfaces
|
|
|
|
def get_interface_config(self, iface_name: str) -> Dict[str, str]:
|
|
configs = _parse_netsh_config()
|
|
return configs.get(iface_name, {
|
|
"method": "unknown", "ip": "", "prefix": "", "gateway": "", "dns": ""
|
|
})
|
|
|
|
def apply_interface_config(
|
|
self, iface_name: str, method: str,
|
|
ip: str = None, prefix: str = None, gateway: str = None, dns: str = None
|
|
) -> Tuple[bool, str]:
|
|
return False, "Configuración IP no disponible en Windows desde esta aplicación."
|
|
|
|
def set_ip_config(self, iface_name: str, ip_address: str, prefix_len: str, gateway: str = None) -> Tuple[bool, str]:
|
|
return False, "No disponible en Windows."
|
|
|
|
def set_dhcp(self, iface_name: str) -> Tuple[bool, str]:
|
|
return False, "No disponible en Windows."
|
|
|
|
def create_vlan_bridge(self, trunk_iface: str, vlan_id: int, access_iface: str) -> Tuple[bool, str]:
|
|
return False, "VLAN/Bridge no disponible en Windows."
|
|
|
|
def create_simple_bridge(self, iface1: str, iface2: str) -> Tuple[bool, str]:
|
|
return False, "Bridge no disponible en Windows."
|
|
|
|
def start_dhcp_server(
|
|
self, interface: str, ip: str, range_start: str, range_end: str
|
|
) -> Tuple[bool, str]:
|
|
return False, "DHCP no disponible en Windows."
|
|
|
|
def delete_bridge(self, bridge_name: str) -> Tuple[bool, str]:
|
|
return False, "No disponible en Windows."
|
|
|
|
def setup_wifi_ap(
|
|
self, wifi_iface: str, ssid: str, password: str, bridge_name: str
|
|
) -> Tuple[bool, str]:
|
|
return False, "Wi-Fi AP no disponible en Windows."
|
|
|
|
def reset_interface(self, iface_name: str) -> Tuple[bool, str]:
|
|
return False, "Reinicio de interfaz no disponible en Windows."
|
|
|
|
def close(self) -> None:
|
|
pass
|