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  }