package snmp import ( "errors" "fmt" "strings" "github.com/sleepinggenius2/gosmi" "github.com/sleepinggenius2/gosmi/models" "github.com/sleepinggenius2/gosmi/types" "github.com/influxdata/telegraf" ) var errCannotFormatUnkownType = errors.New("cannot format value, unknown type") type gosmiTranslator struct { } func NewGosmiTranslator(paths []string, log telegraf.Logger) (*gosmiTranslator, error) { err := LoadMibsFromPath(paths, log, &GosmiMibLoader{}) if err == nil { return &gosmiTranslator{}, nil } return nil, err } //nolint:revive //function-result-limit conditionally 5 return results allowed func (g *gosmiTranslator) SnmpTranslate(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) { mibName, oidNum, oidText, conversion, _, err = snmpTranslateCall(oid) return mibName, oidNum, oidText, conversion, err } // snmpTable resolves the given OID as a table, providing information about the // table and fields within. // //nolint:revive //Too many return variable but necessary func (g *gosmiTranslator) SnmpTable(oid string) ( mibName string, oidNum string, oidText string, fields []Field, err error) { mibName, oidNum, oidText, _, node, err := snmpTranslateCall(oid) if err != nil { return "", "", "", nil, fmt.Errorf("translating: %w", err) } mibPrefix := mibName + "::" col, tagOids := getIndex(mibPrefix, node) for _, c := range col { _, isTag := tagOids[mibPrefix+c] fields = append(fields, Field{Name: c, Oid: mibPrefix + c, IsTag: isTag}) } return mibName, oidNum, oidText, fields, nil } func (*gosmiTranslator) SnmpFormatEnum(oid string, value interface{}, full bool) (string, error) { if value == nil { return "", nil } //nolint:dogsled // only need to get the node _, _, _, _, node, err := snmpTranslateCall(oid) if err != nil { return "", err } if node.Type == nil { return "", errCannotFormatUnkownType } var v models.Value if full { v = node.FormatValue(value, models.FormatEnumName, models.FormatEnumValue) } else { v = node.FormatValue(value, models.FormatEnumName) } return v.String(), nil } func (*gosmiTranslator) SnmpFormatDisplayHint(oid string, value interface{}) (string, error) { if value == nil { return "", nil } //nolint:dogsled // only need to get the node _, _, _, _, node, err := snmpTranslateCall(oid) if err != nil { return "", err } if node.Type == nil { return "", errCannotFormatUnkownType } return node.FormatValue(value).String(), nil } func getIndex(mibPrefix string, node gosmi.SmiNode) (col []string, tagOids map[string]struct{}) { // first attempt to get the table's tags // mimcks grabbing INDEX {} that is returned from snmptranslate -Td MibName indices := node.GetIndex() tagOids = make(map[string]struct{}, len(indices)) for _, index := range indices { tagOids[mibPrefix+index.Name] = struct{}{} } // grabs all columns from the table // mimmicks grabbing everything returned from snmptable -Ch -Cl -c public 127.0.0.1 oidFullName _, col = node.GetColumns() return col, tagOids } //nolint:revive //Too many return variable but necessary func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, node gosmi.SmiNode, err error) { var out gosmi.SmiNode var end string if strings.ContainsAny(oid, "::") { // split given oid // for example RFC1213-MIB::sysUpTime.0 s := strings.SplitN(oid, "::", 2) // moduleName becomes RFC1213 moduleName := s[0] module, err := gosmi.GetModule(moduleName) if err != nil { return oid, oid, oid, "", gosmi.SmiNode{}, err } if s[1] == "" { return "", oid, oid, "", gosmi.SmiNode{}, fmt.Errorf("cannot parse %v", oid) } // node becomes sysUpTime.0 node := s[1] if strings.ContainsAny(node, ".") { s = strings.SplitN(node, ".", 2) // node becomes sysUpTime node = s[0] end = "." + s[1] } out, err = module.GetNode(node) if err != nil { return oid, oid, oid, "", out, err } if oidNum = out.RenderNumeric(); oidNum == "" { return oid, oid, oid, "", out, fmt.Errorf("cannot translate %v into a numeric OID, please ensure all imported MIBs are in the path", oid) } oidNum = "." + oidNum + end } else if strings.ContainsAny(oid, "abcdefghijklnmopqrstuvwxyz") { //handle mixed oid ex. .iso.2.3 s := strings.Split(oid, ".") for i := range s { if strings.ContainsAny(s[i], "abcdefghijklmnopqrstuvwxyz") { out, err = gosmi.GetNode(s[i]) if err != nil { return oid, oid, oid, "", out, err } s[i] = out.RenderNumeric() } } oidNum = strings.Join(s, ".") out, err = gosmi.GetNodeByOID(types.OidMustFromString(oidNum)) if err != nil { return oid, oid, oid, "", out, err } } else { out, err = gosmi.GetNodeByOID(types.OidMustFromString(oid)) oidNum = oid // ensure modules are loaded or node will be empty (might not error) //nolint:nilerr // do not return the err as the oid is numeric and telegraf can continue if err != nil || out.Name == "iso" { return oid, oid, oid, "", out, nil } } tc := out.GetSubtree() for i := range tc { // case where the mib doesn't have a conversion so Type struct will be nil // prevents seg fault if tc[i].Type == nil { break } if tc[i].Type.Format != "" { conversion = "displayhint" } else { switch tc[i].Type.Name { case "InetAddress", "IPSIpAddress": conversion = "ipaddr" } } } oidText = out.RenderQualified() i := strings.Index(oidText, "::") if i == -1 { return "", oid, oid, "", out, errors.New("not found") } mibName = oidText[:i] oidText = oidText[i+2:] + end return mibName, oidNum, oidText, conversion, out, nil } // The following is for snmp_trap type MibEntry struct { MibName string OidText string } func TrapLookup(oid string) (e MibEntry, err error) { var givenOid types.Oid if givenOid, err = types.OidFromString(oid); err != nil { return e, fmt.Errorf("could not convert OID %s: %w", oid, err) } // Get node name var node gosmi.SmiNode if node, err = gosmi.GetNodeByOID(givenOid); err != nil { return e, err } e.OidText = node.Name // Add not found OID part if !givenOid.Equals(node.Oid) { e.OidText += "." + givenOid[len(node.Oid):].String() } // Get module name module := node.GetModule() if module.Name != "" { e.MibName = module.Name } return e, nil }