bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/scollector/collectors/snmp.go (about) 1 package collectors 2 3 import ( 4 "fmt" 5 "io" 6 "math/big" 7 "reflect" 8 "strconv" 9 "strings" 10 11 "bosun.org/cmd/scollector/conf" 12 "bosun.org/metadata" 13 "bosun.org/opentsdb" 14 "bosun.org/slog" 15 "bosun.org/snmp" 16 ) 17 18 var builtInSNMPs = map[string]func(cfg conf.SNMP){ 19 "ifaces": SNMPIfaces, 20 "ios": SNMPCiscoIOS, 21 "asa": SNMPCiscoASA, 22 "nxos": SNMPCiscoNXOS, 23 "bridge": SNMPBridge, 24 "ips": SNMPIPAddresses, 25 "ciscobgp": SNMPCiscoBGP, 26 "sys": SNMPSys, 27 "fortinet": SNMPFortinet, 28 "lag": SNMPLag, 29 } 30 31 func SNMP(cfg conf.SNMP, mibs map[string]conf.MIB) error { 32 if cfg.Host == "" { 33 return fmt.Errorf("empty SNMP hostname") 34 } 35 if cfg.Community == "" { 36 return fmt.Errorf("empty SNMP community") 37 } 38 if len(cfg.MIBs) == 0 { 39 cfg.MIBs = []string{"ifaces", "cisco", "bridge"} 40 } 41 for _, m := range cfg.MIBs { 42 mib, ok := mibs[m] 43 if ok { 44 collectors = append(collectors, &IntervalCollector{ 45 F: func() (opentsdb.MultiDataPoint, error) { 46 return GenericSnmp(cfg, mib) 47 }, 48 name: fmt.Sprintf("snmp-generic-%s-%s", cfg.Host, m), 49 }) 50 } else { 51 if f, ok := builtInSNMPs[m]; ok { 52 f(cfg) 53 } else { 54 return fmt.Errorf("unknown MIB \"%s\" specified", m) 55 } 56 } 57 } 58 return nil 59 } 60 61 // snmp_subtree takes an oid and returns all data exactly one level below it. It 62 // produces an error if there is more than one level below. 63 func snmp_subtree(host, community, oid string) (map[string]interface{}, error) { 64 rows, err := snmp.Walk(host, community, oid) 65 if err != nil { 66 return nil, err 67 } 68 m := make(map[string]interface{}) 69 for rows.Next() { 70 key := "" 71 var a interface{} 72 switch oid { 73 case ifHCInBroadcastPkts: 74 a = new(big.Int) 75 id, err := rows.Scan(&a) 76 if err != nil { 77 slog.Errorf("Error scanning oid %v on host %v: %v", oid, host, err) 78 continue 79 } 80 switch t := id.(type) { 81 case int: 82 key = fmt.Sprint(t) 83 default: 84 return nil, fmt.Errorf("snmp subtree: only one level allowed") 85 } 86 default: 87 id, err := rows.Scan(&a) 88 if err != nil { 89 slog.Errorf("Error scanning oid %v on host %v: %v", oid, host, err) 90 continue 91 } 92 switch t := id.(type) { 93 case int: 94 key = fmt.Sprint(t) 95 case []int: 96 key = snmpOidArrayToString(t) 97 default: 98 return nil, fmt.Errorf("Unknown key type: %s", reflect.TypeOf(id).String()) 99 } 100 } 101 m[key] = a 102 } 103 if err := rows.Err(); err != nil && err != io.EOF { 104 return nil, err 105 } 106 return m, nil 107 } 108 109 // recombine an oid array to a dot-delimited string 110 func snmpOidArrayToString(path []int) string { 111 s := make([]string, len(path)) 112 for i := range path { 113 s[i] = fmt.Sprint(path[i]) 114 } 115 return strings.Join(s, ".") 116 } 117 118 func snmp_oid(host, community, oid string) (*big.Int, error) { 119 v := new(big.Int) 120 err := snmp.Get(host, community, oid, &v) 121 return v, err 122 } 123 124 func snmpOidString(host, community, oid string) (string, error) { 125 var v []byte 126 err := snmp.Get(host, community, oid, &v) 127 return string(v), err 128 } 129 130 func snmp_convertToFloat(v interface{}) (float64, error) { 131 switch val := v.(type) { 132 case int: 133 return float64(val), nil 134 case int32: 135 return float64(val), nil 136 case int64: 137 return float64(val), nil 138 case float64: 139 return val, nil 140 case *big.Int: 141 return float64(val.Int64()), nil 142 case string: 143 return strconv.ParseFloat(val, 64) 144 case []uint8: 145 return strconv.ParseFloat(string(val), 64) 146 default: 147 return 0, fmt.Errorf("Cannot convert type %s to float64", reflect.TypeOf(v)) 148 } 149 } 150 151 func combineOids(oid, base string) string { 152 if oid != "" && oid[0] == '.' { 153 return base + oid 154 } 155 return oid 156 } 157 158 func GenericSnmp(cfg conf.SNMP, mib conf.MIB) (opentsdb.MultiDataPoint, error) { 159 md := opentsdb.MultiDataPoint{} 160 baseOid := mib.BaseOid 161 162 rateUnitTags := func(m conf.MIBMetric) (r metadata.RateType, u metadata.Unit, t opentsdb.TagSet, err error) { 163 if r = metadata.RateType(m.RateType); r == "" { 164 r = metadata.Gauge 165 } 166 if u = metadata.Unit(m.Unit); u == "" { 167 u = metadata.None 168 } 169 if m.Tags == "" { 170 t = make(opentsdb.TagSet) 171 } else { 172 t, err = opentsdb.ParseTags(m.Tags) 173 if err != nil { 174 return "", "", nil, err 175 } 176 } 177 t["host"] = cfg.Host 178 return 179 } 180 181 for _, metric := range mib.Metrics { 182 rate, unit, tagset, err := rateUnitTags(metric) 183 if err != nil { 184 return md, err 185 } 186 187 v, err := snmp_oid(cfg.Host, cfg.Community, combineOids(metric.Oid, baseOid)) 188 if err != nil && metric.FallbackOid != "" { 189 v, err = snmp_oid(cfg.Host, cfg.Community, combineOids(metric.FallbackOid, baseOid)) 190 } 191 if err != nil { 192 return md, err 193 } 194 val, err := snmp_convertToFloat(v) 195 if err != nil { 196 return md, err 197 } 198 if metric.Scale != 0 { 199 val = val * metric.Scale 200 } 201 Add(&md, metric.Metric, val, tagset, rate, unit, metric.Description) 202 } 203 204 for _, tree := range mib.Trees { 205 treeOid := combineOids(tree.BaseOid, baseOid) 206 tagCache := make(map[string]map[string]interface{}) // tag key to map of values 207 for _, tag := range tree.Tags { 208 if tag.Oid == "idx" { 209 continue 210 } 211 vals, err := snmp_subtree(cfg.Host, cfg.Community, combineOids(tag.Oid, treeOid)) 212 if err != nil { 213 return md, err 214 } 215 tagCache[tag.Key] = vals 216 } 217 for _, metric := range tree.Metrics { 218 rate, unit, tagset, err := rateUnitTags(metric) 219 if err != nil { 220 return md, err 221 222 } 223 nodes, err := snmp_subtree(cfg.Host, cfg.Community, combineOids(metric.Oid, treeOid)) 224 if err != nil && metric.FallbackOid != "" { 225 nodes, err = snmp_subtree(cfg.Host, cfg.Community, combineOids(metric.FallbackOid, treeOid)) 226 } 227 if err != nil { 228 return md, err 229 } 230 // check all lengths 231 for k, list := range tagCache { 232 if len(list) != len(nodes) { 233 return md, fmt.Errorf("snmp tree for tag key %s, and metric %s do not have same length", k, metric.Metric) 234 } 235 } 236 for i, v := range nodes { 237 for _, tag := range tree.Tags { 238 var tagVal interface{} 239 if tag.Oid == "idx" { 240 tagVal = i 241 } else { 242 var ok bool 243 tagVal, ok = tagCache[tag.Key][i] 244 if !ok { 245 return md, fmt.Errorf("tree for tag %s has no entry for metric %s index %s", tag.Key, metric.Metric, i) 246 } 247 } 248 if byteSlice, ok := tagVal.([]byte); ok { 249 tagVal = string(byteSlice) 250 } 251 tagset[tag.Key] = fmt.Sprint(tagVal) 252 } 253 val, err := snmp_convertToFloat(v) 254 if err != nil { 255 return md, err 256 } 257 if metric.Scale != 0 { 258 val = val * metric.Scale 259 } 260 Add(&md, metric.Metric, val, tagset, rate, unit, metric.Description) 261 } 262 } 263 } 264 return md, nil 265 }