Powered by AppSignal & Oban Pro

MIB Management

livebooks/03_mib_management.livemd

MIB Management

Understanding and working with MIBs (Management Information Bases) in SnmpKit.

Setup

Mix.install([
  {:snmpkit, "~> 1.3"}
])

alias SnmpKit.MIB

IO.puts("MIB module ready!")

What is a MIB?

A MIB defines the structure and meaning of SNMP data:

  • Maps human-readable names to numeric OIDs
  • Defines data types for each object
  • Organizes objects into a hierarchical tree

SnmpKit includes standard MIBs (SNMPv2-MIB, IF-MIB, etc.) loaded by default.

OID Name Resolution

Resolve Names to OIDs

# Common SNMP objects
names = ["sysDescr", "sysUpTime", "sysContact", "sysName", "sysLocation", "ifNumber"]

IO.puts("Name to OID resolution:")
Enum.each(names, fn name ->
  case MIB.resolve(name) do
    {:ok, oid} ->
      IO.puts("  #{name} -> #{Enum.join(oid, ".")}")
    {:error, reason} ->
      IO.puts("  #{name} -> Error: #{inspect(reason)}")
  end
end)

Resolve with Instance Index

# Add .0 for scalar objects or .index for table entries
names_with_index = ["sysDescr.0", "sysUpTime.0", "ifDescr.1", "ifDescr.2"]

IO.puts("Names with instance index:")
Enum.each(names_with_index, fn name ->
  case MIB.resolve(name) do
    {:ok, oid} ->
      IO.puts("  #{name} -> #{Enum.join(oid, ".")}")
    {:error, _} ->
      IO.puts("  #{name} -> Not found")
  end
end)

Reverse Lookup

Convert OIDs back to names:

oids = [
  [1, 3, 6, 1, 2, 1, 1, 1, 0],      # sysDescr.0
  [1, 3, 6, 1, 2, 1, 1, 3, 0],      # sysUpTime.0
  [1, 3, 6, 1, 2, 1, 2, 1, 0],      # ifNumber.0
  [1, 3, 6, 1, 2, 1, 2, 2, 1, 2, 1] # ifDescr.1
]

IO.puts("OID to name resolution:")
Enum.each(oids, fn oid ->
  oid_str = Enum.join(oid, ".")
  case MIB.reverse_lookup(oid) do
    {:ok, name} ->
      IO.puts("  #{oid_str} -> #{name}")
    {:error, reason} ->
      IO.puts("  #{oid_str} -> #{inspect(reason)}")
  end
end)

Reverse Lookup from String

# Also works with string format
{:ok, name} = MIB.reverse_lookup("1.3.6.1.2.1.1.1.0")
IO.puts("1.3.6.1.2.1.1.1.0 -> #{name}")

MIB Tree Navigation

Get Children of a Node

# Get children of the system group
{:ok, system_oid} = MIB.resolve("system")
{:ok, children} = MIB.children(system_oid)

IO.puts("Children of 'system' (#{Enum.join(system_oid, ".")}):")
Enum.each(children, fn child_name ->
  case MIB.resolve(child_name) do
    {:ok, oid} ->
      IO.puts("  #{child_name} (#{Enum.join(oid, ".")})")
    {:error, _} ->
      IO.puts("  #{child_name}")
  end
end)

Get Parent of a Node

{:ok, sys_descr_oid} = MIB.resolve("sysDescr.0")
{:ok, parent_oid} = MIB.parent(sys_descr_oid)
{:ok, parent_name} = MIB.reverse_lookup(parent_oid)

IO.puts("sysDescr.0 parent: #{parent_name} (#{Enum.join(parent_oid, ".")})")

# Go up another level
{:ok, grandparent_oid} = MIB.parent(parent_oid)
{:ok, grandparent_name} = MIB.reverse_lookup(grandparent_oid)

IO.puts("sysDescr parent: #{grandparent_name} (#{Enum.join(grandparent_oid, ".")})")

Walk the MIB Tree

{:ok, mib_oid} = MIB.resolve("mib-2")
{:ok, tree} = MIB.walk_tree(mib_oid)

IO.puts("MIB-2 subtree (first 10 entries):")
tree
|> Enum.take(10)
|> Enum.each(fn {name, oid} ->
  IO.puts("  #{name}: #{Enum.join(oid, ".")}")
end)

Standard MIB Groups

SnmpKit includes these standard MIBs:

Group OID Description
system 1.3.6.1.2.1.1 Basic device info
interfaces 1.3.6.1.2.1.2 Network interfaces
ip 1.3.6.1.2.1.4 IP statistics, ipAddrTable, ipRouteTable, ipNetToMediaTable
tcp 1.3.6.1.2.1.6 TCP statistics
udp 1.3.6.1.2.1.7 UDP statistics
snmp 1.3.6.1.2.1.11 SNMP statistics
host 1.3.6.1.2.1.25 Host resources (storage, device, processor, SW tables)
docsis 1.3.6.1.2.1.10.127 DOCSIS IF-MIB (downstream, upstream, signal quality, CM status)

Note: The deprecated AT group (1.3.6.1.2.1.3) and ICMP group are not included.

groups = ["system", "interfaces", "ip", "tcp", "udp", "snmp"]

IO.puts("Standard MIB groups:")
Enum.each(groups, fn group ->
  case MIB.resolve(group) do
    {:ok, oid} ->
      {:ok, children} = MIB.children(oid)
      IO.puts("  #{group} (#{Enum.join(oid, ".")}): #{length(children)} children")
    {:error, _} ->
      IO.puts("  #{group}: Not found")
  end
end)

Common Interface Table OIDs

if_oids = [
  {"ifIndex", "Interface index"},
  {"ifDescr", "Interface description"},
  {"ifType", "Interface type"},
  {"ifMtu", "Maximum transmission unit"},
  {"ifSpeed", "Interface speed (bps)"},
  {"ifPhysAddress", "MAC address"},
  {"ifAdminStatus", "Admin status (up/down)"},
  {"ifOperStatus", "Operational status"},
  {"ifInOctets", "Bytes received"},
  {"ifOutOctets", "Bytes sent"}
]

IO.puts("Interface table columns:")
Enum.each(if_oids, fn {name, desc} ->
  case MIB.resolve(name) do
    {:ok, oid} ->
      IO.puts("  #{name}: #{Enum.join(oid, ".")} - #{desc}")
    {:error, _} ->
      IO.puts("  #{name}: Not in standard MIBs")
  end
end)

Enhanced Resolution

Get additional metadata about an OID:

case MIB.resolve_enhanced("sysDescr") do
  {:ok, info} ->
    IO.puts("Enhanced info for sysDescr:")
    IO.inspect(info, label: "  ")
  {:error, reason} ->
    IO.puts("Error: #{inspect(reason)}")
end

Using MIB Resolution with SNMP Operations

# Start a quick test device
device_oids = %{
  "1.3.6.1.2.1.1.1.0" => "Test Device",
  "1.3.6.1.2.1.1.5.0" => "test-device-01"
}

{:ok, profile} = SnmpKit.SnmpSim.ProfileLoader.load_profile(:test, {:manual, device_oids})
{:ok, _device} = SnmpKit.Sim.start_device(profile, port: 1163)

target = "127.0.0.1:1163"

# Use names instead of numeric OIDs
{:ok, result} = SnmpKit.SNMP.get(target, "sysDescr.0")
IO.puts("sysDescr.0: #{result.formatted}")

# The result includes the resolved name
IO.puts("Name in result: #{result.name}")

Next Steps