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  }