+проверка микротика

This commit is contained in:
dv 2026-04-13 16:38:19 +03:00
parent 5f1bc35579
commit a6f8722ad3
2 changed files with 66 additions and 1 deletions

View file

@ -32,6 +32,7 @@ from .ssh_client import (
fetch_slave_config, fetch_slave_config,
get_ip_neighbors_multi, get_ip_neighbors_multi,
ssh_run_multi, ssh_run_multi,
verify_is_mikrotik,
) )
from .types import DeviceAction, DiscoveredDevice, DiscoveryResult, ProgressCallback from .types import DeviceAction, DiscoveredDevice, DiscoveryResult, ProgressCallback
@ -811,7 +812,15 @@ async def _discover_mikrotik(
logger.info("[discovery] MikroTik: no IP found via SSH_CLIENT") logger.info("[discovery] MikroTik: no IP found via SSH_CLIENT")
return return
logger.info("[discovery] MikroTik found at %s", mikrotik_ip) logger.info("[discovery] MikroTik candidate: %s — verifying...", mikrotik_ip)
await _emit("disc_action", {"message": f"Проверка MikroTik {mikrotik_ip} (WinBox/SSH)..."})
is_mikrotik = await verify_is_mikrotik(mikrotik_ip, ssh_port, ssh_options)
if not is_mikrotik:
logger.info("[discovery] %s — NOT a MikroTik (WinBox and SSH banner check failed), skipping", mikrotik_ip)
await _emit("disc_action", {"message": f"{mikrotik_ip} — не MikroTik, пропускаем"})
return
logger.info("[discovery] MikroTik confirmed at %s", mikrotik_ip)
await _emit("disc_infra_found", {"type": "mikrotik", "ip": mikrotik_ip}) await _emit("disc_infra_found", {"type": "mikrotik", "ip": mikrotik_ip})
try: try:
existing = (await db.execute( existing = (await db.execute(

View file

@ -177,6 +177,62 @@ async def open_ssh_tunnel(
raise last_exc raise last_exc
# ── MikroTik verification ─────────────────────────────────────────────────────
async def verify_is_mikrotik(
host: str,
ssh_port: int = 22,
ssh_options: list | None = None,
timeout: float = 3.0,
) -> bool:
"""Check whether host is actually a MikroTik RouterOS device.
Two independent probes are tried (first positive result wins):
1. TCP connect to port 8291 (WinBox) exclusively used by MikroTik.
2. SSH banner check RouterOS advertises "ROSSSH" as its SSH version.
Returns True if either probe confirms MikroTik, False otherwise.
"""
# Probe 1: WinBox port 8291 (MikroTik-exclusive)
try:
reader, writer = await asyncio.wait_for(
asyncio.open_connection(host, 8291), timeout=timeout
)
writer.close()
try:
await writer.wait_closed()
except Exception:
pass
logger.info("[discovery] %s — MikroTik confirmed via WinBox port 8291", host)
return True
except Exception:
pass
# Probe 2: SSH banner
if ssh_options:
for opt in ssh_options:
try:
kwargs = _build_connect_kwargs(host, ssh_port, opt)
kwargs["connect_timeout"] = int(timeout)
async with asyncssh.connect(**kwargs) as conn:
banner = (conn.get_extra_info("server_version") or "").lower()
if "rosssh" in banner or "mikrotik" in banner:
logger.info(
"[discovery] %s — MikroTik confirmed via SSH banner: %s", host, banner
)
return True
logger.info(
"[discovery] %s — SSH connected but banner does not match MikroTik: %s",
host, banner,
)
return False # Connected successfully — definitely not MikroTik
except Exception:
continue
logger.info("[discovery] %s — could not verify MikroTik (no probe succeeded)", host)
return False
# ── Internal helpers ────────────────────────────────────────────────────────── # ── Internal helpers ──────────────────────────────────────────────────────────
def _parse_neigh_output(stdout: str) -> list[str]: def _parse_neigh_output(stdout: str) -> list[str]: