fix/counter-wrap-detection (#164)

Reviewed-on: graham/towerops-web#164
This commit is contained in:
Graham McIntire 2026-03-25 12:25:13 -05:00
parent 86db804729
commit 94fd8780de
3 changed files with 26 additions and 5 deletions

View file

@ -1326,7 +1326,10 @@ defmodule Towerops.Workers.DevicePollerWorker do
end
defp fetch_octet_counters(client_opts, hc_oids, std_oids) do
hc_results = Enum.map(hc_oids, &try_fetch_hc_counter(client_opts, &1))
# Counter64 (HC) can't be encoded in SNMPv1 responses, so upgrade to v2c
# for HC queries. Most devices support v2c with the same community string.
hc_opts = upgrade_to_v2c_for_hc(client_opts)
hc_results = Enum.map(hc_oids, &try_fetch_hc_counter(hc_opts, &1))
hc_available = Enum.all?(hc_results, fn {_key, value} -> value != :not_available end)
@ -1338,6 +1341,13 @@ defmodule Towerops.Workers.DevicePollerWorker do
end
end
defp upgrade_to_v2c_for_hc(client_opts) do
case Keyword.get(client_opts, :version) do
v when v in ["1", :v1] -> Keyword.put(client_opts, :version, "2c")
_ -> client_opts
end
end
defp try_fetch_hc_counter(client_opts, {key, oid}) do
case Client.get(client_opts, oid) do
{:ok, value} ->

View file

@ -1083,9 +1083,14 @@ defmodule ToweropsWeb.AgentChannel do
defp build_v2c_snmp_device(device, snmp_config) do
community = snmp_config.community || ""
# Always use v2c for agent polling — Counter64 (HC counters) can't be
# encoded in SNMPv1 responses, causing 64-bit counter queries to fail.
# v2c uses the same community string and is supported by virtually all
# SNMP-capable devices. Without HC counters, 32-bit counters on fast
# links wrap undetectably (full cycle in ~69s at 500 Mbps).
%SnmpDevice{
ip: device.ip_address,
version: device.snmp_version,
version: "2c",
port: device.snmp_port || 161,
community: community
}

View file

@ -1291,12 +1291,18 @@ defmodule ToweropsWeb.GraphLive.Show do
# Poll octets for a single interface (try 64-bit, fall back to 32-bit)
defp poll_single_interface_octets(interface, client_opts) do
# Try 64-bit counters first
# Counter64 (HC) can't be encoded in SNMPv1 — upgrade to v2c for HC queries
hc_opts =
case Keyword.get(client_opts, :version) do
v when v in ["1", :v1] -> Keyword.put(client_opts, :version, "2c")
_ -> client_opts
end
in_octets_oid = "1.3.6.1.2.1.31.1.1.1.6.#{interface.if_index}"
out_octets_oid = "1.3.6.1.2.1.31.1.1.1.10.#{interface.if_index}"
with {:ok, in_octets} <- Snmp.Client.get(client_opts, in_octets_oid),
{:ok, out_octets} <- Snmp.Client.get(client_opts, out_octets_oid),
with {:ok, in_octets} <- Snmp.Client.get(hc_opts, in_octets_oid),
{:ok, out_octets} <- Snmp.Client.get(hc_opts, out_octets_oid),
true <- is_number(in_octets) and is_number(out_octets) do
build_interface_stat(interface, in_octets, out_octets)
else