diff --git a/lib/towerops/snmp/profiles/vendors/airfiber.ex b/lib/towerops/snmp/profiles/vendors/airfiber.ex index 48267d5a..72282903 100644 --- a/lib/towerops/snmp/profiles/vendors/airfiber.ex +++ b/lib/towerops/snmp/profiles/vendors/airfiber.ex @@ -23,9 +23,32 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airfiber do end @impl true - def detect_hardware(_client_opts) do - # Hardware detection handled by YAML profile - nil + def detect_hardware(client_opts) do + # The YAML profile tries IEEE802dot11-MIB::dot11manufacturerProductName.5 + # but many AirFiber devices (AF11X, AF24) don't support that OID. + # Fall back to sysName which often contains the model (e.g., "Climax-380 AF24"). + # Force v1 since these devices may not support v2c. + v1_opts = Keyword.put(client_opts, :version, "1") + + case Client.get(v1_opts, "1.3.6.1.2.1.1.5.0") do + {:ok, sys_name} when is_binary(sys_name) and sys_name != "" -> + "Ubiquiti AirFiber (#{sys_name})" + _ -> + "Ubiquiti AirFiber" + end + end + + @doc """ + Detects firmware version from the AirFiber status table. + OID 1.3.6.1.4.1.41112.1.3.2.1.40.1 returns the firmware version string (e.g., "v4.1.0"). + """ + def detect_version(client_opts) do + v1_opts = Keyword.put(client_opts, :version, "1") + + case Client.get(v1_opts, "1.3.6.1.4.1.41112.1.3.2.1.40.1") do + {:ok, version} when is_binary(version) and version != "" -> version + _ -> nil + end end @impl true diff --git a/lib/towerops/workers/device_poller_worker.ex b/lib/towerops/workers/device_poller_worker.ex index f096f332..8722787e 100644 --- a/lib/towerops/workers/device_poller_worker.ex +++ b/lib/towerops/workers/device_poller_worker.ex @@ -1042,23 +1042,32 @@ defmodule Towerops.Workers.DevicePollerWorker do # because their IF-MIB counters are unreliable. defp detect_airfiber_counter_overrides(nil, _client_opts), do: nil - defp detect_airfiber_counter_overrides(snmp_device, _client_opts) do + defp detect_airfiber_counter_overrides(snmp_device, client_opts) do sys_oid = snmp_device.sys_object_id || "" - sys_descr = String.downcase(snmp_device.sys_descr || "") - cond do - # Ubiquiti AirFiber — detect by sysObjectID (enterprise 41112) or sysDescr - # These devices need proprietary MIBs because IF-MIB eth0 counters are zero - # and air0 32-bit counters wrap too fast on high-bandwidth links. - # Note: detection is based on device identity, not SNMP probing, because - # some AirFiber devices only support SNMPv1 (can't return Counter64 via probe). - String.starts_with?(sys_oid, "1.3.6.1.4.1.41112") and String.contains?(sys_descr, "airfiber") -> - :airfiber + # Ubiquiti uses two enterprise OIDs: + # - 1.3.6.1.4.1.41112 (new UBNT enterprise OID) + # - 1.3.6.1.4.1.10002 (old UBNT enterprise OID, used by AirFiber AF11/AF24) + is_ubnt = String.starts_with?(sys_oid, "1.3.6.1.4.1.41112") or + String.starts_with?(sys_oid, "1.3.6.1.4.1.10002") - String.starts_with?(sys_oid, "1.3.6.1.4.1.41112") and String.contains?(sys_descr, "afltu") -> - :airfiber_ltu + if is_ubnt do + # Probe for the AirFiber statistics table — if it exists, this device + # needs proprietary counters. Use v1 since these devices may not support v2c. + v1_opts = Keyword.put(client_opts, :version, "1") - true -> nil + # Check for LTU first (more specific) + case Client.get(v1_opts, "1.3.6.1.4.1.41112.1.10.1.2.2.0") do + {:ok, val} when val != nil -> :airfiber_ltu + _ -> + # Check for regular AirFiber stats table (index field) + case Client.get(v1_opts, "1.3.6.1.4.1.41112.1.3.3.1.1.1") do + {:ok, val} when val != nil -> :airfiber + _ -> nil + end + end + else + nil end end @@ -1078,8 +1087,6 @@ defmodule Towerops.Workers.DevicePollerWorker do # 8=txPauseFrames, 9=rxPauseFrames, 10=rxErroredFrames, 11=txErroredFrames # # These devices may only support SNMPv1 — use v1 for all queries. - # Counter64 values returned via v1 vary by implementation; the Ubiquiti - # firmware handles it despite the RFC saying Counter64 is v2c+ only. v1_opts = Keyword.put(client_opts, :version, "1") oids = [ diff --git a/test/towerops/snmp/profiles/vendors/airfiber_test.exs b/test/towerops/snmp/profiles/vendors/airfiber_test.exs index abee042d..3d3482b3 100644 --- a/test/towerops/snmp/profiles/vendors/airfiber_test.exs +++ b/test/towerops/snmp/profiles/vendors/airfiber_test.exs @@ -23,8 +23,22 @@ defmodule Towerops.Snmp.Profiles.Vendors.AirfiberTest do end describe "detect_hardware/1" do - test "returns nil (hardware detection handled by YAML)" do - assert Airfiber.detect_hardware(@client_opts) == nil + test "returns hardware string with sysName when available" do + SnmpMock + |> expect(:get, fn _target, "1.3.6.1.2.1.1.5.0", _opts -> + {:ok, {:octet_string, "Climax-380 AF24"}} + end) + + assert Airfiber.detect_hardware(@client_opts) == "Ubiquiti AirFiber (Climax-380 AF24)" + end + + test "returns generic hardware string when sysName unavailable" do + SnmpMock + |> expect(:get, fn _target, "1.3.6.1.2.1.1.5.0", _opts -> + {:error, :timeout} + end) + + assert Airfiber.detect_hardware(@client_opts) == "Ubiquiti AirFiber" end end