Files
lan_checker/checkers/snmp.py
Antoine Van Elstraete b1ba8b9fab Use ping RTT for SNMP checker latency
Ping the host first to check availability and measure actual network
latency. Only proceed with SNMP query if the host is reachable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 19:49:26 +01:00

160 lines
5.1 KiB
Python

"""
Checker SNMP.
Vérifie la disponibilité d'un équipement réseau via SNMP et peut
récupérer sa température.
"""
import asyncio
import platform
import re
import subprocess
from pysnmp.hlapi.v1arch.asyncio import (
Slim,
ObjectIdentity,
ObjectType,
)
from .base import BaseChecker, CheckResult
class SnmpChecker(BaseChecker):
"""
Vérifie la disponibilité d'un équipement via SNMP.
Utilise SNMPv2c pour interroger un OID et optionnellement
récupérer la température de l'équipement.
Configuration YAML:
host: Adresse IP de l'équipement (obligatoire).
port: Port SNMP (défaut: 161).
community: Communauté SNMP (défaut: public).
oid: OID à interroger (défaut: sysDescr).
temperature_oid: OID de la température (optionnel).
Note: Utiliser l'OID complet (feuille, pas branche).
Exemples:
- Mikrotik: 1.3.6.1.4.1.14988.1.1.3.100.1.3.52.0
- Synology: 1.3.6.1.4.1.6574.1.2.0
timeout: Délai d'attente en secondes (défaut: 5).
"""
def check(self) -> CheckResult:
"""
Exécute la vérification SNMP.
Wrapper synchrone autour de _async_check() pour compatibilité
avec l'interface BaseChecker.
Returns:
CheckResult avec success=True si l'équipement répond.
"""
return asyncio.run(self._async_check())
def _ping(self, host: str, timeout: int) -> tuple[bool, float | None]:
"""
Exécute un ping vers l'hôte pour vérifier sa disponibilité.
Args:
host: Adresse IP ou nom d'hôte.
timeout: Délai d'attente en secondes.
Returns:
Tuple (succès, RTT en ms ou None).
"""
if platform.system().lower() == "windows":
cmd = ["ping", "-n", "1", "-w", str(timeout * 1000), host]
else:
cmd = ["ping", "-c", "1", "-W", str(timeout), host]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout + 5
)
if result.returncode == 0:
match = re.search(r"time[=<]([\d.]+)\s*ms", result.stdout)
return True, float(match.group(1)) if match else None
return False, None
except Exception:
return False, None
async def _async_check(self) -> CheckResult:
"""
Exécute la vérification SNMP de manière asynchrone.
Effectue d'abord un ping pour vérifier la disponibilité et mesurer
la latence, puis interroge l'OID principal et optionnellement
l'OID de température via SNMP.
Returns:
CheckResult contenant le résultat et la température si configurée.
"""
host = self.config["host"]
port = self.config.get("port", 161)
community = self.config.get("community", "public")
oid = self.config.get("oid", "1.3.6.1.2.1.1.1.0") # sysDescr
temperature_oid = self.config.get("temperature_oid")
timeout_val = self.config.get("timeout", 5)
# Ping d'abord pour vérifier la disponibilité et mesurer la latence
ping_success, response_time = self._ping(host, timeout_val)
if not ping_success:
return CheckResult(
success=False,
message="Host is unreachable",
response_time=None
)
# Requête SNMP
try:
with Slim() as slim:
oids = [ObjectType(ObjectIdentity(oid))]
if temperature_oid:
oids.append(ObjectType(ObjectIdentity(temperature_oid)))
error_indication, error_status, error_index, var_binds = await slim.get(
community,
host,
port,
*oids,
timeout=timeout_val,
retries=1
)
if error_indication:
return CheckResult(
success=False,
message=f"SNMP error: {error_indication}",
response_time=response_time
)
elif error_status:
return CheckResult(
success=False,
message=f"SNMP error: {error_status.prettyPrint()}",
response_time=response_time
)
else:
details = {str(var_binds[0][0]): str(var_binds[0][1])}
if temperature_oid and len(var_binds) >= 2:
try:
details["temperature"] = int(var_binds[1][1])
except (ValueError, TypeError):
pass
return CheckResult(
success=True,
message="SNMP response OK",
response_time=response_time,
details=details
)
except Exception as e:
return CheckResult(
success=False,
message=f"SNMP error: {e}",
response_time=response_time
)