From 10d1157ec3ca372979fa80e5b310b5434628a2ed Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 11 Feb 2026 12:46:44 -0600 Subject: [PATCH] fix agent discovery system OIDs returning no_such_name gosnmp returns OIDs with leading dots (e.g., ".1.3.6.1.2.1.1.1.0") but the Replay adapter did exact-match lookups without the dot. Walks already normalized both sides, so interfaces worked. GETs did not, breaking all system OID resolution (sysDescr, sysName, etc). --- lib/towerops/snmp/adapters/replay.ex | 15 ++++++- .../live/device_live/show.html.heex | 5 ++- test/towerops/snmp/adapters/replay_test.exs | 45 +++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/lib/towerops/snmp/adapters/replay.ex b/lib/towerops/snmp/adapters/replay.ex index d8c7baad..e4c714fd 100644 --- a/lib/towerops/snmp/adapters/replay.ex +++ b/lib/towerops/snmp/adapters/replay.ex @@ -73,16 +73,27 @@ defmodule Towerops.Snmp.Adapters.Replay do def get(opts, oid) when is_binary(oid) do oid_map = Keyword.fetch!(opts, :oid_map) - case Map.fetch(oid_map, oid) do + case lookup_oid(oid_map, oid) do {:ok, value} -> {:ok, parse_value(value)} :error -> - # Mimic SNMP "no such name" error {:error, :no_such_name} end end + # Normalize OID lookup to handle leading dot differences. + # gosnmp returns OIDs with leading dots (e.g., ".1.3.6.1.2.1.1.1.0") + # but server code uses OIDs without (e.g., "1.3.6.1.2.1.1.1.0"). + defp lookup_oid(oid_map, oid) do + normalized = String.trim_leading(oid, ".") + + with :error <- Map.fetch(oid_map, oid), + :error <- Map.fetch(oid_map, normalized) do + Map.fetch(oid_map, "." <> normalized) + end + end + @impl true def walk(opts, base_oid) when is_binary(base_oid) do oid_map = Keyword.fetch!(opts, :oid_map) diff --git a/lib/towerops_web/live/device_live/show.html.heex b/lib/towerops_web/live/device_live/show.html.heex index 9ffd347f..218c091e 100644 --- a/lib/towerops_web/live/device_live/show.html.heex +++ b/lib/towerops_web/live/device_live/show.html.heex @@ -1865,7 +1865,10 @@

Raw SNMP OID values collected during the last discovery run at <%= if @snmp_device.last_discovery_at do %> - {ToweropsWeb.TimeHelpers.format_iso8601(@snmp_device.last_discovery_at, @timezone)} + {ToweropsWeb.TimeHelpers.format_iso8601( + @snmp_device.last_discovery_at, + @timezone + )} <% else %> unknown time <% end %>. diff --git a/test/towerops/snmp/adapters/replay_test.exs b/test/towerops/snmp/adapters/replay_test.exs index ace7c936..a35cd975 100644 --- a/test/towerops/snmp/adapters/replay_test.exs +++ b/test/towerops/snmp/adapters/replay_test.exs @@ -229,6 +229,51 @@ defmodule Towerops.Snmp.Adapters.ReplayTest do end end + describe "leading dot normalization (gosnmp format)" do + test "get/2 finds value when map has leading-dot keys" do + # gosnmp returns OIDs like ".1.3.6.1.2.1.1.1.0" (with leading dot) + # but server code looks up "1.3.6.1.2.1.1.1.0" (without leading dot) + oid_map = %{".1.3.6.1.2.1.1.1.0" => "RouterOS CHR"} + opts = Replay.new(oid_map) + + assert {:ok, "RouterOS CHR"} = Replay.get(opts, "1.3.6.1.2.1.1.1.0") + end + + test "get/2 finds value when map has no-dot keys but lookup has dot" do + oid_map = %{"1.3.6.1.2.1.1.1.0" => "Cisco IOS"} + opts = Replay.new(oid_map) + + assert {:ok, "Cisco IOS"} = Replay.get(opts, ".1.3.6.1.2.1.1.1.0") + end + + test "get_multiple/2 finds values with mixed dot formats" do + oid_map = %{ + ".1.3.6.1.2.1.1.1.0" => "RouterOS CHR", + ".1.3.6.1.2.1.1.5.0" => "mikrotik-router" + } + + opts = Replay.new(oid_map) + + oids = ["1.3.6.1.2.1.1.1.0", "1.3.6.1.2.1.1.5.0"] + + assert {:ok, results} = Replay.get_multiple(opts, oids) + assert results["1.3.6.1.2.1.1.1.0"] == "RouterOS CHR" + assert results["1.3.6.1.2.1.1.5.0"] == "mikrotik-router" + end + + test "walk/2 handles leading-dot keys in map" do + oid_map = %{ + ".1.3.6.1.2.1.2.2.1.1.1" => "1", + ".1.3.6.1.2.1.2.2.1.1.2" => "2" + } + + opts = Replay.new(oid_map) + + assert {:ok, results} = Replay.walk(opts, "1.3.6.1.2.1.2.2.1.1") + assert length(results) == 2 + end + end + describe "type inference edge cases" do test "parses MAC address with hyphens" do oid_map = %{"oid" => "AA-BB-CC-DD-EE-FF"}