github.com/influxdata/telegraf@v1.30.3/internal/snmp/field.go (about) 1 package snmp 2 3 import ( 4 "encoding/binary" 5 "errors" 6 "fmt" 7 "math" 8 "net" 9 "strconv" 10 "strings" 11 12 "github.com/gosnmp/gosnmp" 13 ) 14 15 // Field holds the configuration for a Field to look up. 16 type Field struct { 17 // Name will be the name of the field. 18 Name string 19 // OID is prefix for this field. The plugin will perform a walk through all 20 // OIDs with this as their parent. For each value found, the plugin will strip 21 // off the OID prefix, and use the remainder as the index. For multiple fields 22 // to show up in the same row, they must share the same index. 23 Oid string 24 // OidIndexSuffix is the trailing sub-identifier on a table record OID that will be stripped off to get the record's index. 25 OidIndexSuffix string 26 // OidIndexLength specifies the length of the index in OID path segments. It can be used to remove sub-identifiers that vary in content or length. 27 OidIndexLength int 28 // IsTag controls whether this OID is output as a tag or a value. 29 IsTag bool 30 // Conversion controls any type conversion that is done on the value. 31 // "float"/"float(0)" will convert the value into a float. 32 // "float(X)" will convert the value into a float, and then move the decimal before Xth right-most digit. 33 // "int" will convert the value into an integer. 34 // "hwaddr" will convert a 6-byte string to a MAC address. 35 // "ipaddr" will convert the value to an IPv4 or IPv6 address. 36 // "enum"/"enum(1)" will convert the value according to its syntax. (Only supported with gosmi translator) 37 Conversion string 38 // Translate tells if the value of the field should be snmptranslated 39 Translate bool 40 // Secondary index table allows to merge data from two tables with different index 41 // that this filed will be used to join them. There can be only one secondary index table. 42 SecondaryIndexTable bool 43 // This field is using secondary index, and will be later merged with primary index 44 // using SecondaryIndexTable. SecondaryIndexTable and SecondaryIndexUse are exclusive. 45 SecondaryIndexUse bool 46 // Controls if entries from secondary table should be added or not if joining 47 // index is present or not. I set to true, means that join is outer, and 48 // index is prepended with "Secondary." for missing values to avoid overlapping 49 // indexes from both tables. 50 // Can be set per field or globally with SecondaryIndexTable, global true overrides 51 // per field false. 52 SecondaryOuterJoin bool 53 54 initialized bool 55 translator Translator 56 } 57 58 // init() converts OID names to numbers, and sets the .Name attribute if unset. 59 func (f *Field) Init(tr Translator) error { 60 if f.initialized { 61 return nil 62 } 63 64 f.translator = tr 65 66 // check if oid needs translation or name is not set 67 if strings.ContainsAny(f.Oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") || f.Name == "" { 68 _, oidNum, oidText, conversion, err := f.translator.SnmpTranslate(f.Oid) 69 if err != nil { 70 return fmt.Errorf("translating: %w", err) 71 } 72 f.Oid = oidNum 73 if f.Name == "" { 74 f.Name = oidText 75 } 76 if f.Conversion == "" { 77 f.Conversion = conversion 78 } 79 //TODO use textual convention conversion from the MIB 80 } 81 82 if f.SecondaryIndexTable && f.SecondaryIndexUse { 83 return errors.New("SecondaryIndexTable and UseSecondaryIndex are exclusive") 84 } 85 86 if !f.SecondaryIndexTable && !f.SecondaryIndexUse && f.SecondaryOuterJoin { 87 return errors.New("SecondaryOuterJoin set to true, but field is not being used in join") 88 } 89 90 f.initialized = true 91 return nil 92 } 93 94 // fieldConvert converts from any type according to the conv specification 95 func (f *Field) Convert(ent gosnmp.SnmpPDU) (v interface{}, err error) { 96 if f.Conversion == "" { 97 if bs, ok := ent.Value.([]byte); ok { 98 return string(bs), nil 99 } 100 return ent.Value, nil 101 } 102 103 var d int 104 if _, err := fmt.Sscanf(f.Conversion, "float(%d)", &d); err == nil || f.Conversion == "float" { 105 v = ent.Value 106 switch vt := v.(type) { 107 case float32: 108 v = float64(vt) / math.Pow10(d) 109 case float64: 110 v = vt / math.Pow10(d) 111 case int: 112 v = float64(vt) / math.Pow10(d) 113 case int8: 114 v = float64(vt) / math.Pow10(d) 115 case int16: 116 v = float64(vt) / math.Pow10(d) 117 case int32: 118 v = float64(vt) / math.Pow10(d) 119 case int64: 120 v = float64(vt) / math.Pow10(d) 121 case uint: 122 v = float64(vt) / math.Pow10(d) 123 case uint8: 124 v = float64(vt) / math.Pow10(d) 125 case uint16: 126 v = float64(vt) / math.Pow10(d) 127 case uint32: 128 v = float64(vt) / math.Pow10(d) 129 case uint64: 130 v = float64(vt) / math.Pow10(d) 131 case []byte: 132 vf, _ := strconv.ParseFloat(string(vt), 64) 133 v = vf / math.Pow10(d) 134 case string: 135 vf, _ := strconv.ParseFloat(vt, 64) 136 v = vf / math.Pow10(d) 137 } 138 return v, nil 139 } 140 141 if f.Conversion == "int" { 142 v = ent.Value 143 switch vt := v.(type) { 144 case float32: 145 v = int64(vt) 146 case float64: 147 v = int64(vt) 148 case int: 149 v = int64(vt) 150 case int8: 151 v = int64(vt) 152 case int16: 153 v = int64(vt) 154 case int32: 155 v = int64(vt) 156 case int64: 157 v = vt 158 case uint: 159 v = int64(vt) 160 case uint8: 161 v = int64(vt) 162 case uint16: 163 v = int64(vt) 164 case uint32: 165 v = int64(vt) 166 case uint64: 167 v = int64(vt) 168 case []byte: 169 v, _ = strconv.ParseInt(string(vt), 10, 64) 170 case string: 171 v, _ = strconv.ParseInt(vt, 10, 64) 172 } 173 return v, nil 174 } 175 176 if f.Conversion == "hwaddr" { 177 switch vt := ent.Value.(type) { 178 case string: 179 v = net.HardwareAddr(vt).String() 180 case []byte: 181 v = net.HardwareAddr(vt).String() 182 default: 183 return nil, fmt.Errorf("invalid type (%T) for hwaddr conversion", v) 184 } 185 return v, nil 186 } 187 188 split := strings.Split(f.Conversion, ":") 189 if split[0] == "hextoint" && len(split) == 3 { 190 endian := split[1] 191 bit := split[2] 192 193 bv, ok := ent.Value.([]byte) 194 if !ok { 195 return ent.Value, nil 196 } 197 198 switch endian { 199 case "LittleEndian": 200 switch bit { 201 case "uint64": 202 v = binary.LittleEndian.Uint64(bv) 203 case "uint32": 204 v = binary.LittleEndian.Uint32(bv) 205 case "uint16": 206 v = binary.LittleEndian.Uint16(bv) 207 default: 208 return nil, fmt.Errorf("invalid bit value (%s) for hex to int conversion", bit) 209 } 210 case "BigEndian": 211 switch bit { 212 case "uint64": 213 v = binary.BigEndian.Uint64(bv) 214 case "uint32": 215 v = binary.BigEndian.Uint32(bv) 216 case "uint16": 217 v = binary.BigEndian.Uint16(bv) 218 default: 219 return nil, fmt.Errorf("invalid bit value (%s) for hex to int conversion", bit) 220 } 221 default: 222 return nil, fmt.Errorf("invalid Endian value (%s) for hex to int conversion", endian) 223 } 224 225 return v, nil 226 } 227 228 if f.Conversion == "ipaddr" { 229 var ipbs []byte 230 231 switch vt := ent.Value.(type) { 232 case string: 233 ipbs = []byte(vt) 234 case []byte: 235 ipbs = vt 236 default: 237 return nil, fmt.Errorf("invalid type (%T) for ipaddr conversion", v) 238 } 239 240 switch len(ipbs) { 241 case 4, 16: 242 v = net.IP(ipbs).String() 243 default: 244 return nil, fmt.Errorf("invalid length (%d) for ipaddr conversion", len(ipbs)) 245 } 246 247 return v, nil 248 } 249 250 if f.Conversion == "enum" { 251 return f.translator.SnmpFormatEnum(ent.Name, ent.Value, false) 252 } 253 254 if f.Conversion == "enum(1)" { 255 return f.translator.SnmpFormatEnum(ent.Name, ent.Value, true) 256 } 257 258 return nil, fmt.Errorf("invalid conversion type %q", f.Conversion) 259 }