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.
This commit is contained in:
Tony Murray
2017-11-02 07:41:18 -05:00
committed by Neil Lathwood
parent 771ff3669c
commit 0d07f85257
28 changed files with 320 additions and 144 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -8,3 +8,5 @@ over:
- { graph: device_ucd_memory, text: 'Memory Usage' }
discovery_modules:
applications: 1
discovery:
- sysDescr: FreeBSD

View File

@@ -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

View File

@@ -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

View File

@@ -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/'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -3,4 +3,10 @@ text: 'Toshiba RemotEye4'
type: power
icon: toshiba
mib_dir:
- toshiba
- toshiba
discovery:
-
sysObjectId: .1.3.6.1.4.1.8072.3.2.10
snmpget:
oid: UPS-MIB::upsIdentAgentSoftwareVersion.0
value: RemotEye4

View File

@@ -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]/'

View File

@@ -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]/'

View File

@@ -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

View File

@@ -1,17 +0,0 @@
<?php
// Synology DSM
if (starts_with($sysDescr, 'Linux')) {
$init_params = array(
'syno_hw_version',
'syno_dyn_module',
);
if (snmp_get($device, 'systemStatus.0', '-Osqnv', 'SYNOLOGY-SYSTEM-MIB', 'synology')) {
$os = 'dsm';
}
if (str_contains(snmp_get($device, 'HOST-RESOURCES-MIB::hrSystemInitialLoadParameters.0', '-Osqnv'), $init_params)) {
$os = 'dsm';
}
}

View File

@@ -1,6 +0,0 @@
<?php
// do not move to yaml, this check needs to happen last
if (str_contains($sysDescr, 'FreeBSD')) {
$os = 'freebsd';
}

View File

@@ -1,7 +0,0 @@
<?php
if (starts_with($sysDescr, 'Linux GSE200M')) {
if (str_contains(snmp_get($device, 'UPS-MIB::upsIdentManufacturer.0', '-Oqv', ''), 'HUAWEI')) {
$os = 'huaweiups';
}
}

View File

@@ -1,17 +0,0 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2015 Søren Friis Rosiak <sorenrosiak@gmail.com>
* 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';
}

View File

@@ -1,35 +0,0 @@
<?php
if (starts_with($sysDescr, 'Linux') || starts_with($sysObjectId, '.1.3.6.1.4.1.8072.3.2.10')) {
$os = 'linux';
// Specific Linux-derivatives
if (starts_with($sysObjectId, array('.1.3.6.1.4.1.10002.1', '.1.3.6.1.4.1.41112.1.4')) || str_contains(snmp_walk($device, 'dot11manufacturerName', '-Osqnv', 'IEEE802dot11-MIB'), 'Ubiquiti')) {
$os = 'airos';
if (str_contains(snmp_walk($device, 'dot11manufacturerProductName', '-Osqnv', 'IEEE802dot11-MIB'), 'UAP')) {
$os = 'unifi';
} elseif (snmp_get($device, 'fwVersion.1', '-Osqnv', 'UBNT-AirFIBER-MIB', 'ubnt') !== false) {
$os = 'airos-af';
}
} elseif (snmp_get($device, 'extrahopInfoVersionString', '-Osqnv', 'EXTRAHOP-MIB', 'extrahop') !== false) {
$os = 'extrahop';
} elseif (snmp_get($device, 'GANDI-MIB::rxCounter.0', '-Osqnv', 'GANDI-MIB') !== false) {
$os = 'pktj';
} elseif (snmp_get($device, 'SFA-INFO::systemName.0', '-Osqnv', 'SFA-INFO') !== false) {
$os = 'ddnos';
} elseif (is_numeric(trim(snmp_get($device, 'roomTemp.0', '-OqvU', 'CAREL-ug40cdz-MIB', 'carel')))) {
$os = 'pcoweb'; // Carel PCOweb
} elseif ($wrt = snmp_get($device, '.1.3.6.1.4.1.2021.7890.1.101.1', '-Osqnv')) {
$wrt = trim($wrt, '"');
if (starts_with($wrt, 'ASUSWRT-Merlin')) {
$os = 'asuswrt-merlin';
} elseif (starts_with($wrt, 'Tomato ')) {
$os = 'tomato';
}
} elseif (preg_match('/^QNAP Systems/', snmp_get($device, "entPhysicalMfgName.1", "-Ovqn", "ENTITY-MIB"))) {
$os = 'qnap';
} elseif (str_contains(snmp_get($device, 'upsIdentAgentSoftwareVersion.0', '-Osqnv', 'UPS-MIB'), 'RemotEye4')) {
$os = 'remoteye4';
}
}

