SnmpKit.Agent (snmpkit v2.0.1)

An SNMP agent: exposes your application's own data to managers over SNMPv1, v2c and v3.

{:ok, agent} = SnmpKit.Agent.start_link(
  port: 1161,
  communities: %{"public" => :read, "private" => :write},
  system: [descr: "orders-api 3.2", name: "orders-01", location: "rack 4"]
)

SnmpKit.Agent.put(agent, "hrSystemNumUsers.0", :gauge32, fn -> MyApp.Sessions.count() end)
SnmpKit.Agent.register(agent, [1, 3, 6, 1, 4, 1, 99999, 1], MyApp.QueueStats)

Or in a supervision tree:

children = [
  {SnmpKit.Agent, port: 161, communities: ["public"], name: MyApp.SnmpAgent,
   subtrees: [{"ifEntry", SnmpKit.Agent.Table, columns: ..., rows: &MyApp.Ports.rows/0}]}
]

The MIB is made of subtrees, each served by a SnmpKit.Agent.Handler registered at an OID prefix. The longest matching prefix serves an object. Two handlers are built in: SnmpKit.Agent.Store for scalars (used by put/4, and pre-loaded with the system group) and SnmpKit.Agent.Table for tables backed by a function. Every request is answered in its own process, so handlers run concurrently.

Options

  • :port - UDP port (default 161; 0 picks a free port, read it with port/1)
  • :bind_address - interface to bind (default all IPv4; an IPv6 address binds IPv6)
  • :communities - "public", a list of read-only communities, or a map or keyword of community to :read | :write (default "public" read-only)
  • :v3_users - USM users as for SnmpKit.Sim.start_device/2 (%{name, auth, auth_password, priv, priv_password}), plus an optional access: :read | :write (default :read)
  • :engine_id - SNMPv3 engine id (default derived from :name or the port)
  • :system - the system group: descr, object_id, contact, name, location, services; sysUpTime is computed. Contact, name and location accept SET from a :write principal
  • :subtrees - {prefix, module} or {prefix, module, opts} to register at start
  • :notify_targets - where notify/4 sends: "host", "host:port", {host, port}, or %{host:, port:, community:, version:, inform:}
  • :name - GenServer registration
  • :max_concurrent_requests, :max_packet_size - as for the simulator server

OIDs everywhere may be lists, dotted strings, or MIB names ("sysName.0", "ifEntry").

Access control

A principal (community or v3 user) is :read or :write. Reads are allowed to both; SET needs :write, otherwise the agent answers noAccess (noSuchName to SNMPv1). A request from a community that is not configured is dropped without a response, as SNMP requires; a v3 request from a user that is not configured is answered by USM with a usmStatsUnknownUserNames report and never reaches the MIB. Should a PDU from an unknown principal reach the request path anyway, it is dropped there too, for reads as well as writes.

Notifications

notify/4 sends a v2c trap (or inform) to every configured target with the agent's sysUpTime:

SnmpKit.Agent.notify(agent, "linkDown", [{"ifIndex.3", :integer, 3}])

Telemetry

Every answered request emits [:snmpkit, :agent, :request] with %{duration} and metadata pdu_type, version, principal, error_status, varbinds. Dropped requests emit nothing; the server counts them in stats/1 under auth_failures.

Summary

Functions

Removes a scalar from the store.

The SNMPv3 engine id.

The value the agent would answer a GET for oid with, from any subtree.

The object the agent would answer a GETNEXT for oid with.

Sends a notification to every configured target (or to targets: in opts). trap and varbind OIDs may be names. Options are those of SnmpKit.SNMP.send_trap/4; inform: true sends informs and waits for the acknowledgements. Returns :ok, or {:error, [{target, reason}]} listing the targets that failed.

The UDP port the agent listens on.

Puts a scalar into the agent's store. value may be a zero-arity function, called on every read. writable: true allows SET from a :write principal.

Registers module (a SnmpKit.Agent.Handler) for the subtree at prefix. opts go to the module's init/1, or become its ctx when it has none. Registering at a prefix that is already taken replaces the handler.

Resolves an OID list, dotted string or MIB name to an OID list.

Starts an agent. See the module documentation for options.

Request and error counters, from the underlying server.

Stops the agent.

The registered subtrees as {prefix, module}, most specific first.

Removes the handler registered at prefix.

The agent's sysUpTime in centiseconds.

Types

agent()

@type agent() :: GenServer.server()

oid()

@type oid() :: [non_neg_integer()] | String.t()

Functions

delete(agent, oid)

@spec delete(agent(), oid()) :: :ok | {:error, term()}

Removes a scalar from the store.

engine_id(agent)

@spec engine_id(agent()) :: binary()

The SNMPv3 engine id.

get(agent, oid)

@spec get(agent(), oid()) :: {:ok, {atom(), term()}} | {:error, term()}

The value the agent would answer a GET for oid with, from any subtree.

next(agent, oid)

@spec next(agent(), oid()) ::
  {:ok, {[non_neg_integer()], atom(), term()}}
  | :end_of_mib_view
  | {:error, term()}

The object the agent would answer a GETNEXT for oid with.

notify(agent, trap, varbinds \\ [], opts \\ [])

@spec notify(agent(), term(), [{term(), atom(), term()}], keyword()) ::
  :ok | {:error, [{term(), term()}]}

Sends a notification to every configured target (or to targets: in opts). trap and varbind OIDs may be names. Options are those of SnmpKit.SNMP.send_trap/4; inform: true sends informs and waits for the acknowledgements. Returns :ok, or {:error, [{target, reason}]} listing the targets that failed.

port(agent)

@spec port(agent()) :: :inet.port_number()

The UDP port the agent listens on.

put(agent, oid, type, value, opts \\ [])

@spec put(agent(), oid(), atom(), term(), keyword()) :: :ok | {:error, term()}

Puts a scalar into the agent's store. value may be a zero-arity function, called on every read. writable: true allows SET from a :write principal.

register(agent, prefix, module, opts \\ [])

@spec register(agent(), oid(), module(), keyword()) :: :ok | {:error, term()}

Registers module (a SnmpKit.Agent.Handler) for the subtree at prefix. opts go to the module's init/1, or become its ctx when it has none. Registering at a prefix that is already taken replaces the handler.

resolve_oid(oid)

@spec resolve_oid(term()) :: {:ok, [non_neg_integer()]} | {:error, term()}

Resolves an OID list, dotted string or MIB name to an OID list.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts an agent. See the module documentation for options.

stats(agent)

@spec stats(agent()) :: map()

Request and error counters, from the underlying server.

stop(agent)

@spec stop(agent()) :: :ok

Stops the agent.

subtrees(agent)

@spec subtrees(agent()) :: [{[non_neg_integer()], module()}]

The registered subtrees as {prefix, module}, most specific first.

unregister(agent, prefix)

@spec unregister(agent(), oid()) :: :ok

Removes the handler registered at prefix.

uptime(agent)

@spec uptime(agent()) :: non_neg_integer()

The agent's sysUpTime in centiseconds.