Рефакторинг. docstring
This commit is contained in:
parent
060f4de175
commit
37f9e3abfb
14 changed files with 1234 additions and 243 deletions
|
|
@ -11,7 +11,6 @@ from functools import lru_cache
|
|||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
import subprocess
|
||||
|
||||
from models.subnet import SubnetScanResult, Device
|
||||
|
|
@ -26,11 +25,33 @@ logging.basicConfig(
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
class NetworkScanner:
|
||||
"""Класс для сканирования подсети с использованием ARP-запросов.
|
||||
|
||||
Выполняет обнаружение устройств в указанной подсети через ARP-пакеты.
|
||||
"""
|
||||
|
||||
def __init__(self, interface: str):
|
||||
"""Инициализирует сканер сети с указанным сетевым интерфейсом.
|
||||
|
||||
Args:
|
||||
interface (str): Название сетевого интерфейса (например, "eth0").
|
||||
"""
|
||||
self.interface = interface
|
||||
conf.iface = interface
|
||||
|
||||
def scan(self, subnet: str) -> List[Tuple[str, str]]:
|
||||
"""Сканирует подсеть с использованием ARP-запросов.
|
||||
|
||||
Args:
|
||||
subnet (str): Подсеть в формате CIDR (например, "192.168.1.0/24").
|
||||
|
||||
Returns:
|
||||
List[Tuple[str, str]]: Список кортежей (IP-адрес, MAC-адрес) обнаруженных
|
||||
устройств.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при отправке ARP-пакетов.
|
||||
"""
|
||||
try:
|
||||
arp = ARP(pdst=subnet)
|
||||
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
|
||||
|
|
@ -46,13 +67,42 @@ class NetworkScanner:
|
|||
return []
|
||||
|
||||
class PortScanner:
|
||||
"""Класс для сканирования открытых портов на устройствах.
|
||||
|
||||
Выполняет сканирование портов с использованием TCP SYN-запросов.
|
||||
"""
|
||||
|
||||
def __init__(self, interface: str):
|
||||
"""Инициализирует сканер портов с указанным сетевым интерфейсом.
|
||||
|
||||
Args:
|
||||
interface (str): Название сетевого интерфейса (например, "eth0").
|
||||
"""
|
||||
self.interface = interface
|
||||
|
||||
def scan(self, ip: str) -> List[str]:
|
||||
"""Сканирует открытые порты на указанном IP-адресе.
|
||||
|
||||
Args:
|
||||
ip (str): IP-адрес устройства для сканирования.
|
||||
|
||||
Returns:
|
||||
List[str]: Список имён открытых портов (например, "HTTP", "SSH").
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при сканировании портов.
|
||||
"""
|
||||
open_ports = []
|
||||
|
||||
def check_port(port: int) -> Optional[str]:
|
||||
"""Проверяет доступность конкретного порта.
|
||||
|
||||
Args:
|
||||
port (int): Номер порта для проверки.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Имя порта, если открыт, иначе None.
|
||||
"""
|
||||
try:
|
||||
pkt = sr1(
|
||||
IP(dst=ip) / TCP(dport=port, flags="S"),
|
||||
|
|
@ -80,13 +130,33 @@ class PortScanner:
|
|||
return []
|
||||
|
||||
class DeviceInfoCollector:
|
||||
"""Класс для сбора информации об устройствах, включая вендоров и хостнеймы.
|
||||
|
||||
Управляет кэшированием данных NetBIOS и DNS.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Инициализирует коллектор информации об устройствах.
|
||||
|
||||
Создаёт объект MacLookup для определения вендоров и инициализирует кэши.
|
||||
"""
|
||||
self.mac_lookup = MacLookup()
|
||||
self.nbtscan_cache = {}
|
||||
self.nbtscan_results = {}
|
||||
|
||||
@lru_cache(maxsize=1000)
|
||||
def get_vendor(self, mac: str) -> str:
|
||||
"""Определяет вендора по MAC-адресу с использованием кэширования.
|
||||
|
||||
Args:
|
||||
mac (str): MAC-адрес устройства в формате XX:XX:XX:XX:XX:XX.
|
||||
|
||||
Returns:
|
||||
str: Название вендора или "Unknown", если вендор не определён.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при запросе к базе вендоров.
|
||||
"""
|
||||
try:
|
||||
return self.mac_lookup.lookup(mac)
|
||||
except Exception as e:
|
||||
|
|
@ -95,6 +165,13 @@ class DeviceInfoCollector:
|
|||
return "Unknown"
|
||||
|
||||
def scan_nbtscan(self, subnet: str) -> None:
|
||||
"""Выполняет сканирование NetBIOS для указанной подсети.
|
||||
|
||||
Args:
|
||||
subnet (str): Подсеть в формате CIDR (например, "192.168.1.0/24").
|
||||
|
||||
Сохраняет результаты в кэш, игнорируя хостнеймы "<unknown>".
|
||||
"""
|
||||
if subnet not in self.nbtscan_results:
|
||||
try:
|
||||
logger.debug(f"Запуск nbtscan для подсети {subnet}")
|
||||
|
|
@ -121,6 +198,16 @@ class DeviceInfoCollector:
|
|||
logger.error(f"NetBIOS: Ошибка для подсети {subnet}: {str(e)}")
|
||||
|
||||
def get_hostname(self, ip: str) -> Optional[str]:
|
||||
"""Определяет хостнейм для указанного IP-адреса.
|
||||
|
||||
Args:
|
||||
ip (str): IP-адрес устройства.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Хостнейм, если найден, иначе None.
|
||||
|
||||
Сначала пытается использовать DNS, затем NetBIOS через nbtscan.
|
||||
"""
|
||||
try:
|
||||
socket.setdefaulttimeout(2)
|
||||
return socket.gethostbyaddr(ip)[0]
|
||||
|
|
@ -138,12 +225,33 @@ class DeviceInfoCollector:
|
|||
return None
|
||||
|
||||
class DeviceStorage:
|
||||
"""Класс для хранения и управления данными об устройствах.
|
||||
|
||||
Хранит устройства в JSON-файле и удаляет устаревшие записи.
|
||||
"""
|
||||
|
||||
def __init__(self, storage_path: str = "etc/gonets/SRV/storage/devices/devices.json"):
|
||||
"""Инициализирует хранилище устройств с указанным путём.
|
||||
|
||||
Args:
|
||||
storage_path (str, optional): Путь к файлу хранения.
|
||||
По умолчанию "etc/gonets/SRV/storage/devices/devices.json".
|
||||
|
||||
Создаёт родительские директории, если они не существуют.
|
||||
"""
|
||||
self.storage_file = Path(storage_path)
|
||||
self.storage_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.offline_timeout = timedelta(days=7)
|
||||
|
||||
def load_devices(self) -> Dict[str, Device]:
|
||||
"""Загружает устройства из файла хранения.
|
||||
|
||||
Returns:
|
||||
Dict[str, Device]: Словарь устройств, где ключ — MAC-адрес.
|
||||
|
||||
Удаляет устройства с неверным форматом last_seen и возвращает пустой словарь
|
||||
при ошибке.
|
||||
"""
|
||||
try:
|
||||
if self.storage_file.exists():
|
||||
with open(self.storage_file, "r", encoding="utf-8") as f:
|
||||
|
|
@ -160,6 +268,15 @@ class DeviceStorage:
|
|||
return {}
|
||||
|
||||
def _clean_old_devices(self, devices: Dict[str, Device]) -> Dict[str, Device]:
|
||||
"""Очищает устаревшие устройства из словаря.
|
||||
|
||||
Args:
|
||||
devices (Dict[str, Device]): Словарь устройств с MAC-адресами как ключами.
|
||||
|
||||
Returns:
|
||||
Dict[str, Device]: Отфильтрованный словарь, исключающий устройства
|
||||
offline более 7 дней.
|
||||
"""
|
||||
now = datetime.now()
|
||||
filtered = {}
|
||||
for mac, device in devices.items():
|
||||
|
|
@ -177,6 +294,14 @@ class DeviceStorage:
|
|||
return filtered
|
||||
|
||||
def save_devices(self, devices: List[Device]) -> None:
|
||||
"""Сохраняет список устройств в файл.
|
||||
|
||||
Args:
|
||||
devices (List[Device]): Список объектов Device для сохранения.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при записи в файл.
|
||||
"""
|
||||
try:
|
||||
data = SubnetScanResult(devices=devices).model_dump()
|
||||
with open(self.storage_file, "w", encoding="utf-8") as f:
|
||||
|
|
@ -186,7 +311,16 @@ class DeviceStorage:
|
|||
logger.error(f"Ошибка при сохранении устройств: {str(e)}")
|
||||
|
||||
class SubnetService:
|
||||
"""Сервис для сканирования подсетей и управления устройствами.
|
||||
|
||||
Объединяет функционал сканирования сети, портов и хранения данных об устройствах.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Инициализирует сервис подсети с настройкой всех компонентов.
|
||||
|
||||
Автоматически определяет интерфейс и инициализирует сканеры и хранилище.
|
||||
"""
|
||||
self.interface = self._get_default_interface()
|
||||
self.network_scanner = NetworkScanner(self.interface)
|
||||
self.port_scanner = PortScanner(self.interface)
|
||||
|
|
@ -194,6 +328,13 @@ class SubnetService:
|
|||
self.storage = DeviceStorage()
|
||||
|
||||
def _get_default_interface(self) -> str:
|
||||
"""Определяет имя сетевого интерфейса по умолчанию.
|
||||
|
||||
Returns:
|
||||
str: Название интерфейса (например, "eth0").
|
||||
|
||||
Использует информацию о шлюзе или первый доступный интерфейс, отличный от "lo".
|
||||
"""
|
||||
try:
|
||||
gateways = netifaces.gateways()
|
||||
default_gateway = gateways.get("default", {}).get(netifaces.AF_INET)
|
||||
|
|
@ -209,6 +350,19 @@ class SubnetService:
|
|||
return "eth0"
|
||||
|
||||
def scan_subnet(self, subnet: str, ports: List[int] = None) -> SubnetScanResult:
|
||||
"""Сканирует указанную подсеть и возвращает результаты.
|
||||
|
||||
Args:
|
||||
subnet (str): Подсеть в формате CIDR (например, "192.168.1.0/24").
|
||||
ports (List[int], optional): Список портов для сканирования.
|
||||
По умолчанию None (используются порты из config.ports).
|
||||
|
||||
Returns:
|
||||
SubnetScanResult: Объект с результатами сканирования, содержащий список
|
||||
устройств.
|
||||
|
||||
Сохраняет данные в хранилище и обновляет статус устройств.
|
||||
"""
|
||||
start_time = time.time()
|
||||
devices = []
|
||||
now = datetime.now().astimezone()
|
||||
|
|
@ -228,6 +382,14 @@ class SubnetService:
|
|||
self.device_info.scan_nbtscan(subnet)
|
||||
|
||||
def process_device(ip_mac: Tuple[str, str]) -> Optional[Device]:
|
||||
"""Обрабатывает устройство из пары IP и MAC.
|
||||
|
||||
Args:
|
||||
ip_mac (Tuple[str, str]): Кортеж с IP-адресом и MAC-адресом.
|
||||
|
||||
Returns:
|
||||
Optional[Device]: Объект Device или None, если устройство локальное.
|
||||
"""
|
||||
ip, mac = ip_mac
|
||||
if local_device and ip == local_device.ip:
|
||||
return None
|
||||
|
|
@ -286,6 +448,14 @@ class SubnetService:
|
|||
return SubnetScanResult(devices=devices)
|
||||
|
||||
def _get_local_device(self, subnet: str) -> Optional[Device]:
|
||||
"""Определяет локальное устройство в указанной подсети.
|
||||
|
||||
Args:
|
||||
subnet (str): Подсеть в формате CIDR (например, "192.168.1.0/24").
|
||||
|
||||
Returns:
|
||||
Optional[Device]: Объект локального устройства или None, если не найдено.
|
||||
"""
|
||||
now = datetime.now().astimezone()
|
||||
formatted_now = now.strftime("%d-%m-%Y, %H:%M")
|
||||
try:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue