Рефакторинг. docstring
This commit is contained in:
parent
060f4de175
commit
37f9e3abfb
14 changed files with 1234 additions and 243 deletions
|
|
@ -5,10 +5,21 @@ from netaddr import IPNetwork
|
|||
|
||||
|
||||
class NetworkScanner:
|
||||
"""Иницилазицая сетевого сканера"""
|
||||
"""Инициализация сетевого сканера.
|
||||
|
||||
Предоставляет методы для сканирования сети с использованием ARP-запросов и
|
||||
вывода информации о клиентах.
|
||||
"""
|
||||
|
||||
def __init__(self, interface=None):
|
||||
"""Инициализация параметров интерфейсов"""
|
||||
"""Инициализация параметров сетевого сканера.
|
||||
|
||||
Args:
|
||||
interface (str, optional): Название сетевого интерфейса. По умолчанию
|
||||
используется интерфейс по умолчанию из netifaces.gateways().
|
||||
|
||||
Инициализирует интерфейс, информацию об IP-адресах и локальный MAC-адрес.
|
||||
"""
|
||||
self.interface = (
|
||||
interface or netifaces.gateways()["default"][netifaces.AF_INET][1])
|
||||
self.ip_info = netifaces.ifaddresses(self.interface).get(
|
||||
|
|
@ -20,13 +31,34 @@ class NetworkScanner:
|
|||
self.interface)[netifaces.AF_LINK][0]["addr"]
|
||||
|
||||
def _create_arp_packet(self, target_ip):
|
||||
"""Создание пакета ARP для отправки"""
|
||||
"""Создаёт ARP-пакет для отправки на указанный IP-адрес.
|
||||
|
||||
Args:
|
||||
target_ip (str): Целевой IP-адрес или подсеть для сканирования.
|
||||
|
||||
Returns:
|
||||
scapy.packet.Packet: Сформированный ARP-пакет с Ethernet-заголовком.
|
||||
"""
|
||||
arp = ARP(pdst=target_ip)
|
||||
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
|
||||
return ether / arp
|
||||
|
||||
def scan_network(self, attempts=3, timeout=2):
|
||||
"""Подготовка к сканированию сети"""
|
||||
"""Сканирует сеть для обнаружения активных клиентов.
|
||||
|
||||
Args:
|
||||
attempts (int, optional): Количество попыток отправки ARP-запросов.
|
||||
По умолчанию 3.
|
||||
timeout (int, optional): Таймаут ожидания ответа в секундах.
|
||||
По умолчанию 2.
|
||||
|
||||
Returns:
|
||||
List[dict]: Список словарей с ключами "ip" и "mac", представляющих
|
||||
обнаруженных клиентов, включая локальные IP-адреса.
|
||||
|
||||
Note:
|
||||
Учитывает локальные IP-адреса интерфейса и удаляет дубликаты.
|
||||
"""
|
||||
all_clients = []
|
||||
|
||||
for network in self.networks:
|
||||
|
|
@ -41,30 +73,35 @@ class NetworkScanner:
|
|||
verbose=0)[0]
|
||||
for _, received in result:
|
||||
client = {"ip": received.psrc, "mac": received.hwsrc}
|
||||
if client not in clients: # Избегаем дублирования клиентов
|
||||
if client not in clients:
|
||||
clients.append(client)
|
||||
|
||||
all_clients.extend(clients)
|
||||
|
||||
# Добавляет все локальные IP-адреса интерфейса как клиенты
|
||||
for ip_info in self.ip_info:
|
||||
local_client = {"ip": ip_info["addr"], "mac": self.local_mac}
|
||||
if local_client not in all_clients:
|
||||
all_clients.append(local_client)
|
||||
|
||||
# Удаление дубликатов
|
||||
unique_clients = {tuple(client.items()) for client in all_clients}
|
||||
return [dict(client) for client in unique_clients]
|
||||
|
||||
def print_clients(self, clients):
|
||||
"""Лист адресов клиентов"""
|
||||
"""Выводит список адресов клиентов в формате таблицы.
|
||||
|
||||
Args:
|
||||
clients (List[dict]): Список словарей с ключами "ip" и "mac",
|
||||
представляющих клиентов.
|
||||
|
||||
Выводит IP-адреса и MAC-адреса в отсортированном по IP порядке.
|
||||
"""
|
||||
print("Доступные устройства в сети:")
|
||||
print("IP" + " " * 18 + "MAC")
|
||||
for client in sorted(clients, key=lambda x: x["ip"]):
|
||||
print(f"{client['ip']:<16} {client['mac']}")
|
||||
|
||||
|
||||
# Оставил нативный запуск сервиса для тестов
|
||||
if __name__ == "__main__":
|
||||
scanner = NetworkScanner()
|
||||
clients = scanner.scan_network()
|
||||
scanner.print_clients(clients)
|
||||
scanner.print_clients(clients)
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
# services/auth_service.py
|
||||
from jose import JWTError, jwt
|
||||
from datetime import datetime, timedelta
|
||||
from fastapi import HTTPException, Depends, Request
|
||||
|
|
|
|||
|
|
@ -6,11 +6,28 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(level
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
class WifiConnectionService:
|
||||
"""Сервис для управления подключением к Wi-Fi сетям с использованием nmcli.
|
||||
|
||||
Предоставляет методы для подключения, отключения и проверки статуса Wi-Fi соединений.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Инициализирует сервис Wi-Fi.
|
||||
|
||||
Автоматически определяет доступный Wi-Fi-интерфейс и сохраняет его имя.
|
||||
"""
|
||||
self.interface = self._get_wifi_interface()
|
||||
|
||||
def _get_wifi_interface(self) -> str:
|
||||
"""Автоматически определяет Wi-Fi-интерфейс."""
|
||||
"""Автоматически определяет Wi-Fi-интерфейс.
|
||||
|
||||
Returns:
|
||||
str: Название Wi-Fi-интерфейса (например, "wlan0") или пустая строка,
|
||||
если интерфейс не найден или Wi-Fi отключён.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при выполнении команд nmcli.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(["nmcli", "radio", "wifi"], capture_output=True, text=True)
|
||||
if "enabled" in result.stdout.lower():
|
||||
|
|
@ -29,7 +46,21 @@ class WifiConnectionService:
|
|||
return ""
|
||||
|
||||
def connect(self, ssid: str, password: str) -> Tuple[bool, Optional[str]]:
|
||||
"""Подключается к Wi-Fi сети по SSID и паролю."""
|
||||
"""Подключается к Wi-Fi сети по SSID и паролю.
|
||||
|
||||
Args:
|
||||
ssid (str): Идентификатор сети Wi-Fi.
|
||||
password (str): Пароль для подключения.
|
||||
|
||||
Returns:
|
||||
Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
|
||||
- Успех: (True, None).
|
||||
- Ошибка: (False, сообщение об ошибке).
|
||||
|
||||
Raises:
|
||||
subprocess.CalledProcessError: Если команда nmcli завершилась с ошибкой.
|
||||
Exception: Если произошла неизвестная ошибка при выполнении.
|
||||
"""
|
||||
try:
|
||||
if not self.interface:
|
||||
return False, "Wi-Fi-интерфейс не доступен"
|
||||
|
|
@ -63,7 +94,20 @@ class WifiConnectionService:
|
|||
return False, f"Ошибка подключения к {ssid}: Неизвестная ошибка - {str(e)}"
|
||||
|
||||
def disconnect(self, ssid: str) -> Tuple[bool, Optional[str]]:
|
||||
"""Отключается от Wi-Fi сети по указанному SSID."""
|
||||
"""Отключается от Wi-Fi сети по указанному SSID.
|
||||
|
||||
Args:
|
||||
ssid (str): Идентификатор сети Wi-Fi для отключения.
|
||||
|
||||
Returns:
|
||||
Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
|
||||
- Успех: (True, None или сообщение, если подключение не найдено).
|
||||
- Ошибка: (False, сообщение об ошибке).
|
||||
|
||||
Raises:
|
||||
subprocess.CalledProcessError: Если команда nmcli завершилась с ошибкой.
|
||||
Exception: Если произошла неизвестная ошибка при выполнении.
|
||||
"""
|
||||
try:
|
||||
if not self.interface:
|
||||
return False, "Wi-Fi-интерфейс не доступен"
|
||||
|
|
@ -91,7 +135,16 @@ class WifiConnectionService:
|
|||
return False, f"Ошибка отключения от {ssid}: {str(e)}"
|
||||
|
||||
def get_status(self) -> Tuple[bool, Optional[str]]:
|
||||
"""Проверяет статус текущего подключения к Wi-Fi."""
|
||||
"""Проверяет статус текущего подключения к Wi-Fi.
|
||||
|
||||
Returns:
|
||||
Tuple[bool, Optional[str]]: Кортеж (успех, SSID или сообщение об ошибке).
|
||||
- Успех: (True, SSID текущей сети).
|
||||
- Ошибка или отсутствие подключения: (False, сообщение об ошибке).
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при выполнении команды nmcli.
|
||||
"""
|
||||
try:
|
||||
if not self.interface:
|
||||
return False, "Wi-Fi-интерфейс не доступен"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import asyncio
|
||||
from typing import Dict, Any
|
||||
from sdbus_async.networkmanager import (
|
||||
NetworkManager,
|
||||
|
|
@ -12,17 +11,28 @@ from services.arp_service import NetworkScanner
|
|||
|
||||
|
||||
class IPService:
|
||||
"""
|
||||
Сервис проверки доступности IP-адреса и применения настроек сети
|
||||
"""Сервис проверки доступности IP-адреса и применения настроек сети.
|
||||
|
||||
Предоставляет асинхронные методы для проверки занятости IP-адреса с использованием
|
||||
ARP-сканирования и применения настроек через NetworkManager.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Инициализирует сервис IP.
|
||||
|
||||
Создаёт объект ARP-сканера и устанавливает системную шину D-Bus как
|
||||
по умолчанию.
|
||||
"""
|
||||
self.arp_scanner = NetworkScanner()
|
||||
self.bus = sdbus.sd_bus_open_system()
|
||||
sdbus.set_default_bus(self.bus)
|
||||
|
||||
def _reset_bus(self):
|
||||
"""Перезапускает соединение с системной шиной D-Bus"""
|
||||
"""Перезапускает соединение с системной шиной D-Bus.
|
||||
|
||||
Закрывает текущее соединение и создаёт новое, устанавливая его как
|
||||
по умолчанию.
|
||||
"""
|
||||
new_bus = sdbus.sd_bus_open_system()
|
||||
sdbus.set_default_bus(new_bus)
|
||||
if hasattr(self, "bus") and self.bus is not None:
|
||||
|
|
@ -30,7 +40,23 @@ class IPService:
|
|||
self.bus = new_bus
|
||||
|
||||
async def check_ip_config(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Проверяет доступность IP-адреса перед его применением"""
|
||||
"""Проверяет доступность IP-адреса перед его применением.
|
||||
|
||||
Выполняет ARP-сканирование сети для проверки, занят ли указанный IP-адрес.
|
||||
Перезапускает шину D-Bus перед выполнением.
|
||||
|
||||
Args:
|
||||
args (Dict[str, Any]): Словарь с данными, включая "ipv4_updates"
|
||||
с полем "ipv4.address-data" для нового IP-адреса.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Словарь с ключами:
|
||||
- "available" (bool): Доступен ли IP-адрес.
|
||||
- "message" (str): Сообщение о результате проверки.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при сканировании ARP.
|
||||
"""
|
||||
self._reset_bus()
|
||||
clients = self.arp_scanner.scan_network()
|
||||
new_ip = args["ipv4_updates"]["ipv4.address-data"][0]["address"]
|
||||
|
|
@ -45,7 +71,23 @@ class IPService:
|
|||
}
|
||||
|
||||
async def apply_ip_config(self, args: Dict[str, Any]) -> None:
|
||||
"""Применяет проверенные настройки IP"""
|
||||
"""Применяет проверенные настройки IP к указанному соединению.
|
||||
|
||||
Перезапускает шину D-Bus, обновляет настройки соединения через NetworkManager,
|
||||
отключает и переподключает устройство к сети.
|
||||
|
||||
Args:
|
||||
args (Dict[str, Any]): Словарь с данными, включая:
|
||||
- "connection_id" (str): Идентификатор соединения.
|
||||
- "ipv4_updates" (Dict): Обновления настроек IPv4.
|
||||
|
||||
Raises:
|
||||
ValueError: Если соединение с указанным ID не найдено.
|
||||
RuntimeError: Если устройство для интерфейса не найдено.
|
||||
|
||||
Note:
|
||||
Требуются права для изменения настроек через NetworkManager.
|
||||
"""
|
||||
self._reset_bus()
|
||||
nm = NetworkManager()
|
||||
settings = NetworkManagerSettings()
|
||||
|
|
@ -77,10 +119,8 @@ class IPService:
|
|||
if await device.interface == interface_name:
|
||||
await device.disconnect()
|
||||
print(f"Отключён от {interface_name}")
|
||||
await nm.activate_connection(connection=connection_paths[0],
|
||||
device=device_path)
|
||||
await nm.activate_connection(connection=connection_paths[0],device=device_path)
|
||||
print(f"Переподключён к {args['connection_id']}")
|
||||
return
|
||||
|
||||
raise RuntimeError(
|
||||
f"Устройство для интерфейса {interface_name} не найдено")
|
||||
raise RuntimeError(f"Устройство для интерфейса {interface_name} не найдено")
|
||||
|
|
@ -23,16 +23,35 @@ import subprocess
|
|||
|
||||
|
||||
class NetworkService:
|
||||
"""Сервис для взаимодействия с NetworkManager через D-Bus"""
|
||||
"""Сервис для взаимодействия с NetworkManager через D-Bus.
|
||||
|
||||
Предоставляет асинхронные методы для получения информации о сетевых интерфейсах,
|
||||
их конфигурациях IP, Wi-Fi и состояниях.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Инициализирует сервис NetworkManager.
|
||||
|
||||
Устанавливает системный D-Bus и создаёт объекты для взаимодействия с
|
||||
NetworkManager и его настройками.
|
||||
"""
|
||||
self.system_bus = sd_bus_open_system()
|
||||
self.nm = NetworkManager(self.system_bus)
|
||||
self.nm_settings = NetworkManagerSettings(self.system_bus)
|
||||
|
||||
async def get_ip_method(self, device: NetworkDeviceGeneric,
|
||||
ip_type: str) -> str:
|
||||
"""Получает метод IP для устройства"""
|
||||
async def get_ip_method(self, device: NetworkDeviceGeneric, ip_type: str) -> str:
|
||||
"""Получает метод IP (например, "auto", "manual") для указанного устройства.
|
||||
|
||||
Args:
|
||||
device (NetworkDeviceGeneric): Объект устройства NetworkManager.
|
||||
ip_type (str): Тип IP (например, "ipv4" или "ipv6").
|
||||
|
||||
Returns:
|
||||
str: Метод IP или "unknown" в случае ошибки.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при получении настроек соединения.
|
||||
"""
|
||||
try:
|
||||
config = await device.get_applied_connection()
|
||||
settings = config[0].get(ip_type, {})
|
||||
|
|
@ -41,64 +60,80 @@ class NetworkService:
|
|||
return "unknown"
|
||||
|
||||
async def get_connection_name(self, device: NetworkDeviceGeneric) -> str:
|
||||
"""Извлекает имя соединения (GENERAL.CONNECTION) для устройства"""
|
||||
"""Извлекает имя соединения (GENERAL.CONNECTION) для устройства.
|
||||
|
||||
Args:
|
||||
device (NetworkDeviceGeneric): Объект устройства NetworkManager.
|
||||
|
||||
Returns:
|
||||
str: Имя соединения или "unknown" в случае ошибки.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при получении настроек соединения.
|
||||
"""
|
||||
try:
|
||||
applied_connection = await device.get_applied_connection()
|
||||
connection_settings = applied_connection[0]
|
||||
connection_info = connection_settings.get("connection", {})
|
||||
return str(connection_info.get("id", ("s", "unknown"))[1])
|
||||
except Exception as e:
|
||||
# print(f"Ошибка при получении соединения для устройства {await device.interface}: {e}")
|
||||
# срёт ошибки дл отключенных интерфейсов
|
||||
# добавить потом условия для отключенных интерфейсов
|
||||
return "unknown"
|
||||
|
||||
async def get_ip_config(self, device: NetworkDeviceGeneric,
|
||||
ip_version: str) -> List[IndividualIPConfig]:
|
||||
"""Получает конфигурацию IP для устройства"""
|
||||
async def get_ip_config(self, device: NetworkDeviceGeneric, ip_version: str) -> List[IndividualIPConfig]:
|
||||
"""Получает конфигурацию IP для устройства.
|
||||
|
||||
Args:
|
||||
device (NetworkDeviceGeneric): Объект устройства NetworkManager.
|
||||
ip_version (str): Версия IP ("4" или "6").
|
||||
|
||||
Returns:
|
||||
List[IndividualIPConfig]: Список конфигураций IP, включая адреса и маршруты.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при получении данных IP.
|
||||
"""
|
||||
configs = []
|
||||
device_ip = await getattr(device, f"ip{ip_version}_config")
|
||||
if device_ip != "/":
|
||||
ip_config = globals()[f"IPv{ip_version}Config"](device_ip,
|
||||
self.system_bus)
|
||||
ip_config = globals()[f"IPv{ip_version}Config"](device_ip, self.system_bus)
|
||||
address_data = await ip_config.address_data
|
||||
for address in address_data:
|
||||
config = IndividualIPConfig(
|
||||
address=str(address["address"][1]),
|
||||
routes=[
|
||||
globals()[f"IPv{ip_version}Route"](
|
||||
destination=route.get(
|
||||
"dest",
|
||||
("s",
|
||||
"0.0.0.0" if ip_version == "4" else "::"))[1],
|
||||
destination=route.get("dest", ("s", "0.0.0.0" if ip_version == "4" else "::"))[1],
|
||||
prefix=route.get("prefix", ("u", 0))[1],
|
||||
next_hop=route.get(
|
||||
"next-hop",
|
||||
("s",
|
||||
"0.0.0.0" if ip_version == "4" else "::"),
|
||||
)[1],
|
||||
next_hop=route.get("next-hop", ("s", "0.0.0.0" if ip_version == "4" else "::"))[1],
|
||||
metric=route.get("metric", ("u", 0))[1],
|
||||
) for route in await ip_config.route_data
|
||||
],
|
||||
method=await self.get_ip_method(device,
|
||||
f"ipv{ip_version}"),
|
||||
method=await self.get_ip_method(device, f"ipv{ip_version}"),
|
||||
gateway=str(await ip_config.gateway),
|
||||
dns=[
|
||||
str(ns["address"][1]) if ip_version == "4" else str(ns)
|
||||
for ns in await getattr(
|
||||
ip_config,
|
||||
"nameserver_data" if ip_version ==
|
||||
"4" else "nameservers",
|
||||
"nameserver_data" if ip_version == "4" else "nameservers",
|
||||
)
|
||||
],
|
||||
)
|
||||
configs.append(config)
|
||||
return configs
|
||||
|
||||
async def get_wifi_info(
|
||||
self, wifi_device: NetworkDeviceWireless
|
||||
) -> Dict[str, Optional[Union[str, int]]]:
|
||||
"""Получает информацию о Wi-Fi устройстве"""
|
||||
async def get_wifi_info(self, wifi_device: NetworkDeviceWireless) -> Dict[str, Optional[Union[str, int]]]:
|
||||
"""Получает информацию о Wi-Fi устройстве.
|
||||
|
||||
Args:
|
||||
wifi_device (NetworkDeviceWireless): Объект Wi-Fi устройства NetworkManager.
|
||||
|
||||
Returns:
|
||||
Dict[str, Optional[Union[str, int]]]: Словарь с данными Wi-Fi (SSID, BSSID,
|
||||
bitrate, strength, frequency, mode), где значения могут быть None.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при получении данных точки доступа.
|
||||
"""
|
||||
active_ap = await wifi_device.active_access_point
|
||||
wifi_info = {
|
||||
"ssid": None,
|
||||
|
|
@ -123,7 +158,18 @@ class NetworkService:
|
|||
self,
|
||||
initial_positions: Dict[str, Tuple[int, int]] = None
|
||||
) -> List[Union[NetworkInterface, WiFiDevice]]:
|
||||
"""Получает список сетевых интерфейсов"""
|
||||
"""Получает список сетевых интерфейсов.
|
||||
|
||||
Args:
|
||||
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
|
||||
координат (x, y) для интерфейсов. По умолчанию None (используются (100, 100)).
|
||||
|
||||
Returns:
|
||||
List[Union[NetworkInterface, WiFiDevice]]: Список объектов интерфейсов,
|
||||
включая Wi-Fi устройства.
|
||||
|
||||
Исключает интерфейсы типа LOOPBACK и неактивные состояния (state == "10").
|
||||
"""
|
||||
devices_paths = await self.nm.get_devices()
|
||||
network_interfaces = []
|
||||
initial_positions = initial_positions or {}
|
||||
|
|
@ -141,43 +187,37 @@ class NetworkService:
|
|||
continue
|
||||
|
||||
common_data = {
|
||||
"name":
|
||||
interface_name,
|
||||
"type":
|
||||
device_type,
|
||||
"device_state":
|
||||
device_state,
|
||||
"active_connection":
|
||||
await self.get_connection_name(generic_device),
|
||||
"mac_address":
|
||||
str(await generic_device.hw_address),
|
||||
"device_mtu":
|
||||
str(await generic_device.mtu),
|
||||
"ipv4":
|
||||
IPConfig(configurations=await self.get_ip_config(
|
||||
generic_device, "4")),
|
||||
"ipv6":
|
||||
IPConfig(configurations=await self.get_ip_config(
|
||||
generic_device, "6")),
|
||||
"x":
|
||||
initial_positions.get(interface_name, (100, 100))[0],
|
||||
"y":
|
||||
initial_positions.get(interface_name, (100, 100))[1],
|
||||
"name": interface_name,
|
||||
"type": device_type,
|
||||
"device_state": device_state,
|
||||
"active_connection": await self.get_connection_name(generic_device),
|
||||
"mac_address": str(await generic_device.hw_address),
|
||||
"device_mtu": str(await generic_device.mtu),
|
||||
"ipv4": IPConfig(configurations=await self.get_ip_config(generic_device, "4")),
|
||||
"ipv6": IPConfig(configurations=await self.get_ip_config(generic_device, "6")),
|
||||
"x": initial_positions.get(interface_name, (100, 100))[0],
|
||||
"y": initial_positions.get(interface_name, (100, 100))[1],
|
||||
}
|
||||
|
||||
if device_type == "WIFI":
|
||||
wifi_device = NetworkDeviceWireless(device_path,
|
||||
self.system_bus)
|
||||
wifi_device = NetworkDeviceWireless(device_path, self.system_bus)
|
||||
wifi_info = await self.get_wifi_info(wifi_device)
|
||||
network_interfaces.append(
|
||||
WiFiDevice(**common_data, **wifi_info))
|
||||
network_interfaces.append(WiFiDevice(**common_data, **wifi_info))
|
||||
else:
|
||||
network_interfaces.append(NetworkInterface(**common_data))
|
||||
|
||||
return network_interfaces
|
||||
|
||||
async def get_hostname(self) -> Dict[str, str]:
|
||||
"""Возвращает имя хоста из команды hostname в формате JSON"""
|
||||
"""Возвращает имя хоста из команды hostname в формате JSON.
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: Словарь с ключом "hostname" и значением имени хоста.
|
||||
|
||||
Raises:
|
||||
RuntimeError: Если произошла ошибка при выполнении команды hostname
|
||||
или обработке вывода.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(["hostname"],
|
||||
capture_output=True,
|
||||
|
|
@ -195,8 +235,15 @@ class NetworkService:
|
|||
state: str,
|
||||
initial_positions: Dict[str, Tuple[int, int]] = None
|
||||
) -> List[NetworkInterface]:
|
||||
"""
|
||||
Получает список интерфейсов по состоянию
|
||||
"""Получает список интерфейсов по указанному состоянию.
|
||||
|
||||
Args:
|
||||
state (str): Состояние интерфейса (например, "100" для активных).
|
||||
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
|
||||
координат (x, y) для интерфейсов. По умолчанию None.
|
||||
|
||||
Returns:
|
||||
List[NetworkInterface]: Список интерфейсов с указанным состоянием.
|
||||
"""
|
||||
all_interfaces = await self.get_network_interfaces(initial_positions)
|
||||
return [
|
||||
|
|
@ -211,10 +258,21 @@ class NetworkService:
|
|||
container_x: int = 0,
|
||||
container_y: int = 0,
|
||||
) -> NetworkInterfacesContainer:
|
||||
"""Получает объект, содержащий список интерфейсов и координаты контейнера"""
|
||||
"""Получает объект, содержащий список интерфейсов и координаты контейнера.
|
||||
|
||||
Args:
|
||||
state (Optional[str], optional): Состояние интерфейсов (например, "100").
|
||||
По умолчанию None (все интерфейсы).
|
||||
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
|
||||
координат (x, y) для интерфейсов. По умолчанию None.
|
||||
container_x (int, optional): Координата X контейнера. По умолчанию 0.
|
||||
container_y (int, optional): Координата Y контейнера. По умолчанию 0.
|
||||
|
||||
Returns:
|
||||
NetworkInterfacesContainer: Объект с интерфейсами и координатами контейнера.
|
||||
"""
|
||||
if state:
|
||||
interfaces = await self.get_interfaces_by_state(
|
||||
state, initial_positions)
|
||||
interfaces = await self.get_interfaces_by_state(state, initial_positions)
|
||||
else:
|
||||
interfaces = await self.get_network_interfaces(initial_positions)
|
||||
return NetworkInterfacesContainer(interfaces=interfaces,
|
||||
|
|
@ -225,7 +283,15 @@ class NetworkService:
|
|||
self,
|
||||
initial_positions: Optional[Dict[str, Tuple[int, int]]] = None,
|
||||
) -> NetworkInterfacesContainer:
|
||||
"""Получает контейнер с сетевыми интерфейсами и координатами контейнера"""
|
||||
"""Получает контейнер с сетевыми интерфейсами и координатами контейнера.
|
||||
|
||||
Args:
|
||||
initial_positions (Optional[Dict[str, Tuple[int, int]]], optional):
|
||||
Словарь начальных координат (x, y) для интерфейсов. По умолчанию None.
|
||||
|
||||
Returns:
|
||||
NetworkInterfacesContainer: Объект с интерфейсами и координатами (0, 0).
|
||||
"""
|
||||
interfaces = await self.get_network_interfaces(initial_positions)
|
||||
return NetworkInterfacesContainer(interfaces=interfaces,
|
||||
container_x=0,
|
||||
|
|
@ -235,14 +301,30 @@ class NetworkService:
|
|||
self,
|
||||
initial_positions: Dict[str, Tuple[int, int]] = None
|
||||
) -> List[NetworkInterface]:
|
||||
"""Получает список активных сетевых интерфейсов"""
|
||||
"""Получает список активных сетевых интерфейсов.
|
||||
|
||||
Args:
|
||||
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
|
||||
координат (x, y) для интерфейсов. По умолчанию None.
|
||||
|
||||
Returns:
|
||||
List[NetworkInterface]: Список активных интерфейсов (state == "100").
|
||||
"""
|
||||
return await self.get_interfaces_by_state("100", initial_positions)
|
||||
|
||||
async def get_deactivated_interfaces(
|
||||
self,
|
||||
initial_positions: Dict[str, Tuple[int, int]] = None
|
||||
) -> List[NetworkInterface]:
|
||||
"""Получает список неактивных сетевых интерфейсов"""
|
||||
"""Получает список неактивных сетевых интерфейсов.
|
||||
|
||||
Args:
|
||||
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
|
||||
координат (x, y) для интерфейсов. По умолчанию None.
|
||||
|
||||
Returns:
|
||||
List[NetworkInterface]: Список интерфейсов с state != "100".
|
||||
"""
|
||||
all_interfaces = await self.get_network_interfaces(initial_positions)
|
||||
return [
|
||||
interface for interface in all_interfaces
|
||||
|
|
@ -255,9 +337,18 @@ class NetworkService:
|
|||
container_x: int = 0,
|
||||
container_y: int = 0,
|
||||
) -> NetworkInterfacesContainer:
|
||||
"""Получает объект, содержащий список активных интерфейсов и координаты контейнера"""
|
||||
return await self.get_interfaces_container("100", initial_positions,
|
||||
container_x, container_y)
|
||||
"""Получает объект, содержащий список активных интерфейсов и координаты контейнера.
|
||||
|
||||
Args:
|
||||
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
|
||||
координат (x, y) для интерфейсов. По умолчанию None.
|
||||
container_x (int, optional): Координата X контейнера. По умолчанию 0.
|
||||
container_y (int, optional): Координата Y контейнера. По умолчанию 0.
|
||||
|
||||
Returns:
|
||||
NetworkInterfacesContainer: Объект с активными интерфейсами и координатами.
|
||||
"""
|
||||
return await self.get_interfaces_container("100", initial_positions, container_x, container_y)
|
||||
|
||||
async def get_deactivated_interfaces_container(
|
||||
self,
|
||||
|
|
@ -265,8 +356,16 @@ class NetworkService:
|
|||
container_x: int = 0,
|
||||
container_y: int = 0,
|
||||
) -> NetworkInterfacesContainer:
|
||||
"""Получает объект, содержащий список неактивных интерфейсов и координаты контейнера"""
|
||||
"""Получает объект, содержащий список неактивных интерфейсов и координаты контейнера.
|
||||
|
||||
Args:
|
||||
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
|
||||
координат (x, y) для интерфейсов. По умолчанию None.
|
||||
container_x (int, optional): Координата X контейнера. По умолчанию 0.
|
||||
container_y (int, optional): Координата Y контейнера. По умолчанию 0.
|
||||
|
||||
Returns:
|
||||
NetworkInterfacesContainer: Объект с неактивными интерфейсами и координатами.
|
||||
"""
|
||||
interfaces = await self.get_deactivated_interfaces(initial_positions)
|
||||
return NetworkInterfacesContainer(interfaces=interfaces,
|
||||
container_x=container_x,
|
||||
container_y=container_y)
|
||||
return NetworkInterfacesContainer(interfaces=interfaces, container_x=container_x, container_y=container_y)
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@ from functools import lru_cache
|
|||
from typing import List, Optional, Dict, Tuple
|
||||
from mac_vendor_lookup import MacLookup
|
||||
import pywifi
|
||||
from pywifi import Profile
|
||||
from models.wifi import WifiAccessPoint, WifiScanResult
|
||||
from pathlib import Path
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
|
@ -18,14 +16,34 @@ logging.basicConfig(
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
class WifiStorage:
|
||||
"""Класс для хранения данных о Wi-Fi сетях и их заметках по SSID."""
|
||||
"""Класс для хранения данных о Wi-Fi сетях и их заметках по SSID.
|
||||
|
||||
Предоставляет методы для загрузки и сохранения заметок, связанных с SSID сетей,
|
||||
в JSON-файле на диске.
|
||||
"""
|
||||
|
||||
def __init__(self, storage_path: str = "etc/gonets/SRV/storage/wifi/wifi.json"):
|
||||
"""Инициализирует объект хранилища Wi-Fi с указанным путём к файлу.
|
||||
|
||||
Args:
|
||||
storage_path (str, optional): Путь к файлу для хранения заметок.
|
||||
По умолчанию "etc/gonets/SRV/storage/wifi/wifi.json".
|
||||
|
||||
Создаёт родительские директории, если они не существуют.
|
||||
"""
|
||||
self.storage_file = Path(storage_path)
|
||||
self.storage_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def load_notes(self) -> Dict[str, Optional[str]]:
|
||||
"""Загружает заметки по SSID из файла хранения."""
|
||||
"""Загружает заметки по SSID из файла хранения.
|
||||
|
||||
Returns:
|
||||
Dict[str, Optional[str]]: Словарь, где ключ — SSID, значение — заметка
|
||||
или None, если заметки нет.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при чтении или парсинге файла.
|
||||
"""
|
||||
try:
|
||||
if self.storage_file.exists():
|
||||
with open(self.storage_file, "r", encoding="utf-8") as f:
|
||||
|
|
@ -37,7 +55,15 @@ class WifiStorage:
|
|||
return {}
|
||||
|
||||
def save_notes(self, notes: Dict[str, Optional[str]]) -> None:
|
||||
"""Сохраняет заметки по SSID в файл."""
|
||||
"""Сохраняет заметки по SSID в файл.
|
||||
|
||||
Args:
|
||||
notes (Dict[str, Optional[str]]): Словарь с SSID как ключами и заметками
|
||||
как значениями.
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при записи в файл.
|
||||
"""
|
||||
try:
|
||||
data = {"networks": [{"ssid": ssid, "note": note} for ssid, note in notes.items()]}
|
||||
with open(self.storage_file, "w", encoding="utf-8") as f:
|
||||
|
|
@ -47,16 +73,33 @@ class WifiStorage:
|
|||
logger.error(f"Ошибка при сохранении заметок: {str(e)}")
|
||||
|
||||
class WifiService:
|
||||
"""Сервис для сканирования текущих Wi-Fi точек доступа"""
|
||||
"""Сервис для сканирования текущих Wi-Fi точек доступа.
|
||||
|
||||
Предоставляет функционал для обнаружения Wi-Fi сетей с использованием pywifi и iw,
|
||||
определения характеристик точек доступа (сигнал, шифрование, канал и т.д.),
|
||||
а также управления заметками.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Инициализирует сервис Wi-Fi с настройкой интерфейса и хранилища.
|
||||
|
||||
Инициализирует объект MacLookup для определения вендоров по MAC-адресам,
|
||||
PyWiFi для работы с Wi-Fi-интерфейсами и объект хранилища заметок.
|
||||
"""
|
||||
self.mac_lookup = MacLookup()
|
||||
self.wifi = pywifi.PyWiFi()
|
||||
self.interface = self._get_wifi_interface()
|
||||
self.storage = WifiStorage()
|
||||
|
||||
def _get_wifi_interface(self) -> Optional[object]:
|
||||
"""Автоматически определяет активный Wi-Fi-интерфейс"""
|
||||
"""Автоматически определяет активный Wi-Fi-интерфейс.
|
||||
|
||||
Returns:
|
||||
Optional[object]: Объект интерфейса PyWiFi или None, если интерфейс не найден.
|
||||
|
||||
Проверяет доступные интерфейсы, выполняет тестовое сканирование и возвращает
|
||||
первый активный интерфейс, исключая P2P-интерфейсы.
|
||||
"""
|
||||
try:
|
||||
interfaces = self.wifi.interfaces()
|
||||
if not interfaces:
|
||||
|
|
@ -93,6 +136,17 @@ class WifiService:
|
|||
|
||||
@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:
|
||||
|
|
@ -101,7 +155,14 @@ class WifiService:
|
|||
return "Unknown"
|
||||
|
||||
def _get_encryption_from_iw(self, ap_info: Dict) -> str:
|
||||
"""Определяет шифрование из данных iw"""
|
||||
"""Определяет тип шифрования из данных, полученных через iw.
|
||||
|
||||
Args:
|
||||
ap_info (Dict): Словарь с данными точки доступа, включая поле "security".
|
||||
|
||||
Returns:
|
||||
str: Тип шифрования ("WPA3", "WPA2", "WPA", "WEP" или "Open").
|
||||
"""
|
||||
security = ap_info.get("security", "")
|
||||
logger.debug(
|
||||
f"Шифрование для BSSID {ap_info.get('bssid')}: {security}")
|
||||
|
|
@ -116,7 +177,14 @@ class WifiService:
|
|||
return "Open"
|
||||
|
||||
def _get_channel(self, frequency: Optional[int]) -> Optional[int]:
|
||||
"""Преобразует частоту в канал."""
|
||||
"""Преобразует частоту в номер канала.
|
||||
|
||||
Args:
|
||||
frequency (Optional[int]): Частота в МГц или None.
|
||||
|
||||
Returns:
|
||||
Optional[int]: Номер канала или None, если частота вне диапазона.
|
||||
"""
|
||||
if frequency is None:
|
||||
return None
|
||||
try:
|
||||
|
|
@ -133,7 +201,14 @@ class WifiService:
|
|||
return None
|
||||
|
||||
def _get_frequency(self, frequency: Optional[int]) -> Optional[str]:
|
||||
"""Определяет частотный диапазон."""
|
||||
"""Определяет частотный диапазон на основе частоты.
|
||||
|
||||
Args:
|
||||
frequency (Optional[int]): Частота в МГц или None.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Диапазон ("2.4 GHz", "5 GHz" или None).
|
||||
"""
|
||||
if frequency is None:
|
||||
return None
|
||||
try:
|
||||
|
|
@ -150,7 +225,17 @@ class WifiService:
|
|||
return None
|
||||
|
||||
def _get_iw_scan_data(self) -> List[Dict]:
|
||||
"""Получает данные сканирования через iw."""
|
||||
"""Получает данные сканирования Wi-Fi через команду iw.
|
||||
|
||||
Returns:
|
||||
List[Dict]: Список словарей с данными точек доступа (BSSID, SSID, частота,
|
||||
сигнал, шифрование).
|
||||
|
||||
Raises:
|
||||
subprocess.CalledProcessError: Если команда iw завершилась с ошибкой.
|
||||
subprocess.TimeoutExpired: Если превышен таймаут выполнения.
|
||||
Exception: Если произошла ошибка при парсинге вывода.
|
||||
"""
|
||||
try:
|
||||
if not self.interface:
|
||||
logger.error("Интерфейс не определён")
|
||||
|
|
@ -225,7 +310,18 @@ class WifiService:
|
|||
return []
|
||||
|
||||
def scan_wifi(self, timeout: int = 30) -> WifiScanResult:
|
||||
"""Сканирует текущие Wi-Fi точки доступа"""
|
||||
"""Сканирует текущие Wi-Fi точки доступа.
|
||||
|
||||
Args:
|
||||
timeout (int, optional): Таймаут ожидания в секундах. По умолчанию 30.
|
||||
|
||||
Returns:
|
||||
WifiScanResult: Объект с результатами сканирования, содержащий список точек
|
||||
доступа.
|
||||
|
||||
Сканирует Wi-Fi с использованием pywifi и iw, объединяет данные, определяет
|
||||
лучшие точки по сигналу и сохраняет заметки.
|
||||
"""
|
||||
start_time = time.time()
|
||||
aps = []
|
||||
|
||||
|
|
@ -282,12 +378,10 @@ class WifiService:
|
|||
if ssid not in best_aps_by_ssid or (
|
||||
current_ap.signal and best_aps_by_ssid[ssid].signal
|
||||
and current_ap.signal > best_aps_by_ssid[ssid].signal):
|
||||
# При обновлении или добавлении точки сохраняем заметку из saved_notes
|
||||
current_ap.note = saved_notes.get(ssid)
|
||||
best_aps_by_ssid[ssid] = current_ap
|
||||
|
||||
aps = list(best_aps_by_ssid.values())
|
||||
# Сохраняем заметки для всех найденных SSID
|
||||
notes_to_save = {ap.ssid: ap.note for ap in aps if ap.ssid}
|
||||
self.storage.save_notes(notes_to_save)
|
||||
|
||||
|
|
@ -313,7 +407,7 @@ class WifiService:
|
|||
- Ошибка: (False, сообщение об ошибке).
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при сохранении.
|
||||
Exception: Если произошла ошибка при сохранении заметки.
|
||||
"""
|
||||
try:
|
||||
saved_notes = self.storage.load_notes()
|
||||
|
|
@ -343,7 +437,7 @@ class WifiService:
|
|||
- Ошибка: (False, сообщение об ошибке).
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при сохранении.
|
||||
Exception: Если произошла ошибка при сохранении заметки.
|
||||
"""
|
||||
try:
|
||||
saved_notes = self.storage.load_notes()
|
||||
|
|
@ -373,7 +467,7 @@ class WifiService:
|
|||
- Ошибка: (False, сообщение об ошибке).
|
||||
|
||||
Raises:
|
||||
Exception: Если произошла ошибка при сохранении.
|
||||
Exception: Если произошла ошибка при сохранении изменений.
|
||||
"""
|
||||
try:
|
||||
saved_notes = self.storage.load_notes()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue