import yamls

This commit is contained in:
Graham McIntire 2026-01-21 14:30:04 -06:00
parent d68991c484
commit 7656ac7212
No known key found for this signature in database
1474 changed files with 77977 additions and 3744 deletions

View file

@ -82,7 +82,7 @@ FROM ${RUNNER_IMAGE} AS final
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates iputils-ping \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates iputils-ping snmp \
&& rm -rf /var/lib/apt/lists/*
# Set the locale

View file

@ -2,27 +2,30 @@ defmodule Mix.Tasks.ImportProfiles do
@shortdoc "Import device profiles from LibreNMS YAML definitions"
@moduledoc """
Imports device profiles from LibreNMS YAML definition files.
Imports device profiles from YAML definition files.
## Usage
# Import all profile definitions
mix import_profiles --source-path ~/librenms/includes/definitions
# Import all profiles from priv/profiles (default)
mix import_profiles
# Import from a custom path
mix import_profiles --source-path ~/librenms/resources/definitions
# Import specific profiles
mix import_profiles --source-path ~/librenms/includes/definitions --profiles mikrotik,cisco
mix import_profiles --profiles mikrotik,cisco,epmp
## Options
* `--source-path` - Path to LibreNMS includes/definitions directory (required)
* `--source-path` - Path to definitions directory (default: priv/profiles)
* `--profiles` - Comma-separated list of profiles to import (default: all)
## Notes
* Reads YAML files from the definitions directory
* Reads YAML files from os_detection/ and os_discovery/ subdirectories
* Creates database profiles with MIB symbolic names
* Updates existing profiles if they already exist
* Requires the application to be started to access the database
* Run ./scripts/sync-librenms.sh to sync profiles from LibreNMS
"""
use Mix.Task
@ -47,7 +50,7 @@ defmodule Mix.Tasks.ImportProfiles do
]
)
source_path = opts[:source_path] || raise "Missing --source-path argument"
source_path = opts[:source_path] || default_source_path()
profiles_filter = parse_profiles(opts[:profiles])
if !File.exists?(source_path) do
@ -63,6 +66,10 @@ defmodule Mix.Tasks.ImportProfiles do
end
end
defp default_source_path do
Path.join(:code.priv_dir(:towerops), "profiles")
end
defp parse_profiles(nil), do: :all
defp parse_profiles(""), do: :all
@ -122,7 +129,8 @@ defmodule Mix.Tasks.ImportProfiles do
discovery_file = Path.join([source_path, "os_discovery", "#{profile_name}.yaml"])
with {:ok, detection_data} <- read_yaml_file(detection_file),
{:ok, discovery_data} <- read_yaml_file(discovery_file),
# Discovery file is optional - use empty map if not found
discovery_data = read_yaml_file_optional(discovery_file),
{:ok, _profile} <- create_or_update_profile(detection_data, discovery_data, profile_name),
# Reload profile to get correct binary ID
profile when not is_nil(profile) <- Profiles.get_profile(profile_name),
@ -132,6 +140,13 @@ defmodule Mix.Tasks.ImportProfiles do
end
end
defp read_yaml_file_optional(file_path) do
case read_yaml_file(file_path) do
{:ok, data} -> data
{:error, _} -> %{}
end
end
defp read_yaml_file(file_path) do
with {:ok, yaml_content} <- File.read(file_path),
{:ok, yaml_data} <- YamlElixir.read_from_string(yaml_content) do
@ -231,33 +246,29 @@ defmodule Mix.Tasks.ImportProfiles do
defp extract_device_oids(yaml_data) do
# Extract from modules.os section in os_discovery YAML
device_oids = []
# Get the modules.os section
os_module = get_in(yaml_data, ["modules", "os"]) || %{}
# Serial number
device_oids =
case Map.get(os_module, "serial") do
nil -> device_oids
mib_name -> [{"serial_number", mib_name} | device_oids]
end
# Map of YAML field names to our field names
field_mappings = [
{"serial", "serial_number"},
{"version", "firmware_version"},
{"hardware", "hardware"},
{"lat", "latitude"},
{"long", "longitude"},
{"features", "features"},
{"sysName", "sys_name_oid"}
]
# Firmware version
device_oids =
case Map.get(os_module, "version") do
nil -> device_oids
mib_name -> [{"firmware_version", mib_name} | device_oids]
Enum.flat_map(field_mappings, fn {yaml_field, db_field} ->
case Map.get(os_module, yaml_field) do
nil -> []
# Handle list of OIDs (fallback order)
mib_names when is_list(mib_names) -> [{db_field, List.first(mib_names)}]
mib_name when is_binary(mib_name) -> [{db_field, mib_name}]
_ -> []
end
# Hardware/model
device_oids =
case Map.get(os_module, "hardware") do
nil -> device_oids
mib_name -> [{"hardware", mib_name} | device_oids]
end
device_oids
end)
end
defp import_sensor_oids(profile, yaml_data) do

View file

@ -26,9 +26,9 @@ defmodule Towerops.Application do
ToweropsWeb.Telemetry,
Towerops.Repo,
{DNSCluster, query: Application.get_env(:towerops, :dns_cluster_query) || :ignore},
pubsub_spec()
# Start a worker by calling: Towerops.Worker.start_link(arg)
# {Towerops.Worker, arg},
pubsub_spec(),
# Load YAML profiles into ETS cache
Towerops.Profiles.YamlProfiles
] ++
background_workers() ++
exq_workers() ++

View file

@ -0,0 +1,286 @@
defmodule Towerops.Profiles.YamlProfiles do
@moduledoc """
Loads and caches device profiles directly from YAML files.
Profiles are stored in priv/profiles/ and loaded into an ETS table on startup.
This avoids database storage and keeps YAML files as the source of truth.
"""
use GenServer
require Logger
@table :yaml_profiles
@profiles_dir "profiles"
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Gets a profile by name from the cache.
"""
def get_profile(name) when is_binary(name) do
case :ets.lookup(@table, {:profile, name}) do
[{_, profile}] -> profile
[] -> nil
end
end
@doc """
Lists all cached profiles.
"""
def list_profiles do
@table
|> :ets.match_object({{:profile, :_}, :_})
|> Enum.map(fn {_, profile} -> profile end)
end
@doc """
Matches a profile based on system info (sysObjectID or sysDescr).
Returns the best matching profile or nil.
"""
def match_profile(system_info) do
sys_object_id = Map.get(system_info, :sys_object_id, "")
sys_descr = Map.get(system_info, :sys_descr, "")
# First try to match by sysObjectID (more specific)
case match_by_oid(sys_object_id) do
nil -> match_by_descr(sys_descr)
profile -> profile
end
end
@doc """
Reloads all profiles from YAML files.
"""
def reload do
GenServer.call(__MODULE__, :reload)
end
# GenServer callbacks
@impl true
def init(_opts) do
# Create ETS table
:ets.new(@table, [:named_table, :set, :public, read_concurrency: true])
# Load profiles
load_all_profiles()
{:ok, %{}}
end
@impl true
def handle_call(:reload, _from, state) do
:ets.delete_all_objects(@table)
count = load_all_profiles()
{:reply, {:ok, count}, state}
end
# Private functions
defp load_all_profiles do
profiles_path = Path.join(:code.priv_dir(:towerops), @profiles_dir)
detection_path = Path.join(profiles_path, "os_detection")
if File.exists?(detection_path) do
yaml_files =
detection_path
|> Path.join("*.yaml")
|> Path.wildcard()
Enum.each(yaml_files, fn file ->
name = Path.basename(file, ".yaml")
load_profile(profiles_path, name)
end)
count = length(yaml_files)
Logger.info("Loaded #{count} device profiles from YAML files")
count
else
Logger.debug("No profiles directory found at #{detection_path}")
0
end
end
defp load_profile(profiles_path, name) do
detection_file = Path.join([profiles_path, "os_detection", "#{name}.yaml"])
discovery_file = Path.join([profiles_path, "os_discovery", "#{name}.yaml"])
with {:ok, detection} <- read_yaml(detection_file) do
discovery = read_yaml_optional(discovery_file)
profile = build_profile(name, detection, discovery)
:ets.insert(@table, {{:profile, name}, profile})
# Index by detection OID for fast lookups
if profile.detection_oid do
:ets.insert(@table, {{:oid, profile.detection_oid}, name})
end
# Index by detection patterns
Enum.each(profile.detection_patterns, fn pattern ->
:ets.insert(@table, {{:pattern, pattern}, name})
end)
end
end
defp build_profile(name, detection, discovery) do
%{
name: name,
vendor: Map.get(detection, "text") || String.capitalize(name),
detection_oid: extract_detection_oid(detection),
detection_patterns: extract_detection_patterns(detection),
device_oids: extract_device_oids(discovery),
sensor_oids: extract_sensor_oids(discovery)
}
end
defp extract_detection_oid(yaml) do
case get_in(yaml, ["discovery"]) do
discovery when is_list(discovery) ->
discovery
|> Enum.flat_map(fn
%{"sysObjectID" => patterns} when is_list(patterns) -> patterns
_ -> []
end)
|> List.first()
_ ->
nil
end
end
defp extract_detection_patterns(yaml) do
case get_in(yaml, ["discovery"]) do
discovery when is_list(discovery) ->
Enum.flat_map(discovery, fn
%{"sysDescr" => patterns} when is_list(patterns) -> patterns
_ -> []
end)
_ ->
[]
end
end
defp extract_device_oids(yaml) do
os_module = get_in(yaml, ["modules", "os"]) || %{}
field_mappings = [
{"serial", :serial_number},
{"version", :firmware_version},
{"hardware", :hardware},
{"lat", :latitude},
{"long", :longitude}
]
field_mappings
|> Enum.flat_map(fn {yaml_field, field} ->
case Map.get(os_module, yaml_field) do
nil -> []
mib_names when is_list(mib_names) -> [{field, List.first(mib_names)}]
mib_name when is_binary(mib_name) -> [{field, mib_name}]
_ -> []
end
end)
|> Map.new()
end
defp extract_sensor_oids(yaml) do
sensors_module = get_in(yaml, ["modules", "sensors"]) || %{}
sensor_types = ["temperature", "voltage", "current", "power", "fanspeed"]
Enum.flat_map(sensor_types, fn sensor_type ->
case get_in(sensors_module, [sensor_type, "data"]) do
data when is_list(data) ->
data
|> Enum.map(&parse_sensor(&1, sensor_type))
|> Enum.reject(&is_nil/1)
_ ->
[]
end
end)
end
defp parse_sensor(sensor_def, sensor_type) do
mib_name = Map.get(sensor_def, "oid") || Map.get(sensor_def, "value")
num_oid = Map.get(sensor_def, "num_oid")
# Skip table-based sensors (with {{ $index }})
cond do
is_binary(num_oid) and String.contains?(num_oid, "{{ $index }}") ->
nil
is_binary(mib_name) and String.ends_with?(mib_name, ".0") ->
%{
sensor_type: sensor_type,
mib_name: mib_name,
sensor_descr: extract_descr(sensor_def, sensor_type),
sensor_unit: Map.get(sensor_def, "unit"),
sensor_divisor: Map.get(sensor_def, "divisor", 1)
}
true ->
nil
end
end
defp extract_descr(sensor_def, default) do
case Map.get(sensor_def, "descr") do
nil ->
default
descr when is_binary(descr) ->
cleaned = descr |> String.replace(~r/\{\{ [^}]+ \}\}/, "") |> String.trim()
if cleaned == "", do: default, else: cleaned
_ ->
default
end
end
defp match_by_oid(sys_object_id) when is_binary(sys_object_id) and sys_object_id != "" do
# Try exact match first, then prefix matches
list_profiles()
|> Enum.filter(fn profile ->
profile.detection_oid && String.contains?(sys_object_id, profile.detection_oid)
end)
|> Enum.max_by(fn profile -> String.length(profile.detection_oid || "") end, fn -> nil end)
end
defp match_by_oid(_), do: nil
defp match_by_descr(sys_descr) when is_binary(sys_descr) and sys_descr != "" do
Enum.find(list_profiles(), fn profile ->
Enum.any?(profile.detection_patterns, fn pattern ->
Regex.match?(~r/#{Regex.escape(pattern)}/i, sys_descr)
end)
end)
end
defp match_by_descr(_), do: nil
defp read_yaml(file_path) do
with {:ok, content} <- File.read(file_path),
{:ok, data} <- YamlElixir.read_from_string(content) do
data =
case data do
[d] -> d
d when is_map(d) -> d
_ -> data
end
{:ok, data}
end
end
defp read_yaml_optional(file_path) do
case read_yaml(file_path) do
{:ok, data} -> data
_ -> %{}
end
end
end

View file

@ -16,8 +16,7 @@ defmodule Towerops.Snmp.Discovery do
alias Towerops.Devices
alias Towerops.Devices.Device, as: DeviceSchema
alias Towerops.Profiles
alias Towerops.Profiles.DeviceProfile
alias Towerops.Profiles.YamlProfiles
alias Towerops.Repo
alias Towerops.Snmp.Client
alias Towerops.Snmp.DeferredDiscovery
@ -25,11 +24,7 @@ defmodule Towerops.Snmp.Discovery do
alias Towerops.Snmp.Interface
alias Towerops.Snmp.NeighborDiscovery
alias Towerops.Snmp.Profiles.Base
alias Towerops.Snmp.Profiles.Cisco
alias Towerops.Snmp.Profiles.Dynamic
alias Towerops.Snmp.Profiles.Mikrotik
alias Towerops.Snmp.Profiles.NetSnmp
alias Towerops.Snmp.Profiles.Ubiquiti
alias Towerops.Snmp.Sensor
alias Towerops.Snmp.Vlan
@ -79,7 +74,7 @@ defmodule Towerops.Snmp.Discovery do
optional(atom()) => term()
}
@type profile :: module() | {:dynamic, DeviceProfile.t()}
@type profile :: module() | {:yaml, map()}
@type discovery_summary :: %{
success: non_neg_integer(),
@ -262,70 +257,39 @@ defmodule Towerops.Snmp.Discovery do
@doc """
Selects the appropriate SNMP profile based on system information.
First tries to match against database-stored device profiles.
If no database match is found, falls back to hard-coded profile modules.
First tries to match against database-stored device profiles (imported from LibreNMS).
If no database match is found, falls back to the generic Base profile.
## Examples
iex> select_profile(%{sys_descr: "Cisco IOS"})
Towerops.Snmp.Profiles.Cisco
{:dynamic, %DeviceProfile{name: "ios"}}
iex> select_profile(%{sys_object_id: "1.3.6.1.4.1.41112"})
Towerops.Snmp.Profiles.Ubiquiti
{:dynamic, %DeviceProfile{name: "airos"}}
"""
@spec select_profile(system_info()) :: profile()
def select_profile(system_info) do
# First try database profiles
case Profiles.match_profile(system_info) do
%DeviceProfile{} = profile ->
Logger.debug("Selected database profile: #{profile.name}")
{:dynamic, profile}
case YamlProfiles.match_profile(system_info) do
%{name: name} = profile ->
Logger.debug("Selected YAML profile: #{name}")
{:yaml, profile}
nil ->
# Fall back to hardcoded profiles
select_hardcoded_profile(system_info)
end
end
defp select_hardcoded_profile(system_info) do
sys_descr = Map.get(system_info, :sys_descr, "")
sys_object_id = Map.get(system_info, :sys_object_id, "")
cond do
String.contains?(sys_descr, "RouterOS") ->
Logger.debug("Selected MikroTik profile")
Mikrotik
String.contains?(sys_descr, ["Cisco", "IOS"]) ->
Logger.debug("Selected Cisco profile")
Cisco
# Ubiquiti devices (check before generic Linux since they run Linux)
# OID 10002 = old Ubiquiti/UniFi devices, 41112 = newer airOS devices
String.contains?(sys_object_id, ["10002", "41112"]) or
String.contains?(sys_descr, ["airOS", "AirFiber", "airMAX", "Ubiquiti"]) ->
Logger.debug("Selected Ubiquiti profile")
Ubiquiti
String.contains?(sys_descr, "Linux") ->
Logger.debug("Selected NetSNMP profile")
NetSnmp
true ->
Logger.debug("Selected Base profile")
Logger.debug("No YAML profile matched, using Base profile")
Base
end
end
@spec build_device_info(Client.connection_opts(), system_info(), profile()) ::
{:ok, device_info()}
defp build_device_info(client_opts, system_info, {:dynamic, profile}) do
# Use dynamic profile to identify device
defp build_device_info(client_opts, system_info, {:yaml, profile}) do
# Use YAML profile to identify device
identified_info = Dynamic.identify_device(profile, client_opts, system_info)
{:ok, identified_info}
rescue
error ->
Logger.error("Failed to identify device with dynamic profile: #{inspect(error)}")
Logger.error("Failed to identify device with YAML profile: #{inspect(error)}")
{:ok, system_info}
end
@ -380,7 +344,7 @@ defmodule Towerops.Snmp.Discovery do
@spec discover_interfaces(Client.connection_opts(), profile()) ::
{:ok, [interface_data()]}
defp discover_interfaces(client_opts, {:dynamic, profile}) do
defp discover_interfaces(client_opts, {:yaml, profile}) do
case Dynamic.discover_interfaces(profile, client_opts) do
{:ok, interfaces} ->
Logger.debug("Discovered #{length(interfaces)} interfaces")
@ -405,7 +369,7 @@ defmodule Towerops.Snmp.Discovery do
end
@spec discover_sensors(Client.connection_opts(), profile()) :: {:ok, [sensor_data()]}
defp discover_sensors(client_opts, {:dynamic, profile}) do
defp discover_sensors(client_opts, {:yaml, profile}) do
{:ok, sensors} = Dynamic.discover_sensors(profile, client_opts)
Logger.debug("Discovered #{length(sensors)} sensors")
{:ok, sensors}
@ -424,7 +388,7 @@ defmodule Towerops.Snmp.Discovery do
end
@spec discover_vlans(Client.connection_opts(), profile()) :: {:ok, [map()]}
defp discover_vlans(client_opts, {:dynamic, _profile}) do
defp discover_vlans(client_opts, {:yaml, _profile}) do
# Dynamic profiles use Base VLAN discovery
{:ok, vlans} = Base.discover_vlans(client_opts)
Logger.debug("Discovered #{length(vlans)} VLANs")

View file

@ -84,7 +84,12 @@ defmodule Towerops.Snmp.MibTranslator do
"UCD-SNMP-MIB::memAvailSwap" => "1.3.6.1.4.1.2021.4.4.0",
# Ubiquiti (enterprise 41112)
"UBNT-UniFi-MIB::unifiApSystemModel" => "1.3.6.1.4.1.41112.1.6.3.3.0",
"UBNT-UniFi-MIB::unifiApSystemVersion" => "1.3.6.1.4.1.41112.1.6.3.6.0"
"UBNT-UniFi-MIB::unifiApSystemVersion" => "1.3.6.1.4.1.41112.1.6.3.6.0",
# Cambium ePMP (enterprise 17713.21)
"CAMBIUM-PMP80211-MIB::cambiumCurrentuImageVersion.0" => "1.3.6.1.4.1.17713.21.1.1.17.0",
"CAMBIUM-PMP80211-MIB::cambiumEPMPMSN.0" => "1.3.6.1.4.1.17713.21.1.1.31.0",
"CAMBIUM-PMP80211-MIB::cambiumDeviceLatitude.0" => "1.3.6.1.4.1.17713.21.1.1.18.0",
"CAMBIUM-PMP80211-MIB::cambiumDeviceLongitude.0" => "1.3.6.1.4.1.17713.21.1.1.19.0"
}
@doc """
@ -146,7 +151,9 @@ defmodule Towerops.Snmp.MibTranslator do
defp try_snmptranslate(mib_name) do
# Get MIB directories from config or use default
mib_dirs = Application.get_env(:towerops, :mib_dirs, ["/app/priv/mibs", "/usr/share/snmp/mibs"])
# Use :code.priv_dir to find the actual priv directory location in releases
default_priv_mibs = Path.join(:code.priv_dir(:towerops), "mibs")
mib_dirs = Application.get_env(:towerops, :mib_dirs, [default_priv_mibs, "/usr/share/snmp/mibs"])
# Expand directories to include subdirectories (snmptranslate doesn't search recursively)
expanded_dirs = Enum.flat_map(mib_dirs, &expand_mib_directory/1)

View file

@ -831,10 +831,11 @@ defmodule Towerops.Snmp.Profiles.Base do
# Calculate divisor from scale and precision (like LibreNMS)
# Scale: 10^scale exponent, Precision: decimal places
# Always returns an integer for compatibility with Ecto schema
defp calculate_divisor(scale, precision) when is_integer(scale) and is_integer(precision) do
scale_factor = Map.get(@scale_factors, scale, 1)
precision_factor = :math.pow(10, precision)
scale_factor * precision_factor
precision_factor = Integer.pow(10, precision)
round(scale_factor * precision_factor)
end
defp calculate_divisor(scale, _) when is_integer(scale), do: scale_to_divisor(scale)
@ -852,19 +853,95 @@ defmodule Towerops.Snmp.Profiles.Base do
end)
end
defp parse_sys_descr(sys_descr, _sys_object_id) do
# Vendor detection patterns: {descr_patterns, oid_patterns, manufacturer, model_extractor}
@vendor_patterns [
{["Cisco", "IOS"], [], "Cisco", :cisco},
{["Cambium", "ePMP", "PMP", "PTP", "cnPilot"], ["17713"], "Cambium Networks", :cambium},
{["Ubiquiti", "airOS", "AirFiber", "airMAX", "EdgeOS"], ["10002", "41112"], "Ubiquiti", :ubiquiti},
{["MikroTik", "RouterOS"], ["14988"], "MikroTik", :mikrotik},
{["Linux"], [], "Linux", :linux},
{["Windows"], [], "Microsoft", :windows}
]
defp parse_sys_descr(sys_descr, sys_object_id) do
case find_vendor_match(sys_descr, sys_object_id) do
{manufacturer, extractor} -> {manufacturer, extract_model(extractor, sys_descr, sys_object_id)}
nil -> {"Unknown", "Generic Device"}
end
end
defp find_vendor_match(sys_descr, sys_object_id) do
Enum.find_value(@vendor_patterns, fn {descr_patterns, oid_patterns, manufacturer, extractor} ->
descr_match = descr_patterns != [] and String.contains?(sys_descr, descr_patterns)
oid_match = oid_patterns != [] and String.contains?(sys_object_id, oid_patterns)
if descr_match or oid_match do
{manufacturer, extractor}
end
end)
end
defp extract_model(:cisco, sys_descr, _sys_object_id), do: extract_cisco_model(sys_descr)
defp extract_model(:cambium, sys_descr, sys_object_id), do: extract_cambium_model(sys_descr, sys_object_id)
defp extract_model(:ubiquiti, sys_descr, _sys_object_id), do: extract_ubiquiti_model(sys_descr)
defp extract_model(:mikrotik, sys_descr, _sys_object_id), do: extract_mikrotik_model(sys_descr)
defp extract_model(:linux, sys_descr, _sys_object_id), do: sys_descr
defp extract_model(:windows, sys_descr, _sys_object_id), do: sys_descr
# Cambium sysObjectID product line mapping
# Format: .1.3.6.1.4.1.17713.X.Y.Z where X indicates product line
# Order matters - more specific patterns first (21 before 2)
@cambium_product_lines [
{~r/\.17713\.21\./, "ePMP"},
{~r/\.17713\.21$/, "ePMP"},
{~r/\.17713\.[12]\./, "PMP"},
{~r/\.17713\.[12]$/, "PMP"},
{~r/\.17713\.[678]\./, "PTP"},
{~r/\.17713\.[678]$/, "PTP"},
{~r/\.17713\.9\./, "cnPilot"},
{~r/\.17713\.9$/, "cnPilot"}
]
defp extract_cambium_model(sys_descr, sys_object_id) do
# First try to extract from sysDescr, then fall back to sysObjectID product line
extract_cambium_model_from_descr(sys_descr) ||
extract_cambium_product_line(sys_object_id) ||
"Wireless"
end
defp extract_cambium_model_from_descr(sys_descr) do
cond do
String.contains?(sys_descr, ["Cisco", "IOS"]) ->
{"Cisco", extract_cisco_model(sys_descr)}
match = Regex.run(~r/ePMP\s*(\d+)/i, sys_descr) -> "ePMP #{Enum.at(match, 1)}"
Regex.match?(~r/ePMP/i, sys_descr) -> "ePMP"
match = Regex.run(~r/PMP\s*(\d+)/i, sys_descr) -> "PMP #{Enum.at(match, 1)}"
match = Regex.run(~r/PTP\s*(\d+)/i, sys_descr) -> "PTP #{Enum.at(match, 1)}"
match = Regex.run(~r/cnPilot\s*(\w+)/i, sys_descr) -> "cnPilot #{Enum.at(match, 1)}"
Regex.match?(~r/cnPilot/i, sys_descr) -> "cnPilot"
true -> nil
end
end
String.contains?(sys_descr, "Linux") ->
{"Linux", "Server"}
defp extract_cambium_product_line(sys_object_id) when is_binary(sys_object_id) do
Enum.find_value(@cambium_product_lines, fn {regex, product} ->
if Regex.match?(regex, sys_object_id), do: product
end)
end
String.contains?(sys_descr, "Windows") ->
{"Microsoft", "Windows Server"}
defp extract_cambium_product_line(_), do: nil
true ->
{"Unknown", "Generic Device"}
defp extract_ubiquiti_model(sys_descr) do
cond do
match = Regex.run(~r/(AF-?\w+)/i, sys_descr) -> hd(match)
match = Regex.run(~r/(LBE-?\w+|NBE-?\w+|NSM\d+|PBE-?\w+)/i, sys_descr) -> hd(match)
Regex.match?(~r/EdgeOS/i, sys_descr) -> "EdgeRouter"
true -> "Wireless"
end
end
defp extract_mikrotik_model(sys_descr) do
case Regex.run(~r/(RB\w+|CCR\w+|CRS\w+|hAP\w*|hEX\w*)/i, sys_descr) do
[model | _] -> model
_ -> "RouterOS"
end
end

View file

@ -1,379 +0,0 @@
defmodule Towerops.Snmp.Profiles.Cisco do
@moduledoc """
Cisco-specific SNMP device profile.
Extends Base profile with Cisco-specific sensor discovery from CISCO-ENTITY-SENSOR-MIB.
Discovers additional sensors:
- Temperature sensors (chassis, modules, power supplies)
- Power supply sensors
- Fan speed sensors
- Voltage sensors
"""
alias Towerops.Snmp.Client
alias Towerops.Snmp.Discovery
alias Towerops.Snmp.Profiles.Base
require Logger
@cisco_sensor_oids %{
# CISCO-ENTITY-SENSOR-MIB
ent_sensor_type: "1.3.6.1.4.1.9.9.91.1.1.1.1.1",
ent_sensor_scale: "1.3.6.1.4.1.9.9.91.1.1.1.1.2",
ent_sensor_precision: "1.3.6.1.4.1.9.9.91.1.1.1.1.3",
ent_sensor_value: "1.3.6.1.4.1.9.9.91.1.1.1.1.4",
ent_sensor_status: "1.3.6.1.4.1.9.9.91.1.1.1.1.5",
# ENTITY-MIB for physical descriptions
ent_phys_descr: "1.3.6.1.2.1.47.1.1.1.1.2",
ent_phys_name: "1.3.6.1.2.1.47.1.1.1.1.7"
}
@cisco_vtp_oids %{
# CISCO-VTP-MIB - VTP VLAN discovery
vtp_vlan_state: "1.3.6.1.4.1.9.9.46.1.3.1.1.2",
vtp_vlan_type: "1.3.6.1.4.1.9.9.46.1.3.1.1.3",
vtp_vlan_name: "1.3.6.1.4.1.9.9.46.1.3.1.1.4"
}
@doc """
Discovers system information.
Cisco profile doesn't query additional system OIDs beyond Base profile.
"""
@spec discover_system_info(Client.connection_opts()) ::
{:ok, Discovery.system_info()} | {:error, term()}
def discover_system_info(_client_opts) do
# No additional system info beyond Base profile
{:ok, %{}}
end
@doc """
Discovers network interfaces using Base profile.
"""
@spec discover_interfaces(Client.connection_opts()) ::
{:ok, [Discovery.interface_data()]} | {:error, term()}
def discover_interfaces(client_opts) do
Base.discover_interfaces(client_opts)
end
@doc """
Discovers sensors from both standard ENTITY-SENSOR-MIB and CISCO-ENTITY-SENSOR-MIB.
Cisco-specific sensors provide more detailed temperature, power, and fan monitoring.
"""
@spec discover_sensors(Client.connection_opts()) ::
{:ok, [Discovery.sensor_data()]} | {:error, term()}
def discover_sensors(client_opts) do
# Try Cisco-specific sensors first
case discover_cisco_sensors(client_opts) do
{:ok, [_ | _] = sensors} ->
{:ok, sensors}
_ ->
# Fall back to standard ENTITY-SENSOR-MIB
Logger.debug("Cisco-specific sensors not available, using standard discovery")
Base.discover_sensors(client_opts)
end
end
@doc """
Discovers VLANs from Cisco VTP MIB, falling back to Q-BRIDGE-MIB if VTP not available.
"""
@spec discover_vlans(Client.connection_opts()) :: {:ok, [map()]}
def discover_vlans(client_opts) do
# Try Cisco VTP MIB first
case Client.walk(client_opts, @cisco_vtp_oids.vtp_vlan_name) do
{:ok, name_results} when is_map(name_results) and map_size(name_results) > 0 ->
do_discover_vtp_vlans(client_opts, name_results)
_ ->
# Fall back to Q-BRIDGE-MIB
Logger.debug("Cisco VTP MIB not available, falling back to Q-BRIDGE-MIB")
Base.discover_vlans(client_opts)
end
end
defp do_discover_vtp_vlans(client_opts, name_results) do
# Fetch VLAN states
state_map =
case Client.walk(client_opts, @cisco_vtp_oids.vtp_vlan_state) do
{:ok, results} when is_map(results) -> build_vtp_vlan_index_map(results)
_ -> %{}
end
# Fetch VLAN types
type_map =
case Client.walk(client_opts, @cisco_vtp_oids.vtp_vlan_type) do
{:ok, results} when is_map(results) -> build_vtp_vlan_index_map(results)
_ -> %{}
end
# Build VLAN list from names (name_results is a map of OID -> name)
vlans =
name_results
|> Enum.map(fn {oid, name} ->
vlan_id = extract_vtp_vlan_id_from_oid(oid)
state = Map.get(state_map, vlan_id, 1)
type = Map.get(type_map, vlan_id, 1)
if valid_vlan_id?(vlan_id) do
%{
vlan_id: vlan_id,
vlan_name: name,
vlan_type: vtp_vlan_type_to_string(type),
status: vtp_vlan_state_to_status(state),
last_checked_at: DateTime.utc_now()
}
end
end)
|> Enum.reject(&is_nil/1)
Logger.debug("Discovered #{length(vlans)} VLANs from Cisco VTP MIB")
{:ok, vlans}
end
defp build_vtp_vlan_index_map(results) when is_map(results) do
Map.new(results, fn {oid, value} ->
vlan_id = extract_vtp_vlan_id_from_oid(oid)
{vlan_id, value}
end)
end
# VTP VLAN OIDs are indexed by managementDomainIndex.vlanIndex
# e.g., 1.3.6.1.4.1.9.9.46.1.3.1.1.4.1.100 = domain 1, VLAN 100
defp extract_vtp_vlan_id_from_oid(oid) when is_binary(oid) do
oid
|> String.split(".")
|> List.last()
|> String.to_integer()
rescue
_ -> 0
end
defp extract_vtp_vlan_id_from_oid(_), do: 0
defp valid_vlan_id?(vlan_id) when is_integer(vlan_id) do
vlan_id >= 1 and vlan_id <= 4094
end
defp valid_vlan_id?(_), do: false
# CISCO-VTP-MIB vtpVlanState values:
# 1=operational, 2=suspended, 3=mtuTooBigForDevice, 4=mtuTooBigForTrunk
defp vtp_vlan_state_to_status(1), do: "active"
defp vtp_vlan_state_to_status(_), do: "suspended"
# CISCO-VTP-MIB vtpVlanType values:
# 1=ethernet, 2=fddi, 3=tokenRing, 4=fddiNet, 5=trNet, 6=deprecated
defp vtp_vlan_type_to_string(1), do: "ethernet"
defp vtp_vlan_type_to_string(2), do: "fddi"
defp vtp_vlan_type_to_string(3), do: "tokenRing"
defp vtp_vlan_type_to_string(4), do: "fddiNet"
defp vtp_vlan_type_to_string(5), do: "trNet"
defp vtp_vlan_type_to_string(_), do: "ethernet"
@doc """
Identifies Cisco device model more accurately from sysDescr.
"""
@spec identify_device(Discovery.system_info()) :: Discovery.device_info()
def identify_device(system_info) do
sys_descr = Map.get(system_info, :sys_descr, "")
model = extract_cisco_model_detailed(sys_descr)
Map.merge(system_info, %{
manufacturer: "Cisco",
model: model,
firmware_version: extract_firmware_version(sys_descr)
})
end
# Private functions
defp discover_cisco_sensors(client_opts) do
case Client.walk(client_opts, @cisco_sensor_oids.ent_sensor_type) do
{:ok, sensor_types} when map_size(sensor_types) > 0 ->
sensor_indices = extract_indices_from_oids(sensor_types)
sensors =
sensor_indices
|> Enum.map(fn index ->
build_cisco_sensor(client_opts, index)
end)
|> Enum.reject(&is_nil/1)
{:ok, sensors}
_ ->
{:error, :cisco_sensors_not_available}
end
end
defp build_cisco_sensor(client_opts, index) do
oids = [
@cisco_sensor_oids.ent_sensor_type <> ".#{index}",
@cisco_sensor_oids.ent_sensor_scale <> ".#{index}",
@cisco_sensor_oids.ent_sensor_precision <> ".#{index}",
@cisco_sensor_oids.ent_sensor_value <> ".#{index}",
@cisco_sensor_oids.ent_sensor_status <> ".#{index}",
@cisco_sensor_oids.ent_phys_descr <> ".#{index}",
@cisco_sensor_oids.ent_phys_name <> ".#{index}"
]
case Client.get_multiple(client_opts, oids) do
{:ok, [type, scale, precision, value, status, descr, name]} ->
sensor_type = parse_cisco_sensor_type(type)
divisor = calculate_divisor(scale, precision)
%{
sensor_type: sensor_type,
sensor_index: "#{index}",
sensor_oid: @cisco_sensor_oids.ent_sensor_value <> ".#{index}",
sensor_descr: parse_sensor_description(descr, name),
sensor_unit: cisco_sensor_type_to_unit(sensor_type),
sensor_divisor: divisor,
last_value: parse_sensor_value(value, divisor),
status: parse_cisco_sensor_status(status)
}
{:error, reason} ->
Logger.debug("Failed to fetch Cisco sensor #{index}: #{inspect(reason)}")
nil
end
end
defp extract_indices_from_oids(oid_map) do
oid_map
|> Map.keys()
|> Enum.map(fn oid_string ->
oid_string
|> String.split(".")
|> List.last()
|> String.to_integer()
end)
end
defp extract_cisco_model_detailed(sys_descr) do
cond do
# Catalyst switches: "Cisco IOS Software, C2960 Software..."
match = Regex.run(~r/\b(C\d+\w*|WS-C\d+\w*)\b/, sys_descr) ->
[_, model] = match
model
# ISR routers: "Cisco IOS Software, 1900 Software..."
match = Regex.run(~r/\b(ISR\d+\w*|[0-9]{4})\b/, sys_descr) ->
[_, model] = match
model
# ASR routers
match = Regex.run(~r/\b(ASR\d+\w*)\b/, sys_descr) ->
[_, model] = match
model
true ->
"Unknown Cisco Model"
end
end
defp extract_firmware_version(sys_descr) do
# Try to extract version like "Version 15.2(4)E7"
case Regex.run(~r/Version\s+([\d.()A-Za-z]+)/, sys_descr) do
[_, version] -> version
_ -> nil
end
end
# CISCO-ENTITY-SENSOR-MIB sensor types
defp parse_cisco_sensor_type(1), do: "other"
defp parse_cisco_sensor_type(2), do: "unknown"
defp parse_cisco_sensor_type(3), do: "volts_ac"
defp parse_cisco_sensor_type(4), do: "volts_dc"
defp parse_cisco_sensor_type(5), do: "amperes"
defp parse_cisco_sensor_type(6), do: "watts"
defp parse_cisco_sensor_type(7), do: "hertz"
defp parse_cisco_sensor_type(8), do: "celsius"
defp parse_cisco_sensor_type(9), do: "percent_rh"
defp parse_cisco_sensor_type(10), do: "rpm"
defp parse_cisco_sensor_type(11), do: "cmm"
defp parse_cisco_sensor_type(12), do: "truthvalue"
defp parse_cisco_sensor_type(13), do: "specialenum"
defp parse_cisco_sensor_type(14), do: "dBm"
defp parse_cisco_sensor_type(_), do: "unknown"
defp cisco_sensor_type_to_unit("celsius"), do: "°C"
defp cisco_sensor_type_to_unit("volts_ac"), do: "VAC"
defp cisco_sensor_type_to_unit("volts_dc"), do: "VDC"
defp cisco_sensor_type_to_unit("amperes"), do: "A"
defp cisco_sensor_type_to_unit("watts"), do: "W"
defp cisco_sensor_type_to_unit("hertz"), do: "Hz"
defp cisco_sensor_type_to_unit("percent_rh"), do: "%RH"
defp cisco_sensor_type_to_unit("rpm"), do: "RPM"
defp cisco_sensor_type_to_unit("dBm"), do: "dBm"
defp cisco_sensor_type_to_unit(_), do: ""
# Calculate divisor from scale and precision
# Scale: 10^scale (e.g., -3 = milli, 0 = units)
# Precision: number of decimal places
defp calculate_divisor(scale, _precision) when is_integer(scale) do
case scale do
-24 -> 1_000_000_000_000_000_000_000_000
-12 -> 1_000_000_000_000
-9 -> 1_000_000_000
-6 -> 1_000_000
-3 -> 1_000
0 -> 1
3 -> 0.001
_ -> 1
end
end
defp calculate_divisor(_, _), do: 1
defp parse_sensor_value(value, divisor) when is_integer(value) and is_number(divisor) do
value / divisor
end
defp parse_sensor_value(_, _), do: nil
defp parse_sensor_description(descr, _name) when is_binary(descr) and byte_size(descr) > 0 do
descr
end
defp parse_sensor_description(_descr, name) when is_binary(name) and byte_size(name) > 0 do
name
end
defp parse_sensor_description(_, _), do: "Unknown Sensor"
# CISCO-ENTITY-SENSOR-MIB sensor status
defp parse_cisco_sensor_status(1), do: "ok"
defp parse_cisco_sensor_status(2), do: "unavailable"
defp parse_cisco_sensor_status(3), do: "nonoperational"
defp parse_cisco_sensor_status(_), do: "unknown"
@doc """
Collects raw debug data for troubleshooting.
Includes Cisco-specific sensor OIDs and entity tables.
"""
def collect_raw_debug_data(client_opts) do
# Walk Cisco entity sensor table
cisco_sensor_table =
case Client.walk(client_opts, "1.3.6.1.4.1.9.9.91.1.1.1") do
{:ok, results} -> results
{:error, _} -> %{}
end
# Walk entity physical table for descriptions
entity_phys_table =
case Client.walk(client_opts, "1.3.6.1.2.1.47.1.1.1") do
{:ok, results} -> results
{:error, _} -> %{}
end
%{
timestamp: DateTime.utc_now(),
cisco_sensor_oids: @cisco_sensor_oids,
cisco_sensor_table: cisco_sensor_table,
entity_phys_table: entity_phys_table
}
end
end

View file

@ -1,11 +1,9 @@
defmodule Towerops.Snmp.Profiles.Dynamic do
@moduledoc """
Dynamic profile that uses database-stored profiles with MIB name translation.
Allows profiles to be updated without redeployment.
Dynamic profile that uses YAML-loaded profiles with MIB name translation.
Profiles are loaded from priv/profiles/ and cached in ETS.
"""
alias Towerops.Profiles
alias Towerops.Profiles.DeviceProfile
alias Towerops.Snmp.Client
alias Towerops.Snmp.Discovery
alias Towerops.Snmp.MibTranslator
@ -16,38 +14,29 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
Identifies device using profile's device_oids.
Translates MIB names to numeric OIDs and polls the device.
"""
@spec identify_device(DeviceProfile.t(), Client.connection_opts(), Discovery.system_info()) ::
@spec identify_device(map(), Client.connection_opts(), Discovery.system_info()) ::
Discovery.device_info()
def identify_device(profile, client_opts, system_info) do
# Get device OIDs from profile
profile = Profiles.get_profile(profile.name)
device_oids = Map.get(profile, :device_oids, %{})
device_data =
profile.device_oids
|> Enum.map(&fetch_device_oid_value(&1, client_opts))
device_oids
|> Enum.map(fn {field, mib_name} -> fetch_device_oid_value(field, mib_name, client_opts) end)
|> Enum.reject(&is_nil/1)
|> Map.new()
# Format firmware version for MikroTik devices
firmware_version =
case {Map.get(device_data, :firmware_version), Map.get(device_data, :license_version)} do
{fw, license} when not is_nil(fw) and not is_nil(license) ->
"#{profile.vendor} RouterOS #{fw} (Level #{license})"
{fw, _} when not is_nil(fw) ->
"#{profile.vendor} RouterOS #{fw}"
_ ->
nil
end
# Format firmware version (MikroTik has special formatting with license level)
firmware_version = format_firmware_version(profile, device_data)
# Merge with system_info
system_info
|> Map.merge(%{
manufacturer: profile.vendor,
model: system_info.sys_descr,
model: system_info[:sys_descr],
firmware_version: firmware_version,
serial_number: Map.get(device_data, :serial_number)
serial_number: Map.get(device_data, :serial_number),
latitude: Map.get(device_data, :latitude),
longitude: Map.get(device_data, :longitude)
})
|> Map.merge(device_data)
end
@ -55,24 +44,32 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
@doc """
Discovers sensors using profile's sensor_oids.
Translates MIB names and polls sensors.
Falls back to Base ENTITY-SENSOR-MIB discovery if profile has no sensor_oids.
"""
@spec discover_sensors(DeviceProfile.t(), Client.connection_opts()) ::
@spec discover_sensors(map(), Client.connection_opts()) ::
{:ok, [Discovery.sensor_data()]}
def discover_sensors(profile, client_opts) do
profile = Profiles.get_profile(profile.name)
alias Towerops.Snmp.Profiles.Base
sensors =
profile.sensor_oids
|> Enum.map(&fetch_sensor_value(&1, client_opts))
|> Enum.reject(&is_nil/1)
sensor_oids = Map.get(profile, :sensor_oids, [])
{:ok, sensors}
if Enum.empty?(sensor_oids) do
# Fall back to ENTITY-SENSOR-MIB discovery from Base profile
Base.discover_sensors(client_opts)
else
sensors =
sensor_oids
|> Enum.map(&fetch_sensor_value(&1, client_opts))
|> Enum.reject(&is_nil/1)
{:ok, sensors}
end
end
@doc """
Uses Base profile for interface discovery.
"""
@spec discover_interfaces(DeviceProfile.t(), Client.connection_opts()) ::
@spec discover_interfaces(map(), Client.connection_opts()) ::
{:ok, [Discovery.interface_data()]}
def discover_interfaces(_profile, client_opts) do
alias Towerops.Snmp.Profiles.Base
@ -82,13 +79,31 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
# Private helper functions
defp fetch_device_oid_value(device_oid, client_opts) do
with {:ok, numeric_oid} <- MibTranslator.translate(device_oid.mib_name),
defp format_firmware_version(%{vendor: vendor}, device_data) when vendor in ["MikroTik", "Mikrotik RouterOS"] do
case {Map.get(device_data, :firmware_version), Map.get(device_data, :license_version)} do
{fw, license} when not is_nil(fw) and not is_nil(license) ->
"MikroTik RouterOS #{fw} (Level #{license})"
{fw, _} when not is_nil(fw) ->
"MikroTik RouterOS #{fw}"
_ ->
nil
end
end
defp format_firmware_version(_profile, device_data) do
# For non-MikroTik devices, use raw firmware version
Map.get(device_data, :firmware_version)
end
defp fetch_device_oid_value(field, mib_name, client_opts) do
with {:ok, numeric_oid} <- MibTranslator.translate(mib_name),
{:ok, value} <- Client.get(client_opts, numeric_oid) do
{String.to_atom(device_oid.field), value}
{field, value}
else
{:error, :translation_failed} ->
Logger.warning("Failed to translate MIB name: #{device_oid.mib_name}")
Logger.warning("Failed to translate MIB name: #{mib_name}")
nil
{:error, _} ->
@ -97,21 +112,23 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
end
defp fetch_sensor_value(sensor_oid, client_opts) do
with {:ok, numeric_oid} <- MibTranslator.translate(sensor_oid.mib_name),
mib_name = Map.get(sensor_oid, :mib_name)
with {:ok, numeric_oid} <- MibTranslator.translate(mib_name),
{:ok, value} when is_integer(value) and value > 0 <-
Client.get(client_opts, numeric_oid) do
%{
sensor_type: sensor_oid.sensor_type,
sensor_index: sensor_oid.sensor_type,
sensor_type: sensor_oid[:sensor_type],
sensor_index: sensor_oid[:sensor_type],
sensor_oid: numeric_oid,
sensor_descr: sensor_oid.sensor_descr || sensor_oid.sensor_type,
sensor_unit: sensor_oid.sensor_unit || "",
sensor_divisor: sensor_oid.sensor_divisor || 1,
sensor_descr: sensor_oid[:sensor_descr] || sensor_oid[:sensor_type],
sensor_unit: sensor_oid[:sensor_unit] || "",
sensor_divisor: sensor_oid[:sensor_divisor] || 1,
sensor_value: value
}
else
{:error, :translation_failed} ->
Logger.warning("Failed to translate sensor MIB name: #{sensor_oid.mib_name}")
Logger.warning("Failed to translate sensor MIB name: #{mib_name}")
nil
_ ->

View file

@ -1,725 +0,0 @@
defmodule Towerops.Snmp.Profiles.Mikrotik do
@moduledoc """
MikroTik RouterOS device profile.
Extends Base profile with MikroTik-specific monitoring via MIKROTIK-MIB.
Discovers and monitors:
- RouterBoard hardware information
- Health sensors (temperature, voltage, current, fan speed)
- CPU and memory usage
- Power supply status
- Wireless interfaces (if present)
"""
alias Towerops.Snmp.Client
alias Towerops.Snmp.Discovery
alias Towerops.Snmp.Profiles.Base
require Logger
# MikroTik enterprise OID: 1.3.6.1.4.1.14988
@mikrotik_oids %{
# System Information (mtxrSystem)
serial_number: "1.3.6.1.4.1.14988.1.1.7.3.0",
firmware_version: "1.3.6.1.4.1.14988.1.1.7.4.0",
board_name: "1.3.6.1.4.1.14988.1.1.7.9.0",
display_name: "1.3.6.1.4.1.14988.1.1.7.8.0",
build_time: "1.3.6.1.4.1.14988.1.1.7.6.0",
license_level: "1.3.6.1.4.1.14988.1.1.7.5.0",
# Health - Voltages (mtxrHealth)
core_voltage: "1.3.6.1.4.1.14988.1.1.3.1.0",
three_volt: "1.3.6.1.4.1.14988.1.1.3.2.0",
five_volt: "1.3.6.1.4.1.14988.1.1.3.3.0",
twelve_volt: "1.3.6.1.4.1.14988.1.1.3.4.0",
voltage: "1.3.6.1.4.1.14988.1.1.3.8.0",
# Health - Temperatures
sensor_temperature: "1.3.6.1.4.1.14988.1.1.3.5.0",
cpu_temperature: "1.3.6.1.4.1.14988.1.1.3.6.0",
board_temperature: "1.3.6.1.4.1.14988.1.1.3.7.0",
temperature: "1.3.6.1.4.1.14988.1.1.3.10.0",
processor_temperature: "1.3.6.1.4.1.14988.1.1.3.11.0",
# Health - Power & Current
current: "1.3.6.1.4.1.14988.1.1.3.13.0",
power: "1.3.6.1.4.1.14988.1.1.3.12.0",
# Health - Fans & Cooling
active_fan: "1.3.6.1.4.1.14988.1.1.3.9.0",
fan_speed_1: "1.3.6.1.4.1.14988.1.1.3.17.0",
fan_speed_2: "1.3.6.1.4.1.14988.1.1.3.18.0",
# Health - Power Supply
processor_frequency: "1.3.6.1.4.1.14988.1.1.3.14.0",
power_supply_state: "1.3.6.1.4.1.14988.1.1.3.15.0",
backup_psu_state: "1.3.6.1.4.1.14988.1.1.3.16.0",
# System Resources
cpu_load: "1.3.6.1.2.1.25.3.3.1.2.1",
total_memory: "1.3.6.1.2.1.25.2.2.0",
used_memory: "1.3.6.1.4.1.14988.1.1.1.2.0",
total_hdd: "1.3.6.1.4.1.14988.1.1.1.6.0",
used_hdd: "1.3.6.1.4.1.14988.1.1.1.7.0",
# Network Services
dhcp_lease_count: "1.3.6.1.4.1.14988.1.1.1.3.0",
# Connection Tracking (mtxrFWConnection)
fw_connection_count: "1.3.6.1.4.1.14988.1.1.13.1.1.1.0",
fw_connection_max: "1.3.6.1.4.1.14988.1.1.13.1.1.2.0"
}
# Table OID bases for walking
@health_sensor_table "1.3.6.1.4.1.14988.1.1.3.100.1"
@poe_table "1.3.6.1.4.1.14988.1.1.15.1"
@optical_table "1.3.6.1.4.1.14988.1.1.16.1"
@doc """
Discovers system information using Base profile and adds MikroTik-specific details.
"""
@spec discover_system_info(Client.connection_opts()) ::
{:ok, Discovery.system_info()} | {:error, term()}
def discover_system_info(client_opts) do
with {:ok, base_info} <- Base.discover_system_info(client_opts),
{:ok, mikrotik_info} <- discover_mikrotik_system_info(client_opts) do
{:ok, Map.merge(base_info, mikrotik_info)}
end
end
@doc """
Discovers network interfaces using Base profile.
"""
@spec discover_interfaces(Client.connection_opts()) ::
{:ok, [Discovery.interface_data()]} | {:error, term()}
def discover_interfaces(client_opts) do
Base.discover_interfaces(client_opts)
end
@doc """
Discovers MikroTik health sensors and system resources.
Returns comprehensive sensor list including temperature, voltage, current, fans, CPU, memory, disk.
Also discovers entity sensors from ENTITY-SENSOR-MIB (for additional temperature, voltage, etc.).
"""
@spec discover_sensors(Client.connection_opts()) ::
{:ok, [Discovery.sensor_data()]} | {:error, term()}
def discover_sensors(client_opts) do
Logger.debug("Discovering MikroTik sensors...")
health_sensors = discover_health_sensors(client_opts)
resource_sensors = discover_resource_sensors(client_opts)
# Also try to discover entity sensors (for additional temperature, voltage sensors)
{:ok, entity_sensors} = Base.discover_sensors(client_opts)
all_sensors = health_sensors ++ resource_sensors ++ entity_sensors
if all_sensors == [] do
Logger.warning("No MikroTik sensors found")
{:ok, []}
else
Logger.debug("Discovered #{length(all_sensors)} MikroTik sensors")
{:ok, all_sensors}
end
end
@doc """
Identifies MikroTik device from sysDescr.
"""
@spec identify_device(Discovery.system_info()) :: Discovery.device_info()
def identify_device(system_info) do
sys_descr = Map.get(system_info, :sys_descr, "")
# Extract model from sysDescr (e.g., "RouterOS RB5009UG+S+")
model =
case Regex.run(~r/RouterOS\s+([A-Z0-9\+\-]+)/, sys_descr) do
[_, model] -> model
_ -> "MikroTik RouterOS"
end
# Build formatted firmware version
firmware_version = Map.get(system_info, :firmware_version)
license_level = Map.get(system_info, :license_version)
# Treat empty string as nil for license level
license_level = if license_level == "", do: nil, else: license_level
formatted_firmware =
cond do
firmware_version && license_level ->
"RouterOS #{firmware_version} (Level #{license_level})"
firmware_version ->
"RouterOS #{firmware_version}"
true ->
# Fallback to extracting from sysDescr
extract_firmware_from_sysdescr(sys_descr)
end
Map.merge(system_info, %{
manufacturer: "MikroTik",
model: model,
firmware_version: formatted_firmware,
serial_number: Map.get(system_info, :serial_number)
})
end
# Private functions
defp discover_mikrotik_system_info(client_opts) do
alias Towerops.Snmp.MibTranslator
# Use MIB symbolic names instead of hardcoded OIDs
mib_names = [
"MIKROTIK-MIB::mtxrSerialNumber.0",
"MIKROTIK-MIB::mtxrFirmwareVersion.0",
"MIKROTIK-MIB::mtxrBoardName.0",
"MIKROTIK-MIB::mtxrSystemDisplayName.0",
"MIKROTIK-MIB::mtxrLicenseVersion.0"
]
# Translate MIB names to numeric OIDs
translated = MibTranslator.translate_batch(mib_names)
oids =
Enum.map(mib_names, fn name ->
case Map.get(translated, name) do
{:ok, oid} -> oid
_ -> nil
end
end)
# If any translation failed, fall back to hardcoded OIDs
oids =
if Enum.any?(oids, &is_nil/1) do
Logger.warning("MIB translation failed for some MikroTik OIDs, using hardcoded values")
[
@mikrotik_oids.serial_number,
@mikrotik_oids.firmware_version,
@mikrotik_oids.board_name,
@mikrotik_oids.display_name,
@mikrotik_oids.license_level
]
else
oids
end
case Client.get_multiple(client_opts, oids) do
{:ok, [serial, firmware, board, display, license_level]} ->
{:ok,
%{
serial_number: serial,
firmware_version: firmware,
board_name: board,
display_name: display,
license_version: license_level
}}
{:error, reason} ->
# MikroTik-specific OIDs not available or partially available
Logger.warning("Failed to get MikroTik device info: #{inspect(reason)}")
{:ok, %{}}
end
end
defp discover_health_sensors(client_opts) do
# First try the dynamic health sensor table (available on newer devices like CCR2004)
table_sensors = discover_health_table_sensors(client_opts)
# Also try individual health OIDs for older devices
oid_sensors = discover_health_oid_sensors(client_opts)
# Combine both, preferring table sensors (more accurate)
table_sensors ++ oid_sensors
end
defp discover_health_table_sensors(client_opts) do
# Walk health sensor table: mtxrHealthTable (1.3.6.1.4.1.14988.1.1.3.100.1)
# Column 2: Name, Column 3: Value, Column 4: Type
with {:ok, name_results} <- Client.walk(client_opts, "#{@health_sensor_table}.2"),
{:ok, value_results} <- Client.walk(client_opts, "#{@health_sensor_table}.3"),
{:ok, type_results} <- Client.walk(client_opts, "#{@health_sensor_table}.4") do
indices =
name_results
|> Map.keys()
|> Enum.map(fn oid -> oid |> String.split(".") |> List.last() end)
indices
|> Enum.map(fn index ->
name_oid = "#{@health_sensor_table}.2.#{index}"
value_oid = "#{@health_sensor_table}.3.#{index}"
type_oid = "#{@health_sensor_table}.4.#{index}"
name = Map.get(name_results, name_oid, "unknown")
value = Map.get(value_results, value_oid, 0)
type_code = Map.get(type_results, type_oid, 0)
{sensor_type, unit, divisor} = decode_health_sensor_type(type_code, name)
# Skip sensors with value 0 (disabled PSUs, etc.)
if value > 0 do
%{
sensor_type: sensor_type,
sensor_index: "health_#{index}",
sensor_oid: value_oid,
sensor_descr: format_health_sensor_name(name),
sensor_unit: unit,
sensor_divisor: divisor,
last_value: value / divisor,
status: "ok"
}
end
end)
|> Enum.reject(&is_nil/1)
else
_ -> []
end
end
defp decode_health_sensor_type(type_code, name) do
cond do
# Type 1 = Temperature (Celsius)
type_code == 1 -> {"temperature", "°C", 1}
# Type 3 = Voltage (V, value in decivolts)
type_code == 3 -> {"voltage", "V", 10}
# Type 2 = Current (mA)
type_code == 2 -> {"current", "mA", 1}
# Fallback: infer from name
String.contains?(name, "temperature") -> {"temperature", "°C", 1}
String.contains?(name, "voltage") -> {"voltage", "V", 10}
String.contains?(name, "current") -> {"current", "mA", 1}
String.contains?(name, "power") -> {"power", "W", 10}
true -> {"unknown", "", 1}
end
end
defp format_health_sensor_name(name) do
name
|> String.replace("-", " ")
|> String.split()
|> Enum.map_join(" ", &String.capitalize/1)
end
defp discover_health_oid_sensors(client_opts) do
# Try to get all health OIDs - voltages, temperatures, power, fans
health_oids = [
# Voltages
{@mikrotik_oids.core_voltage, "core_voltage", "V", 10},
{@mikrotik_oids.three_volt, "3v3_voltage", "V", 10},
{@mikrotik_oids.five_volt, "5v_voltage", "V", 10},
{@mikrotik_oids.twelve_volt, "12v_voltage", "V", 10},
{@mikrotik_oids.voltage, "input_voltage", "V", 10},
# Temperatures
{@mikrotik_oids.sensor_temperature, "sensor_temperature", "°C", 10},
{@mikrotik_oids.cpu_temperature, "cpu_temperature", "°C", 10},
{@mikrotik_oids.board_temperature, "board_temperature", "°C", 10},
{@mikrotik_oids.temperature, "temperature", "°C", 10},
{@mikrotik_oids.processor_temperature, "processor_temperature", "°C", 10},
# Power & Current
{@mikrotik_oids.current, "current", "mA", 1},
{@mikrotik_oids.power, "power", "W", 10},
# Fans
{@mikrotik_oids.fan_speed_1, "fan1", "RPM", 1},
{@mikrotik_oids.fan_speed_2, "fan2", "RPM", 1},
# Processor
{@mikrotik_oids.processor_frequency, "processor_frequency", "MHz", 1}
]
health_oids
|> Enum.map(fn {oid, type, unit, divisor} ->
case Client.get(client_opts, oid) do
{:ok, value} when is_integer(value) and value > 0 ->
%{
sensor_type: type,
sensor_index: type,
sensor_oid: oid,
sensor_descr: format_sensor_name(type),
sensor_unit: unit,
sensor_divisor: divisor,
last_value: value / divisor,
status: "ok"
}
_ ->
nil
end
end)
|> Enum.reject(&is_nil/1)
end
defp discover_resource_sensors(client_opts) do
cpu_sensors = discover_cpu_sensors(client_opts)
storage_sensors = discover_storage_sensors(client_opts)
network_sensors = discover_network_sensors(client_opts)
poe_sensors = discover_poe_sensors(client_opts)
optical_sensors = discover_optical_sensors(client_opts)
cpu_sensors ++ storage_sensors ++ network_sensors ++ poe_sensors ++ optical_sensors
end
defp discover_cpu_sensors(client_opts) do
# Walk hrProcessorLoad (1.3.6.1.2.1.25.3.3.1.2) to get CPU load per core
case Client.walk(client_opts, "1.3.6.1.2.1.25.3.3.1.2") do
{:ok, results} ->
Enum.map(results, fn {oid, load} ->
index = oid |> String.split(".") |> List.last()
%{
sensor_type: "cpu_load",
sensor_index: "cpu#{index}",
sensor_oid: oid,
sensor_descr: "CPU #{index} Load",
sensor_unit: "%",
sensor_divisor: 1,
last_value: load / 1.0,
status: cpu_status(load)
}
end)
_ ->
[]
end
end
defp discover_network_sensors(client_opts) do
# Get DHCP lease count and connection tracking stats
network_oids = [
{@mikrotik_oids.dhcp_lease_count, "dhcp_leases", "count", 1},
{@mikrotik_oids.fw_connection_count, "connection_count", "count", 1}
]
network_oids
|> Enum.map(fn {oid, type, unit, divisor} ->
case Client.get(client_opts, oid) do
{:ok, value} when is_integer(value) and value >= 0 ->
%{
sensor_type: type,
sensor_index: type,
sensor_oid: oid,
sensor_descr: format_network_sensor_name(type),
sensor_unit: unit,
sensor_divisor: divisor,
last_value: value / divisor,
status: "ok"
}
_ ->
nil
end
end)
|> Enum.reject(&is_nil/1)
end
defp discover_storage_sensors(client_opts) do
with {:ok, descr_results} <- Client.walk(client_opts, "1.3.6.1.2.1.25.2.3.1.3"),
{:ok, size_results} <- Client.walk(client_opts, "1.3.6.1.2.1.25.2.3.1.5"),
{:ok, used_results} <- Client.walk(client_opts, "1.3.6.1.2.1.25.2.3.1.6") do
indices = extract_storage_indices(descr_results)
build_storage_sensors(indices, descr_results, size_results, used_results)
else
_ -> []
end
end
defp extract_storage_indices(descr_results) do
descr_results
|> Map.keys()
|> Enum.map(fn oid -> oid |> String.split(".") |> List.last() end)
end
defp build_storage_sensors(indices, descr_results, size_results, used_results) do
indices
|> Enum.map(&build_storage_sensor(&1, descr_results, size_results, used_results))
|> Enum.reject(&is_nil/1)
end
defp build_storage_sensor(index, descr_results, size_results, used_results) do
descr_oid = "1.3.6.1.2.1.25.2.3.1.3.#{index}"
size_oid = "1.3.6.1.2.1.25.2.3.1.5.#{index}"
used_oid = "1.3.6.1.2.1.25.2.3.1.6.#{index}"
descr = Map.get(descr_results, descr_oid, "")
size = Map.get(size_results, size_oid, 0)
used = Map.get(used_results, used_oid, 0)
if size > 0 do
percent = used / size * 100
type = determine_storage_type(descr)
# Cap percentage at 100% and log warning if exceeded
# This can happen if the device reports incorrect SNMP values
capped_percent =
if percent > 100 do
Logger.warning(
"Storage sensor #{descr} exceeded 100%: #{Float.round(percent, 2)}% " <>
"(used=#{used} from #{used_oid}, size=#{size} from #{size_oid}). " <>
"Capping at 100%. This may indicate a device firmware bug."
)
100.0
else
percent
end
%{
sensor_type: "#{type}_usage",
sensor_index: index,
sensor_oid: used_oid,
sensor_descr: descr,
sensor_unit: "%",
sensor_divisor: 1,
last_value: capped_percent,
status: storage_status(type, capped_percent),
metadata: %{
size_oid: size_oid,
calculation: "percentage"
}
}
end
end
defp determine_storage_type(descr) do
cond do
String.contains?(descr, "memory") -> "memory"
String.contains?(descr, "disk") -> "disk"
true -> "storage"
end
end
defp discover_poe_sensors(client_opts) do
# Walk POE table to get per-port POE statistics
# Table columns: Name(3), Status(4), Voltage(5), Current(6), Power(7)
with {:ok, name_results} <- Client.walk(client_opts, "#{@poe_table}.1.3"),
{:ok, voltage_results} <- Client.walk(client_opts, "#{@poe_table}.1.5"),
{:ok, current_results} <- Client.walk(client_opts, "#{@poe_table}.1.6"),
{:ok, power_results} <- Client.walk(client_opts, "#{@poe_table}.1.7") do
# Get indices from the name results
indices =
name_results
|> Map.keys()
|> Enum.map(fn oid -> oid |> String.split(".") |> List.last() end)
# Create sensors for voltage, current, and power per port
Enum.flat_map(indices, fn index ->
name_oid = "#{@poe_table}.1.3.#{index}"
voltage_oid = "#{@poe_table}.1.5.#{index}"
current_oid = "#{@poe_table}.1.6.#{index}"
power_oid = "#{@poe_table}.1.7.#{index}"
port_name = Map.get(name_results, name_oid, "Port #{index}")
voltage = Map.get(voltage_results, voltage_oid, 0)
current = Map.get(current_results, current_oid, 0)
power = Map.get(power_results, power_oid, 0)
Enum.reject(
[
if voltage > 0 do
%{
sensor_type: "poe_voltage",
sensor_index: "poe_#{index}_voltage",
sensor_oid: voltage_oid,
sensor_descr: "#{port_name} POE Voltage",
sensor_unit: "V",
sensor_divisor: 10,
last_value: voltage / 10.0,
status: "ok"
}
end,
if current > 0 do
%{
sensor_type: "poe_current",
sensor_index: "poe_#{index}_current",
sensor_oid: current_oid,
sensor_descr: "#{port_name} POE Current",
sensor_unit: "mA",
sensor_divisor: 1,
last_value: current / 1.0,
status: "ok"
}
end,
if power > 0 do
%{
sensor_type: "poe_power",
sensor_index: "poe_#{index}_power",
sensor_oid: power_oid,
sensor_descr: "#{port_name} POE Power",
sensor_unit: "W",
sensor_divisor: 10,
last_value: power / 10.0,
status: "ok"
}
end
],
&is_nil/1
)
end)
else
_ -> []
end
end
defp discover_optical_sensors(client_opts) do
# Walk optical table to get SFP/optical monitoring data
# Table columns: Name(2), Temperature(3), Voltage(5), TxBiasCurrent(6), TxPower(7), RxPower(8)
with {:ok, name_results} <- Client.walk(client_opts, "#{@optical_table}.1.2"),
{:ok, temp_results} <- Client.walk(client_opts, "#{@optical_table}.1.3"),
{:ok, voltage_results} <- Client.walk(client_opts, "#{@optical_table}.1.5"),
{:ok, tx_power_results} <- Client.walk(client_opts, "#{@optical_table}.1.7"),
{:ok, rx_power_results} <- Client.walk(client_opts, "#{@optical_table}.1.8") do
# Get indices from the name results
indices =
name_results
|> Map.keys()
|> Enum.map(fn oid -> oid |> String.split(".") |> List.last() end)
# Create sensors for each optical metric
Enum.flat_map(indices, fn index ->
name_oid = "#{@optical_table}.1.2.#{index}"
temp_oid = "#{@optical_table}.1.3.#{index}"
voltage_oid = "#{@optical_table}.1.5.#{index}"
tx_power_oid = "#{@optical_table}.1.7.#{index}"
rx_power_oid = "#{@optical_table}.1.8.#{index}"
sfp_name = Map.get(name_results, name_oid, "SFP #{index}")
temperature = Map.get(temp_results, temp_oid)
voltage = Map.get(voltage_results, voltage_oid)
tx_power = Map.get(tx_power_results, tx_power_oid)
rx_power = Map.get(rx_power_results, rx_power_oid)
Enum.reject(
[
if temperature do
%{
sensor_type: "sfp_temperature",
sensor_index: "sfp_#{index}_temp",
sensor_oid: temp_oid,
sensor_descr: "#{sfp_name} Temperature",
sensor_unit: "°C",
sensor_divisor: 1000,
last_value: temperature / 1000.0,
status: "ok"
}
end,
if voltage do
%{
sensor_type: "sfp_voltage",
sensor_index: "sfp_#{index}_voltage",
sensor_oid: voltage_oid,
sensor_descr: "#{sfp_name} Voltage",
sensor_unit: "V",
sensor_divisor: 1000,
last_value: voltage / 1000.0,
status: "ok"
}
end,
if tx_power do
%{
sensor_type: "sfp_tx_power",
sensor_index: "sfp_#{index}_tx",
sensor_oid: tx_power_oid,
sensor_descr: "#{sfp_name} TX Power",
sensor_unit: "dBm",
sensor_divisor: 1000,
last_value: tx_power / 1000.0,
status: "ok"
}
end,
if rx_power do
%{
sensor_type: "sfp_rx_power",
sensor_index: "sfp_#{index}_rx",
sensor_oid: rx_power_oid,
sensor_descr: "#{sfp_name} RX Power",
sensor_unit: "dBm",
sensor_divisor: 1000,
last_value: rx_power / 1000.0,
status: "ok"
}
end
],
&is_nil/1
)
end)
else
_ -> []
end
end
defp format_sensor_name("core_voltage"), do: "Core Voltage"
defp format_sensor_name("3v3_voltage"), do: "3.3V Supply"
defp format_sensor_name("5v_voltage"), do: "5V Supply"
defp format_sensor_name("12v_voltage"), do: "12V Supply"
defp format_sensor_name("input_voltage"), do: "Input Voltage"
defp format_sensor_name("sensor_temperature"), do: "Sensor Chip Temperature"
defp format_sensor_name("cpu_temperature"), do: "CPU Temperature"
defp format_sensor_name("board_temperature"), do: "Board Temperature"
defp format_sensor_name("temperature"), do: "System Temperature"
defp format_sensor_name("processor_temperature"), do: "Processor Temperature"
defp format_sensor_name("current"), do: "Current Draw"
defp format_sensor_name("power"), do: "Power Consumption"
defp format_sensor_name("fan1"), do: "Fan 1 Speed"
defp format_sensor_name("fan2"), do: "Fan 2 Speed"
defp format_sensor_name("processor_frequency"), do: "Processor Frequency"
defp format_sensor_name(other), do: String.capitalize(other)
defp format_network_sensor_name("dhcp_leases"), do: "DHCP Leases"
defp format_network_sensor_name("connection_count"), do: "Active Connections"
defp format_network_sensor_name(other), do: String.capitalize(other)
defp cpu_status(load) when load < 70, do: "ok"
defp cpu_status(load) when load < 90, do: "warning"
defp cpu_status(_), do: "critical"
defp storage_status("memory", percent) when percent < 80, do: "ok"
defp storage_status("memory", percent) when percent < 95, do: "warning"
defp storage_status("memory", _), do: "critical"
defp storage_status("disk", percent) when percent < 85, do: "ok"
defp storage_status("disk", percent) when percent < 95, do: "warning"
defp storage_status("disk", _), do: "critical"
defp storage_status(_, percent) when percent < 85, do: "ok"
defp storage_status(_, percent) when percent < 95, do: "warning"
defp storage_status(_, _), do: "critical"
defp extract_firmware_from_sysdescr(sys_descr) do
# Try to extract version from sysDescr
# Example: "RouterOS RB5009UG+S+ (stable) 7.13.2"
case Regex.run(~r/RouterOS.*?(\d+\.\d+(?:\.\d+)?)/, sys_descr) do
[_, version] -> version
_ -> nil
end
end
@doc """
Collects raw SNMP OID data for debugging purposes.
"""
def collect_raw_debug_data(client_opts) do
# Collect raw SNMP data for debugging
system_oids =
Map.new(@mikrotik_oids, fn {key, oid} ->
value =
case Client.get(client_opts, oid) do
{:ok, val} -> val
{:error, reason} -> "ERROR: #{inspect(reason)}"
end
{key, %{oid: oid, value: value}}
end)
# Walk health sensor table
health_table =
case Client.walk(client_opts, @health_sensor_table) do
{:ok, results} -> results
{:error, _} -> %{}
end
%{
timestamp: DateTime.utc_now(),
system_oids: system_oids,
health_sensor_table: health_table
}
end
end

View file

@ -1,342 +0,0 @@
defmodule Towerops.Snmp.Profiles.NetSnmp do
@moduledoc """
Net-SNMP device profile for Linux/Unix servers.
Extends Base profile with Net-SNMP specific sensor discovery from:
- LM-SENSORS-MIB (lm-sensors hardware monitoring)
- UCD-SNMP-MIB (system statistics)
Discovers:
- CPU temperature, fan speed, voltage from lm-sensors
- System load, memory, disk usage from UCD-SNMP-MIB
"""
alias Towerops.Snmp.Client
alias Towerops.Snmp.Discovery
alias Towerops.Snmp.Profiles.Base
require Logger
@lm_sensors_oids %{
# LM-SENSORS-MIB
lm_temp_sensors_device: "1.3.6.1.4.1.2021.13.16.2.1.2",
lm_temp_sensors_value: "1.3.6.1.4.1.2021.13.16.2.1.3",
lm_fans_device: "1.3.6.1.4.1.2021.13.16.3.1.2",
lm_fans_value: "1.3.6.1.4.1.2021.13.16.3.1.3",
lm_voltage_device: "1.3.6.1.4.1.2021.13.16.4.1.2",
lm_voltage_value: "1.3.6.1.4.1.2021.13.16.4.1.3"
}
@ucd_snmp_oids %{
# UCD-SNMP-MIB system statistics
load_average_1: "1.3.6.1.4.1.2021.10.1.3.1",
load_average_5: "1.3.6.1.4.1.2021.10.1.3.2",
load_average_15: "1.3.6.1.4.1.2021.10.1.3.3",
mem_total: "1.3.6.1.4.1.2021.4.5.0",
mem_available: "1.3.6.1.4.1.2021.4.6.0",
mem_buffer: "1.3.6.1.4.1.2021.4.14.0",
mem_cached: "1.3.6.1.4.1.2021.4.15.0"
}
@doc """
Discovers system information.
NetSnmp profile doesn't query additional system OIDs beyond Base profile.
"""
@spec discover_system_info(Client.connection_opts()) ::
{:ok, Discovery.system_info()} | {:error, term()}
def discover_system_info(_client_opts) do
# No additional system info beyond Base profile
{:ok, %{}}
end
@doc """
Discovers network interfaces using Base profile.
"""
@spec discover_interfaces(Client.connection_opts()) ::
{:ok, [Discovery.interface_data()]} | {:error, term()}
def discover_interfaces(client_opts) do
Base.discover_interfaces(client_opts)
end
@doc """
Discovers sensors from LM-SENSORS-MIB and UCD-SNMP-MIB.
Combines hardware sensors (temperature, fans, voltage) with system stats (load, memory).
"""
def discover_sensors(client_opts) do
lm_sensors = discover_lm_sensors(client_opts)
ucd_sensors = discover_ucd_sensors(client_opts)
all_sensors = lm_sensors ++ ucd_sensors
if all_sensors == [] do
# Fall back to standard ENTITY-SENSOR-MIB
Logger.debug("Net-SNMP specific sensors not available, using standard discovery")
Base.discover_sensors(client_opts)
else
{:ok, all_sensors}
end
end
@doc """
Identifies Linux/Unix device from sysDescr.
"""
def identify_device(system_info) do
sys_descr = Map.get(system_info, :sys_descr, "")
{os, version} = extract_linux_info(sys_descr)
Map.merge(system_info, %{
manufacturer: "Linux",
model: os,
firmware_version: version
})
end
# Private functions
defp discover_lm_sensors(client_opts) do
temp_sensors = discover_temperature_sensors(client_opts)
fan_sensors = discover_fan_sensors(client_opts)
voltage_sensors = discover_voltage_sensors(client_opts)
temp_sensors ++ fan_sensors ++ voltage_sensors
end
defp discover_temperature_sensors(client_opts) do
with {:ok, devices} <- Client.walk(client_opts, @lm_sensors_oids.lm_temp_sensors_device),
{:ok, values} <- Client.walk(client_opts, @lm_sensors_oids.lm_temp_sensors_value) do
build_lm_sensors(devices, values, "celsius", "°C")
else
_ -> []
end
end
defp discover_fan_sensors(client_opts) do
with {:ok, devices} <- Client.walk(client_opts, @lm_sensors_oids.lm_fans_device),
{:ok, values} <- Client.walk(client_opts, @lm_sensors_oids.lm_fans_value) do
build_lm_sensors(devices, values, "rpm", "RPM")
else
_ -> []
end
end
defp discover_voltage_sensors(client_opts) do
with {:ok, devices} <- Client.walk(client_opts, @lm_sensors_oids.lm_voltage_device),
{:ok, values} <- Client.walk(client_opts, @lm_sensors_oids.lm_voltage_value) do
build_lm_sensors(devices, values, "volts", "V")
else
_ -> []
end
end
defp build_lm_sensors(devices, values, sensor_type, unit) do
Enum.map(devices, fn {oid, device_name} ->
index = extract_last_oid_part(oid)
value_oid = find_matching_oid(values, index)
%{
sensor_type: sensor_type,
sensor_index: index,
sensor_oid: value_oid,
sensor_descr: device_name,
sensor_unit: unit,
sensor_divisor: divisor_for_type(sensor_type),
last_value: get_sensor_value(values, index),
status: "ok"
}
end)
end
defp discover_ucd_sensors(client_opts) do
load_sensors = discover_load_sensors(client_opts)
memory_sensors = discover_memory_sensors(client_opts)
load_sensors ++ memory_sensors
end
defp discover_load_sensors(client_opts) do
case Client.get_multiple(client_opts, [
@ucd_snmp_oids.load_average_1,
@ucd_snmp_oids.load_average_5,
@ucd_snmp_oids.load_average_15
]) do
{:ok, [load1, load5, load15]} ->
[
%{
sensor_type: "load",
sensor_index: "1min",
sensor_oid: @ucd_snmp_oids.load_average_1,
sensor_descr: "System Load (1 min)",
sensor_unit: "",
sensor_divisor: 100,
last_value: parse_float(load1),
status: "ok"
},
%{
sensor_type: "load",
sensor_index: "5min",
sensor_oid: @ucd_snmp_oids.load_average_5,
sensor_descr: "System Load (5 min)",
sensor_unit: "",
sensor_divisor: 100,
last_value: parse_float(load5),
status: "ok"
},
%{
sensor_type: "load",
sensor_index: "15min",
sensor_oid: @ucd_snmp_oids.load_average_15,
sensor_descr: "System Load (15 min)",
sensor_unit: "",
sensor_divisor: 100,
last_value: parse_float(load15),
status: "ok"
}
]
_ ->
[]
end
end
defp discover_memory_sensors(client_opts) do
case Client.get_multiple(client_opts, [
@ucd_snmp_oids.mem_total,
@ucd_snmp_oids.mem_available
]) do
{:ok, [total, available]} ->
total_kb = parse_integer(total)
available_kb = parse_integer(available)
used_kb = calculate_used_memory(total_kb, available_kb)
used_percent = calculate_memory_percentage(total_kb, used_kb)
[
%{
sensor_type: "memory",
sensor_index: "usage",
sensor_oid: @ucd_snmp_oids.mem_available,
sensor_descr: "Memory Usage",
sensor_unit: "%",
sensor_divisor: 1,
last_value: used_percent,
status: "ok"
}
]
_ ->
[]
end
end
defp calculate_used_memory(nil, _available_kb), do: nil
defp calculate_used_memory(_total_kb, nil), do: nil
defp calculate_used_memory(total_kb, available_kb), do: total_kb - available_kb
defp calculate_memory_percentage(total_kb, used_kb) when is_number(total_kb) and total_kb > 0 and is_number(used_kb) do
percent = used_kb / total_kb * 100
cap_memory_percentage(percent, used_kb, total_kb)
end
defp calculate_memory_percentage(_total_kb, _used_kb), do: nil
defp cap_memory_percentage(percent, _used_kb, _total_kb) when percent <= 100, do: percent
defp cap_memory_percentage(percent, used_kb, total_kb) do
Logger.warning(
"Memory usage exceeded 100%: #{Float.round(percent, 2)}% " <>
"(used=#{used_kb} KB, total=#{total_kb} KB). " <>
"Capping at 100%. This may indicate a device firmware bug."
)
100.0
end
defp extract_linux_info(sys_descr) do
cond do
String.contains?(sys_descr, "Ubuntu") ->
version = extract_version(sys_descr, ~r/Ubuntu[\/\s]+([\d.]+)/)
{"Ubuntu", version}
String.contains?(sys_descr, "Debian") ->
version = extract_version(sys_descr, ~r/Debian[\/\s]+([\d.]+)/)
{"Debian", version}
String.contains?(sys_descr, "CentOS") ->
version = extract_version(sys_descr, ~r/CentOS[\/\s]+([\d.]+)/)
{"CentOS", version}
String.contains?(sys_descr, "Red Hat") ->
version = extract_version(sys_descr, ~r/Red Hat[\/\s]+([\d.]+)/)
{"Red Hat Enterprise Linux", version}
String.contains?(sys_descr, "Linux") ->
version = extract_version(sys_descr, ~r/Linux[\/\s]+([\d.]+)/)
{"Linux", version}
true ->
{"Unix", nil}
end
end
defp extract_version(sys_descr, regex) do
case Regex.run(regex, sys_descr) do
[_, version] -> version
_ -> nil
end
end
defp extract_last_oid_part(oid_string) do
oid_string
|> String.split(".")
|> List.last()
end
defp find_matching_oid(values, index) do
values
|> Map.keys()
|> Enum.find(fn oid -> String.ends_with?(oid, ".#{index}") end)
end
defp get_sensor_value(values, index) do
oid = find_matching_oid(values, index)
value = Map.get(values, oid)
parse_float(value)
end
defp divisor_for_type("celsius"), do: 1000
defp divisor_for_type("volts"), do: 1000
defp divisor_for_type("rpm"), do: 1
defp divisor_for_type(_), do: 1
defp parse_integer(value) when is_integer(value), do: value
defp parse_integer(value) when is_binary(value), do: String.to_integer(value)
defp parse_integer(_), do: nil
defp parse_float(value) when is_float(value), do: value
defp parse_float(value) when is_integer(value), do: value / 1.0
defp parse_float(value) when is_binary(value), do: String.to_float(value)
defp parse_float(_), do: nil
@doc """
Collects raw debug data for troubleshooting.
Includes LM-SENSORS and UCD-SNMP tables.
"""
def collect_raw_debug_data(client_opts) do
%{
timestamp: DateTime.utc_now(),
lm_sensors_oids: @lm_sensors_oids,
ucd_snmp_oids: @ucd_snmp_oids,
lm_temp_table: safe_walk(client_opts, "1.3.6.1.4.1.2021.13.16.2"),
lm_fan_table: safe_walk(client_opts, "1.3.6.1.4.1.2021.13.16.3"),
lm_voltage_table: safe_walk(client_opts, "1.3.6.1.4.1.2021.13.16.4"),
ucd_load_table: safe_walk(client_opts, "1.3.6.1.4.1.2021.10"),
ucd_memory_table: safe_walk(client_opts, "1.3.6.1.4.1.2021.4")
}
end
defp safe_walk(client_opts, oid) do
case Client.walk(client_opts, oid) do
{:ok, results} -> results
{:error, _} -> %{}
end
end
end

View file

@ -1,262 +0,0 @@
defmodule Towerops.Snmp.Profiles.Ubiquiti do
@moduledoc """
SNMP profile for Ubiquiti devices (AirFiber, AirMax, etc.) using Ubiquiti airOS MIB.
Discovers wireless-specific sensors:
- TX/RX Frequency
- TX/RX Throughput
- TX/RX Capacity
- Signal Strength
- Remote device information
"""
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Base
require Logger
# Ubiquiti enterprise MIB base: 1.3.6.1.4.1.41112
@ubnt_base "1.3.6.1.4.1.41112"
# UniFi specific OIDs (.1.6.3)
@unifi_system_model "#{@ubnt_base}.1.6.3.3.0"
@unifi_system_version "#{@ubnt_base}.1.6.3.6.0"
# Radio configuration OIDs (.1.3.1.1)
@radio_config_base "#{@ubnt_base}.1.3.1.1"
@radio_tx_freq "#{@radio_config_base}.5"
@radio_rx_freq "#{@radio_config_base}.6"
@radio_tx_power "#{@radio_config_base}.9"
# Radio statistics OIDs (.1.3.2.1)
@radio_stats_base "#{@ubnt_base}.1.3.2.1"
@radio_tx_throughput "#{@radio_stats_base}.3"
@radio_rx_throughput "#{@radio_stats_base}.4"
@radio_tx_capacity_bps "#{@radio_stats_base}.5"
@radio_rx_capacity_bps "#{@radio_stats_base}.6"
@radio_tx_signal "#{@radio_stats_base}.7"
@radio_rx_signal "#{@radio_stats_base}.8"
@radio_remote_tx_power "#{@radio_stats_base}.11"
@radio_remote_rx_power "#{@radio_stats_base}.14"
@doc """
Discovers system information, extending Base with UniFi-specific OIDs.
"""
def discover_system_info(client_opts) do
with {:ok, base_info} <- Base.discover_system_info(client_opts) do
# Try to get UniFi-specific model and version
unifi_info = discover_unifi_system_info(client_opts)
{:ok, Map.merge(base_info, unifi_info)}
end
end
defp discover_unifi_system_info(client_opts) do
case Client.get_multiple(client_opts, [@unifi_system_model, @unifi_system_version]) do
{:ok, [model, version]} when is_binary(model) or is_binary(version) ->
%{
unifi_model: model,
unifi_version: version
}
_ ->
%{}
end
end
@doc """
Discovers interfaces using base profile.
"""
def discover_interfaces(client_opts) do
Base.discover_interfaces(client_opts)
end
@doc """
Discovers Ubiquiti wireless sensors.
Includes frequency, throughput, capacity, and signal strength metrics.
"""
def discover_sensors(client_opts) do
# Walk the radio config base to see if this is a radio device
case Client.walk(client_opts, @radio_config_base) do
{:ok, config} when map_size(config) > 0 ->
# This is a Ubiquiti radio device, discover wireless sensors
Logger.debug("Discovered Ubiquiti radio device")
sensors = discover_radio_sensors(client_opts)
{:ok, sensors}
_ ->
# Not a radio device or no radio data, fall back to base
Logger.debug("No Ubiquiti radio sensors found, using base profile")
Base.discover_sensors(client_opts)
end
end
@doc """
Identifies Ubiquiti device from sysDescr and UniFi-specific OIDs.
"""
def identify_device(system_info) do
sys_descr = Map.get(system_info, :sys_descr, "")
unifi_model = Map.get(system_info, :unifi_model)
unifi_version = Map.get(system_info, :unifi_version)
# Prefer UniFi-specific model/version if available, otherwise detect from sysDescr
{model, firmware} =
if unifi_model || unifi_version do
{unifi_model || "UniFi Device", unifi_version}
else
{detect_model(sys_descr), extract_airos_version(sys_descr)}
end
Map.merge(system_info, %{
manufacturer: "Ubiquiti",
model: model,
firmware_version: firmware
})
end
@doc """
Collects raw debug data for troubleshooting.
Includes UniFi system OIDs, radio configuration and statistics.
"""
def collect_raw_debug_data(client_opts) do
# UniFi system OIDs
unifi_system_oids = %{
unifi_model: @unifi_system_model,
unifi_version: @unifi_system_version
}
# Radio configuration OIDs
radio_config_oids = %{
radio_tx_freq: @radio_tx_freq,
radio_rx_freq: @radio_rx_freq,
radio_tx_power: @radio_tx_power
}
# Radio statistics OIDs
radio_stats_oids = %{
radio_tx_throughput: @radio_tx_throughput,
radio_rx_throughput: @radio_rx_throughput,
radio_tx_capacity: @radio_tx_capacity_bps,
radio_rx_capacity: @radio_rx_capacity_bps,
radio_tx_signal: @radio_tx_signal,
radio_rx_signal: @radio_rx_signal,
radio_remote_tx_power: @radio_remote_tx_power,
radio_remote_rx_power: @radio_remote_rx_power
}
# Get UniFi system values
unifi_system_values =
Map.new(unifi_system_oids, fn {key, oid} ->
value =
case Client.get(client_opts, oid) do
{:ok, val} -> val
{:error, reason} -> "ERROR: #{inspect(reason)}"
end
{key, %{oid: oid, value: value}}
end)
# Walk radio config and stats tables
radio_config_table =
case Client.walk(client_opts, @radio_config_base) do
{:ok, results} -> results
{:error, _} -> %{}
end
radio_stats_table =
case Client.walk(client_opts, @radio_stats_base) do
{:ok, results} -> results
{:error, _} -> %{}
end
%{
timestamp: DateTime.utc_now(),
unifi_system_oids: unifi_system_values,
radio_config_oids: radio_config_oids,
radio_stats_oids: radio_stats_oids,
radio_config_table: radio_config_table,
radio_stats_table: radio_stats_table
}
end
# Private functions
defp discover_radio_sensors(client_opts) do
# Discover radio index (usually 1 for most devices)
case Client.walk(client_opts, @radio_config_base) do
{:ok, entries} when map_size(entries) > 0 ->
# Extract radio index from first entry
{first_oid, _} = Enum.at(entries, 0)
index = extract_radio_index(first_oid)
build_radio_sensors(client_opts, index)
_ ->
[]
end
end
defp build_radio_sensors(client_opts, index) do
Enum.reject(
[
build_sensor(client_opts, "#{@radio_tx_freq}.#{index}", "tx_frequency", "TX Frequency", "MHz", 1),
build_sensor(client_opts, "#{@radio_rx_freq}.#{index}", "rx_frequency", "RX Frequency", "MHz", 1),
build_sensor(client_opts, "#{@radio_tx_throughput}.#{index}", "tx_throughput", "TX Throughput", "kbps", 1),
build_sensor(client_opts, "#{@radio_rx_throughput}.#{index}", "rx_throughput", "RX Throughput", "kbps", 1),
build_sensor(client_opts, "#{@radio_tx_capacity_bps}.#{index}", "tx_capacity", "TX Capacity", "bps", 1),
build_sensor(client_opts, "#{@radio_rx_capacity_bps}.#{index}", "rx_capacity", "RX Capacity", "bps", 1),
build_sensor(client_opts, "#{@radio_tx_signal}.#{index}", "tx_signal", "TX Signal", "dBm", 1),
build_sensor(client_opts, "#{@radio_rx_signal}.#{index}", "rx_signal", "RX Signal", "dBm", 1),
build_sensor(client_opts, "#{@radio_remote_tx_power}.#{index}", "remote_tx_power", "Remote TX Power", "dBm", 1),
build_sensor(client_opts, "#{@radio_remote_rx_power}.#{index}", "remote_rx_power", "Remote RX Power", "dBm", 1),
build_sensor(client_opts, "#{@radio_tx_power}.#{index}", "tx_power", "TX Power", "dBm", 1)
],
&is_nil/1
)
# Frequency sensors
# Throughput sensors (kbps)
# Capacity sensors (bps)
# Signal strength sensors
# Remote power sensors
# TX Power from config
end
defp build_sensor(_client_opts, oid, type, descr, unit, divisor) do
%{
sensor_type: type,
sensor_index: oid,
sensor_oid: oid,
sensor_descr: descr,
sensor_unit: unit,
sensor_divisor: divisor
}
end
defp extract_radio_index(oid) do
# OID format: 1.3.6.1.4.1.41112.1.3.1.1.X.INDEX
parts = String.split(oid, ".")
List.last(parts)
end
defp extract_airos_version(sys_descr) do
# Extract version from something like "Linux 2.6.33 #1 Wed Mar 18 11:38:22 UTC 2020 armv5tejl"
case Regex.run(~r/Linux (\d+\.\d+\.\d+)/, sys_descr) do
[_, version] -> version
_ -> nil
end
end
defp detect_model(sys_descr) do
cond do
String.contains?(sys_descr, "AirFiber") -> "AirFiber"
String.contains?(sys_descr, "airMAX") -> "airMAX"
String.contains?(sys_descr, "airOS") -> "airOS Device"
true -> "Ubiquiti Device"
end
end
end

View file

@ -0,0 +1,10 @@
os: 3com
text: 3Com
over:
- { graph: device_bits, text: Traffic }
type: network
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.43.1.
- .1.3.6.1.4.1.43.10.

View file

@ -0,0 +1,11 @@
os: aaron
text: 'Aaron FM Rebroadcaster'
type: appliance
icon: inovonics
group: inovonics
over:
- { graph: device_dbm, text: RSSI }
discovery:
- sysObjectID:
- .1.3.6.1.4.1.42111.640
- .1.3.6.1.4.1.42111.650

View file

@ -0,0 +1,19 @@
os: abbups
text: ABB UPS
group: ups
type: power
icon: abb
mib_dir: abb
rfc1628_compat: true
over:
- { graph: device_voltage, text: Voltage }
- { graph: device_current, text: Current }
- { graph: device_frequency, text: Load }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.8072.3.2.10
snmpget:
oid: .1.3.6.1.4.1.53973.1.1.1.1.1.1.0
op: contains
value: ABB

View file

@ -0,0 +1,26 @@
os: acano
group: cisco
text: 'Acano OS'
type: collaboration
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
icon: cisco
discovery:
- { sysObjectID: .1.3.6.1.4.1.8072.3.2.10, sysDescr: Acano }
poller_modules:
cisco-cef: true
cisco-mac-accounting: true
cisco-remote-access-monitor: true
slas: true
cisco-ipsec-flow-monitor: true
cipsec-tunnels: true
cisco-otv: true
discovery_modules:
cisco-cef: true
slas: true
cisco-mac-accounting: true
cisco-otv: true
cisco-pw: true
vrf: true
cisco-vrf-lite: true

View file

@ -0,0 +1,13 @@
os: acos
text: 'A10 Networks'
type: network
icon: a10
group: a10
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.22610.1.3

View file

@ -0,0 +1,35 @@
os: acs
group: cisco
text: 'Cisco ACS'
ifname: true
type: server
icon: cisco
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
poller_modules:
cisco-ace-serverfarms: true
cisco-ace-loadbalancer: true
cisco-cef: true
cisco-mac-accounting: true
cisco-remote-access-monitor: true
slas: true
cisco-ipsec-flow-monitor: true
cipsec-tunnels: true
cisco-otv: true
discovery_modules:
cisco-cef: true
slas: true
cisco-mac-accounting: true
cisco-otv: true
cisco-pw: true
vrf: true
cisco-vrf-lite: true
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.9.1.1117
-
sysDescr:
- 'Cisco Secure Access Control System'

View file

@ -0,0 +1,35 @@
os: acsw
group: cisco
text: 'Cisco ACE'
ifname: true
type: loadbalancer
icon: cisco
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
poller_modules:
cisco-ace-serverfarms: true
cisco-ace-loadbalancer: true
cisco-cef: true
cisco-mac-accounting: true
cisco-remote-access-monitor: true
slas: true
cisco-ipsec-flow-monitor: true
cipsec-tunnels: true
cisco-otv: true
discovery_modules:
cisco-cef: true
slas: true
cisco-mac-accounting: true
cisco-otv: true
cisco-pw: true
vrf: true
cisco-vrf-lite: true
discovery:
-
sysObjectID: .1.3.6.1.4.1.9.1.1291
-
sysDescr:
- 'Application Control Engine'
- 'Cisco Application Control Software'

View file

@ -0,0 +1,16 @@
os: adtran-aos
text: 'Adtran AOS'
type: network
icon: adtran
mib_dir: adtran
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID: .1.3.6.1.4.1.664
-
sysDescr: 'NetVanta'
good_if:
- EtsecBasedVirtualEthernet1

View file

@ -0,0 +1,11 @@
os: adva-alm
text: 'Adtran ALM'
type: network
icon: adtran
mib_dir: adva
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.2544.1.14

View file

@ -0,0 +1,12 @@
os: adva-aos
text: 'ADVA AOS'
type: network
icon: adva
group: ADVA
over:
- { graph: device_dbm, text: dbm }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.2544.1.20.2.
mib_dir: adva

View file

@ -0,0 +1,19 @@
os: adva-fsp150cp
text: 'ADVA FSP150CP'
type: network
icon: adva
group: ADVA
ifname: true
mib_dir: adva
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.2544.1.10.1.1
discovery_modules:
cisco-vrf-lite: false
storage: false
hr-device: false
arp-table: false
ucd-diskio: false

View file

@ -0,0 +1,14 @@
os: adva-osa
text: 'ADVA OSA'
type: timing
icon: adva
group: ADVA
ifname: true
over:
- { graph: device_bits, text: Traffic }
- { graph: device_temperature, text: Temperature }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.5551.3.
mib_dir: adva

View file

@ -0,0 +1,15 @@
os: adva_fsp150
text: 'ADVA FSP150CC'
type: network
icon: adva
group: ADVA
ifname: true
mib_dir: adva
over:
- { graph: device_bits, text: Traffic }
- { graph: device_temperature, text: Temperature }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.2544.1.12.1.1.
sysDescr_except: ['FSP150CM-HUB'] #Exclude 150CM nemihubshelf (different OS, and failing to respond to most OID/crashing)

View file

@ -0,0 +1,13 @@
os: adva_fsp3kr7
text: 'ADVA FSP3000 R7'
type: network
icon: adva
group: ADVA
over:
- { graph: device_dbm, text: dbm }
- { graph: device_temperature, text: Temperature }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.2544.1.11.1.1
mib_dir: adva

View file

@ -0,0 +1,15 @@
os: adva_xg300
text: 'ADVA OptiSwitch'
type: network
icon: adva
group: ADVA
ifname: true
over:
- { graph: device_bits, text: Traffic }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.629.22
mib_dir: adva

View file

@ -0,0 +1,12 @@
os: advantech
text: 'Advantech'
type: network
icon: advantech
group: advantech
mib_dir: advantech
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.10297.202

View file

@ -0,0 +1,16 @@
os: aen
text: 'Accedian AEN'
type: network
icon: accedian
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
mib_dir: accedian
discovery:
-
sysDescr_regex:
- '/^AMN-/'
- '/^AEN-/'
- '/^AMO-/'

View file

@ -0,0 +1,9 @@
os: airconsole
text: 'Airconsole Server'
type: network
icon: airconsole
group: airconsole
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.14358.1

View file

@ -0,0 +1,18 @@
os: airos-af-ltu
text: 'Ubiquiti AirFiber LTU'
type: wireless
icon: ubiquiti
snmp_bulk: false
mib_dir: ubnt
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_wireless_rate, text: 'Wireless Rate' }
- { graph: device_processor, text: 'CPU Usage' }
discovery:
-
sysObjectID: .1.3.6.1.4.1.8072.3.2.10
sysDescr: Linux
snmpget:
oid: UBNT-AFLTU-MIB::afLTUFirmwareVersion.0
op: '!='
value: false

View file

@ -0,0 +1,18 @@
os: airos-af
text: 'Ubiquiti AirFiber'
type: wireless
icon: ubiquiti
snmp_bulk: false
mib_dir: ubnt
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_wireless_rate, text: 'Wireless Rate' }
- { graph: device_processor, text: 'CPU Usage' }
discovery:
-
sysObjectID: .1.3.6.1.4.1.10002.1
sysDescr: Linux
snmpget:
oid: UBNT-AirFIBER-MIB::fwVersion.1
op: '!='
value: false

View file

@ -0,0 +1,20 @@
os: airos-af60
text: 'Ubiquiti AirFiber 60'
type: wireless
icon: ubiquiti
snmp_bulk: false
mib_dir: ubnt
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_wireless_rate, text: 'Wireless Rate' }
- { graph: device_processor, text: 'CPU Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.10002.1
- .1.3.6.1.4.1.41112
sysDescr: Linux
snmpget:
oid: UI-AF60-MIB::af60FirmwareVersion.1
op: '!='
value: false

View file

@ -0,0 +1,20 @@
os: airos
text: 'Ubiquiti AirOS'
type: wireless
icon: ubiquiti
snmp_bulk: false
mib_dir: ubnt
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_wireless_clients, text: 'Connected Clients' }
- { graph: device_wireless_ccq, text: 'Connection Quality' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.10002.1
- .1.3.6.1.4.1.41112.1.4
sysDescr: Linux
snmpget:
oid: UI-AF60-MIB::af60Role.1
op: '='
value: false

View file

@ -0,0 +1,13 @@
os: airport
type: wireless
text: 'Apple AirPort'
icon: apple
over:
- { graph: device_bits, text: Traffic }
- { graph: device_wireless_clients, text: Clients }
discovery:
-
sysDescr:
- Apple AirPort
- Apple Base Station
- Base Station V3.84

View file

@ -0,0 +1,27 @@
os: aix
text: 'AIX'
type: server
icon: aix
group: unix
mib_dir: ibm
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
poller_modules:
bgp-peers: false
ospf: false
stp: false
discovery_modules:
applications: false
bgp-peers: false
stp: false
wireless: false
processor_stacked: true
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.8072.3.2.15
- .1.3.6.1.4.1.2.3.1.
ignore_mount_type:
- procfs

View file

@ -0,0 +1,10 @@
os: akcp
text: 'AKCP SensorProbe'
type: environment
mib_dir: akcp
over:
- { graph: device_temperature, text: temperature }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.3854

View file

@ -0,0 +1,11 @@
os: alcoma-almp
text: Alcoma
type: wireless
over:
- { graph: device_wireless_rssi, text: 'Wireless rssi' }
- { graph: device_wireless_snr, text: 'Wireles snr' }
- { graph: device_wireless_power, text: 'Wireless power' }
discovery:
-
sysObjectID: .1.3.6.1.4.1.12140.2
mib_dir: alcoma

View file

@ -0,0 +1,10 @@
os: alfo80hd
text: 'SIAE Alfoplus 80HD'
type: wireless
icon: siae
over:
- { graph: device_bits, text: Traffic }
- { graph: device_wireless_rssi, text: RSSI }
- { graph: device_wireless_power, text: POWER }
discovery:
- { sysObjectID: .1.3.6.1.4.1.3373.1103, sysDescr: 'ALFOplus80HD - SIAE Microelettronica' }

View file

@ -0,0 +1,17 @@
os: algcom-dc-ups
text: 'ALGCOM DC UPS'
type: power
icon: algcom
over:
- { graph: device_temperature, text: Temperature }
- { graph: device_humidity, text: Humidity }
- { graph: device_voltage, text: Voltage }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.49136
sysObjectID_except:
- .1.3.6.1.4.1.49136.100
mib_dir: algcom

View file

@ -0,0 +1,13 @@
os: algcom-sm
text: 'ALGCOM SM'
type: environment
icon: algcom
mib_dir: algcom
over:
- { graph: device_temperature, text: Temperature }
- { graph: device_humidity, text: Humidity }
- { graph: device_voltage, text: Voltage }
discovery:
- sysObjectID:
- .1.3.6.1.4.1.49136.100

View file

@ -0,0 +1,13 @@
os: allied-tq
text: 'Allied Telesis Wireless (TQ)'
type: wireless
icon: alliedtelesis
group: allied
ifname: true
over:
- { graph: device_bits, text: Traffic }
discovery:
-
sysObjectID: .1.3.6.1.4.1.207.1.13.
sysDescr_except: 'AW+'
mib_dir: awplus

View file

@ -0,0 +1,21 @@
os: allied
text: Alliedware
type: network
icon: alliedtelesis
group: allied
ifname: true
over:
- { graph: device_bits, text: Traffic }
discovery:
-
sysObjectID: .1.3.6.1.4.1.207.
sysObjectID_except:
- .1.3.6.1.4.1.207.1.4.125 #exclude the following ATI 8000S/8000GS which identify as "radlan" OS.
- .1.3.6.1.4.1.207.1.4.126
- .1.3.6.1.4.1.207.1.4.127
- .1.3.6.1.4.1.207.1.4.128
- .1.3.6.1.4.1.207.1.4.143
- .1.3.6.1.4.1.207.1.4.144
- .1.3.6.1.4.1.207.1.4.145
sysDescr_except: ['AW+', 'Software (AlliedWare Plus)']
mib_dir: allied

View file

@ -0,0 +1,11 @@
os: allworx_voip
text: 'Allworx VoIP'
type: appliance
icon: allworx
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.38516.2.

View file

@ -0,0 +1,15 @@
os: aloha
text: HAProxy ALOHA
over:
- { graph: device_bits, text: Traffic }
type: loadbalancer
group: linux
mib_dir: haproxy
icon: haproxy
discovery:
-
sysObjectID: .1.3.6.1.4.1.8072.3.2.10
snmpget:
oid: EXCELIANCE-MIB::alProductName
op: '='
value: 'aloha'

View file

@ -0,0 +1,13 @@
os: alpineoe-tdcmedfa
text: 'Alpine OptoElectronics TDCM-EDFA platform'
type: appliance
icon: alpineoe
mib_dir: alpineoe
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.52326

View file

@ -0,0 +1,12 @@
os: altalabs-ap
text: Alta Labs AP
icon: altalabs
type: wireless
over:
- { graph: device_bits, text: Traffic }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.8072.3.2.10
sysDescr_regex:
- '/^Alta AP/'

View file

@ -0,0 +1,12 @@
os: altalabs
text: Alta Labs
type: network
over:
- { graph: device_bits, text: Traffic }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.8072.3.2.10
sysDescr_regex:
- '/^Alta Route/'
- '/^Alta S/'

View file

@ -0,0 +1,14 @@
os: alteonos
group: radware
text: AlteonOS
type: network
icon: radware
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.1872.1.13.3.5
- .1.3.6.1.4.1.1872.2.

View file

@ -0,0 +1,13 @@
os: anue
text: 'Anue'
type: network
icon: ixia
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'Processor Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
mib_dir: ixia
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.32620.

View file

@ -0,0 +1,18 @@
os: anyos
text: AnyOS
type: network
group: zyxel
icon: zyxel
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.4413
sysDescr:
- 'AATO'
- 'AAJZ'
poller_modules:
xdsl: true
discovery_modules:
xdsl: true

View file

@ -0,0 +1,51 @@
os: aos-emu2
text: 'APC Environmental Monitoring Unit'
type: environment
icon: apc
over:
- { graph: device_temperature, text: Temperature }
- { graph: device_humidity, text: Humidity }
mib_dir: apc
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.318.1.3.8.2
- .1.3.6.1.4.1.318.1.3.8.3
discovery_modules:
ports: false
ports-stack: false
entity-physical: false
processors: false
mempools: false
cisco-vrf-lite: false
storage: false
hr-device: false
discovery-protocols: false
bgp-peers: false
vlans: false
ucd-diskio: false
services: false
stp: false
ntp: false
wireless: false
fdb-table: false
poller_modules:
ipmi: false
entity-physical: false
processors: false
mempools: false
storage: false
netstats: false
hr-mib: false
ucd-mib: false
ipSystemStats: false
ports: false
bgp-peers: false
ospf: false
ucd-diskio: false
services: false
stp: false
ntp: false
wireless: false
fdb-table: false
applications: false

View file

@ -0,0 +1,16 @@
os: aos
group: nokia
text: 'Alcatel-Lucent OS'
type: network
ifXmcbc: true
ifname: true
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_temperature, text: 'Temperature' }
icon: alcatellucent
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.6486.800.1.1.2.2.4. #OmniStack LS
mib_dir: radlan

View file

@ -0,0 +1,16 @@
os: aos6
group: nokia
text: 'Alcatel-Lucent OS'
type: network
ifXmcbc: true
ifname: true
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_temperature, text: 'Temperature' }
icon: alcatellucent
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.6486.800.1.1.2.1. #aos6
mib_dir: nokia

View file

@ -0,0 +1,20 @@
os: aos7
group: nokia
text: 'Alcatel-Lucent OS'
type: network
ifXmcbc: true
ifname: true
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_temperature, text: 'Temperature' }
icon: alcatellucent
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.6486.801. #aos7
mib_dir: nokia/aos7
discovery_modules:
bgp-peers: true
poller_modules:
bgp-peers: true

View file

@ -0,0 +1,12 @@
os: apc-cpdu
text: 'APC cPDU'
type: power
icon: apc
mib_dir: apc-cpdu
over:
- { graph: device_current, text: Current }
- { graph: device_power, text: Power }
discovery:
- sysObjectID:
- .1.3.6.1.4.1.318.1.1.32.1

View file

@ -0,0 +1,18 @@
os: apc-epdu
text: 'APC ePDU'
type: power
icon: apc
mib_dir: apc
over:
- { graph: device_current, text: Current }
- { graph: device_voltage, text: Voltage }
discovery:
- sysObjectID:
- .1.3.6.1.4.1.318.1.3.4.9
- sysObjectID:
- .1.3.6.1.4.1.0
snmpget:
oid: .1.3.6.1.6.3.10.2.1.1.0
op: '='
value: APC

View file

@ -0,0 +1,20 @@
os: apc-mgeups
text: 'APC MGE UPS'
group: ups
type: power
icon: apc
rfc1628_compat: true
mib_dir: eaton
over:
- { graph: device_current, text: Current }
- { graph: device_voltage, text: Voltage }
- { graph: device_load, text: Load }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.705.1
sysDescr_regex:
- '/^Galaxy/'
- '/^GALAXY/'
- '/^MGE Galaxy/'
# Upsilon too (no data)

View file

@ -0,0 +1,12 @@
os: apc-netbotz
text: 'NetBotz Environment Sensor'
type: environment
icon: apc
over:
- { graph: device_temperature, text: Temperature }
- { graph: device_humidity, text: Humidity }
mib_dir: netbotz
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.52674.500

View file

@ -0,0 +1,25 @@
os: apc
text: 'APC Management Module'
type: power
over:
- { graph: device_current, text: Current }
- { graph: device_voltage, text: Voltage }
- { graph: device_runtime, text: Runtime }
discovery:
-
sysDescr:
- APC Web/SNMP Management Card
- APC Switched Rack PDU
- APC MasterSwitch PDU
- APC Metered Rack PDU
- APC Embedded PowerNet
- APC Environmental Manager
- APC InRow DX
- APC Uniflair SP
- APC InfraStruXure ATS
- APC SNMP Agent
-
sysObjectID:
- .1.3.6.1.4.1.318.1
sysObjectID_except:
- .1.3.6.1.4.1.318.1.3.4.9

View file

@ -0,0 +1,10 @@
os: apex-lynx
text: 'Apex Lynx'
type: wireless
icon: trango
mib_dir: trango
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysDescr_regex: '/^Apex Lynx/'

View file

@ -0,0 +1,10 @@
os: apex-plus
text: 'Apex Plus'
type: wireless
icon: trango
mib_dir: trango
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysDescr_regex: '/^ApexPlus.*/'

View file

@ -0,0 +1,29 @@
os: apic
group: cisco
text: 'Cisco APIC'
type: network
icon: cisco
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_temperature, text: Temperature }
poller_modules:
cisco-cef: true
cisco-mac-accounting: true
cisco-remote-access-monitor: true
slas: true
cisco-ipsec-flow-monitor: true
cipsec-tunnels: true
cisco-otv: true
discovery_modules:
cisco-cef: true
slas: true
cisco-mac-accounting: true
cisco-otv: true
cisco-pw: true
vrf: true
cisco-vrf-lite: true
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.9.1.2238

View file

@ -0,0 +1,10 @@
os: applicationsware
text: 'Vanguard ApplicationsWare'
type: network
icon: vanguard
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.449.2.1.

View file

@ -0,0 +1,14 @@
os: aprisa
text: 'Aprisa'
type: wireless
icon: 4rf
group: 4rf
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.14817.7.3
mib_dir: 4rf

View file

@ -0,0 +1,14 @@
os: apsoluteos
text: ApsoluteOS
type: network
icon: radware
group: radware
mib_dir: radware
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.89.1.1.62

View file

@ -0,0 +1,21 @@
os: arbos
text: ArbOS
type: network
icon: arbor
group: arbor
ifXmcbc: true
ifname: true
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.9694.1
-
sysObjectID:
- .1.3.6.1.4.1.8072.3.2.10
sysDescr:
- arbux
mib_dir: arbornet

View file

@ -0,0 +1,10 @@
os: ardmore-encoder
text: 'AvediaStream Encoder'
type: appliance
icon: exterity
over:
- { graph: device_bits, text: Traffic }
discovery:
-
sysDescr_regex:
- '/Ardmore-Encoder_VX/'

View file

@ -0,0 +1,11 @@
os: areca
text: 'Areca RAID Subsystem'
type: appliance
over:
- { graph: '', text: '' }
discovery:
-
sysDescr:
- Raid Subsystem V
sysObjectID_except:
- .1.3.6.1.4.1.14752.1.3 #Proware/EuroStor

View file

@ -0,0 +1,22 @@
os: arista-mos
text: 'Arista MOS'
type: network
icon: arista
group: arista
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.43191.1.2.5
- .1.3.6.1.4.1.43191.1.2.7
- .1.3.6.1.4.1.43191.1.2.8
- .1.3.6.1.4.1.43191.1.6.6
- .1.3.6.1.4.1.43191.1.6.7
- .1.3.6.1.4.1.43191.1.6.9
- .1.3.6.1.4.1.43191.1.6.10
- .1.3.6.1.4.1.43191.1.7.5
- .1.3.6.1.4.1.43191.1.7.6
- .1.3.6.1.4.1.43191.1.7.7

