feat: ePMP firmware extraction + ePMP3000 NetSNMP-quirk detection
ePMP devices were not reporting firmware_version because the YAML's cambiumCurrentuImageVersion.0 OID returns empty on ePMP3000 firmware. Add detect_version/1 to the Epmp vendor module with two-OID fallback: cambiumCurrentSWInfo.0 (verbose, ePMP3000-friendly) → cambiumCurrentuImageVersion.0 (short, ePMP1000/2000), with a regex to extract the version triplet from whatever the device returns. ePMP3000 also has a sysObjectID quirk where some firmware reports the generic NetSNMP-on-Linux ID (.1.3.6.1.4.1.8072.3.2.10) instead of the Cambium enterprise OID, causing devices to misclassify as the linux profile. Add a second discovery block to the epmp profile that matches NetSNMP-OID + PREEMPT_RT armv7l kernel signature + a confirming snmpget against the Cambium ePMP MIB tree, so detection runs in pass-2 (conditional non-generic) before linux falls through in pass-3.
This commit is contained in:
parent
7519808e92
commit
3606ba19b3
3 changed files with 125 additions and 0 deletions
48
lib/towerops/snmp/profiles/vendors/epmp.ex
vendored
48
lib/towerops/snmp/profiles/vendors/epmp.ex
vendored
|
|
@ -21,6 +21,15 @@ defmodule Towerops.Snmp.Profiles.Vendors.Epmp do
|
|||
# cambiumSubModeType = { cambiumGeneralStatus 33 } = 1.3.6.1.4.1.17713.21.1.1.33
|
||||
@sub_mode_oid "1.3.6.1.4.1.17713.21.1.1.33.0"
|
||||
|
||||
# OIDs for firmware version detection (from CAMBIUM-PMP80211-MIB)
|
||||
# cambiumCurrentSWInfo = { cambiumGeneralStatus 1 } - verbose software info string (1..128 chars)
|
||||
@sw_info_oid "1.3.6.1.4.1.17713.21.1.1.1.0"
|
||||
# cambiumCurrentuImageVersion = { cambiumGeneralStatus 17 } - short version string (0..20 chars)
|
||||
@image_version_oid "1.3.6.1.4.1.17713.21.1.1.17.0"
|
||||
|
||||
# Match a typical version triplet/quad like "4.7.1.1", "4.7.1-RC4", etc.
|
||||
@version_regex ~r/(\d+\.\d+(?:\.\d+)*(?:[-_][A-Za-z0-9]+)?)/
|
||||
|
||||
@impl true
|
||||
def profile_names, do: ["epmp"]
|
||||
|
||||
|
|
@ -42,6 +51,45 @@ defmodule Towerops.Snmp.Profiles.Vendors.Epmp do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Detect firmware version with a two-OID fallback strategy.
|
||||
|
||||
Tries `cambiumCurrentSWInfo.0` first (verbose, 128-char string — more
|
||||
reliably populated on ePMP3000 firmware images), then falls back to
|
||||
`cambiumCurrentuImageVersion.0` (short 20-char string used by ePMP1000/2000).
|
||||
|
||||
Extracts a version-looking substring (e.g. `4.7.1.1`) from whatever the
|
||||
device returns; if no version pattern matches, returns the trimmed raw
|
||||
string. Returns `nil` when both OIDs error or return empty.
|
||||
"""
|
||||
def detect_version(client_opts) do
|
||||
fetch_version(@sw_info_oid, client_opts) ||
|
||||
fetch_version(@image_version_oid, client_opts)
|
||||
end
|
||||
|
||||
defp fetch_version(oid, client_opts) do
|
||||
case Client.get(client_opts, oid) do
|
||||
{:ok, value} when is_binary(value) -> normalize_version(value)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_version(value) do
|
||||
trimmed = String.trim(value)
|
||||
|
||||
cond do
|
||||
trimmed == "" ->
|
||||
nil
|
||||
|
||||
match = Regex.run(@version_regex, trimmed) ->
|
||||
[_, version] = match
|
||||
version
|
||||
|
||||
true ->
|
||||
trimmed
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def discover_wireless_sensors(client_opts) do
|
||||
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
|
||||
|
|
|
|||
|
|
@ -9,3 +9,15 @@ discovery:
|
|||
-
|
||||
sysObjectID:
|
||||
- .1.3.6.1.4.1.17713.21
|
||||
-
|
||||
# ePMP3000 firmware quirk: some images report sysObjectID as the
|
||||
# generic NetSNMP-on-Linux ID (.1.3.6.1.4.1.8072.3.2.10) instead of
|
||||
# the Cambium enterprise OID. Detect by matching the PREEMPT_RT
|
||||
# armv7l kernel signature and confirming the Cambium ePMP MIB tree
|
||||
# responds. Both must pass before claiming the device as ePMP.
|
||||
sysObjectID: .1.3.6.1.4.1.8072.3.2.10
|
||||
sysDescr_regex: '/PREEMPT RT.+armv7l/'
|
||||
snmpget:
|
||||
oid: CAMBIUM-PMP80211-MIB::cambiumCurrentSWInfo.0
|
||||
op: '!='
|
||||
value: false
|
||||
|
|
|
|||
|
|
@ -58,6 +58,71 @@ defmodule Towerops.Snmp.Profiles.Vendors.EpmpTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "detect_version/1" do
|
||||
@sw_info_oid "1.3.6.1.4.1.17713.21.1.1.1.0"
|
||||
@image_version_oid "1.3.6.1.4.1.17713.21.1.1.17.0"
|
||||
|
||||
test "extracts version from cambiumCurrentSWInfo verbose string" do
|
||||
expect(SnmpMock, :get, fn _, @sw_info_oid, _ ->
|
||||
{:ok, "ePMP-AP_Latest 4.7.1.1"}
|
||||
end)
|
||||
|
||||
assert Epmp.detect_version(@client_opts) == "4.7.1.1"
|
||||
end
|
||||
|
||||
test "returns plain version string from cambiumCurrentSWInfo" do
|
||||
expect(SnmpMock, :get, fn _, @sw_info_oid, _ ->
|
||||
{:ok, "4.7.1.1"}
|
||||
end)
|
||||
|
||||
assert Epmp.detect_version(@client_opts) == "4.7.1.1"
|
||||
end
|
||||
|
||||
test "preserves -RC suffix in version" do
|
||||
expect(SnmpMock, :get, fn _, @sw_info_oid, _ ->
|
||||
{:ok, "4.7.1-RC4"}
|
||||
end)
|
||||
|
||||
assert Epmp.detect_version(@client_opts) == "4.7.1-RC4"
|
||||
end
|
||||
|
||||
test "falls back to cambiumCurrentuImageVersion when SW info errors" do
|
||||
expect(SnmpMock, :get, fn _, @sw_info_oid, _ -> {:error, :no_such_object} end)
|
||||
expect(SnmpMock, :get, fn _, @image_version_oid, _ -> {:ok, "4.6.2"} end)
|
||||
|
||||
assert Epmp.detect_version(@client_opts) == "4.6.2"
|
||||
end
|
||||
|
||||
test "falls back when SW info returns empty string" do
|
||||
expect(SnmpMock, :get, fn _, @sw_info_oid, _ -> {:ok, ""} end)
|
||||
expect(SnmpMock, :get, fn _, @image_version_oid, _ -> {:ok, "4.6.2"} end)
|
||||
|
||||
assert Epmp.detect_version(@client_opts) == "4.6.2"
|
||||
end
|
||||
|
||||
test "returns nil when both OIDs fail" do
|
||||
expect(SnmpMock, :get, fn _, @sw_info_oid, _ -> {:error, :timeout} end)
|
||||
expect(SnmpMock, :get, fn _, @image_version_oid, _ -> {:error, :timeout} end)
|
||||
|
||||
assert Epmp.detect_version(@client_opts) == nil
|
||||
end
|
||||
|
||||
test "returns nil when both OIDs return empty" do
|
||||
expect(SnmpMock, :get, fn _, @sw_info_oid, _ -> {:ok, ""} end)
|
||||
expect(SnmpMock, :get, fn _, @image_version_oid, _ -> {:ok, ""} end)
|
||||
|
||||
assert Epmp.detect_version(@client_opts) == nil
|
||||
end
|
||||
|
||||
test "trims whitespace from raw value when no version pattern matches" do
|
||||
expect(SnmpMock, :get, fn _, @sw_info_oid, _ ->
|
||||
{:ok, " unknown-build "}
|
||||
end)
|
||||
|
||||
assert Epmp.detect_version(@client_opts) == "unknown-build"
|
||||
end
|
||||
end
|
||||
|
||||
describe "wireless_oid_defs/0" do
|
||||
test "returns list of sensor definitions" do
|
||||
defs = Epmp.wireless_oid_defs()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue