towerops/MIB_RESOLUTION_NETSNMP.md

5.3 KiB

MIB Resolution with net-snmp C Library

Implementation Summary

Successfully integrated the net-snmp C library for ultra-fast MIB name resolution, achieving LibreNMS-style performance.

Performance Comparison

Method First Resolution Cached Resolution
Subprocess (old) ~300-400ms <1µs (ETS cache)
net-snmp C library (new) ~1-20µs ~1-20µs (in-memory)

Benchmark Results

sysDescr                       avg: 1.6µs   min: 1µs    max: 6µs
IF-MIB::ifDescr                avg: 17.4µs  min: 9µs    max: 23µs
SNMPv2-MIB::sysUpTime          avg: 12.3µs  min: 7µs    max: 19µs
IF-MIB::ifInOctets             avg: 3.5µs   min: 2µs    max: 6µs
IP-MIB::ipAdEntAddr            avg: 4.8µs   min: 1µs    max: 14µs

Result: 17-300x faster than subprocess approach!

Architecture

Stack

Elixir (MibCache GenServer)
    ↓
Rustler NIF (towerops_native)
    ↓
Rust FFI bindings (netsnmp_ffi.rs)
    ↓
net-snmp C library (libnetsnmp)

Key Components

  1. netsnmp_ffi.rs - Rust FFI bindings to net-snmp C functions

    • initialize_snmp_library() - One-time MIB loading (~1-2s startup)
    • resolve_mib_name() - Fast OID resolution (<20µs)
  2. mib.rs - Rust NIF implementation

    • load_mib_directory_impl() - Configure MIB search paths
    • init_mib_library_impl() - Initialize net-snmp (DirtyCpu scheduler)
    • resolve_oid_impl() - Resolve MIB names with Rust HashMap cache
  3. lib.rs - Rustler NIF exports

    • Exports functions to Elixir as ToweropsNative module
  4. lib/towerops_native.ex - Elixir NIF wrapper

    • load_mib_directory/1 - Set MIB search paths
    • init_mib_library/0 - Initialize net-snmp library
    • resolve_oid/1 - Resolve MIB name to numeric OID
  5. lib/towerops/profiles/mib_cache.ex - GenServer cache layer

    • Async pre-resolution of common MIBs at startup
    • ETS cache for ultra-fast lookups
    • Runtime fallback for cache misses

Initialization Flow

  1. Application starts → load_mib_directory/1 called with MIB paths
  2. First resolve_oid/1 call triggers lazy initialization:
    • Sets MIBDIRS and MIBS=ALL environment variables
    • Calls init_snmp() and netsnmp_init_mib()
    • Calls read_all_mibs() to load all MIBs into memory (~1-2s)
  3. Subsequent calls use in-memory MIB tree (fast!)

Resolution Flow

  1. Elixir calls ToweropsNative.resolve_oid("sysDescr")
  2. Rust checks Rust HashMap cache (miss on first call)
  3. Calls C read_objid() to resolve MIB name → OID array
  4. Calls C snprint_objid() to format OID as string
  5. Caches result in Rust HashMap
  6. Returns to Elixir
  7. MibCache GenServer caches in ETS

Files Created/Modified

New Files

  • native/towerops_native/src/netsnmp_ffi.rs - C library FFI bindings
  • native/towerops_native/build.rs - Cargo build script for linking

Modified Files

  • native/towerops_native/Cargo.toml - Added libc and cc dependencies
  • native/towerops_native/src/lib.rs - NIF function exports
  • native/towerops_native/src/mib.rs - Refactored for C library integration
  • lib/towerops_native.ex - Added init_mib_library/0 function

Existing Files (unchanged)

  • lib/towerops/profiles/mib_cache.ex - Still provides caching layer
  • lib/towerops/profiles/yaml_profiles.ex - Uses MibCache as before
  • lib/towerops/snmp/client.ex - Uses MibCache as before

Build Requirements

Development (macOS)

  • Homebrew net-snmp: brew install net-snmp
  • Headers: /usr/include/net-snmp/* or /opt/homebrew/include/net-snmp/*
  • Library: /usr/lib/libnetsnmp.dylib or /opt/homebrew/lib/libnetsnmp.dylib

Production (Docker/Linux)

  • Package: net-snmp and net-snmp-devel (CentOS) or libsnmp-dev (Debian/Ubuntu)
  • Headers: /usr/include/net-snmp/*
  • Library: /usr/lib/x86_64-linux-gnu/libnetsnmp.so

Build Process

  1. build.rs uses net-snmp-config --libs to get library paths
  2. Links against libnetsnmp dynamically
  3. Works in both dev (macOS) and prod (Linux Docker)

Advantages

  1. Performance: 17-300x faster than subprocess calls
  2. LibreNMS-aligned: Same C library as LibreNMS PHP extension
  3. Memory-efficient: MIBs loaded once, shared across all resolutions
  4. Thread-safe: C library handles concurrent access
  5. Works in Docker: Dynamic linking finds system libnetsnmp
  6. No MIB pre-compilation: MIBs loaded as text files (easy updates)

Trade-offs

  1. Startup time: ~1-2 seconds to load all MIBs (one-time cost)

    • Happens lazily on first resolution call
    • Runs on DirtyCpu scheduler (doesn't block main BEAM schedulers)
  2. C dependency: Requires net-snmp library installed

    • Already required for snmptranslate command
    • Standard package on all Linux distros
  3. Complexity: Added Rust FFI layer

    • Well-documented and type-safe
    • Similar to PHP's SNMP extension internals

Testing

All existing tests pass:

mix test test/towerops/profiles/yaml_profiles_test.exs  # 12 tests, 0 failures
mix test test/towerops/snmp/discovery_test.exs          # 13 tests, 0 failures

Future Enhancements

  1. Lazy MIB loading: Only load MIBs for matched vendors (faster startup)
  2. MIB dependency tracking: Load only required MIB dependencies
  3. Shared library caching: Share MIB tree across multiple NIF instances
  4. Telemetry: Track resolution times and cache hit rates