fix: _action_fix_snmp skips reinstall if plugin already present

Calling firmware/install on an already-installed os-net-snmp plugin
triggers an async reinstall that overwrites the config with factory
defaults a few minutes later — causing SNMP to stop working again.
Now checks /api/netsnmp/general/get first and only installs if the
plugin is genuinely absent.

Also adds a lightweight UDP/161 probe to verify SNMP is actually
reachable after the fix (falls back to API config check if the
socket probe is unavailable).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Manivong
2026-06-20 03:41:18 +02:00
co-authored by Claude Sonnet 4.6
parent 8e413e4c60
commit 9f9fafb6f5
+47 -8
View File
@@ -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:
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 skipped or failed: %s", exc)
lines.append(f"[install] already installed or skipped")
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:
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 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"))
except Exception as exc:
logger.debug("SNMP verification failed: %s", exc)
success = False
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]]: