SnmpKit
A pure Elixir SNMP toolkit: manager operations for SNMPv1, v2c and v3, a
trap and inform receiver, a native MIB compiler, and simulated devices for
tests. It does not depend on Erlang's :snmp application.
Installation
def deps do
[
{:snmpkit, "~> 2.0"}
]
end
Breaking changes in 2.0
2.0 is a consolidation release. Most facade request and response shapes are the same as in 1.4, but the following changes can require application updates:
- Renamed modules:
SnmpMgr.EngineV2is nowSnmpMgr.Engine,SnmpMgr.MultiV2is nowSnmpMgr.Multi,SnmpLib.MIB.*is nowSnmpKit.MIB.*,SnmpSim.SafeFileis nowSnmpKit.SafeFile(a delegate is retained), andSnmpSim.Device.ErrorInjectoris nowSnmpSim.Device.ErrorConditions. - Removed manager and library APIs: the old opt-in request-batching engine
and its
Router,CircuitBreaker,Metrics,SnmpMgr.ApplicationandSnmpMgr.Supervisor;SnmpMgr.SocketManager; the Task-per-targetMulti, itsstrategy:option andMulti.monitor/3;SnmpLib.Config,Pool,Cache,Monitor,Dashboardand theSnmpLib.MIBfacade; andSnmpKit.TestSupport. The relatedSnmpMgr/SnmpKit.SNMPengine delegates,ErrorHandlercircuit-breaker functions,Corespawn-based async/value-only GET helpers,Bulk.get_bulk_multi/2, and unused publicSecurity,Auth,Priv,USMandKeyshelpers were also removed. - Removed simulator APIs:
SnmpSim.Application,MultiDeviceStartup,TestScenarios,TestHelpers.*andPerformance.*. A simulated device no longer invents hard-coded objects when it has no profile orobjects:map. Several publicDevice.OidHandlercalculation/fallback helpers were removed; its uptime helpers moved toDevice.Metrics. The counter and jitter helpers formerly onValueSimulatormoved toValueSimulator.Countersand.Variance. - Return values:
get_async/3andget_bulk_async/3return aTask;set/4returns:okinstead of{:ok, :success};Multi.execute_mixed/2returns enriched varbind maps instead of{oid, type, value}tuples;Sim.start_device_population/2pre-warms devices and returns[%{type, port, pid, target}]; andbenchmark_device/3changes the meaning ofavg_response_timeand addsoptimal_response_time. - Validation, errors and defaults: invalid, empty or mistyped OIDs now
return
{:error, {:invalid_oid, input, reason}}instead of querying a MIB root; retries default to one everywhere (some lower layers used three and some shared-socket paths used zero); malformed unsigned SNMP values are rejected rather than decoded as zero; SNMPv1 end-of-MIB is{:error, :no_such_name}; and privacy without authentication is{:error, :priv_requires_auth}. Multi-OIDSnmpMgr.Bulk.get_bulk/3requests are rejected instead of silently sending only the first OID. - Formatting and MIB parsing:
formattedvalues now follow loaded or built-in MIB metadata instead of guessing meanings from bare integer and counter values. Raw parser/tokenizer output uses binary identifiers, changesDEFVALand literal handling, includes awarningslist, and changes illegal-character errors to include the line number. - Configuration and dependencies: manager defaults are read from
config :snmpkit(the:snmp_mgrkey still works);input_roots:now also confines MIB compilation, linting and loading; and:telemetryis a required dependency rather than an optional one.
The 2.0 migration guide has the full rename and removal tables with replacements for each entry.
Quick start
Everything below runs against a simulated device, so it works offline.
# A simulated router on localhost:1161, built from a walk file that ships
# with the library (also :cable_modem and :switch). v3_users: is only
# needed for the SNMPv3 call below.
{:ok, profile} = SnmpKit.SnmpSim.ProfileLoader.load_profile(:router)
{:ok, _device} =
SnmpKit.Sim.start_device(profile,
port: 1161,
v3_users: [%{name: "admin", auth: :sha256, auth_password: "auth-secret",
priv: :aes128, priv_password: "priv-secret"}]
)
target = "127.0.0.1:1161"
# GET returns one enriched varbind map
{:ok, %{value: descr, type: :octet_string, oid: "1.3.6.1.2.1.1.1.0"}} =
SnmpKit.SNMP.get(target, "sysDescr.0")
# WALK returns a list of them, in OID order
{:ok, system} = SnmpKit.SNMP.walk(target, "system")
require Logger
Enum.each(system, fn %{name: name, formatted: value} -> Logger.info("#{name} = #{value}") end)
# SNMPv3: discovery, key localization and time sync are automatic
{:ok, _} = SnmpKit.SNMP.get(target, "sysDescr.0",
version: :v3, security_name: "admin",
auth_protocol: :sha256, auth_password: "auth-secret",
priv_protocol: :aes128, priv_password: "priv-secret")
# Multi-target calls return one result per request, in request order
[{:ok, [%{value: ^descr}]}, {:ok, [%{name: "sysName.0"}]}] =
SnmpKit.SNMP.get_multi([{target, "sysDescr.0"}, {target, "sysName.0"}])
# MIB lookups work without any loading; the common IETF MIBs are built in
{:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]} = SnmpKit.MIB.resolve("sysDescr.0")
{:ok, "sysDescr.0"} = SnmpKit.MIB.reverse_lookup([1, 3, 6, 1, 2, 1, 1, 1, 0])
# Your own MIBs
{:ok, compiled} = SnmpKit.MIB.compile("priv/mibs/MY-ENTERPRISE-MIB.mib")
:ok = SnmpKit.MIB.load(compiled)
The API in one screen
| Module | What it is for |
|---|---|
SnmpKit.SNMP |
Manager operations: get, get_next, set, walk, get_bulk, bulk walks, tables, streams, async, multi-target, pretty formatting |
SnmpKit.MIB |
Name/OID resolution, tree navigation, MIB compilation and loading |
SnmpKit.Trap |
Receive SNMPv1/v2c traps and informs; SnmpKit.SNMP.send_trap/4 and send_inform/4 send them |
SnmpKit.Telemetry |
The :telemetry spans and events every request, walk, multi-target call, trap and simulated device emits |
SnmpKit.Agent |
Serve your own data over SNMP: scalars, tables and custom handlers, v1/v2c/v3, traps out |
SnmpKit.Sim |
Start one simulated device, or a population of them |
SnmpKit.SnmpSim |
Configuration-driven simulation of whole device groups |
SnmpKit |
Shortcuts for the most common calls (get, walk, resolve, ...) |
Lower layers are public too when you need them: SnmpKit.SnmpLib (PDU
encoding, ASN.1, transport, SNMPv3 security), SnmpKit.SnmpMgr (engine,
multi-target coordinator, walk strategies) and SnmpKit.MIB.Parser /
SnmpKit.MIB.Compiler (the native MIB toolchain).
Your own SNMP agent
SnmpKit.Agent exposes an application's data to any NMS over SNMPv1, v2c
and v3. Scalars go in with put/4 (a function value is read live), tables
come from a row-producing function, and anything else is a small module
implementing SnmpKit.Agent.Handler:
{:ok, agent} =
SnmpKit.Agent.start_link(
port: 1161,
communities: %{"public" => :read, "private" => :write},
v3_users: [%{name: "ops", auth: :sha256, auth_password: "auth-secret", access: :write}],
system: [descr: "orders-api 3.2", name: "orders-01", location: "rack 4"]
)
:ok = SnmpKit.Agent.put(agent, "hrSystemProcesses.0", :gauge32, fn -> length(Process.list()) end)
:ok =
SnmpKit.Agent.register(agent, "ifEntry", SnmpKit.Agent.Table,
columns: [{1, :integer}, {2, :octet_string}, {8, :integer}],
rows: fn -> [{1, %{1 => 1, 2 => "lo", 8 => 1}}, {2, %{1 => 2, 2 => "eth0", 8 => 1}}] end
)
# Any manager, including this one, can read it now
{:ok, %{1 => %{2 => "lo"}, 2 => %{2 => "eth0"}}} =
SnmpKit.SNMP.get_table("127.0.0.1:1161", "ifTable")
# and traps go out with the agent's sysUpTime
:ok = SnmpKit.Agent.notify(agent, "linkDown", [{"ifIndex.2", :integer, 2}], targets: ["nms.example.com"])
Put {SnmpKit.Agent, port: 161, name: MyApp.Agent, subtrees: [...]} in a
supervision tree for production. The API guide
covers access control, SET handling and writing handlers.
Results
Every operation returns enriched varbind maps:
%{
name: "sysUpTime.0", # nil when no MIB name is known
oid: "1.3.6.1.2.1.1.3.0",
oid_list: [1, 3, 6, 1, 2, 1, 1, 3, 0],
type: :timeticks,
value: 12345678,
formatted: "1 day 10 hours 17 minutes 36 seconds 78 centiseconds"
}
formatted follows the MIB: ifOperStatus reads "up", ifType reads
"ethernetCsmacd", ifPhysAddress reads "00:1a:2b:3c:4d:5e", and a loaded
vendor MIB's enumerations and DISPLAY-HINTs apply the same way. Name
resolution and formatting can be switched off per call
(include_names: false, include_formatted: false) or globally through
configuration, which matters on hot paths that walk large tables.
Errors are tagged tuples: {:error, :timeout}, {:error, :no_such_object}
(SNMPv2c), {:error, :no_such_name} (SNMPv1), {:error, :not_writable}, and
so on.
Multi-target operations
get_multi, get_bulk_multi, walk_multi and walk_table_multi run every
request concurrently over one shared UDP socket with centralized response
correlation. Nothing needs to be started by hand; the engine comes up on the
first call.
requests = [
{"switch-1", "ifTable"},
{"switch-2", "ifTable", timeout: 30_000}, # per-request options
{"router-1", "ipRouteTable"}
]
results = SnmpKit.SNMP.walk_multi(requests, max_concurrent: 20, walk_timeout: 120_000)
# [{:ok, [...]}, {:ok, [...]}, {:error, :timeout}] (request order)
SnmpKit.SNMP.get_multi(requests, return_format: :map)
# %{{"switch-1", "ifTable"} => {:ok, [...]}, ...}
See Concurrent Multi and the timeout guide.
Configuration
Defaults are read from the application environment at startup and can be
changed at runtime through SnmpKit.SnmpMgr.Config:
# config/config.exs
config :snmpkit,
community: "public",
timeout: 5_000, # per-PDU timeout, ms; walks are capped by walk_timeout:
retries: 1,
port: 161,
version: :v2c,
include_names: true,
include_formatted: true,
auto_start_services: true
# Limits applied when reading walk files and MIBs
config :snmpkit,
max_input_file_bytes: 50_000_000,
max_compiled_mib_bytes: 50_000_000,
input_roots: ["priv"] # optional jail for user-supplied file paths
Documentation
- 2.0 migration guide
- Unified API guide
- Concurrent multi-target operations
- Timeouts and retries
- MIB guide and checking the parser against libsmi and net-snmp
- Testing guide
- Livebooks: quickstart, SNMP operations, MIB management, device simulation, high performance, your own SNMP agent, SNMPv3, traps and informs, telemetry and rates
- Examples
- Full API reference
Command line
Five mix tasks give you a shell without writing a script:
mix snmpkit.get 192.168.1.1 sysDescr.0 sysUpTime.0 -c public
mix snmpkit.walk 192.168.1.1 ifTable --table # named columns
mix snmpkit.mib.compile priv/mibs # prints parser warnings
mix snmpkit.mib.lint VENDOR-MIB.mib --context priv/mibs # semantic checks, smilint-style
mix snmpkit.sim --device router --port 1161 # a simulated device until Ctrl-C
mix snmpkit.sim devices.yaml # a whole population from a config
Development
mix test # unit + integration suite, SNMPv3 included
mix test --include performance # timing-sensitive tests
mix test --include mib_oracle # cross-check the MIB parser (needs smilint / snmptranslate)
mix lint # format check, dialyzer
Contributions are welcome; see CONTRIBUTING.md.
License
SnmpKit is released under the MIT License.