import ipaddress import socket import string from typing import Optional, Tuple import psutil def get_network_interfaces(): """Return only interfaces that have an IPv4 address to keep choices sane.""" interfaces = [] for iface, addrs in psutil.net_if_addrs().items(): for addr in addrs: if addr.family == socket.AF_INET: interfaces.append(iface) break return sorted(interfaces) def get_iface_ipv4_config(iface: str) -> Tuple[Optional[str], Optional[str]]: """Return (ipv4_addr, netmask) for interface, or (None, None) if not present.""" addrs = psutil.net_if_addrs().get(iface, []) for a in addrs: if a.family == socket.AF_INET: return a.address, a.netmask return None, None def get_iface_hwinfo(iface: str) -> Tuple[Optional[str], Optional[int]]: """Return (MAC, speed_mbps) where available.""" mac = None addrs = psutil.net_if_addrs().get(iface, []) for a in addrs: if getattr(a, "family", None) == getattr(psutil, "AF_LINK", None): mac = a.address break stats = psutil.net_if_stats().get(iface) speed = stats.speed if stats else None return mac, speed def format_ip_address(ip: str) -> str: """Normalize dotted quad; raises on invalid input.""" ip_obj = ipaddress.ip_address(ip) if ip_obj.version != 4: raise ValueError("Only IPv4 is supported.") return str(ip_obj) def validate_subnet_mask(mask: str) -> None: """Accept masks like 255.255.255.0; raise ValueError if invalid.""" ipaddress.IPv4Network(f"0.0.0.0/{mask}") def normalize_mac(mac: str) -> str: """Return MAC as AA:BB:CC:DD:EE:FF; raises on invalid input.""" cleaned = mac.strip().replace("-", "").replace(":", "").replace(".", "").lower() if len(cleaned) != 12 or any(c not in string.hexdigits for c in cleaned): raise ValueError("Ungültige MAC-Adresse.") return ":".join(cleaned[i:i+2] for i in range(0, 12, 2)).upper()