fix: correct airos SNMP OIDs to match UBNT-AirMAX-MIB definitions
8 of 13 OIDs were pointing to wrong MIB columns: - RSSI: was reading ubntWlStatSignal(.5), now ubntWlStatRssi(.6) - Quality: was reading ubntWlStatSignal(.5), now ubntAirMaxQuality(.6.1.3) - Tx Rate: was reading ubntWlStatSsid(.2), now ubntWlStatTxRate(.9) - Rx Rate: was reading ubntWlStatHideSsid(.3), now ubntWlStatRxRate(.10) - Capacity: was reading ubntWlStatTxRate(.9), now ubntAirMaxCapacity(.6.1.4) - Distance: was reading ubntWlStatSecurity(.11), now ubntRadioDistance(.1.1.7) - Utilization: was reading WDS/Repeater booleans(.12/.13), now ubntAirMaxAirtime(.6.1.7) - Added Signal Level sensor from ubntWlStatSignal(.5)
This commit is contained in:
parent
9b3ffa874a
commit
835b38c4de
2 changed files with 220 additions and 81 deletions
161
lib/towerops/snmp/profiles/vendors/airos.ex
vendored
161
lib/towerops/snmp/profiles/vendors/airos.ex
vendored
|
|
@ -2,10 +2,13 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airos do
|
|||
@moduledoc """
|
||||
Ubiquiti AirOS device-specific SNMP handling.
|
||||
|
||||
Mirrors LibreNMS/OS/Airos.php for wireless sensor discovery and hardware detection.
|
||||
|
||||
Supported devices:
|
||||
- AirMax (LiteBeam, PowerBeam, NanoStation, etc.)
|
||||
|
||||
OIDs sourced from UBNT-AirMAX-MIB:
|
||||
- Radio table: 1.3.6.1.4.1.41112.1.4.1 (ubntRadioTable)
|
||||
- WlStat table: 1.3.6.1.4.1.41112.1.4.5 (ubntWlStatTable)
|
||||
- AirMax table: 1.3.6.1.4.1.41112.1.4.6 (ubntAirMaxTable)
|
||||
"""
|
||||
|
||||
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
|
||||
|
|
@ -16,7 +19,6 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airos do
|
|||
require Logger
|
||||
|
||||
# OIDs for hardware detection
|
||||
# Use get_next like LibreNMS to get the first available value in the IEEE802dot11-MIB tree
|
||||
# IEEE802dot11-MIB::dot11manufacturerProductName = 1.2.840.10036.3.1.2.1.3
|
||||
# IEEE802dot11-MIB::dot11manufacturerProductVersion = 1.2.840.10036.3.1.2.1.4
|
||||
@product_name_oid "1.2.840.10036.3.1.2.1.3"
|
||||
|
|
@ -41,7 +43,6 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airos do
|
|||
|
||||
other ->
|
||||
Logger.warning("AirOS: Failed to detect hardware, got: #{inspect(other)}")
|
||||
# If get_next fails, return nil (will fallback to sysDescr in Dynamic module)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
@ -83,7 +84,9 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airos do
|
|||
@impl true
|
||||
def wireless_oid_defs do
|
||||
[
|
||||
# Frequency in MHz
|
||||
# === Radio table (ubntRadioTable) - 1.3.6.1.4.1.41112.1.4.1 ===
|
||||
|
||||
# ubntRadioFreq - entry 4
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.1.1.4.1",
|
||||
sensor_type: "frequency",
|
||||
|
|
@ -91,47 +94,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airos do
|
|||
sensor_unit: "MHz",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Capacity in Mbps
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.9.1",
|
||||
sensor_type: "rate",
|
||||
sensor_descr: "Capacity",
|
||||
sensor_unit: "Mbps",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# CCQ (Client Connection Quality) %
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.7.1",
|
||||
sensor_type: "ccq",
|
||||
sensor_descr: "CCQ",
|
||||
sensor_unit: "%",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Connected clients
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.15.1",
|
||||
sensor_type: "clients",
|
||||
sensor_descr: "Clients",
|
||||
sensor_unit: "",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Distance in meters
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.11.1",
|
||||
sensor_type: "distance",
|
||||
sensor_descr: "Distance",
|
||||
sensor_unit: "m",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Noise floor in dBm
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.8.1",
|
||||
sensor_type: "noise-floor",
|
||||
sensor_descr: "Noise Floor",
|
||||
sensor_unit: "dBm",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Tx Power in dBm
|
||||
# ubntRadioTxPower - entry 6
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.1.1.6.1",
|
||||
sensor_type: "power",
|
||||
|
|
@ -139,53 +102,99 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airos do
|
|||
sensor_unit: "dBm",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Signal Quality %
|
||||
# ubntRadioDistance - entry 7 (meters, divide by 1000 for km)
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.1.1.7.1",
|
||||
sensor_type: "distance",
|
||||
sensor_descr: "Distance",
|
||||
sensor_unit: "km",
|
||||
sensor_divisor: 1000
|
||||
},
|
||||
|
||||
# === Wireless statistics table (ubntWlStatTable) - 1.3.6.1.4.1.41112.1.4.5 ===
|
||||
|
||||
# ubntWlStatSignal - entry 5 (signal strength dBm)
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.5.1",
|
||||
sensor_type: "quality",
|
||||
sensor_descr: "Signal Quality",
|
||||
sensor_unit: "%",
|
||||
sensor_type: "power",
|
||||
sensor_descr: "Signal Level",
|
||||
sensor_unit: "dBm",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Tx Rate in Mbps
|
||||
# ubntWlStatRssi - entry 6
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.2.1",
|
||||
sensor_type: "rate",
|
||||
sensor_descr: "Tx Rate",
|
||||
sensor_unit: "Mbps",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Rx Rate in Mbps
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.3.1",
|
||||
sensor_type: "rate",
|
||||
sensor_descr: "Rx Rate",
|
||||
sensor_unit: "Mbps",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# RSSI in dBm
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.5.1",
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.6.1",
|
||||
sensor_type: "rssi",
|
||||
sensor_descr: "RSSI",
|
||||
sensor_unit: "dBm",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Channel utilization Tx %
|
||||
# ubntWlStatCcq - entry 7
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.12.1",
|
||||
sensor_type: "utilization",
|
||||
sensor_descr: "Channel Utilization Tx",
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.7.1",
|
||||
sensor_type: "ccq",
|
||||
sensor_descr: "CCQ",
|
||||
sensor_unit: "%",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Channel utilization Rx %
|
||||
# ubntWlStatNoiseFloor - entry 8
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.13.1",
|
||||
sensor_type: "utilization",
|
||||
sensor_descr: "Channel Utilization Rx",
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.8.1",
|
||||
sensor_type: "noise-floor",
|
||||
sensor_descr: "Noise Floor",
|
||||
sensor_unit: "dBm",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# ubntWlStatTxRate - entry 9
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.9.1",
|
||||
sensor_type: "rate",
|
||||
sensor_descr: "Tx Rate",
|
||||
sensor_unit: "Mbps",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# ubntWlStatRxRate - entry 10
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.10.1",
|
||||
sensor_type: "rate",
|
||||
sensor_descr: "Rx Rate",
|
||||
sensor_unit: "Mbps",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# ubntWlStatStaCount - entry 15
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.5.1.15.1",
|
||||
sensor_type: "clients",
|
||||
sensor_descr: "Clients",
|
||||
sensor_unit: "",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
|
||||
# === AirMax table (ubntAirMaxTable) - 1.3.6.1.4.1.41112.1.4.6 ===
|
||||
|
||||
# ubntAirMaxQuality - entry 3 (percentage)
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.6.1.3.1",
|
||||
sensor_type: "quality",
|
||||
sensor_descr: "airMAX Quality",
|
||||
sensor_unit: "%",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# ubntAirMaxCapacity - entry 4 (percentage)
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.6.1.4.1",
|
||||
sensor_type: "capacity",
|
||||
sensor_descr: "Capacity",
|
||||
sensor_unit: "%",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# ubntAirMaxAirtime - entry 7 (% multiplied by 10)
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.41112.1.4.6.1.7.1",
|
||||
sensor_type: "utilization",
|
||||
sensor_descr: "Airtime",
|
||||
sensor_unit: "%",
|
||||
sensor_divisor: 10
|
||||
}
|
||||
]
|
||||
end
|
||||
|
|
|
|||
140
test/towerops/snmp/profiles/vendors/airos_test.exs
vendored
140
test/towerops/snmp/profiles/vendors/airos_test.exs
vendored
|
|
@ -53,7 +53,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.AirosTest do
|
|||
defs = Airos.wireless_oid_defs()
|
||||
|
||||
assert is_list(defs)
|
||||
assert length(defs) >= 10
|
||||
assert length(defs) == 13
|
||||
end
|
||||
|
||||
test "each definition has required fields" do
|
||||
|
|
@ -68,23 +68,103 @@ defmodule Towerops.Snmp.Profiles.Vendors.AirosTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "includes frequency sensor" do
|
||||
test "frequency uses ubntRadioFreq OID" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
freq = Enum.find(defs, &(&1.sensor_descr == "Frequency"))
|
||||
|
||||
assert freq
|
||||
assert freq.oid == "1.3.6.1.4.1.41112.1.4.1.1.4.1"
|
||||
assert freq.sensor_type == "frequency"
|
||||
assert freq.sensor_unit == "MHz"
|
||||
end
|
||||
|
||||
test "includes CCQ sensor" do
|
||||
test "tx power uses ubntRadioTxPower OID" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
sensor = Enum.find(defs, &(&1.sensor_descr == "Tx Power"))
|
||||
|
||||
assert sensor.oid == "1.3.6.1.4.1.41112.1.4.1.1.6.1"
|
||||
assert sensor.sensor_type == "power"
|
||||
assert sensor.sensor_unit == "dBm"
|
||||
end
|
||||
|
||||
test "signal level uses ubntWlStatSignal OID" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
sensor = Enum.find(defs, &(&1.sensor_descr == "Signal Level"))
|
||||
|
||||
assert sensor.oid == "1.3.6.1.4.1.41112.1.4.5.1.5.1"
|
||||
assert sensor.sensor_type == "power"
|
||||
assert sensor.sensor_unit == "dBm"
|
||||
end
|
||||
|
||||
test "RSSI uses ubntWlStatRssi OID (column 6, not Signal column 5)" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
rssi = Enum.find(defs, &(&1.sensor_type == "rssi"))
|
||||
|
||||
assert rssi.oid == "1.3.6.1.4.1.41112.1.4.5.1.6.1"
|
||||
assert rssi.sensor_unit == "dBm"
|
||||
end
|
||||
|
||||
test "CCQ uses ubntWlStatCcq OID" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
ccq = Enum.find(defs, &(&1.sensor_descr == "CCQ"))
|
||||
|
||||
assert ccq
|
||||
assert ccq.oid == "1.3.6.1.4.1.41112.1.4.5.1.7.1"
|
||||
assert ccq.sensor_type == "ccq"
|
||||
assert ccq.sensor_unit == "%"
|
||||
end
|
||||
|
||||
test "tx/rx rate use ubntWlStatTxRate/RxRate OIDs (columns 9/10)" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
tx = Enum.find(defs, &(&1.sensor_descr == "Tx Rate"))
|
||||
rx = Enum.find(defs, &(&1.sensor_descr == "Rx Rate"))
|
||||
|
||||
assert tx.oid == "1.3.6.1.4.1.41112.1.4.5.1.9.1"
|
||||
assert rx.oid == "1.3.6.1.4.1.41112.1.4.5.1.10.1"
|
||||
assert tx.sensor_type == "rate"
|
||||
assert rx.sensor_type == "rate"
|
||||
end
|
||||
|
||||
test "quality uses ubntAirMaxQuality OID from airMax table" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
quality = Enum.find(defs, &(&1.sensor_type == "quality"))
|
||||
|
||||
assert quality.oid == "1.3.6.1.4.1.41112.1.4.6.1.3.1"
|
||||
assert quality.sensor_unit == "%"
|
||||
end
|
||||
|
||||
test "capacity uses ubntAirMaxCapacity OID from airMax table" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
capacity = Enum.find(defs, &(&1.sensor_descr == "Capacity"))
|
||||
|
||||
assert capacity.oid == "1.3.6.1.4.1.41112.1.4.6.1.4.1"
|
||||
assert capacity.sensor_type == "capacity"
|
||||
assert capacity.sensor_unit == "%"
|
||||
end
|
||||
|
||||
test "utilization uses ubntAirMaxAirtime OID with divisor 10" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
util = Enum.find(defs, &(&1.sensor_type == "utilization"))
|
||||
|
||||
assert util.oid == "1.3.6.1.4.1.41112.1.4.6.1.7.1"
|
||||
assert util.sensor_divisor == 10
|
||||
assert util.sensor_unit == "%"
|
||||
end
|
||||
|
||||
test "distance uses ubntRadioDistance OID with divisor 1000" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
distance = Enum.find(defs, &(&1.sensor_type == "distance"))
|
||||
|
||||
assert distance.oid == "1.3.6.1.4.1.41112.1.4.1.1.7.1"
|
||||
assert distance.sensor_divisor == 1000
|
||||
assert distance.sensor_unit == "km"
|
||||
end
|
||||
|
||||
test "no duplicate OIDs" do
|
||||
defs = Airos.wireless_oid_defs()
|
||||
oids = Enum.map(defs, & &1.oid)
|
||||
|
||||
assert length(oids) == length(Enum.uniq(oids)),
|
||||
"Found duplicate OIDs: #{inspect(oids -- Enum.uniq(oids))}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "discover_wireless_sensors/1" do
|
||||
|
|
@ -114,5 +194,55 @@ defmodule Towerops.Snmp.Profiles.Vendors.AirosTest do
|
|||
|
||||
assert sensors == []
|
||||
end
|
||||
|
||||
test "discovers sensors with correct OIDs and values" do
|
||||
expect(SnmpMock, :get, 13, fn _target, oid, _opts ->
|
||||
cond do
|
||||
# Radio table OIDs
|
||||
String.contains?(oid, "41112.1.4.1.1.4.1") -> {:ok, 5180}
|
||||
String.contains?(oid, "41112.1.4.1.1.6.1") -> {:ok, 20}
|
||||
String.contains?(oid, "41112.1.4.1.1.7.1") -> {:ok, 15_200}
|
||||
# WlStat table OIDs
|
||||
String.contains?(oid, "41112.1.4.5.1.5.1") -> {:ok, -62}
|
||||
String.contains?(oid, "41112.1.4.5.1.6.1") -> {:ok, -60}
|
||||
String.contains?(oid, "41112.1.4.5.1.7.1") -> {:ok, 85}
|
||||
String.contains?(oid, "41112.1.4.5.1.8.1") -> {:ok, -95}
|
||||
String.contains?(oid, "41112.1.4.5.1.9.1") -> {:ok, 300}
|
||||
String.contains?(oid, "41112.1.4.5.1.10.1") -> {:ok, 300}
|
||||
String.contains?(oid, "41112.1.4.5.1.15.1") -> {:ok, 12}
|
||||
# AirMax table OIDs
|
||||
String.contains?(oid, "41112.1.4.6.1.3.1") -> {:ok, 75}
|
||||
String.contains?(oid, "41112.1.4.6.1.4.1") -> {:ok, 80}
|
||||
String.contains?(oid, "41112.1.4.6.1.7.1") -> {:ok, 450}
|
||||
true -> {:error, :no_such_object}
|
||||
end
|
||||
end)
|
||||
|
||||
sensors = Airos.discover_wireless_sensors(@client_opts)
|
||||
|
||||
assert length(sensors) == 13
|
||||
|
||||
# Verify frequency
|
||||
freq = Enum.find(sensors, &(&1.sensor_type == "frequency"))
|
||||
assert freq.last_value == 5180.0
|
||||
|
||||
# Verify RSSI uses correct OID (column 6)
|
||||
rssi = Enum.find(sensors, &(&1.sensor_type == "rssi"))
|
||||
assert rssi.last_value == -60.0
|
||||
assert rssi.sensor_oid == "1.3.6.1.4.1.41112.1.4.5.1.6.1"
|
||||
|
||||
# Verify rates
|
||||
rates = Enum.filter(sensors, &(&1.sensor_type == "rate"))
|
||||
assert length(rates) == 2
|
||||
|
||||
# Verify distance with divisor
|
||||
distance = Enum.find(sensors, &(&1.sensor_type == "distance"))
|
||||
assert distance.sensor_divisor == 1000
|
||||
assert distance.last_value == 15_200.0
|
||||
|
||||
# Verify utilization with divisor
|
||||
util = Enum.find(sensors, &(&1.sensor_type == "utilization"))
|
||||
assert util.sensor_divisor == 10
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue