From c396a1254ff20b6837e82959f3bc9e1ac6ad1b7f Mon Sep 17 00:00:00 2001 From: Thomas Roehl Date: Fri, 8 Dec 2023 16:58:49 +0100 Subject: [PATCH] Add SNMP test tool --- receivers/README.md | 1 + receivers/snmpReceiver.md | 6 +- scripts/snmpReceiverTest/README.md | 37 ++++ scripts/snmpReceiverTest/snmpReceiverTest.go | 167 +++++++++++++++++++ 4 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 scripts/snmpReceiverTest/README.md create mode 100644 scripts/snmpReceiverTest/snmpReceiverTest.go diff --git a/receivers/README.md b/receivers/README.md index 1f135fe..012eb0d 100644 --- a/receivers/README.md +++ b/receivers/README.md @@ -24,6 +24,7 @@ This allows to specify - [`http`](./httpReceiver.md): Listen for HTTP Post requests transporting metrics in InfluxDB line protocol - [`ipmi`](./ipmiReceiver.md): Read IPMI sensor readings - [`redfish`](redfishReceiver.md) Use the Redfish (specification) to query thermal and power metrics +- [`snmp`](./snmpReceiver.md) Query SNMP endpoints in the network ## Contributing own receivers diff --git a/receivers/snmpReceiver.md b/receivers/snmpReceiver.md index 0acd509..c4c463d 100644 --- a/receivers/snmpReceiver.md +++ b/receivers/snmpReceiver.md @@ -53,4 +53,8 @@ Each network-attached device that should be queried. A target consits of - `value` has to be an OID - `unit` can be empty, an OID or a user-given string -If a OID is used for `name` or `unit`, the receiver will use the returned values to create the output metric. If there are any issues with the returned values, it uses the `OID`. \ No newline at end of file +If a OID is used for `name` or `unit`, the receiver will use the returned values to create the output metric. If there are any issues with the returned values, it uses the `OID`. + +## Testing + +For testing an SNMP endpoint and OIDs, you can use [`scripts/snmpReceiverTest`](../scripts/snmpReceiverTest) \ No newline at end of file diff --git a/scripts/snmpReceiverTest/README.md b/scripts/snmpReceiverTest/README.md new file mode 100644 index 0000000..c0556f9 --- /dev/null +++ b/scripts/snmpReceiverTest/README.md @@ -0,0 +1,37 @@ +# snmpReceiverTest + +This script is a basic implementation of how the SNMPReceiver to test the connection before configuring +the collector to get the data periodically. + +It does not support the specification of the `type`, `type-id`, `stype` and `stype-id` but since they are +not required to test the functionality, they are left out. + +## Usage + +```sh +$ go run snmpReceiverTest -h +Usage of snmpReceiverTest: + -community string + SNMP community (default "public") + -hostname string + Hostname (default "127.0.0.1") + -name string + Name of metric or OID + -port string + Port number (default "161") + -timeout string + Timeout for SNMP request (default "1s") + -unit string + Unit of metric or OID + -value string + Value OID + -version string + SNMP version (default "2c") +``` + +## Example + +```sh +$ go run scripts/snmpReceiverTest/snmpReceiverTest.go -name serialNumber -value .1.3.6.1.4.1.6574.1.5.2.0 -hostname $IP -community $COMMUNITY +Name: serialNumber, Tags: map[type:node], Meta: map[], fields: map[value:18B0PCNXXXXX], Timestamp: 1702050709599311288 +``` \ No newline at end of file diff --git a/scripts/snmpReceiverTest/snmpReceiverTest.go b/scripts/snmpReceiverTest/snmpReceiverTest.go new file mode 100644 index 0000000..a9d35b7 --- /dev/null +++ b/scripts/snmpReceiverTest/snmpReceiverTest.go @@ -0,0 +1,167 @@ +package main + +import ( + "flag" + "fmt" + "regexp" + "strconv" + "strings" + "time" + + lp "github.com/ClusterCockpit/cc-metric-collector/pkg/ccMetric" + "github.com/gosnmp/gosnmp" +) + +func ReadCLI() map[string]string { + args := map[string]string{ + "port": "161", + "community": "public", + "version": "2c", + "hostname": "127.0.0.1", + "timeout": "1s", + } + + host_cfg := flag.String("hostname", "127.0.0.1", "Hostname") + port_cfg := flag.String("port", "161", "Port number") + comm_cfg := flag.String("community", "public", "SNMP community") + vers_cfg := flag.String("version", "2c", "SNMP version") + time_cfg := flag.String("timeout", "1s", "Timeout for SNMP request") + + name_cfg := flag.String("name", "", "Name of metric or OID") + value_cfg := flag.String("value", "", "Value OID") + unit_cfg := flag.String("unit", "", "Unit of metric or OID") + + flag.Parse() + + args["port"] = *port_cfg + args["community"] = *comm_cfg + args["hostname"] = *host_cfg + args["version"] = *vers_cfg + args["timeout"] = *time_cfg + + args["name"] = *name_cfg + args["value"] = *value_cfg + args["unit"] = *unit_cfg + + if len(args["name"]) == 0 || len(args["value"]) == 0 { + fmt.Printf("Required arguments: --name and --value\n") + flag.Usage() + } + + return args +} + +func validOid(oid string) bool { + // Regex from https://github.com/BornToBeRoot/NETworkManager/blob/6805740762bf19b95051c7eaa73cf2b4727733c3/Source/NETworkManager.Utilities/RegexHelper.cs#L88 + // Match on leading dot added by Thomas Gruber + match, err := regexp.MatchString(`^[\.]?[012]\.(?:[0-9]|[1-3][0-9])(\.\d+)*$`, oid) + if err != nil { + return false + } + return match +} + +func main() { + + args := ReadCLI() + + if len(args["name"]) == 0 || len(args["value"]) == 0 { + return + } + + version := gosnmp.Version2c + if len(args["version"]) > 0 { + switch args["version"] { + case "1": + version = gosnmp.Version1 + case "2c": + version = gosnmp.Version2c + case "3": + version = gosnmp.Version3 + default: + fmt.Printf("Invalid SNMP version '%s'\n", args["version"]) + return + } + } + v, err := strconv.ParseInt(args["port"], 10, 16) + if err != nil { + fmt.Printf("Failed to parse port number '%s'\n", args["port"]) + return + } + port := uint16(v) + + t, err := time.ParseDuration(args["timeout"]) + if err != nil { + fmt.Printf("Failed to parse timeout '%s'\n", args["timeout"]) + return + } + timeout := t + + params := &gosnmp.GoSNMP{ + Target: args["hostname"], + Port: port, + Community: args["community"], + Version: version, + Timeout: timeout, + } + err = params.Connect() + if err != nil { + fmt.Printf("Failed to connect to %s:%d : %v\n", params.Target, params.Port, err.Error()) + return + } + + oids := make([]string, 0) + idx := 0 + name := gosnmp.SnmpPDU{ + Value: args["name"], + Name: args["name"], + } + nameidx := -1 + value := gosnmp.SnmpPDU{ + Value: nil, + Name: args["value"], + } + valueidx := -1 + unit := gosnmp.SnmpPDU{ + Value: args["unit"], + Name: args["unit"], + } + unitidx := -1 + if validOid(args["name"]) { + oids = append(oids, args["name"]) + nameidx = idx + idx++ + } + if validOid(args["value"]) { + oids = append(oids, args["value"]) + valueidx = idx + idx++ + } + if len(args["unit"]) > 0 && validOid(args["unit"]) { + oids = append(oids, args["unit"]) + unitidx = idx + } + result, err := params.Get(oids) + if err != nil { + fmt.Printf("Failed to get data for OIDs [%s] : %v\n", strings.Join(oids, ", "), err.Error()) + return + } + if nameidx >= 0 && len(result.Variables) > nameidx { + name = result.Variables[nameidx] + } + if valueidx >= 0 && len(result.Variables) > valueidx { + value = result.Variables[valueidx] + } + if unitidx >= 0 && len(result.Variables) > unitidx { + unit = result.Variables[unitidx] + } + if value.Value != nil { + y, err := lp.New(name.Value.(string), map[string]string{"type": "node"}, map[string]string{}, map[string]interface{}{"value": value.Value}, time.Now()) + if err == nil { + if len(unit.Name) > 0 && unit.Value != nil { + y.AddMeta("unit", unit.Value.(string)) + } + fmt.Println(y) + } + } +}