diff --git a/napalm_opnsense/opnsense.py b/napalm_opnsense/opnsense.py index ab8af25..4c61e01 100644 --- a/napalm_opnsense/opnsense.py +++ b/napalm_opnsense/opnsense.py @@ -1555,13 +1555,28 @@ class OPNsenseDriver(FirewallDriver): """ lines: list = [] - # 1. Install os-net-snmp plugin + # 1. Install os-net-snmp plugin ONLY if not already present. + # Calling firmware/install on an already-installed plugin triggers an + # async reinstall that overwrites the config with factory defaults a few + # minutes later — causing SNMP to stop working again after the fix. + plugin_installed = False try: - result = self._post("/api/core/firmware/install/os-net-snmp") - lines.append(f"[install] {result}") - except Exception as exc: - logger.debug("os-net-snmp install skipped or failed: %s", exc) - lines.append(f"[install] already installed or skipped") + chk = self._get("/api/netsnmp/general/get") + plugin_installed = isinstance(chk, dict) and "general" in chk + except Exception: + pass + + if not plugin_installed: + try: + result = self._post("/api/core/firmware/install/os-net-snmp") + lines.append(f"[install] {result}") + import time as _time + _time.sleep(5) # wait for install to settle + except Exception as exc: + logger.debug("os-net-snmp install failed: %s", exc) + lines.append(f"[install] failed: {exc}") + else: + lines.append("[install] os-net-snmp already installed — skipping reinstall") # 2. Configure SNMP try: @@ -1628,16 +1643,40 @@ class OPNsenseDriver(FirewallDriver): logger.debug("Firewall rule for SNMP failed: %s", exc) lines.append(f"[firewall] error: {exc}") - # 5. Verify SNMP config + # 5. Verify — try actual UDP/161 probe first, fall back to API config check + success = False try: - cfg = self._get("/api/netsnmp/general/get") - general = cfg.get("general", cfg) - success = str(general.get("enabled", "0")) == "1" and bool(general.get("community")) + import socket as _socket + sock = _socket.socket(_socket.AF_INET, _socket.SOCK_DGRAM) + sock.settimeout(3) + community = b"public" + # Minimal SNMPv2c GetRequest for sysDescr + pdu = (b"\x30\x26\x02\x01\x01\x04" + + bytes([len(community)]) + community + + b"\xa0\x19\x02\x04\x00\x00\x00\x01" + + b"\x02\x01\x00\x02\x01\x00" + + b"\x30\x0b\x30\x09\x06\x05\x2b\x06\x01\x02\x01\x05\x00") + sock.sendto(pdu, (self.hostname, 161)) + try: + data, _ = sock.recvfrom(1024) + success = len(data) > 0 + lines.append("[ok] SNMP UDP probe successful.") + except _socket.timeout: + lines.append("[warn] SNMP UDP probe timed out — service may be starting.") + finally: + sock.close() except Exception as exc: - logger.debug("SNMP verification failed: %s", exc) - success = False + logger.debug("SNMP UDP probe failed: %s", exc) + # Fall back to API config check + try: + cfg = self._get("/api/netsnmp/general/get") + general = cfg.get("general", cfg) + success = str(general.get("enabled", "0")) == "1" and bool(general.get("community")) + lines.append("[ok] SNMP config active (UDP probe unavailable)." if success + else "[warn] SNMP config check failed.") + except Exception: + lines.append("[warn] Could not verify SNMP state.") - lines.append("[ok] SNMP active." if success else "[warn] Could not verify SNMP state.") return {"success": success, "output": "\n".join(lines)} def get_firewall_aliases(self) -> list[dict[str, Any]]: