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 \ RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \ && 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/* && rm -rf /var/lib/apt/lists/*
# Set the locale # Set the locale

View file

@ -2,27 +2,30 @@ defmodule Mix.Tasks.ImportProfiles do
@shortdoc "Import device profiles from LibreNMS YAML definitions" @shortdoc "Import device profiles from LibreNMS YAML definitions"
@moduledoc """ @moduledoc """
Imports device profiles from LibreNMS YAML definition files. Imports device profiles from YAML definition files.
## Usage ## Usage
# Import all profile definitions # Import all profiles from priv/profiles (default)
mix import_profiles --source-path ~/librenms/includes/definitions mix import_profiles
# Import from a custom path
mix import_profiles --source-path ~/librenms/resources/definitions
# Import specific profiles # Import specific profiles
mix import_profiles --source-path ~/librenms/includes/definitions --profiles mikrotik,cisco mix import_profiles --profiles mikrotik,cisco,epmp
## Options ## 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) * `--profiles` - Comma-separated list of profiles to import (default: all)
## Notes ## 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 * Creates database profiles with MIB symbolic names
* Updates existing profiles if they already exist * 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 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]) profiles_filter = parse_profiles(opts[:profiles])
if !File.exists?(source_path) do if !File.exists?(source_path) do
@ -63,6 +66,10 @@ defmodule Mix.Tasks.ImportProfiles do
end end
end end
defp default_source_path do
Path.join(:code.priv_dir(:towerops), "profiles")
end
defp parse_profiles(nil), do: :all defp parse_profiles(nil), do: :all
defp parse_profiles(""), 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"]) discovery_file = Path.join([source_path, "os_discovery", "#{profile_name}.yaml"])
with {:ok, detection_data} <- read_yaml_file(detection_file), 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), {:ok, _profile} <- create_or_update_profile(detection_data, discovery_data, profile_name),
# Reload profile to get correct binary ID # Reload profile to get correct binary ID
profile when not is_nil(profile) <- Profiles.get_profile(profile_name), profile when not is_nil(profile) <- Profiles.get_profile(profile_name),
@ -132,6 +140,13 @@ defmodule Mix.Tasks.ImportProfiles do
end end
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 defp read_yaml_file(file_path) do
with {:ok, yaml_content} <- File.read(file_path), with {:ok, yaml_content} <- File.read(file_path),
{:ok, yaml_data} <- YamlElixir.read_from_string(yaml_content) do {: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 defp extract_device_oids(yaml_data) do
# Extract from modules.os section in os_discovery YAML # Extract from modules.os section in os_discovery YAML
device_oids = []
# Get the modules.os section # Get the modules.os section
os_module = get_in(yaml_data, ["modules", "os"]) || %{} os_module = get_in(yaml_data, ["modules", "os"]) || %{}
# Serial number # Map of YAML field names to our field names
device_oids = field_mappings = [
case Map.get(os_module, "serial") do {"serial", "serial_number"},
nil -> device_oids {"version", "firmware_version"},
mib_name -> [{"serial_number", mib_name} | device_oids] {"hardware", "hardware"},
end {"lat", "latitude"},
{"long", "longitude"},
{"features", "features"},
{"sysName", "sys_name_oid"}
]
# Firmware version Enum.flat_map(field_mappings, fn {yaml_field, db_field} ->
device_oids = case Map.get(os_module, yaml_field) do
case Map.get(os_module, "version") do nil -> []
nil -> device_oids # Handle list of OIDs (fallback order)
mib_name -> [{"firmware_version", mib_name} | device_oids] 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 end
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 defp import_sensor_oids(profile, yaml_data) do

View file

@ -26,9 +26,9 @@ defmodule Towerops.Application do
ToweropsWeb.Telemetry, ToweropsWeb.Telemetry,
Towerops.Repo, Towerops.Repo,
{DNSCluster, query: Application.get_env(:towerops, :dns_cluster_query) || :ignore}, {DNSCluster, query: Application.get_env(:towerops, :dns_cluster_query) || :ignore},
pubsub_spec() pubsub_spec(),
# Start a worker by calling: Towerops.Worker.start_link(arg) # Load YAML profiles into ETS cache
# {Towerops.Worker, arg}, Towerops.Profiles.YamlProfiles
] ++ ] ++
background_workers() ++ background_workers() ++
exq_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
alias Towerops.Devices.Device, as: DeviceSchema alias Towerops.Devices.Device, as: DeviceSchema
alias Towerops.Profiles alias Towerops.Profiles.YamlProfiles
alias Towerops.Profiles.DeviceProfile
alias Towerops.Repo alias Towerops.Repo
alias Towerops.Snmp.Client alias Towerops.Snmp.Client
alias Towerops.Snmp.DeferredDiscovery alias Towerops.Snmp.DeferredDiscovery
@ -25,11 +24,7 @@ defmodule Towerops.Snmp.Discovery do
alias Towerops.Snmp.Interface alias Towerops.Snmp.Interface
alias Towerops.Snmp.NeighborDiscovery alias Towerops.Snmp.NeighborDiscovery
alias Towerops.Snmp.Profiles.Base alias Towerops.Snmp.Profiles.Base
alias Towerops.Snmp.Profiles.Cisco
alias Towerops.Snmp.Profiles.Dynamic 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.Sensor
alias Towerops.Snmp.Vlan alias Towerops.Snmp.Vlan
@ -79,7 +74,7 @@ defmodule Towerops.Snmp.Discovery do
optional(atom()) => term() optional(atom()) => term()
} }
@type profile :: module() | {:dynamic, DeviceProfile.t()} @type profile :: module() | {:yaml, map()}
@type discovery_summary :: %{ @type discovery_summary :: %{
success: non_neg_integer(), success: non_neg_integer(),
@ -262,70 +257,39 @@ defmodule Towerops.Snmp.Discovery do
@doc """ @doc """
Selects the appropriate SNMP profile based on system information. Selects the appropriate SNMP profile based on system information.
First tries to match against database-stored device profiles. First tries to match against database-stored device profiles (imported from LibreNMS).
If no database match is found, falls back to hard-coded profile modules. If no database match is found, falls back to the generic Base profile.
## Examples ## Examples
iex> select_profile(%{sys_descr: "Cisco IOS"}) 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"}) 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() @spec select_profile(system_info()) :: profile()
def select_profile(system_info) do def select_profile(system_info) do
# First try database profiles case YamlProfiles.match_profile(system_info) do
case Profiles.match_profile(system_info) do %{name: name} = profile ->
%DeviceProfile{} = profile -> Logger.debug("Selected YAML profile: #{name}")
Logger.debug("Selected database profile: #{profile.name}") {:yaml, profile}
{:dynamic, profile}
nil -> nil ->
# Fall back to hardcoded profiles Logger.debug("No YAML profile matched, using Base profile")
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")
Base Base
end end
end end
@spec build_device_info(Client.connection_opts(), system_info(), profile()) :: @spec build_device_info(Client.connection_opts(), system_info(), profile()) ::
{:ok, device_info()} {:ok, device_info()}
defp build_device_info(client_opts, system_info, {:dynamic, profile}) do defp build_device_info(client_opts, system_info, {:yaml, profile}) do
# Use dynamic profile to identify device # Use YAML profile to identify device
identified_info = Dynamic.identify_device(profile, client_opts, system_info) identified_info = Dynamic.identify_device(profile, client_opts, system_info)
{:ok, identified_info} {:ok, identified_info}
rescue rescue
error -> 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} {:ok, system_info}
end end
@ -380,7 +344,7 @@ defmodule Towerops.Snmp.Discovery do
@spec discover_interfaces(Client.connection_opts(), profile()) :: @spec discover_interfaces(Client.connection_opts(), profile()) ::
{:ok, [interface_data()]} {: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 case Dynamic.discover_interfaces(profile, client_opts) do
{:ok, interfaces} -> {:ok, interfaces} ->
Logger.debug("Discovered #{length(interfaces)} interfaces") Logger.debug("Discovered #{length(interfaces)} interfaces")
@ -405,7 +369,7 @@ defmodule Towerops.Snmp.Discovery do
end end
@spec discover_sensors(Client.connection_opts(), profile()) :: {:ok, [sensor_data()]} @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) {:ok, sensors} = Dynamic.discover_sensors(profile, client_opts)
Logger.debug("Discovered #{length(sensors)} sensors") Logger.debug("Discovered #{length(sensors)} sensors")
{:ok, sensors} {:ok, sensors}
@ -424,7 +388,7 @@ defmodule Towerops.Snmp.Discovery do
end end
@spec discover_vlans(Client.connection_opts(), profile()) :: {:ok, [map()]} @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 # Dynamic profiles use Base VLAN discovery
{:ok, vlans} = Base.discover_vlans(client_opts) {:ok, vlans} = Base.discover_vlans(client_opts)
Logger.debug("Discovered #{length(vlans)} VLANs") 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", "UCD-SNMP-MIB::memAvailSwap" => "1.3.6.1.4.1.2021.4.4.0",
# Ubiquiti (enterprise 41112) # Ubiquiti (enterprise 41112)
"UBNT-UniFi-MIB::unifiApSystemModel" => "1.3.6.1.4.1.41112.1.6.3.3.0", "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 """ @doc """
@ -146,7 +151,9 @@ defmodule Towerops.Snmp.MibTranslator do
defp try_snmptranslate(mib_name) do defp try_snmptranslate(mib_name) do
# Get MIB directories from config or use default # 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) # Expand directories to include subdirectories (snmptranslate doesn't search recursively)
expanded_dirs = Enum.flat_map(mib_dirs, &expand_mib_directory/1) 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) # Calculate divisor from scale and precision (like LibreNMS)
# Scale: 10^scale exponent, Precision: decimal places # 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 defp calculate_divisor(scale, precision) when is_integer(scale) and is_integer(precision) do
scale_factor = Map.get(@scale_factors, scale, 1) scale_factor = Map.get(@scale_factors, scale, 1)
precision_factor = :math.pow(10, precision) precision_factor = Integer.pow(10, precision)
scale_factor * precision_factor round(scale_factor * precision_factor)
end end
defp calculate_divisor(scale, _) when is_integer(scale), do: scale_to_divisor(scale) 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)
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 cond do
String.contains?(sys_descr, ["Cisco", "IOS"]) -> match = Regex.run(~r/ePMP\s*(\d+)/i, sys_descr) -> "ePMP #{Enum.at(match, 1)}"
{"Cisco", extract_cisco_model(sys_descr)} 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") -> defp extract_cambium_product_line(sys_object_id) when is_binary(sys_object_id) do
{"Linux", "Server"} 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") -> defp extract_cambium_product_line(_), do: nil
{"Microsoft", "Windows Server"}
true -> defp extract_ubiquiti_model(sys_descr) do
{"Unknown", "Generic Device"} 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
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 defmodule Towerops.Snmp.Profiles.Dynamic do
@moduledoc """ @moduledoc """
Dynamic profile that uses database-stored profiles with MIB name translation. Dynamic profile that uses YAML-loaded profiles with MIB name translation.
Allows profiles to be updated without redeployment. Profiles are loaded from priv/profiles/ and cached in ETS.
""" """
alias Towerops.Profiles
alias Towerops.Profiles.DeviceProfile
alias Towerops.Snmp.Client alias Towerops.Snmp.Client
alias Towerops.Snmp.Discovery alias Towerops.Snmp.Discovery
alias Towerops.Snmp.MibTranslator alias Towerops.Snmp.MibTranslator
@ -16,38 +14,29 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
Identifies device using profile's device_oids. Identifies device using profile's device_oids.
Translates MIB names to numeric OIDs and polls the device. 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() Discovery.device_info()
def identify_device(profile, client_opts, system_info) do def identify_device(profile, client_opts, system_info) do
# Get device OIDs from profile device_oids = Map.get(profile, :device_oids, %{})
profile = Profiles.get_profile(profile.name)
device_data = device_data =
profile.device_oids device_oids
|> Enum.map(&fetch_device_oid_value(&1, client_opts)) |> Enum.map(fn {field, mib_name} -> fetch_device_oid_value(field, mib_name, client_opts) end)
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
|> Map.new() |> Map.new()
# Format firmware version for MikroTik devices # Format firmware version (MikroTik has special formatting with license level)
firmware_version = firmware_version = format_firmware_version(profile, device_data)
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
# Merge with system_info # Merge with system_info
system_info system_info
|> Map.merge(%{ |> Map.merge(%{
manufacturer: profile.vendor, manufacturer: profile.vendor,
model: system_info.sys_descr, model: system_info[:sys_descr],
firmware_version: firmware_version, 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) |> Map.merge(device_data)
end end
@ -55,24 +44,32 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
@doc """ @doc """
Discovers sensors using profile's sensor_oids. Discovers sensors using profile's sensor_oids.
Translates MIB names and polls sensors. 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()]} {:ok, [Discovery.sensor_data()]}
def discover_sensors(profile, client_opts) do def discover_sensors(profile, client_opts) do
profile = Profiles.get_profile(profile.name) alias Towerops.Snmp.Profiles.Base
sensor_oids = Map.get(profile, :sensor_oids, [])
if Enum.empty?(sensor_oids) do
# Fall back to ENTITY-SENSOR-MIB discovery from Base profile
Base.discover_sensors(client_opts)
else
sensors = sensors =
profile.sensor_oids sensor_oids
|> Enum.map(&fetch_sensor_value(&1, client_opts)) |> Enum.map(&fetch_sensor_value(&1, client_opts))
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
{:ok, sensors} {:ok, sensors}
end end
end
@doc """ @doc """
Uses Base profile for interface discovery. 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()]} {:ok, [Discovery.interface_data()]}
def discover_interfaces(_profile, client_opts) do def discover_interfaces(_profile, client_opts) do
alias Towerops.Snmp.Profiles.Base alias Towerops.Snmp.Profiles.Base
@ -82,13 +79,31 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
# Private helper functions # Private helper functions
defp fetch_device_oid_value(device_oid, client_opts) do defp format_firmware_version(%{vendor: vendor}, device_data) when vendor in ["MikroTik", "Mikrotik RouterOS"] do
with {:ok, numeric_oid} <- MibTranslator.translate(device_oid.mib_name), 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 {:ok, value} <- Client.get(client_opts, numeric_oid) do
{String.to_atom(device_oid.field), value} {field, value}
else else
{:error, :translation_failed} -> {:error, :translation_failed} ->
Logger.warning("Failed to translate MIB name: #{device_oid.mib_name}") Logger.warning("Failed to translate MIB name: #{mib_name}")
nil nil
{:error, _} -> {:error, _} ->
@ -97,21 +112,23 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
end end
defp fetch_sensor_value(sensor_oid, client_opts) do 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 <- {:ok, value} when is_integer(value) and value > 0 <-
Client.get(client_opts, numeric_oid) do Client.get(client_opts, numeric_oid) do
%{ %{
sensor_type: sensor_oid.sensor_type, sensor_type: sensor_oid[:sensor_type],
sensor_index: sensor_oid.sensor_type, sensor_index: sensor_oid[:sensor_type],
sensor_oid: numeric_oid, sensor_oid: numeric_oid,
sensor_descr: sensor_oid.sensor_descr || sensor_oid.sensor_type, sensor_descr: sensor_oid[:sensor_descr] || sensor_oid[:sensor_type],
sensor_unit: sensor_oid.sensor_unit || "", sensor_unit: sensor_oid[:sensor_unit] || "",
sensor_divisor: sensor_oid.sensor_divisor || 1, sensor_divisor: sensor_oid[:sensor_divisor] || 1,
sensor_value: value sensor_value: value
} }
else else
{:error, :translation_failed} -> {: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 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