You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Go语言gosmi库,从解析MIB中根据SNMP Trap的OID获取节点详情

问题描述

我用Go语言的gosmi库解析了MIB文件,并用以下代码存储MIB模块:

package main

import (
    "fmt"
    "os"
    "path/filepath"

    "github.com/sleepinggenius2/gosmi/parser"
    "github.com/sleepinggenius2/gosmi/types"
)

// Module contains details of a MODULE-IDENTITY
type Module struct {
    Name   string
    Parent types.SmiIdentifier
    OID    types.SmiSubId
    Nodes  map[string]*Node
    Object parser.Module
}

func (m *Module) merge(newModule *Module) {
    // Merge the nodes
    for name, node := range newModule.Nodes {
        if m.Nodes[name] != nil {
            continue
        }
        newNode := *node
        m.Nodes[name] = &newNode
    }
}

var modules = map[string]*Module{}

// Node contains everything inside a MODULE-IDENTITY
type Node struct {
    Name        string
    Parent      types.SmiIdentifier
    OID         types.SmiSubId
    CompleteOID string
    Object      parser.Node
}

func parseNode(node *parser.Node) *Node {
    ret := &Node{
        Name:   node.Name.String(),
        Parent: *node.Oid.SubIdentifiers[0].Name,
        OID:    *node.Oid.SubIdentifiers[1].Number,
        Object: *node,
    }
    return ret
}

func parseModule(module *parser.Module) *Module {
    ret := &Module{
        Nodes:  make(map[string]*Node),
        Object: *module,
    }
    identity := module.Body.Identity
    ret.Name = identity.Name.String()
    ret.Parent = *identity.Oid.SubIdentifiers[0].Name
    ret.OID = *identity.Oid.SubIdentifiers[1].Number
    for _, node := range module.Body.Nodes {
        n := parseNode(&node)
        ret.Nodes[n.Name] = n
    }
    return ret
}

// Init reads all MIBs recursively inside "folder"
// Returns error or nil
func Init(folder string) error {
    var files []string

    err := filepath.Walk(folder, func(path string, info os.FileInfo, err error) error {
        if info.IsDir() {
            return nil
        }
        files = append(files, path)
        return nil
    })
    if err != nil {
        return err
    }

    for _, file := range files {

        module, err := parser.ParseFile(file)
        if err != nil {
            return err
        }

        mod := parseModule(module)

        if modules[mod.Name] == nil {
            modules[mod.Name] = mod
        } else {
            // Merge the modules
            modules[mod.Name].merge(mod)
        }
    }

    return nil
}

func main() {
    Init("/root/testsnmp")
    mod := modules["parallelwirelessTrapMIB"]
    fmt.Println(mod.Name)
    fmt.Println(mod.OID)

}

现在我搭建了SNMP服务器监听并打印Trap数据,数据包里包含OID,希望基于该OID从已解析的模块中获取对应数据,目前有两个问题:

  1. 已解析多个模块,如何确定需要查询的目标模块,以解码OID并获取准确节点?
  2. 是否存在内置库可用于解码OID并从解析的模块中获取数据?若没有,例如针对OID为2.1.1.4057的情况,采用何种逻辑能最优地在模块与节点的层级结构中解码OID并获取4057节点的详情?

解决方案

问题1:确定目标模块

核心思路是构建OID前缀与模块的映射关系——每个MIB模块的MODULE-IDENTITY都有唯一的OID前缀,通过前缀匹配就能快速定位OID所属模块。

具体实现

  1. 预构建前缀映射表:在Init函数解析完所有模块后,生成一个oidPrefixToModule映射,键为模块的完整OID前缀字符串,值为对应模块指针。
  2. 逐级匹配前缀:将输入OID按.拆分为子ID数组,从最长可能的前缀开始尝试匹配映射表,找到第一个匹配的模块即可。

代码补充

import "strings"

var oidPrefixToModule = map[string]*Module{}

// 重写Init函数,添加前缀映射构建逻辑
func Init(folder string) error {
    // ... 原有解析模块的代码 ...

    // 构建OID前缀到模块的映射
    oidPrefixToModule = make(map[string]*Module)
    for _, mod := range modules {
        fullPrefix := getFullOIDPrefix(mod)
        oidPrefixToModule[fullPrefix] = mod
    }
    return nil
}

// 递归获取模块的完整OID前缀
func getFullOIDPrefix(mod *Module) string {
    if mod.Parent.String() == "" {
        return fmt.Sprintf("%d", mod.OID)
    }
    parentMod := modules[mod.Parent.String()]
    if parentMod == nil {
        return fmt.Sprintf("%d", mod.OID)
    }
    parentPrefix := getFullOIDPrefix(parentMod)
    return fmt.Sprintf("%s.%d", parentPrefix, mod.OID)
}

问题2:OID解码与节点查找

关于内置库能力

gosmi本身提供了OID解析和节点关联的工具(比如types.SmiOid的相关方法),你可以直接调用这些方法来简化逻辑;如果要基于自己封装的Module和Node结构实现,可采用以下最优逻辑。

自定义查找逻辑(以2.1.1.4057为例)

采用前缀定位模块+剩余子ID匹配节点的方式,具体步骤:

  1. 拆分OID为子ID数组:2.1.1.4057拆分为[2,1,1,4057]。
  2. 用问题1的方法找到前缀2.1.1对应的模块。
  3. 用剩余子ID4057在模块的节点中匹配;如果是更长的OID(如2.1.1.4057.1),则从匹配到的节点开始递归遍历子节点,直到找到目标。

代码实现

import "strconv"

// 根据OID字符串查找对应节点
func FindNodeByOID(oidStr string) (*Node, error) {
    // 拆分OID为子ID数组
    parts := strings.Split(oidStr, ".")
    subIDs := make([]types.SmiSubId, len(parts))
    for i, part := range parts {
        id, err := strconv.ParseUint(part, 10, 64)
        if err != nil {
            return nil, err
        }
        subIDs[i] = types.SmiSubId(id)
    }

    // 查找所属模块并匹配节点
    for i := len(subIDs); i >= 1; i-- {
        prefixStr := strings.Join(parts[:i], ".")
        if mod, ok := oidPrefixToModule[prefixStr]; ok {
            remainingSubIDs := subIDs[i:]
            return findNodeInModule(mod, remainingSubIDs), nil
        }
    }

    return nil, fmt.Errorf("no module found for OID %s", oidStr)
}

// 在模块内递归查找节点
func findNodeInModule(mod *Module, subIDs []types.SmiSubId) *Node {
    if len(subIDs) == 0 {
        return nil
    }
    targetSubID := subIDs[0]
    for _, node := range mod.Nodes {
        if node.OID == targetSubID {
            if len(subIDs) > 1 {
                return findChildNode(node, subIDs[1:])
            }
            return node
        }
    }
    return nil
}

// 递归查找节点的子节点
func findChildNode(node *Node, subIDs []types.SmiSubId) *Node {
    if len(subIDs) == 0 {
        return node
    }
    targetSubID := subIDs[0]
    for _, child := range node.Object.Children {
        childNode := parseNode(&child)
        if childNode.OID == targetSubID {
            return findChildNode(childNode, subIDs[1:])
        }
    }
    return nil
}

优化建议

  • 解析模块时,为每个节点生成并存储CompleteOID字段,同时构建全局map[string]*Node映射,可直接通过OID字符串快速查找节点,无需逐级遍历。
  • 利用gosmi内置的types.SmiOid.String()方法生成完整OID,避免手动拼接出错。

内容的提问来源于stack exchange,提问作者Tanuja Shinde

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 11:55:04