View File

@@ -1,14 +0,0 @@
<?php
if (starts_with($sysDescr, 'Sentry') && str_contains($sysDescr, array('Switched', 'Smart'))) {
// ServerTech doesn't have a way to distinguish between sentry3 and sentry4 devices
// Hopefully, we can use the version string to figure it out
$version = trim(snmp_get($device, 'serverTech.4.1.1.1.3.0', '-Osqnv', 'Sentry3-MIB', 'sentry'));
$version = explode(" ", $version);
$version = intval($version[1]);
// It appears that version 8 and up is good for sentry4
if ($version < 8) {
$os = 'sentry3';
}
}

View File

@@ -1,14 +0,0 @@
<?php
if (starts_with($sysDescr, 'Sentry') && str_contains($sysDescr, array('Switched', 'Smart'))) {
// ServerTech doesn't have a way to distinguish between sentry3 and sentry4 devices
// Hopefully, we can use the version string to figure it out
$version = trim(snmp_get($device, 'serverTech.4.1.1.1.3.0', '-Osqnv', 'Sentry3-MIB', 'sentry'));
$version = explode(" ", $version);
$version = intval($version[1]);
// It appears that version 8 and up is good for sentry4
if ($version >= 8) {
$os = 'sentry4';
}
}

View File

@@ -0,0 +1,14 @@
<?php
if (starts_with($sysDescr, 'Linux') || starts_with($sysObjectId, '.1.3.6.1.4.1.8072.3.2.10')) {
if (starts_with($sysObjectId, array('.1.3.6.1.4.1.10002.1', '.1.3.6.1.4.1.41112.1.4'))
|| str_contains(snmp_walk($device, 'dot11manufacturerName', '-Osqnv', 'IEEE802dot11-MIB'), 'Ubiquiti')
) {
$os = 'airos';
if (str_contains(snmp_walk($device, 'dot11manufacturerProductName', '-Osqnv', 'IEEE802dot11-MIB'), 'UAP')) {
$os = 'unifi';
} elseif (snmp_get($device, 'fwVersion.1', '-Osqnv', 'UBNT-AirFIBER-MIB', 'ubnt') !== false) {
$os = 'airos-af';
}
}
}

View File

@@ -95,31 +95,25 @@ function getHostOS($device)
{
global $config;
$sysDescr = snmp_get($device, "SNMPv2-MIB::sysDescr.0", "-Ovq");
$sysObjectId = snmp_get($device, "SNMPv2-MIB::sysObjectID.0", "-Ovqn");
$res = snmp_get_multi_oid($device, array('SNMPv2-MIB::sysDescr.0', 'SNMPv2-MIB::sysObjectID.0'));
$sysDescr = isset($res['.1.3.6.1.2.1.1.1.0']) ? $res['.1.3.6.1.2.1.1.1.0'] : '';
$sysObjectId = isset($res['.1.3.6.1.2.1.1.2.0']) ? $res['.1.3.6.1.2.1.1.2.0'] : '';
d_echo("| $sysDescr | $sysObjectId | \n");
$deferred_os = array(
'freebsd',
'linux',
'ibmtl' //only has snmpget check
);
// check yaml files
$pattern = $config['install_dir'] . '/includes/definitions/*.yaml';
foreach (glob($pattern) as $file) {
$os = basename($file, '.yaml');
if (isset($config['os'][$os]['os'])) {
$tmp = $config['os'][$os];
} else {
$tmp = Symfony\Component\Yaml\Yaml::parse(
file_get_contents($file)
);
// pull in user overrides
if (isset($config['os'][$os])) {
$tmp = array_replace_recursive($tmp, $config['os'][$os]);
}
}
if (isset($tmp['discovery']) && is_array($tmp['discovery'])) {
foreach ($tmp['discovery'] as $item) {
// check each item individually, if all the conditions in that item are true, we have a match
if (checkDiscovery($item, $sysObjectId, $sysDescr)) {
return $tmp['os'];
$os_defs = Config::get('os');
foreach ($os_defs as $os => $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));

View File

@@ -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;

View File

@@ -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']}");
}
}
}
}
}
}
}

View File

@@ -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'];