diff --git a/SRV/services/subnet_service.py b/SRV/services/subnet_service.py index 3157fd1..3172125 100644 --- a/SRV/services/subnet_service.py +++ b/SRV/services/subnet_service.py @@ -1,5 +1,5 @@ from scapy.config import conf -from manuf import MacParser +from mac_vendor_lookup import MacLookup from typing import List, Optional, Tuple, Dict import socket import logging @@ -12,7 +12,6 @@ import json from pathlib import Path from datetime import datetime, timedelta from zoneinfo import ZoneInfo -import os from models.subnet import SubnetScanResult, Device from config.ports import port_config @@ -23,17 +22,6 @@ from scapy.sendrecv import srp, sr1 logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) -# Подавляем логи Scapy -logging.getLogger("scapy").setLevel(logging.ERROR) - - -def normalize_mac(mac: str) -> str: - """Нормализует MAC-адрес к формату XX:XX:XX:XX:XX:XX.""" - if not mac: - return "" - mac = mac.replace(":", "").replace("-", "").upper() - return ":".join(mac[i:i + 2] for i in range(0, 12, 2)) - class NetworkScanner: """Класс для сканирования сети с помощью ARP-запросов.""" @@ -41,19 +29,14 @@ class NetworkScanner: def __init__(self, interface: str): self.interface = interface conf.iface = interface - self.arp_cache: Dict[str, str] = {} def scan(self, subnet: str) -> List[Tuple[str, str]]: try: arp = ARP(pdst=subnet) ether = Ether(dst="ff:ff:ff:ff:ff:ff") packet = ether / arp - result = srp(packet, timeout=5, verbose=False, iface=self.interface)[0] - devices = [(received.psrc, normalize_mac(received.hwsrc)) for sent, received in result] - for ip, mac in devices: - self.arp_cache[ip] = mac - logger.debug(f"ARP-кэш обновлён: {len(self.arp_cache)} записей") - return devices + result = srp(packet, timeout=2, verbose=False, iface=self.interface)[0] + return [(received.psrc, received.hwsrc) for sent, received in result] except Exception as e: logger.error(f"Ошибка ARP-сканирования подсети {subnet}: {str(e)}") return [] @@ -62,18 +45,16 @@ class NetworkScanner: class PortScanner: """Класс для сканирования портов на устройстве.""" - def __init__(self, interface: str, arp_cache: Dict[str, str]): + def __init__(self, interface: str): self.interface = interface - self.arp_cache = arp_cache def scan(self, ip: str) -> List[str]: open_ports = [] def check_port(port: int) -> Optional[str]: try: - dst_mac = self.arp_cache.get(ip, "ff:ff:ff:ff:ff:ff") pkt = sr1( - Ether(dst=dst_mac) / IP(dst=ip) / TCP(dport=port, flags="S"), + IP(dst=ip) / TCP(dport=port, flags="S"), timeout=2, verbose=False, iface=self.interface @@ -86,7 +67,7 @@ class PortScanner: return None try: - max_workers = min(len(port_config.get_ports()), 20) # Уменьшено до 20 + max_workers = min(len(port_config.get_ports()), 15) # Снижено с 100 with ThreadPoolExecutor(max_workers=max_workers) as executor: results = executor.map(check_port, port_config.get_ports()) open_ports = [port for port in results if port is not None] @@ -101,33 +82,14 @@ class DeviceInfoCollector: """Класс для сбора информации об устройстве.""" def __init__(self): - self.mac_lookup = MacParser() - try: - logger.info("Обновление базы производителей MAC...") - self.mac_lookup.update() - logger.info("База производителей обновлена") - except Exception as e: - logger.warning(f"Не удалось обновить базу производителей: {str(e)}") - # Проверяем наличие локальной базы - manuf_db = Path(os.path.expanduser("~/.cache/manuf/manuf.txt")) - if manuf_db.exists(): - logger.info(f"Используется локальная база OUI: {manuf_db}") - else: - logger.error("Локальная база OUI отсутствует, vendors могут быть Unknown") + self.mac_lookup = MacLookup() - @lru_cache(maxsize=1000) + @lru_cache(maxsize=1000) # Снижено с 10000 def get_vendor(self, mac: str) -> str: - if not mac or mac.lower() == "ff:ff:ff:ff:ff:ff": - return "Unknown" - normalized_mac = normalize_mac(mac) try: - vendor = self.mac_lookup.get_manuf(normalized_mac) - if vendor is None: - vendor = "Unknown" - logger.debug(f"Vendor для MAC {normalized_mac}: {vendor}") - return vendor + return self.mac_lookup.lookup(mac) except Exception as e: - logger.debug(f"Не удалось определить производителя для MAC {normalized_mac}: {str(e)}") + logger.warning(f"Не удалось определить производителя для MAC {mac}: {str(e)}") return "Unknown" def get_hostname(self, ip: str) -> Optional[str]: @@ -154,7 +116,7 @@ class DeviceStorage: for device_data in data.get("devices", []): device = Device(**device_data) if device.mac: - devices[normalize_mac(device.mac)] = device + devices[device.mac] = device return self._clean_old_devices(devices) return {} except Exception as e: @@ -180,11 +142,6 @@ class DeviceStorage: def save_devices(self, devices: List[Device]) -> None: try: - for device in devices: - if isinstance(device.last_seen, datetime): - device.last_seen = device.last_seen.isoformat() - if device.mac: - device.mac = normalize_mac(device.mac) data = SubnetScanResult(devices=devices).model_dump() with open(self.storage_file, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) @@ -199,18 +156,18 @@ class SubnetService: def __init__(self): self.interface = self._get_default_interface() self.network_scanner = NetworkScanner(self.interface) + self.port_scanner = PortScanner(self.interface) self.device_info = DeviceInfoCollector() - self.port_scanner = PortScanner(self.interface, self.network_scanner.arp_cache) self.storage = DeviceStorage() - def _get_default_interface(self): - """Определяет основной сетевой интерфейс.""" + def _get_default_interface(self) -> str: try: gateways = netifaces.gateways() default_gateway = gateways.get("default", {}).get(netifaces.AF_INET) if default_gateway: return default_gateway[1] - for iface in netifaces.interfaces(): + interfaces = netifaces.interfaces() + for iface in interfaces: if iface != "lo": return iface return "eth0" @@ -219,63 +176,79 @@ class SubnetService: return "eth0" def scan_subnet(self, subnet: str, ports: List[int] = None) -> SubnetScanResult: - """Сканирование подсети и результаты.""" start_time = time.time() devices = [] now = datetime.now(ZoneInfo("UTC")).isoformat() try: + # Загружаем старую карту saved_devices = self.storage.load_devices() + # Получаем локальное устройство local_device = self._get_local_device(subnet) if local_device: local_device.status = "online" local_device.last_seen = now devices.append(local_device) if local_device.mac: - saved_devices[normalize_mac(local_device.mac)] = local_device + saved_devices[local_device.mac] = local_device + # Сканируем подсеть discovered_devices = self.network_scanner.scan(subnet) - with ThreadPoolExecutor(max_workers=20) as executor: # Уменьшено до 20 - def process_device(ip_mac: Tuple[str, str]) -> Optional[Device]: - ip, mac = ip_mac - mac = normalize_mac(mac) - if local_device and ip == local_device.ip: - return None - existing_device = saved_devices.get(mac) - vendor = self.device_info.get_vendor(mac) - if vendor == "Unknown" and existing_device and existing_device.vendor != "Unknown": - vendor = existing_device.vendor - logger.debug(f"Использован сохранённый vendor для MAC {mac}: {vendor}") + # Обрабатываем обнаруженные устройства + def process_device(ip_mac: Tuple[str, str]) -> Optional[Device]: + ip, mac = ip_mac + if local_device and ip == local_device.ip: + return None + # Проверяем, есть ли устройство в старой карте + existing_device = saved_devices.get(mac) + if existing_device: + # Обновляем данные, если MAC совпадает return Device( ip=ip, mac=mac, - hostname=self.device_info.get_hostname(ip) or ( - existing_device.hostname if existing_device else None), - vendor=vendor, - open_ports=self.port_scanner.scan(ip) or [], + hostname=self.device_info.get_hostname(ip) or existing_device.hostname, + vendor=self.device_info.get_vendor(mac), + open_ports=self.port_scanner.scan(ip) or existing_device.open_ports, status="online", last_seen=now ) + # Новое устройство + vendor = self.device_info.get_vendor(mac) + hostname = self.device_info.get_hostname(ip) + open_ports = self.port_scanner.scan(ip) + return Device( + ip=ip, + mac=mac, + hostname=hostname, + vendor=vendor, + open_ports=open_ports, + status="online", + last_seen=now + ) + with ThreadPoolExecutor(max_workers=10) as executor: device_results = executor.map(process_device, discovered_devices) devices.extend([d for d in device_results if d is not None]) + # Добавляем оффлайн-устройства из старой карты active_macs = {d.mac for d in devices if d.mac} for mac, device in saved_devices.items(): if mac not in active_macs: device.status = "offline" devices.append(device) - devices.sort(key=lambda x: int(ipaddress.ip_address(x.ip))) + # Сортировка по IP + devices.sort(key=lambda x: int(ipaddress.IPv4Address(x.ip))) + # Сохраняем обновлённую карту for device in devices: if device.mac: saved_devices[device.mac] = device - self.storage.save_devices(devices) + self.storage.save_devices(list(saved_devices.values())) logger.info( - f"Сканирование подсети {subnet} завершено за {time.time() - start_time:.2f} сек, " + f"Сканирование подсети {subnet} завершено за {time.time() - start_time:.2f} секунд, " f"найдено {len(devices)} устройств (активных: {len(active_macs)})" ) return SubnetScanResult(devices=devices) @@ -285,22 +258,22 @@ class SubnetService: return SubnetScanResult(devices=devices) def _get_local_device(self, subnet: str) -> Optional[Device]: - """Получает информацию о локальном устройстве.""" now = datetime.now(ZoneInfo("UTC")).isoformat() try: subnet_network = ipaddress.ip_network(subnet, strict=False) - for iface in netifaces.interfaces(): + interfaces = netifaces.interfaces() + for iface in interfaces: if iface == "lo": continue addrs = netifaces.ifaddresses(iface) if netifaces.AF_INET in addrs and netifaces.AF_LINK in addrs: ip = addrs[netifaces.AF_INET][0]["addr"] if ipaddress.ip_address(ip) in subnet_network: - mac = normalize_mac(addrs[netifaces.AF_LINK][0]["addr"]) + mac = addrs[netifaces.AF_LINK][0]["addr"] vendor = self.device_info.get_vendor(mac) hostname = self.device_info.get_hostname(ip) open_ports = self.port_scanner.scan(ip) - logger.debug(f"Локальное устройство: IP={ip}, MAC={mac}, Vendor={vendor}, Ports={open_ports}") + logger.debug(f"Локальное устройство: IP={ip}, MAC={mac}, Ports={open_ports}") return Device( ip=ip, mac=mac,