""" 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 )