SnmpKit.Agent.Handler behaviour (snmpkit v2.0.1)

Behaviour for a module that serves one subtree of an agent's MIB.

A handler is registered at an OID prefix with SnmpKit.Agent.register/4. The agent strips the prefix before calling it, so every callback works with the suffix below the prefix: for a handler at ifEntry (1.3.6.1.2.1.2.2.1) a GET of ifDescr.3 arrives as [2, 3].

ctx is whatever init/1 returned at registration (or the registration options themselves when the module has no init/1). Callbacks run in the request's worker process, concurrently with other requests, so a handler that keeps state should read it from ETS, an Agent, or a GenServer rather than from its ctx.

defmodule MyApp.QueueStats do
  @behaviour SnmpKit.Agent.Handler

  # .1.0 = depth, .2.0 = processed; suffixes kept in lexicographic order
  @objects [
    {[1, 0], :gauge32, &MyApp.Queue.depth/0},
    {[2, 0], :counter32, &MyApp.Queue.processed/0}
  ]

  def get(suffix, _ctx) do
    case List.keyfind(@objects, suffix, 0) do
      {_, type, fun} -> {:ok, {type, fun.()}}
      nil -> {:error, :no_such_instance}
    end
  end

  def get_next(suffix, _ctx) do
    case Enum.find(@objects, fn {s, _, _} -> s > suffix end) do
      {s, type, fun} -> {:ok, {s, {type, fun.()}}}
      nil -> :end_of_subtree
    end
  end
end

SnmpKit.Agent.Store (scalars kept in ETS) and SnmpKit.Agent.Table (rows produced by a function) are ready-made handlers for the common cases.

Values

Values are {type, value} with the types used everywhere else in SnmpKit: :integer, :octet_string (or :string, or :bits, which is an OCTET STRING on the wire), :object_identifier (or :oid), :counter32, :gauge32 (or :unsigned32), :timeticks, :counter64, :ip_address (a 4-tuple or 4 octets), :opaque, :null. A SET whose type is an alias of the declared one is accepted by the built-in handlers.

SET

A SET request runs in two phases. SnmpKit.Agent.Handler.check_set/3 is called for every varbind first, and no SnmpKit.Agent.Handler.set/3 runs unless all of them return :ok; then SnmpKit.Agent.Handler.set/3 is called for each varbind in order. A handler without set/3 makes its subtree read-only (notWritable). Error reasons map to SNMP error-status values: :no_access, :wrong_type, :wrong_length, :wrong_encoding, :wrong_value, :no_creation, :inconsistent_value, :resource_unavailable, :commit_failed, :undo_failed, :not_writable, :inconsistent_name, :gen_err.

The two phases make a SET validated as a whole, not applied as a whole: if a set/3 fails after earlier varbinds were written, the agent answers commitFailed (or the handler's own reason) with that varbind's index and the earlier writes stay; there is no undo phase (RFC 3416 4.2.5 allows this, undoFailed is what a fuller implementation would answer). A handler whose check_set/3 guarantees set/3 succeeds gets all-or-nothing behaviour for its own objects.

Summary

Callbacks

Validates a SET without applying it.

The value of the object at suffix. {:error, :no_such_instance} when the object exists but this instance does not, {:error, :no_such_object} when nothing of that name exists in the subtree.

The first object whose suffix is lexicographically greater than suffix ([] asks for the first object in the subtree), or :end_of_subtree.

Turns the registration options into the ctx passed to the other callbacks.

Applies a SET.

Types

ctx()

@type ctx() :: term()

set_error()

@type set_error() ::
  :no_access
  | :wrong_type
  | :wrong_length
  | :wrong_encoding
  | :wrong_value
  | :no_creation
  | :inconsistent_value
  | :resource_unavailable
  | :commit_failed
  | :undo_failed
  | :not_writable
  | :inconsistent_name
  | :gen_err

suffix()

@type suffix() :: [non_neg_integer()]

value()

@type value() :: {atom(), term()}

Callbacks

check_set(suffix, value, ctx)

(optional)
@callback check_set(suffix(), value(), ctx()) :: :ok | {:error, set_error()}

Validates a SET without applying it.

get(suffix, ctx)

@callback get(suffix(), ctx()) ::
  {:ok, value()} | {:error, :no_such_object | :no_such_instance}

The value of the object at suffix. {:error, :no_such_instance} when the object exists but this instance does not, {:error, :no_such_object} when nothing of that name exists in the subtree.

get_next(suffix, ctx)

@callback get_next(suffix(), ctx()) :: {:ok, {suffix(), value()}} | :end_of_subtree

The first object whose suffix is lexicographically greater than suffix ([] asks for the first object in the subtree), or :end_of_subtree.

init(opts)

(optional)
@callback init(opts :: keyword()) :: {:ok, ctx()} | {:error, term()}

Turns the registration options into the ctx passed to the other callbacks.

set(suffix, value, ctx)

(optional)
@callback set(suffix(), value(), ctx()) :: :ok | {:error, set_error()}

Applies a SET.