From 0d07f8525718ba40ee882496a75b9ba3b6dfc067 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Thu, 2 Nov 2017 07:41:18 -0500 Subject: [PATCH] feature: Allow snmpget in os discovery yaml (#7587) * feature: Allow snmpget in os discovery yaml Re-submit after release * Remove extra trim in discovery and in snmp_get. also trim() always returns a string, so is_string() check is a waste of cpu cycles. --- doc/Developing/os/Initial-Detection.md | 2 + includes/definitions/asuswrt-merlin.yaml | 8 +- includes/definitions/ddnos.yaml | 7 ++ includes/definitions/dsm.yaml | 16 ++++ includes/definitions/extrahop.yaml | 9 ++ includes/definitions/freebsd.yaml | 2 + includes/definitions/huaweiups.yaml | 6 ++ includes/definitions/ibmtl.yaml | 4 + includes/definitions/linux.yaml | 3 + includes/definitions/pcoweb.yaml | 10 +++ includes/definitions/pktj.yaml | 7 ++ includes/definitions/qnap.yaml | 7 ++ includes/definitions/remoteye4.yaml | 8 +- includes/definitions/sentry3.yaml | 11 +++ includes/definitions/sentry4.yaml | 11 +++ includes/definitions/tomato.yaml | 7 ++ includes/discovery/os/dsm.inc.php | 17 ---- includes/discovery/os/freebsd.inc.php | 6 -- includes/discovery/os/huaweiups.inc.php | 7 -- includes/discovery/os/ibmtl.inc.php | 17 ---- includes/discovery/os/linux.inc.php | 35 -------- includes/discovery/os/sentry3.inc.php | 14 --- includes/discovery/os/sentry4.inc.php | 14 --- includes/discovery/os/ubnt.inc.php | 14 +++ includes/functions.php | 106 ++++++++++++++++++----- includes/snmp.inc.php | 25 ++++-- tests/YamlTest.php | 57 ++++++++++++ tests/mocks/mock.snmp.inc.php | 34 +++++++- 28 files changed, 320 insertions(+), 144 deletions(-) delete mode 100644 includes/discovery/os/dsm.inc.php delete mode 100644 includes/discovery/os/freebsd.inc.php delete mode 100644 includes/discovery/os/huaweiups.inc.php delete mode 100644 includes/discovery/os/ibmtl.inc.php delete mode 100644 includes/discovery/os/linux.inc.php delete mode 100644 includes/discovery/os/sentry3.inc.php delete mode 100644 includes/discovery/os/sentry4.inc.php create mode 100644 includes/discovery/os/ubnt.inc.php diff --git a/doc/Developing/os/Initial-Detection.md b/doc/Developing/os/Initial-Detection.md index cb33efd368..31a032504a 100644 --- a/doc/Developing/os/Initial-Detection.md +++ b/doc/Developing/os/Initial-Detection.md @@ -29,7 +29,9 @@ Other options are available: - `sysObjectId` The preferred operator. Checks if the sysObjectID starts with one of the strings under this item - `sysDescr` Use this in addition to sysObjectId if required. Check that the sysDescr contains one of the strings under this item + - `sysObjectId_regex` Please avoid use of this. Checks if the sysObjectId matches one of the regex statements under this item - `sysDescr_regex` Please avoid use of this. Checks if the sysDescr matches one of the regex statements under this item + - `snmpget` Do not use this unless none of the other methods work. Fetch an oid and compare it against a value. - `_except` You can add this to any of the above to exclude that element. As an example: ```yaml diff --git a/includes/definitions/asuswrt-merlin.yaml b/includes/definitions/asuswrt-merlin.yaml index 70be880a37..78358078c3 100644 --- a/includes/definitions/asuswrt-merlin.yaml +++ b/includes/definitions/asuswrt-merlin.yaml @@ -6,4 +6,10 @@ over: - { graph: device_bits, text: 'Device Traffic' } - { graph: device_processor, text: 'CPU Usage' } - { graph: device_mempool, text: 'Memory Usage' } - +discovery: + - + sysObjectId: .1.3.6.1.4.1.8072.3.2.10 + snmpget: + oid: .1.3.6.1.4.1.2021.7890.1.101.1 + op: starts + value: ASUSWRT-Merlin diff --git a/includes/definitions/ddnos.yaml b/includes/definitions/ddnos.yaml index ace80263e9..707f16a799 100644 --- a/includes/definitions/ddnos.yaml +++ b/includes/definitions/ddnos.yaml @@ -6,3 +6,10 @@ over: - { graph: device_bits, text: 'Device Traffic' } - { graph: device_processor, text: 'Processor Usage' } - { graph: device_mempool, text: 'Memory Usage' } +discovery: + - + sysObjectId: .1.3.6.1.4.1.8072.3.2.10 + snmpget: + oid: SFA-INFO::systemName.0 + op: '!==' + value: false diff --git a/includes/definitions/dsm.yaml b/includes/definitions/dsm.yaml index 7295718ce8..f9a6e1b67f 100644 --- a/includes/definitions/dsm.yaml +++ b/includes/definitions/dsm.yaml @@ -10,3 +10,19 @@ over: mib_dir: - synology processor_stacked: 1 +discovery: + - + sysDescr_regex: '/^Linux /' + snmpget: + oid: systemStatus.0 + mib: SYNOLOGY-SYSTEM-MIB + mibdir: synology + op: '!==' + value: false + - + sysDescr_regex: '/^Linux /' + snmpget: + oid: HOST-RESOURCES-MIB::hrSystemInitialLoadParameters.0 + value: + - 'syno_hw_version' + - 'syno_dyn_module' diff --git a/includes/definitions/extrahop.yaml b/includes/definitions/extrahop.yaml index e8ff66d2bb..1aa2a756f1 100644 --- a/includes/definitions/extrahop.yaml +++ b/includes/definitions/extrahop.yaml @@ -9,3 +9,12 @@ over: - { graph: device_mempool, text: 'Memory Usage' } mib_dir: extrahop processor_stacked: 1 +discovery: + - + sysObjectId: .1.3.6.1.4.1.8072.3.2.10 + snmpget: + oid: extrahopInfoVersionString + mib: EXTRAHOP-MIB + mibdir: extrahop + op: '!==' + value: false diff --git a/includes/definitions/freebsd.yaml b/includes/definitions/freebsd.yaml index adc2d6ecf1..03d037a543 100644 --- a/includes/definitions/freebsd.yaml +++ b/includes/definitions/freebsd.yaml @@ -8,3 +8,5 @@ over: - { graph: device_ucd_memory, text: 'Memory Usage' } discovery_modules: applications: 1 +discovery: + - sysDescr: FreeBSD diff --git a/includes/definitions/huaweiups.yaml b/includes/definitions/huaweiups.yaml index cc152734f3..feba44d9cf 100644 --- a/includes/definitions/huaweiups.yaml +++ b/includes/definitions/huaweiups.yaml @@ -6,3 +6,9 @@ icon: huawei rfc1628_compat: 1 over: - { graph: device_current, text: Current } +discovery: + - + sysDescr: Linux GSE200M + snmpget: + oid: UPS-MIB::upsIdentManufacturer.0 + value: HUAWEI diff --git a/includes/definitions/ibmtl.yaml b/includes/definitions/ibmtl.yaml index aedbdf7548..f5c25c00c7 100644 --- a/includes/definitions/ibmtl.yaml +++ b/includes/definitions/ibmtl.yaml @@ -4,3 +4,7 @@ type: storage icon: generic over: - { graph: device_bits, text: Traffic } +discovery: + - snmpget: + oid: SML-MIB::product-Name.0 + value: IBM System Storage TS3500 Tape Library diff --git a/includes/definitions/linux.yaml b/includes/definitions/linux.yaml index a72ce0371c..245f198a70 100644 --- a/includes/definitions/linux.yaml +++ b/includes/definitions/linux.yaml @@ -31,3 +31,6 @@ register_mibs: KNIrxCounter: GANDI-MIB KNItxCounter: GANDI-MIB KNIdropCounter: GANDI-MIB +discovery: + - sysObjectId: .1.3.6.1.4.1.8072.3.2.10 + - sysDescr_regex: '/^Linux/' diff --git a/includes/definitions/pcoweb.yaml b/includes/definitions/pcoweb.yaml index 76b565aa83..96d4b35e63 100644 --- a/includes/definitions/pcoweb.yaml +++ b/includes/definitions/pcoweb.yaml @@ -9,3 +9,13 @@ over: icon: carel icons: - uniflair +discovery: + - sysDescr: pCO Gateway + - + sysObjectId: .1.3.6.1.4.1.8072.3.2.10 + snmpget: + oid: roomTemp.0 + mib: CAREL-ug40cdz-MIB + mibdir: carel + op: '!==' + value: false diff --git a/includes/definitions/pktj.yaml b/includes/definitions/pktj.yaml index bcc918ffb9..2e8f64272e 100644 --- a/includes/definitions/pktj.yaml +++ b/includes/definitions/pktj.yaml @@ -8,3 +8,10 @@ over: - { graph: device_processor, text: 'Processor Usage' } - { graph: device_mempool, text: 'Memory Usage' } processor_stacked: 1 +discovery: + - + sysDescr_regex: '/^Linux/' + snmpget: + oid: GANDI-MIB::rxCounter.0 + op: '!==' + value: false diff --git a/includes/definitions/qnap.yaml b/includes/definitions/qnap.yaml index 35ba893830..534270a56e 100644 --- a/includes/definitions/qnap.yaml +++ b/includes/definitions/qnap.yaml @@ -10,3 +10,10 @@ over: processor_stacked: 1 discovery: - sysObjectId: .1.3.6.1.4.1.24681 + - + sysDescr: Linux TS- + snmpget: + oid: entPhysicalMfgName.1 + mib: ENTITY-MIB + op: starts + value: QNAP Systems diff --git a/includes/definitions/remoteye4.yaml b/includes/definitions/remoteye4.yaml index 11195eebd0..84ea18789b 100644 --- a/includes/definitions/remoteye4.yaml +++ b/includes/definitions/remoteye4.yaml @@ -3,4 +3,10 @@ text: 'Toshiba RemotEye4' type: power icon: toshiba mib_dir: - - toshiba \ No newline at end of file + - toshiba +discovery: + - + sysObjectId: .1.3.6.1.4.1.8072.3.2.10 + snmpget: + oid: UPS-MIB::upsIdentAgentSoftwareVersion.0 + value: RemotEye4 diff --git a/includes/definitions/sentry3.yaml b/includes/definitions/sentry3.yaml index a8e008467b..ef823b7c0c 100644 --- a/includes/definitions/sentry3.yaml +++ b/includes/definitions/sentry3.yaml @@ -6,3 +6,14 @@ over: icon: servertech mib_dir: - sentry +discovery: + - + sysDescr: + - Switched + - Smart + snmpget: + oid: serverTech.4.1.1.1.3.0 + mib: Sentry3-MIB + mibdir: sentry + op: regex + value: '/[^\s]* [0-7]/' diff --git a/includes/definitions/sentry4.yaml b/includes/definitions/sentry4.yaml index 5f4b06b908..85199cabdc 100644 --- a/includes/definitions/sentry4.yaml +++ b/includes/definitions/sentry4.yaml @@ -6,3 +6,14 @@ over: icon: servertech mib_dir: - sentry +discovery: + - + sysDescr: + - Switched + - Smart + snmpget: + oid: serverTech.4.1.1.1.3.0 + mib: Sentry3-MIB + mibdir: sentry + op: regex + value: '/[^\s]* [^0-7]/' diff --git a/includes/definitions/tomato.yaml b/includes/definitions/tomato.yaml index 1181824f6f..57931897af 100644 --- a/includes/definitions/tomato.yaml +++ b/includes/definitions/tomato.yaml @@ -6,3 +6,10 @@ over: - { graph: device_bits, text: 'Device Traffic' } - { graph: device_processor, text: 'CPU Usage' } - { graph: device_mempool, text: 'Memory Usage' } +discovery: + - + sysObjectId: .1.3.6.1.4.1.8072.3.2.10 + snmpget: + oid: .1.3.6.1.4.1.2021.7890.1.101.1 + op: starts + value: Tomato diff --git a/includes/discovery/os/dsm.inc.php b/includes/discovery/os/dsm.inc.php deleted file mode 100644 index d32041eff6..0000000000 --- a/includes/discovery/os/dsm.inc.php +++ /dev/null @@ -1,17 +0,0 @@ - -* This program is free software: you can redistribute it and/or modify it -* under the terms of the GNU General Public License as published by the -* Free Software Foundation, either version 3 of the License, or (at your -* option) any later version. Please see LICENSE.txt at the top level of -* the source code distribution for details. -*/ - -$ibmtl_snmpget = snmp_get($device, 'SML-MIB::product-Name.0', '-Oqv', ''); - -if (str_contains($ibmtl_snmpget, 'IBM System Storage TS3500 Tape Library', true)) { - $os = 'ibmtl'; -} diff --git a/includes/discovery/os/linux.inc.php b/includes/discovery/os/linux.inc.php deleted file mode 100644 index 0af95509bd..0000000000 --- a/includes/discovery/os/linux.inc.php +++ /dev/null @@ -1,35 +0,0 @@ -= 8) { - $os = 'sentry4'; - } -} diff --git a/includes/discovery/os/ubnt.inc.php b/includes/discovery/os/ubnt.inc.php new file mode 100644 index 0000000000..d9eca697bf --- /dev/null +++ b/includes/discovery/os/ubnt.inc.php @@ -0,0 +1,14 @@ + $def) { + if (isset($def['discovery']) && !in_array($os, $deferred_os)) { + foreach ($def['discovery'] as $item) { + if (checkDiscovery($device, $item, $sysObjectId, $sysDescr)) { + return $os; } } } @@ -135,6 +129,17 @@ function getHostOS($device) } } + // check deferred os + foreach ($deferred_os as $os) { + if (isset($os_defs[$os]['discovery'])) { + foreach ($os_defs[$os]['discovery'] as $item) { + if (checkDiscovery($device, $item, $sysObjectId, $sysDescr)) { + return $os; + } + } + } + } + return 'generic'; } @@ -143,13 +148,17 @@ function getHostOS($device) * sysObjectId if sysObjectId starts with any of the values under this item * sysDescr if sysDescr contains any of the values under this item * sysDescr_regex if sysDescr matches any of the regexes under this item + * snmpget perform an snmpget on `oid` and check if the result contains `value`. Other subkeys: options, mib, mibdir * + * Appending _except to any condition will invert the match. + * + * @param array $device * @param array $array Array of items, keys should be sysObjectId, sysDescr, or sysDescr_regex * @param string $sysObjectId The sysObjectId to check against * @param string $sysDescr the sysDesr to check against * @return bool the result (all items passed return true) */ -function checkDiscovery($array, $sysObjectId, $sysDescr) +function checkDiscovery($device, $array, $sysObjectId, $sysDescr) { // all items must be true foreach ($array as $key => $value) { @@ -173,6 +182,16 @@ function checkDiscovery($array, $sysObjectId, $sysDescr) if (preg_match_any($sysObjectId, $value) == $check) { return false; } + } elseif ($key == 'snmpget') { + $options = isset($value['options']) ? $value['options'] : '-Oqv'; + $mib = isset($value['mib']) ? $value['mib'] : null; + $mib_dir = isset($value['mibdir']) ? $value['mibdir'] : null; + $op = isset($value['op']) ? $value['op'] : 'contains'; + + $get_value = snmp_get($device, $value['oid'], $options, $mib, $mib_dir); + if (compare_var($get_value, $value['value'], $op) == $check) { + return false; + } } } @@ -196,6 +215,49 @@ function preg_match_any($subject, $regexes) return false; } +/** + * Perform comparison of two items based on give comparison method + * Valid comparisons: =, !=, ==, !==, >=, <=, >, <, contains, starts, ends, regex + * contains, starts, ends: $a haystack, $b needle(s) + * regex: $a subject, $b regex + * + * @param mixed $a + * @param mixed $b + * @param string $comparison =, !=, ==, !== >=, <=, >, <, contains, starts, ends, regex + * @return bool + */ +function compare_var($a, $b, $comparison = '=') +{ + switch ($comparison) { + case "=": + return $a == $b; + case "!=": + return $a != $b; + case "==": + return $a === $b; + case "!==": + return $a !== $b; + case ">=": + return $a >= $b; + case "<=": + return $a <= $b; + case ">": + return $a > $b; + case "<": + return $a < $b; + case "contains": + return str_contains($a, $b); + case "starts": + return starts_with($a, $b); + case "ends": + return ends_with($a, $b); + case "regex": + return (bool)preg_match($b, $a); + default: + return false; + } +} + function percent_colour($perc) { $r = min(255, 5 * ($perc - 25)); diff --git a/includes/snmp.inc.php b/includes/snmp.inc.php index 7b46ce277b..b81121ade3 100644 --- a/includes/snmp.inc.php +++ b/includes/snmp.inc.php @@ -231,12 +231,22 @@ function snmp_get_multi_oid($device, $oids, $options = '-OUQn', $mib = null, $mi $data = trim(external_exec($cmd)); $array = array(); + $oid = ''; foreach (explode("\n", $data) as $entry) { - list($oid,$value) = explode('=', $entry, 2); - $oid = trim($oid); - $value = trim($value, "\" \n\r"); - if (!strstr($value, 'at this OID') && isset($oid)) { - $array[$oid] = $value; + if (str_contains($entry, '=')) { + list($oid,$value) = explode('=', $entry, 2); + $oid = trim($oid); + $value = trim($value, "\" \n\r"); + if (!strstr($value, 'at this OID') && isset($oid)) { + $array[$oid] = $value; + } + } else { + if (isset($array[$oid])) { + // if appending, add a line return + $array[$oid] .= PHP_EOL . $entry; + } else { + $array[$oid] = $entry; + } } } @@ -253,11 +263,10 @@ function snmp_get($device, $oid, $options = null, $mib = null, $mibdir = null) } $cmd = gen_snmpget_cmd($device, $oid, $options, $mib, $mibdir); - $data = trim(external_exec($cmd)); - $data = trim($data, "\" \n\r"); + $data = trim(external_exec($cmd), "\" \n\r"); recordSnmpStatistic('snmpget', $time_start); - if (is_string($data) && (preg_match('/(No Such Instance|No Such Object|No more variables left|Authentication failure)/i', $data))) { + if (preg_match('/(No Such Instance|No Such Object|No more variables left|Authentication failure)/i', $data)) { return false; } elseif ($data || $data === '0') { return $data; diff --git a/tests/YamlTest.php b/tests/YamlTest.php index 3291ef357a..aebd6f391b 100644 --- a/tests/YamlTest.php +++ b/tests/YamlTest.php @@ -33,6 +33,43 @@ use Symfony\Component\Yaml\Yaml; class YamlTest extends \PHPUnit_Framework_TestCase { + private $valid_keys = array( + 'sysDescr', + 'sysDescr_except', + 'sysObjectId', + 'sysObjectId_except', + 'sysDescr_regex', + 'sysDescr_regex_except', + 'sysObjectId_regex', + 'sysObjectId_regex_except', + 'snmpget', + 'snmpget_except' + ); + + private $valid_snmpget_keys = array( + 'oid', + 'options', + 'mib', + 'mibdir', + 'op', + 'value', + ); + + private $valid_comparisons = array( + '=', + '!=', + '==', + '!==', + '<=', + '>=', + '<', + '>', + 'starts', + 'ends', + 'contains', + 'regex', + ); + public function testOSYaml() { $pattern = Config::get('install_dir') . '/includes/definitions/*.yaml'; @@ -46,6 +83,26 @@ class YamlTest extends \PHPUnit_Framework_TestCase $this->assertArrayHasKey('os', $data, $file); $this->assertArrayHasKey('type', $data, $file); $this->assertArrayHasKey('text', $data, $file); + + // test discovery keys + if (isset($data['discovery'])) { + foreach ((array)$data['discovery'] as $group) { + foreach ((array)$group as $key => $item) { + $this->assertContains($key, $this->valid_keys, "$file: invalid discovery type $key"); + + if (starts_with($key, 'snmpget')) { + foreach ($item as $get_key => $get_val) { + $this->assertContains($get_key, $this->valid_snmpget_keys, "$file: invalid snmpget option $get_key"); + } + $this->assertArrayHasKey('oid', $item, "$file: snmpget discovery must specify oid"); + $this->assertArrayHasKey('value', $item, "$file: snmpget discovery must specify value"); + if (isset($item['op'])) { + $this->assertContains($item['op'], $this->valid_comparisons, "$file: invalid op ${item['op']}"); + } + } + } + } + } } } diff --git a/tests/mocks/mock.snmp.inc.php b/tests/mocks/mock.snmp.inc.php index 449ec6594e..d102c4db51 100644 --- a/tests/mocks/mock.snmp.inc.php +++ b/tests/mocks/mock.snmp.inc.php @@ -218,15 +218,45 @@ function snmp_get($device, $oid, $options = null, $mib = null, $mibdir = null) $result = '.' . $data[1]; } - d_echo("[SNMP] snmpget $community $num_oid: $result\n"); + d_echo("[SNMP] snmpget $community $oid ($num_oid): $result\n"); return $result; } catch (Exception $e) { - d_echo("[SNMP] snmpget $community $num_oid: no data\n"); + d_echo("[SNMP] snmpget $community $oid ($num_oid): no data\n"); return false; } } + +function snmp_get_multi_oid($device, $oids, $options = '-OUQn', $mib = null, $mibdir = null) +{ + if (!is_array($oids)) { + $oids = explode(' ', $oids); + } + + $data = array(); + foreach ($oids as $index => $oid) { + if (str_contains($options, 'n')) { + $oid_name = '.' . snmp_translate_number($oid, $mib, $mibdir); + $val = snmp_get($device, $oid_name, $options, $mib, $mibdir); + } elseif (str_contains($options, 's') + && str_contains($oid, '::')) { + $tmp = explode('::', $oid); + $oid_name = $tmp[1]; + $val = snmp_get($device, $oid, $options, $mib, $mibdir); + } else { + $oid_name = $oid; + $val = snmp_get($device, $oid, $options, $mib, $mibdir); + } + + if ($val !== false) { + $data[$oid_name] = $val; + } + } + + return $data; +} + function snmp_walk($device, $oid, $options = null, $mib = null, $mibdir = null) { $community = $device['community'];