Конкретный вывод hostname

This commit is contained in:
parent dfbd387fae
commit 950a09d18b
2 changed files with 55 additions and 34 deletions

View file

@ -12,6 +12,7 @@ import json
from pathlib import Path from pathlib import Path
from datetime import datetime, timedelta from datetime import datetime, timedelta
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
import subprocess
from models.subnet import SubnetScanResult, Device from models.subnet import SubnetScanResult, Device
from config.ports import port_config from config.ports import port_config
@ -24,9 +25,7 @@ logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class NetworkScanner: class NetworkScanner:
def __init__(self, interface: str): def __init__(self, interface: str):
self.interface = interface self.interface = interface
conf.iface = interface conf.iface = interface
@ -46,9 +45,7 @@ class NetworkScanner:
logger.error(f"Ошибка ARP-сканирования подсети {subnet}: {str(e)}") logger.error(f"Ошибка ARP-сканирования подсети {subnet}: {str(e)}")
return [] return []
class PortScanner: class PortScanner:
def __init__(self, interface: str): def __init__(self, interface: str):
self.interface = interface self.interface = interface
@ -61,7 +58,6 @@ class PortScanner:
IP(dst=ip) / TCP(dport=port, flags="S"), IP(dst=ip) / TCP(dport=port, flags="S"),
timeout=2, timeout=2,
verbose=False, verbose=False,
iface=self.interface,
) )
if pkt and pkt.haslayer(TCP) and pkt[TCP].flags == 0x12: if pkt and pkt.haslayer(TCP) and pkt[TCP].flags == 0x12:
return port_config.get_port_name(port) return port_config.get_port_name(port)
@ -83,11 +79,11 @@ class PortScanner:
f"Ошибка при сканировании портов для IP {ip}: {str(e)}") f"Ошибка при сканировании портов для IP {ip}: {str(e)}")
return [] return []
class DeviceInfoCollector: class DeviceInfoCollector:
def __init__(self): def __init__(self):
self.mac_lookup = MacLookup() self.mac_lookup = MacLookup()
self.nbtscan_cache = {}
self.nbtscan_results = {}
@lru_cache(maxsize=1000) @lru_cache(maxsize=1000)
def get_vendor(self, mac: str) -> str: def get_vendor(self, mac: str) -> str:
@ -98,16 +94,51 @@ class DeviceInfoCollector:
f"Не удалось определить производителя для MAC {mac}: {str(e)}") f"Не удалось определить производителя для MAC {mac}: {str(e)}")
return "Unknown" return "Unknown"
def scan_nbtscan(self, subnet: str) -> None:
if subnet not in self.nbtscan_results:
try:
logger.debug(f"Запуск nbtscan для подсети {subnet}")
result = subprocess.run(["sudo", "nbtscan", "-r", str(subnet)],
capture_output=True,
text=True,
check=False,
timeout=10)
logger.debug(f"Полный вывод nbtscan: {result.stdout}")
if result.returncode == 0:
for line in result.stdout.splitlines():
logger.debug(f"Обработка строки: {line}")
if not line.startswith(("IP address", "-", "Doing NBT")):
parts = [p for p in line.split() if p]
if len(parts) >= 2:
ip_addr, hostname = parts[0], parts[1]
if hostname != "<unknown>":
self.nbtscan_cache[ip_addr] = hostname
logger.info(f"NetBIOS: Найден хостнейм {hostname} для IP {ip_addr}")
self.nbtscan_results[subnet] = True
except subprocess.TimeoutExpired:
logger.error(f"NetBIOS: Таймаут для подсети {subnet}")
except Exception as e:
logger.error(f"NetBIOS: Ошибка для подсети {subnet}: {str(e)}")
def get_hostname(self, ip: str) -> Optional[str]: def get_hostname(self, ip: str) -> Optional[str]:
try: try:
socket.setdefaulttimeout(2)
return socket.gethostbyaddr(ip)[0] return socket.gethostbyaddr(ip)[0]
except: except (socket.herror, socket.gaierror, socket.timeout):
logger.debug(f"DNS: Не удалось разрешить имя для IP {ip}")
subnet = ipaddress.ip_network(f"{ip}/24", strict=False)
if subnet not in self.nbtscan_results:
self.scan_nbtscan(str(subnet))
if ip in self.nbtscan_cache:
return self.nbtscan_cache[ip]
logger.debug(f"Не удалось определить хостнейм для IP {ip} через DNS или NetBIOS")
return None return None
class DeviceStorage: class DeviceStorage:
def __init__(self, storage_path: str = "etc/gonets/SRV/storage/devices/devices.json"):
def __init__(self, storage_path: str = "storage/devices/devices.json"):
self.storage_file = Path(storage_path) self.storage_file = Path(storage_path)
self.storage_file.parent.mkdir(parents=True, exist_ok=True) self.storage_file.parent.mkdir(parents=True, exist_ok=True)
self.offline_timeout = timedelta(days=7) self.offline_timeout = timedelta(days=7)
@ -128,8 +159,7 @@ class DeviceStorage:
logger.error(f"Ошибка при загрузке устройств: {str(e)}") logger.error(f"Ошибка при загрузке устройств: {str(e)}")
return {} return {}
def _clean_old_devices(self, devices: Dict[str, def _clean_old_devices(self, devices: Dict[str, Device]) -> Dict[str, Device]:
Device]) -> Dict[str, Device]:
now = datetime.now() now = datetime.now()
filtered = {} filtered = {}
for mac, device in devices.items(): for mac, device in devices.items():
@ -139,11 +169,9 @@ class DeviceStorage:
if now - last_seen <= self.offline_timeout: if now - last_seen <= self.offline_timeout:
filtered[mac] = device filtered[mac] = device
else: else:
logger.info( logger.info(f"Удалено устройство {mac} (offline > 7 дней)")
f"Удалено устройство {mac} (offline > 7 дней)")
except ValueError: except ValueError:
logger.warning( logger.warning(f"Неверный формат last_seen для устройства {mac}")
f"Неверный формат last_seen для устройства {mac}")
else: else:
filtered[mac] = device filtered[mac] = device
return filtered return filtered
@ -153,14 +181,11 @@ class DeviceStorage:
data = SubnetScanResult(devices=devices).model_dump() data = SubnetScanResult(devices=devices).model_dump()
with open(self.storage_file, "w", encoding="utf-8") as f: with open(self.storage_file, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False) json.dump(data, f, indent=2, ensure_ascii=False)
logger.info( logger.info(f"Сохранено {len(devices)} устройств в {self.storage_file}")
f"Сохранено {len(devices)} устройств в {self.storage_file}")
except Exception as e: except Exception as e:
logger.error(f"Ошибка при сохранении устройств: {str(e)}") logger.error(f"Ошибка при сохранении устройств: {str(e)}")
class SubnetService: class SubnetService:
def __init__(self): def __init__(self):
self.interface = self._get_default_interface() self.interface = self._get_default_interface()
self.network_scanner = NetworkScanner(self.interface) self.network_scanner = NetworkScanner(self.interface)
@ -171,7 +196,7 @@ class SubnetService:
def _get_default_interface(self) -> str: def _get_default_interface(self) -> str:
try: try:
gateways = netifaces.gateways() gateways = netifaces.gateways()
default_gateway = gateways.get("default",{}).get(netifaces.AF_INET) default_gateway = gateways.get("default", {}).get(netifaces.AF_INET)
if default_gateway: if default_gateway:
return default_gateway[1] return default_gateway[1]
interfaces = netifaces.interfaces() interfaces = netifaces.interfaces()
@ -183,9 +208,7 @@ class SubnetService:
logger.warning(f"Не удалось определить интерфейс: {str(e)}") logger.warning(f"Не удалось определить интерфейс: {str(e)}")
return "eth0" return "eth0"
def scan_subnet(self, def scan_subnet(self, subnet: str, ports: List[int] = None) -> SubnetScanResult:
subnet: str,
ports: List[int] = None) -> SubnetScanResult:
start_time = time.time() start_time = time.time()
devices = [] devices = []
now = datetime.now().astimezone() now = datetime.now().astimezone()
@ -202,6 +225,7 @@ class SubnetService:
saved_devices[local_device.mac] = local_device saved_devices[local_device.mac] = local_device
discovered_devices = self.network_scanner.scan(subnet) discovered_devices = self.network_scanner.scan(subnet)
self.device_info.scan_nbtscan(subnet)
def process_device(ip_mac: Tuple[str, str]) -> Optional[Device]: def process_device(ip_mac: Tuple[str, str]) -> Optional[Device]:
ip, mac = ip_mac ip, mac = ip_mac
@ -213,11 +237,9 @@ class SubnetService:
return Device( return Device(
ip=ip, ip=ip,
mac=mac, mac=mac,
hostname=self.device_info.get_hostname(ip) hostname=self.device_info.get_hostname(ip) or existing_device.hostname,
or existing_device.hostname,
vendor=self.device_info.get_vendor(mac), vendor=self.device_info.get_vendor(mac),
open_ports=self.port_scanner.scan(ip) open_ports=self.port_scanner.scan(ip) or existing_device.open_ports,
or existing_device.open_ports,
status="online", status="online",
last_seen=formatted_now, last_seen=formatted_now,
note=existing_device.note, note=existing_device.note,
@ -237,8 +259,7 @@ class SubnetService:
) )
with ThreadPoolExecutor(max_workers=10) as executor: with ThreadPoolExecutor(max_workers=10) as executor:
device_results = executor.map(process_device, device_results = executor.map(process_device, discovered_devices)
discovered_devices)
devices.extend([d for d in device_results if d is not None]) devices.extend([d for d in device_results if d is not None])
active_macs = {d.mac for d in devices if d.mac} active_macs = {d.mac for d in devices if d.mac}
@ -282,7 +303,7 @@ class SubnetService:
hostname = self.device_info.get_hostname(ip) hostname = self.device_info.get_hostname(ip)
open_ports = self.port_scanner.scan(ip) open_ports = self.port_scanner.scan(ip)
logger.debug( logger.debug(
f"Локальное устройство: IP={ip}, MAC={mac}, Ports={open_ports}" f"Локальное устройство: IP={ip}, MAC={mac}, Ports={open_ports}, Hostname={hostname}"
) )
return Device( return Device(
ip=ip, ip=ip,

View file

@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
class WifiStorage: class WifiStorage:
"""Класс для хранения данных о Wi-Fi сетях и их заметках по SSID.""" """Класс для хранения данных о Wi-Fi сетях и их заметках по SSID."""
def __init__(self, storage_path: str = "storage/wifi/wifi.json"): def __init__(self, storage_path: str = "etc/gonets/SRV/storage/wifi/wifi.json"):
self.storage_file = Path(storage_path) self.storage_file = Path(storage_path)
self.storage_file.parent.mkdir(parents=True, exist_ok=True) self.storage_file.parent.mkdir(parents=True, exist_ok=True)