基于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从已解析的模块中获取对应数据,目前有两个问题:
- 已解析多个模块,如何确定需要查询的目标模块,以解码OID并获取准确节点?
- 是否存在内置库可用于解码OID并从解析的模块中获取数据?若没有,例如针对OID为
2.1.1.4057的情况,采用何种逻辑能最优地在模块与节点的层级结构中解码OID并获取4057节点的详情?
解决方案
问题1:确定目标模块
核心思路是构建OID前缀与模块的映射关系——每个MIB模块的MODULE-IDENTITY都有唯一的OID前缀,通过前缀匹配就能快速定位OID所属模块。
具体实现
- 预构建前缀映射表:在
Init函数解析完所有模块后,生成一个oidPrefixToModule映射,键为模块的完整OID前缀字符串,值为对应模块指针。 - 逐级匹配前缀:将输入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匹配节点的方式,具体步骤:
- 拆分OID为子ID数组:
2.1.1.4057拆分为[2,1,1,4057]。 - 用问题1的方法找到前缀
2.1.1对应的模块。 - 用剩余子ID
4057在模块的节点中匹配;如果是更长的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
相关产品推荐
相关产品推荐