View file

@ -0,0 +1,23 @@
os: arista_eos
text: 'Arista EOS'
type: network
icon: arista
group: arista
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID: .1.3.6.1.4.1.30065.1
poller_modules:
wireless: false
ipmi: false
applications: false
services: false
ntp: false
discovery_modules:
cisco-vrf-lite: false
services: false
ntp: false
wireless: false

View file

@ -0,0 +1,9 @@
os: arrayos
text: 'Array Networks'
type: loadbalancer
icon: arraynetworks
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.7564
mib_dir: arraynetworks

View file

@ -0,0 +1,11 @@
os: arris-apex
text: 'Arris Apex'
type: network
icon: arris
mib_dir: arris
over:
- { graph: '', text: '' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.1166.1.31

View file

@ -0,0 +1,11 @@
os: arris-c3
text: 'Arris CMTS'
type: network
icon: arris
mib_dir: arris
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.4115.1.4.3

View file

@ -0,0 +1,13 @@
os: arris-c4
text: 'Arris CMTS'
type: network
icon: arris
mib_dir: arris
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.4998.2.1
- .1.3.6.1.4.1.4998.2.2
- .1.3.6.1.4.1.4115.1.9.1

View file

@ -0,0 +1,11 @@
os: arris-cm
text: 'ARRIS DOCSIS'
type: network
icon: arris
mib_dir: arris
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.4115
sysDescr:
- ARRIS DOCSIS

View file

@ -0,0 +1,12 @@
os: arris-d5
text: 'Arris D5 Universal EdgeQAM'
type: network
icon: arris
group: arris
mib_dir: arris/d5
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.4115.1.8

View file

@ -0,0 +1,30 @@
os: arris-dsr4410md
text: 'Arris Satellite Receiver'
type: network
icon: arris
mib_dir: arris
over:
- { graph: device_wireless_rssi, text: Sat Signal Level }
- { graph: device_wireless_quality, text: Sat Signal Quality }
discovery:
-
sysObjectID: .1.3.6.1.4.1.1166.1.621
discovery_modules:
ports: false
mempools: false
processors: false
fdb-table: false
stp: false
ntp: false
bgp-peers: false
arp-table: false
vlans: false
ucd-diskio: false
cisco-vrf-lite: false
entity-physical: false
ports-stack: false
ipv6-addresses: false
services: false
discovery-protocols: false
hr-device: false
storage: false

View file

@ -0,0 +1,15 @@
os: aruba-instant
text: 'Aruba Instant'
type: wireless
icon: aruba
mib_dir: arubaos
over:
- { graph: device_wireless_ap-count, text: 'AP Count' }
- { graph: device_wireless_clients, text: 'Client Count' }
discovery:
-
sysObjectID: .1.3.6.1.4.1.14823.1.2. #Aruba apProducts (Aruba Instant)
sysDescr:
- 'ArubaOS'
- 'AOS-10'
- 'AOS-8'

View file

@ -0,0 +1,13 @@
os: arubaos-cx
text: 'ArubaOS-CX'
type: network
mib_dir: arubaos-cx
icon: aruba
over:
- { graph: device_bits, text: Traffic }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.47196.4.1.1.1 #ArubaOS-CX

View file

@ -0,0 +1,24 @@
os: arubaos
text: ArubaOS
type: wireless
icon: aruba
over:
- { graph: device_wireless_ap-count, text: 'AP Count' }
- { graph: device_wireless_clients, text: 'Client Count' }
- { graph: device_arubacontroller_numaps, text: 'Number of APs (Legacy)' }
- { graph: device_arubacontroller_numclients, text: 'Number of Clients (Legacy)' }
poller_modules:
aruba-controller: true
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.14823. #ArubaOS
- .1.3.6.1.4.1.6486.800.1.1.2.2.2. #AOS-W
sysObjectID_except:
- .1.3.6.1.4.1.14823.1.2 #Aruba apProducts (Aruba Instant)
- .1.3.6.1.4.1.14823.1.6 #ClearPass
-
sysDescr:
- ArubaOS
sysObjectID_except:
- .1.3.6.1.4.1.14823.1.2 #Aruba apProducts (Aruba Instant)

View file

@ -0,0 +1,35 @@
os: asa
group: cisco
text: 'Cisco ASA'
ifname: true
type: firewall
icon: cisco
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
poller_modules:
applications: false
bgp-peers: false
cipsec-tunnels: true
cisco-ipsec-flow-monitor: true
cisco-remote-access-monitor: true
hr-mib: false
ipSystemStats: false
ipmi: false
ntp: false
ospf: false
stp: false
ucd-diskio: false
ucd-mib: false
discovery_modules:
arp-table: false
bgp-peers: false
cisco-vrf-lite: false
ntp: false
stp: false
ucd-diskio: false
vlans: false
discovery:
-
sysDescr_regex:
- '/^Cisco (Adaptive|Industrial) Security Appliance/'

View file

@ -0,0 +1,29 @@
os: ascom
text: 'Ascom'
type: network
icon: ascom
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'Processor Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
mib_dir: ascom
poller_modules:
storage: false
applications: false
bgp-peers: false
ipmi: false
ospf: false
ucd-diskio: false
ucd-mib: false
discovery_modules:
fdb-table: false
storage: false
bgp-peers: false
ntp: false
stp: false
ucd-diskio: false
vlans: false
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.27614

View file

@ -0,0 +1,15 @@
os: asuswrt-merlin
text: 'AsusWRT Merlin'
type: network
icon: asuswrt-merlin
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
-
sysObjectID: .1.3.6.1.4.1.8072.3.2.10
snmpget:
oid: .1.3.6.1.4.1.2021.7890.1.101.1
op: starts
value: ASUSWRT-Merlin

View file

@ -0,0 +1,26 @@
os: asyncos
text: 'Cisco AsyncOS'
type: proxy
icon: cisco
over:
- { graph: device_bits, text: Traffic }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
- { graph: device_asyncos_conns, text: 'Current Connections' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.15497.1
mib_dir: cisco
poller_modules:
ipmi: false
bgp-peers: false
ospf: false
stp: false
ntp: false
discovery_modules:
ports-stack: false
cisco-vrf-lite: false
bgp-peers: false
stp: false
ntp: false

View file

@ -0,0 +1,15 @@
os: atenpdu
text: Aten PDU
type: power
icon: aten
mib_dir: aten
snmp_bulk: false
over:
- { graph: device_current, text: Current }
- { graph: device_voltage, text: Voltage }
- { graph: device_power, text: Power }
discovery:
- sysObjectID:
- .1.3.6.1.4.1.21317.
- sysDescr:
- 'Aten'

View file

@ -0,0 +1,14 @@
os: ats
text: Automatic Transfer Switch
type: power
icon: ats
mib_dir: ats
snmp_bulk: false
over:
- { graph: device_current, text: Current }
- { graph: device_voltage, text: Voltage }
- { graph: device_load, text: Load }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.37662.1.2.2.1

View file

@ -0,0 +1,12 @@
os: audiocodes
group: audiocodes
text: 'Audiocodes'
type: network
icon: audiocodes
over:
- { graph: device_bits, text: 'Device Traffic' }
mib_dir: audiocodes
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.5003.8.1.1

View file

@ -0,0 +1,9 @@
os: avaya-ipo
text: 'IP Office Firmware'
type: network
icon: avaya
mib_dir: nortel
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.6889.

View file

@ -0,0 +1,41 @@
os: aviat-wtm
text: 'Aviat WTM'
type: wireless
icon: aviat
over:
- { graph: device_bits, text: 'Traffic' }
- { graph: device_wireless_rssi, text: 'RSSI' }
- { graph: device_wireless_power, text: 'Power' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.2509.11.1.1.7
- .1.3.6.1.4.1.2509.11.1.1.8
- .1.3.6.1.4.1.2509.11.1.1.9
- .1.3.6.1.4.1.2509.11.1.1.10
- .1.3.6.1.4.1.2509.11.1.1.12
- .1.3.6.1.4.1.2509.11.1.1.13
- .1.3.6.1.4.1.2509.11.1.1.14
- .1.3.6.1.4.1.2509.11.1.1.15
discovery_modules:
ucd-dsktable: false
ucd-diskio: false
arp-table: false
fdb-table: false
vlans: false
stp: false
processors: false
mempools: false
storage: false
ipv4-addresses: false
ipv6-addresses: false
ports-stack: false
hr-device: false
bgp-peers: false
poller_modules:
netstats: false
route: false
hr-mib: false
ucd-mib: false
ospf: false
ipSystemStats: false

View file

@ -0,0 +1,10 @@
os: avocent-mp
text: Vertiv Avocent MergePoint Unity
type: appliance
icon: vertiv
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.10418.18.1

View file

@ -0,0 +1,13 @@
os: avocent
text: Avocent
type: management
icon: avocent
rfc1628_compat: true
discovery:
-
sysDescr_regex:
- '/^Avocent/'
- '/^AlterPath/'
-
sysObjectID:
- .1.3.6.1.4.1.10418.16

View file

@ -0,0 +1,10 @@
os: avr-hd
text: 'AvediaPlayer Receivers'
type: appliance
icon: exterity
over:
- { graph: device_bits, text: Traffic }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.28194.

View file

@ -0,0 +1,11 @@
os: avtech
text: 'Avtech Environment Sensor'
type: environment
icon: avtech
over:
- { graph: device_temperature, text: Temperature }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.20916.1.
- .1.3.6.1.4.1.20916 # RoomAlert 32S

View file

@ -0,0 +1,13 @@
os: awplus
text: 'Alliedware Plus'
type: network
icon: alliedtelesis
group: alliedtelesis
ifname: true
over:
- { graph: device_bits, text: Traffic }
discovery:
-
sysObjectID: .1.3.6.1.4.1.207.
sysDescr: ['AW+', 'Software (AlliedWare Plus)']
mib_dir: awplus

View file

@ -0,0 +1,7 @@
os: axisaudio
text: 'AXIS Audio Appliances'
type: appliance
icon: axis
discovery:
-
sysDescr_regex: '/AXIS .* (Network IO Audio Module|Network Speaker)/'

View file

@ -0,0 +1,12 @@
os: axiscam
text: 'AXIS Network Camera'
type: appliance
icon: axis
mib_dir: axis
discovery:
-
sysDescr_regex: '/AXIS .* ((Network|Bullet|PTZ|Dome|Panoramic|Box) Camera|Security Radar|Video (Server|Decoder)|Network Video Encoder)/'
over:
- { graph: device_bits, text: Traffic }
- { graph: device_temperature, text: 'Device Temperature' }
- { graph: device_state, text: 'Device State' }

View file

@ -0,0 +1,7 @@
os: axisdocserver
text: 'AXIS Network Document Server'
type: network
icon: axis
discovery:
-
sysDescr_regex: '/^AXIS .* Network Document Server/'

View file

@ -0,0 +1,13 @@
os: axos
text: Calix AXOS
type: network
ifname: true
empty_ifdescr: true
mib_dir: calix
icon: calix
over:
- { graph: device_bits, text: 'Device Traffic' }
discovery:
-
sysObjectID:
- .1.3.6.1.4.1.6321.1.2.4

View file

@ -0,0 +1,12 @@
os: baicells-od04
text: 'Baicells CPE'
type: wireless
icon: baicells
mib_dir: baicells/cpe
over:
- { graph: device_bits, text: 'Device Traffic' }
- { graph: device_processor, text: 'CPU Usage' }
- { graph: device_mempool, text: 'Memory Usage' }
discovery:
- sysDescr:
- 'BaiCPE'

Some files were not shown because too many files have changed in this diff Show more