Alternative method scan subnet

This commit is contained in:
parent a69c5ac5fb
commit 660fb66fc0
2 changed files with 357 additions and 2 deletions

View file

@ -433,6 +433,65 @@ class SubnetService:
logger.warning(f"Failed to determine interface: {str(e)}, defaulting to eth0")
return "eth0"
def _get_prefix_from_ip_cmd(self, iface: str) -> Optional[int]:
"""Пытается получить префикс подсети из вывода `ip addr`, если netifaces дал неточную маску."""
try:
result = subprocess.run(
["ip", "-o", "-f", "inet", "addr", "show", "dev", iface],
capture_output=True,
text=True,
check=False,
timeout=2,
)
if result.returncode != 0 or not result.stdout:
return None
# пример строки: "3: wlp0s20f3 inet 10.1.1.31/24 brd 10.1.1.255 scope global dynamic noprefixroute wlp0s20f3"
for part in result.stdout.split():
if "/" in part and part.count(".") == 3:
try:
return int(part.split("/")[1])
except ValueError:
continue
return None
except Exception as e:
logger.debug(f"ip addr prefix detection failed for {iface}: {e}")
return None
def _get_prefix_from_proc_route(self, iface: str) -> Optional[int]:
"""Читает маску для интерфейса из /proc/net/route (не требует прав netlink)."""
try:
with open("/proc/net/route") as f:
for line in f:
parts = line.strip().split()
if len(parts) < 8 or parts[0] != iface:
continue
mask_hex = parts[7]
try:
mask_int = int(mask_hex, 16)
except ValueError:
continue
# mask_int в host byte order, например FF000000 для 255.0.0.0
mask_str = socket.inet_ntoa(mask_int.to_bytes(4, byteorder="little"))
return ipaddress.ip_network(f"0.0.0.0/{mask_str}", strict=False).prefixlen
return None
except Exception as e:
logger.debug(f"/proc/net/route prefix detection failed for {iface}: {e}")
return None
def _get_prefix_from_broadcast(self, ip: str, broadcast: Optional[str]) -> Optional[int]:
"""Вычисляет префикс из пары IP + broadcast, если она присутствует (часто точнее для 10.x)."""
if not ip or not broadcast:
return None
try:
ip_int = int(ipaddress.IPv4Address(ip))
bcast_int = int(ipaddress.IPv4Address(broadcast))
mask_int = ~(ip_int ^ bcast_int) & 0xFFFFFFFF
mask = ipaddress.ip_network(f"0.0.0.0/{ipaddress.IPv4Address(mask_int)}", strict=False).prefixlen
return mask
except Exception as e:
logger.debug(f"Broadcast-based prefix detection failed for {ip}/{broadcast}: {e}")
return None
def _get_default_subnet(self) -> str:
"""Автоматически определяет подсеть локального устройства.
@ -454,8 +513,15 @@ class SubnetService:
ip_info = addrs[netifaces.AF_INET][0]
ip = ip_info["addr"]
netmask = ip_info["netmask"]
# Преобразуем маску из строки (например, "255.255.255.0") в CIDR
mask = ipaddress.ip_network(f"0.0.0.0/{netmask}", strict=False).prefixlen
broadcast = ip_info.get("broadcast")
# netifaces иногда отдаёт неточную маску (например, 255.0.0.0 для 10.x),
# пробуем уточнить через `ip addr`, затем /proc/net/route, затем по broadcast, и только потом берём netmask.
mask = (
self._get_prefix_from_ip_cmd(iface)
or self._get_prefix_from_proc_route(iface)
or self._get_prefix_from_broadcast(ip, broadcast)
or ipaddress.ip_network(f"0.0.0.0/{netmask}", strict=False).prefixlen
)
network = ipaddress.ip_network(f"{ip}/{mask}", strict=False)
logger.info(f"Auto-detected subnet: {network} (IP: {ip}, Mask: {netmask} -> /{mask})")
return str(network)