Go library for parsing and querying SNMP MIB files.
Supports SMIv1 and SMIv2 modules. Loads MIBs from directories, directory trees, or embedded filesystems. Resolves imports, builds the OID tree, and provides typed access to objects, types, notifications, and conformance definitions.
go get github.com/golangsnmp/gomib
Requires Go 1.24+.
package main
import (
"context"
"fmt"
"log"
"github.com/golangsnmp/gomib"
)
func main() {
m, err := gomib.Load(context.Background(),
gomib.WithSystemPaths(),
gomib.WithModules("IF-MIB"),
)
if err != nil {
log.Fatal(err)
}
obj := m.Object("ifIndex")
if obj != nil {
fmt.Printf("%s %s %s %s\n", obj.Name(), obj.OID(), obj.Type().Name(), obj.Access())
// ifIndex 1.3.6.1.2.1.2.2.1.1 InterfaceIndex read-only
}
}// Load everything from a source (parses all files)
m, err := gomib.Load(ctx, gomib.WithSource(src))
// Load only specific modules and their transitive dependencies.
// Faster when the source contains hundreds of MIBs but you only need a few.
m, err := gomib.Load(ctx, gomib.WithSource(src), gomib.WithModules("IF-MIB", "IP-MIB"))Dir recursively indexes a directory tree using content-derived module names. File/Files load individual files by path. FS wraps an fs.FS (useful with embed.FS). Multi tries multiple sources in order.
// Directory tree (indexed once at construction)
src, err := gomib.Dir("/usr/share/snmp/mibs")
// Individual files (module names auto-detected from content)
src, err := gomib.File("/path/to/IF-MIB.mib")
src, err = gomib.Files("/path/to/IF-MIB.mib", "/path/to/IP-MIB.mib")
// Embedded filesystem
//go:embed mibs
var mibFS embed.FS
src := gomib.FS("embedded", mibFS)
// Combine sources (first match wins)
src := gomib.Multi(systemSrc, vendorSrc)MustDir panics on error for use in var blocks.
Files are matched by extension: no extension, .mib, .smi, .txt, .my. Override with WithExtensions. Non-MIB files are filtered during loading by checking for DEFINITIONS and ::= in the content.
ScanModuleNames extracts module names from raw bytes without a full parse, useful for indexing:
names := gomib.ScanModuleNames(content) // e.g. ["IF-MIB"]Sources can also list the modules they know about without parsing them:
src, _ := gomib.Dir("/usr/share/snmp/mibs")
names, _ := src.ListModules() // e.g. ["IF-MIB", "IP-MIB", ...]gomib.Load(ctx,
gomib.WithSource(src),
gomib.WithSystemPaths(), // discover net-snmp/libsmi paths
gomib.WithLogger(slog.Default()), // enable debug/trace logging
gomib.WithResolverStrictness(mib.ResolverPermissive), // strictness preset
gomib.WithDiagnosticConfig(mib.DiagnosticConfig{ // fine-grained control
Reporting: mib.ReportingDefault,
FailAt: mib.SeverityError,
Ignore: []string{"identifier-underscore"},
}),
)Lookup methods take a plain name and return nil if not found:
obj := m.Object("ifIndex")
node := m.Node("ifEntry")
typ := m.Type("DisplayString")For qualified lookup, scope through the module (see Module-scoped queries below).
Other lookup methods: Node, Type, Notification, Group, Compliance, Capability.
Symbol provides a unified lookup across all definition types:
sym := m.Symbol("ifIndex") // returns a Symbol wrapping any definition kind
sym.Name() // "ifIndex"
sym.Object() // *Object (or nil if not an OBJECT-TYPE)
sym.Node() // *Node
sym.IsZero() // true if nothing was foundResolve accepts any common format - plain name, qualified name, or numeric OID - and returns the matching node:
node := m.Resolve("ifDescr") // plain name
node = m.Resolve("IF-MIB::ifDescr") // qualified
node = m.Resolve("1.3.6.1.2.1.2.2.1.2") // numeric OID
node = m.Resolve(".1.3.6.1.2.1.2.2.1.2") // leading dotResolveOID converts any of those formats to a numeric OID, and also handles instance-suffixed forms:
oid, err := m.ResolveOID("IF-MIB::ifDescr.5") // OID{1,3,6,1,2,1,2,2,1,2,5}
oid, err = m.ResolveOID("ifDescr.5") // same result
oid, err = m.ResolveOID("1.3.6.1.2.1.2.2.1.2") // numeric pass-throughResolveOID is the inverse of FormatOID:
formatted := m.FormatOID(oid) // "IF-MIB::ifDescr.5"
back, _ := m.ResolveOID(formatted) // round-trips to the same OIDFor cases where you already know the format, lower-level methods avoid parsing:
oid, _ := mib.ParseOID("1.3.6.1.2.1.2.2.1.1")
node := m.NodeByOID(oid) // exact match
node = m.LongestPrefixByOID(oid) // longest matching prefixLookupInstance returns both the matched node and the instance suffix, which is the standard pattern for processing SNMP varbinds:
lookup := m.LookupInstance(oid)
lookup.Node().Name() // "ifIndex"
lookup.Suffix() // OID{5} (trailing arcs after the matched node)mod := m.Module("IF-MIB")
obj := mod.Object("ifIndex")
typ := mod.Type("InterfaceIndex")Module exposes both MODULE-IDENTITY metadata and import-resolution details:
mod := m.Module("IF-MIB")
mod.Name() // "IF-MIB"
mod.Language() // SMIv2
mod.SourcePath() // file path, or "" for synthetic base modules
mod.IsBase() // false for user/system modules, true for built-ins
mod.OID() // module identity OID
mod.Organization() // ORGANIZATION clause
mod.ContactInfo() // CONTACT-INFO clause
mod.Description() // DESCRIPTION clause
mod.LastUpdated() // LAST-UPDATED value
mod.Revisions() // []Revision
mod.Imports() // []Import from the IMPORTS clause
mod.ImportsSymbol("DisplayString") // true if listed in IMPORTS
mod.IsImportUsed("DisplayString") // true if referenced during resolution
mod.ImportSource("DisplayString") // resolved source moduleBase modules are always present and define the SMI language itself:
base := m.Module("SNMPv2-SMI")
base.IsBase() // true
base.SourcePath() // ""
base.Node("enterprises").OID() // 1.3.6.1.4.1Module introspection methods:
mod.DefinesSymbol("ifIndex") // true if module defines this name
mod.ImportsSymbol("DisplayString") // true if module imports this name
mod.IsImportUsed("DisplayString") // true if import was referenced during resolution
mod.ImportSource("DisplayString") // *Module for the source of the import
for sym := range mod.Definitions() { // iterate all definitions as Symbol values
fmt.Println(sym.Name())
}m.Objects() // all OBJECT-TYPE definitions
m.Tables() // tables only
m.Scalars() // scalars only
m.Columns() // columns only
m.Rows() // rows only
m.Types() // all type definitions
m.Notifications() // all notifications
m.Groups() // all groups
m.Compliances() // all compliances
m.Capabilities() // all capabilities
m.Modules() // all loaded modules
m.AllSymbols() // all definitions across all modules (iter.Seq[Symbol])for node := range m.Nodes() {
fmt.Println(node.OID(), node.Name(), node.Kind())
}
// Subtree iteration
node := m.Node("ifEntry")
for child := range node.Subtree() {
fmt.Println(child.Name())
}
// Node metadata (populated for base module OIDs and OBJECT-IDENTITY definitions)
node.Description() // OID assignment description text
node.Reference() // reference stringEach Object carries its type, access level, status, and position in the OID tree:
obj := m.Object("ifType")
obj.Name() // "ifType"
obj.OID() // 1.3.6.1.2.1.2.2.1.3
obj.Kind() // column
obj.Access() // read-only
obj.Status() // current
obj.Type().Name() // "IANAifType"
obj.Units() // "" (empty if not set)
obj.Description() // "The type of interface..."table := m.Object("ifTable")
table.IsTable() // true
row := table.Entry() // ifEntry
cols := row.Columns() // [ifIndex, ifDescr, ifType, ...]
idxs := row.EffectiveIndexes() // handles AUGMENTS
for _, idx := range idxs {
fmt.Printf("INDEX %s (implied=%v)\n", idx.Object.Name(), idx.Implied)
}Navigate from any level: obj.Table() returns the containing table, obj.Row() returns the containing row.
Constraints can be defined inline on the object or inherited through the type chain. The Effective* methods walk both:
obj.EffectiveEnums() // enum values
obj.EffectiveBits() // BITS values
obj.EffectiveRanges() // value ranges
obj.EffectiveSizes() // size constraints
obj.EffectiveDisplayHint() // display hint stringTypes form chains: a textual convention references a parent type, which may reference another, down to a base SMI type.
typ := m.Type("DisplayString")
typ.Name() // "DisplayString"
typ.IsTextualConvention() // true
typ.Base() // OCTET STRING
typ.DisplayHint() // "255a"
typ.Sizes() // [{0 255}]
typ.Parent().Name() // base type reference
// Walk the chain
for t := typ; t != nil; t = t.Parent() {
fmt.Printf("%s (base: %s)\n", t.Name(), t.Base())
}
// Effective values resolve through the chain
typ.EffectiveBase() // underlying base type
typ.EffectiveDisplayHint() // first non-empty hint in chain
typ.EffectiveEnums() // first non-empty enum setClassification helpers: IsCounter(), IsGauge(), IsString(), IsEnumeration(), IsBits().
notif := m.Notification("linkDown")
notif.Name() // "linkDown"
notif.OID() // 1.3.6.1.6.3.1.1.5.3
notif.Status() // current
notif.Description() // "A linkDown trap..."
for _, obj := range notif.Objects() {
fmt.Printf(" varbind: %s (%s)\n", obj.Name(), obj.OID())
}SMIv1 TRAP-TYPE definitions populate TrapInfo():
if trap := notif.TrapInfo(); trap != nil {
fmt.Printf("enterprise=%s trap-number=%d\n", trap.Enterprise, trap.TrapNumber)
}gomib also exposes OBJECT-GROUP, NOTIFICATION-GROUP, MODULE-COMPLIANCE, and AGENT-CAPABILITIES:
grp := m.Group("ifGeneralInformationGroup")
grp.IsNotificationGroup() // false for OBJECT-GROUP
for _, member := range grp.Members() {
fmt.Println(member.Name(), member.OID())
}
comp := m.Compliance("ifCompliance3")
for _, mod := range comp.Modules() {
fmt.Println("MODULE", mod.ModuleName) // empty = current module
fmt.Println("mandatory groups:", mod.MandatoryGroups)
}
cap := m.Capability("someAgentCaps")
if cap != nil {
fmt.Println(cap.ProductRelease())
for _, sup := range cap.Supports() {
fmt.Println("SUPPORTS", sup.ModuleName, sup.Includes)
}
}Loading produces diagnostics for issues found during parsing and resolution.
m, err := gomib.Load(ctx, gomib.WithSource(src))
// Check for errors
if m.HasErrors() {
fmt.Println("errors found")
}
// Inspect all diagnostics
for _, d := range m.Diagnostics() {
fmt.Printf("[%s] %s: %s (line %d)\n", d.Severity, d.Module, d.Message, d.Line)
}
// Check unresolved references
for _, ref := range m.Unresolved() {
fmt.Printf("unresolved %s: %s in %s\n", ref.Kind, ref.Symbol, ref.Module)
}Resolver strictness controls fallback behavior:
| Resolver strictness | Constant | Behavior |
|---|---|---|
| Strict | ResolverStrict |
Tier 1 only (deterministic scope/import resolution) |
| Normal | ResolverNormal |
Tier 1 + Tier 2 (constrained assumptions) |
| Permissive | ResolverPermissive |
Tier 1 + Tier 2 + Tier 3 (global search fallbacks) |
m, _ := gomib.Load(ctx, gomib.WithSource(src), gomib.WithResolverStrictness(mib.ResolverPermissive))Diagnostic reporting is independent and configured with WithDiagnosticConfig:
gomib.WithDiagnosticConfig(mib.DiagnosticConfig{
Reporting: mib.ReportingDefault,
FailAt: mib.SeverityError, // fail on Error or worse
Ignore: []string{"identifier-underscore"}, // suppress specific codes
Overrides: map[string]mib.Severity{
"import-not-found": mib.SeverityWarning, // downgrade to warning
},
})CLI equivalents follow the same split:
gomib load --strictandgomib load --permissivechange resolver behavior.gomib load --report silent|quiet|default|verbosechanges diagnostic output.gomib getandgomib finddefault to permissive resolution with silent reporting.gomib lintis separate from resolver strictness and filters diagnostics by severity.
The token package provides direct access to the lexer for tooling, linting, or syntax highlighting:
import "github.com/golangsnmp/gomib/token"
source := []byte(`ifIndex OBJECT-TYPE SYNTAX InterfaceIndex`)
tokens := token.Tokenize(source)
for _, tok := range tokens {
if tok.Kind == token.EOF {
break
}
text := source[tok.Span.Start:tok.Span.End]
fmt.Printf("%-20s %s\n", tok.Kind.LibsmiName(), text)
}Token kind classification methods on TokenKind:
tok.Kind.IsKeyword() // any keyword
tok.Kind.IsMacroKeyword() // OBJECT-TYPE, MODULE-IDENTITY, etc.
tok.Kind.IsTypeKeyword() // INTEGER, Counter32, OCTET, etc.
tok.Kind.IsClauseKeyword() // SYNTAX, MAX-ACCESS, STATUS, etc.
tok.Kind.IsIdentifier() // uppercase or lowercase identifier
tok.Kind.IsStructuralKeyword() // DEFINITIONS, BEGIN, END, IMPORTS, etc.
tok.Kind.IsStatusAccessKeyword() // current, deprecated, read-only, etc.The cmd/gomib tool provides a command-line interface for MIB operations:
gomib load IF-MIB # load and show statistics
gomib get -m IF-MIB ifIndex # query by name
gomib get -m IF-MIB 1.3.6.1.2.1.2 # query by OID
gomib inspect ifDescr # deep-dive with type chain, provenance, diagnostics
gomib dump IF-MIB # JSON output
gomib lint IF-MIB # check for issues
gomib find -p testdata/corpus/primary 'if*' # search by pattern
gomib normalize IF-MIB # emit canonical SMIv2 text
gomib trace -m IF-MIB ifEntry # trace resolution
gomib paths # show search paths
gomib list # list available modules
Use -p PATH to specify MIB search paths (repeatable). Without -p, paths are discovered from net-snmp and libsmi configuration (config files, MIBDIRS/SMIPATH env vars, standard default directories).
See cmd/gomib/README.md for full command reference.