fix: correct AirFiber proprietary OID mapping and v1-only device support (#170)
Root cause: AF11X devices only support SNMPv1, IF-MIB eth0 counters return zero, and air0 32-bit counters wrap too fast for backhaul speeds. Fixes: - Use correct UBNT-AirFIBER-MIB OID indices from MIB SEQUENCE definition: .7.1 = rxOctetsOK (inbound), .6.1 = txOctetsOK (outbound) .10.1 = rxErroredFrames, .11.1 = txErroredFrames - Detect AirFiber devices by sysDescr instead of SNMP probing (v1 devices cannot return Counter64 via standard probe queries) - Force SNMPv1 for AirFiber proprietary queries - Apply proprietary counters to all real interfaces (not just eth0) Verified against live AF11X at 10.250.1.90: - IF-MIB eth0 returns zero counters - Proprietary .6.1/.7.1 show real traffic (3.75TB rx / 43TB tx) Reviewed-on: graham/towerops-web#170
This commit is contained in:
parent
ce682991fe
commit
3d5c74e7ae
3 changed files with 64 additions and 20 deletions
29
lib/towerops/snmp/profiles/vendors/airfiber.ex
vendored
29
lib/towerops/snmp/profiles/vendors/airfiber.ex
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue