From 9918f1633c40c858c3bacbb2c8be4e69d8d8e478 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sun, 4 Jan 2026 13:02:01 -0600 Subject: [PATCH] Handle SNMP Counter64 binary values in poller SNMP Counter64 values (used for interface byte counters) are returned as 8-byte binary data by SNMPKit and need to be decoded to integers before database insertion. Added decode_snmp_value/1 helper that: - Passes through numeric values unchanged - Decodes 8-byte binaries as 64-bit unsigned big-endian integers - Returns nil for unknown formats Updated both sensor and interface polling to use this decoder, preventing DBConnection.EncodeError when trying to insert binary values into integer columns. --- lib/towerops/snmp/poller_worker.ex | 60 ++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/lib/towerops/snmp/poller_worker.ex b/lib/towerops/snmp/poller_worker.ex index 5a69eb14..4a65e839 100644 --- a/lib/towerops/snmp/poller_worker.ex +++ b/lib/towerops/snmp/poller_worker.ex @@ -100,23 +100,34 @@ defmodule Towerops.Snmp.PollerWorker do defp poll_sensors(sensors, client_opts, timestamp) do Enum.each(sensors, fn sensor -> case Client.get(client_opts, sensor.sensor_oid) do - {:ok, raw_value} when is_number(raw_value) -> - # Calculate actual value using divisor - value = raw_value / sensor.sensor_divisor + {:ok, raw_value} -> + decoded_value = decode_snmp_value(raw_value) - # Determine status based on value (could be enhanced with thresholds) - status = "ok" + if is_number(decoded_value) do + # Calculate actual value using divisor + value = decoded_value / sensor.sensor_divisor - # Save the reading - Snmp.create_sensor_reading(%{ - sensor_id: sensor.id, - value: value, - status: status, - checked_at: timestamp - }) + # Determine status based on value (could be enhanced with thresholds) + status = "ok" - {:ok, _non_numeric} -> - Logger.debug("Non-numeric SNMP value for sensor #{sensor.sensor_descr}") + # Save the reading + Snmp.create_sensor_reading(%{ + sensor_id: sensor.id, + value: value, + status: status, + checked_at: timestamp + }) + else + Logger.debug("Non-numeric SNMP value for sensor #{sensor.sensor_descr}") + + # Save reading with nil value + Snmp.create_sensor_reading(%{ + sensor_id: sensor.id, + value: nil, + status: "unknown", + checked_at: timestamp + }) + end {:error, reason} -> Logger.warning("Failed to poll sensor #{sensor.sensor_descr}: #{inspect(reason)}") @@ -160,7 +171,7 @@ defmodule Towerops.Snmp.PollerWorker do results = Enum.map(oids, fn {key, oid} -> case Client.get(client_opts, oid) do - {:ok, value} when is_number(value) -> {key, value} + {:ok, value} -> {key, decode_snmp_value(value)} _ -> {key, nil} end end) @@ -168,6 +179,25 @@ defmodule Towerops.Snmp.PollerWorker do {:ok, Map.new(results)} end + # Decode SNMP values, handling Counter64 and other binary types + defp decode_snmp_value(value) when is_number(value), do: value + + defp decode_snmp_value(value) when is_binary(value) do + # Try to decode as Counter64 (8 bytes, big-endian unsigned integer) + case byte_size(value) do + 8 -> + <> = value + counter + + _ -> + # Unknown binary format, return nil + Logger.debug("Unknown SNMP binary value format, size: #{byte_size(value)}") + nil + end + end + + defp decode_snmp_value(_), do: nil + defp build_client_opts(equipment) do [ ip: equipment.ip_address,