From f94f7f23b8d2268e3530e0ebc9a246a426ca8e03 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Tue, 19 Oct 2021 15:43:43 -0500 Subject: [PATCH] Fully convert core to a modern module (#13347) * Core module WIP * update OS::make() * core WIP * try to finish up * switch all core do os Model * Mock WIP * Working tests * cleanup * phpstan fixes * style fixes * fix agent * trim space too and a couple of cleanups * corrected ios test data * missed space * update test data * put escapes back * another net-snmp difference * Fix class description * revert snmp.inc.php change, that can be a different PR * revert snmp.inc.php change, that can be a different PR --- LibreNMS/Data/Source/SnmpQuery.php | 20 +- LibreNMS/Data/Source/SnmpQueryInterface.php | 109 +++++++ LibreNMS/Modules/Core.php | 150 ++++++++-- LibreNMS/OS.php | 13 +- LibreNMS/Util/StringHelpers.php | 21 ++ app/Models/Device.php | 50 ++-- includes/common.php | 10 +- includes/definitions/dell-compellent.yaml | 1 - includes/discovery/core.inc.php | 42 +-- includes/functions.php | 20 +- includes/init.php | 6 +- includes/polling/core.inc.php | 55 +--- includes/polling/unix-agent.inc.php | 2 + scripts/new-os.php | 2 +- tests/Mocks/SnmpQueryMock.php | 307 ++++++++++++++++++++ tests/OSDiscoveryTest.php | 21 +- tests/bootstrap.php | 4 - tests/data/ios.json | 2 +- tests/data/ios_c3560e.json | 2 +- tests/data/iosxe.json | 2 +- tests/data/iosxe_c9400.json | 2 +- tests/data/iosxr.json | 2 +- tests/data/vxoa.json | 2 +- tests/mocks/mock.snmp.inc.php | 297 ------------------- 24 files changed, 639 insertions(+), 503 deletions(-) create mode 100644 LibreNMS/Data/Source/SnmpQueryInterface.php create mode 100644 tests/Mocks/SnmpQueryMock.php delete mode 100644 tests/mocks/mock.snmp.inc.php diff --git a/LibreNMS/Data/Source/SnmpQuery.php b/LibreNMS/Data/Source/SnmpQuery.php index 661216eaa9..46550a40bf 100644 --- a/LibreNMS/Data/Source/SnmpQuery.php +++ b/LibreNMS/Data/Source/SnmpQuery.php @@ -37,7 +37,7 @@ use LibreNMS\Util\Rewrite; use Log; use Symfony\Component\Process\Process; -class SnmpQuery +class SnmpQuery implements SnmpQueryInterface { /** * @var array @@ -98,7 +98,7 @@ class SnmpQuery /** * Easy way to start a new instance */ - public static function make(): SnmpQuery + public static function make(): SnmpQueryInterface { return new static; } @@ -107,7 +107,7 @@ class SnmpQuery * Specify a device to make the snmp query against. * By default the query will use the primary device. */ - public function device(Device $device): SnmpQuery + public function device(Device $device): SnmpQueryInterface { $this->device = $device; @@ -118,7 +118,7 @@ class SnmpQuery * Specify a device by a device array. * The device will be fetched from the cache if it is loaded, otherwise, it will fill the array into a new Device */ - public function deviceArray(array $device): SnmpQuery + public function deviceArray(array $device): SnmpQueryInterface { if (isset($device['device_id']) && DeviceCache::has($device['device_id'])) { $this->device = DeviceCache::get($device['device_id']); @@ -135,7 +135,7 @@ class SnmpQuery * Set a context for the snmp query * This is most commonly used to fetch alternate sets of data, such as different VRFs */ - public function context(string $context): SnmpQuery + public function context(string $context): SnmpQueryInterface { $this->context = $context; @@ -146,7 +146,7 @@ class SnmpQuery * Set an additional MIB directory to search for MIBs. * You do not need to specify the base and os directories, they are already included. */ - public function mibDir(string $dir): SnmpQuery + public function mibDir(?string $dir): SnmpQueryInterface { $this->mibDir = $dir; @@ -156,7 +156,7 @@ class SnmpQuery /** * Output all OIDs numerically */ - public function numeric(): SnmpQuery + public function numeric(): SnmpQueryInterface { $this->options = array_merge($this->options, ['-On']); @@ -172,7 +172,7 @@ class SnmpQuery * @param array|string $options * @return $this */ - public function options($options = []): SnmpQuery + public function options($options = []): SnmpQueryInterface { $this->options = Arr::wrap($options); @@ -218,10 +218,6 @@ class SnmpQuery /** * Translate an OID. * call numeric() on the query to output numeric OID - * - * @param array|string $oid - * @param string $oid - * @return \LibreNMS\Data\Source\SnmpResponse */ public function translate(string $oid, ?string $mib = null): SnmpResponse { diff --git a/LibreNMS/Data/Source/SnmpQueryInterface.php b/LibreNMS/Data/Source/SnmpQueryInterface.php new file mode 100644 index 0000000000..242f14ea50 --- /dev/null +++ b/LibreNMS/Data/Source/SnmpQueryInterface.php @@ -0,0 +1,109 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2021 Tony Murray + * @author Tony Murray + */ + +namespace LibreNMS\Data\Source; + +use App\Models\Device; + +interface SnmpQueryInterface +{ + /** + * Easy way to start a new instance + */ + public static function make(): SnmpQueryInterface; + + /** + * Specify a device to make the snmp query against. + * By default the query will use the primary device. + */ + public function device(Device $device): SnmpQueryInterface; + + /** + * Specify a device by a device array. + * The device will be fetched from the cache if it is loaded, otherwise, it will fill the array into a new Device + */ + public function deviceArray(array $device): SnmpQueryInterface; + + /** + * Set a context for the snmp query + * This is most commonly used to fetch alternate sets of data, such as different VRFs + */ + public function context(string $context): SnmpQueryInterface; + + /** + * Set an additional MIB directory to search for MIBs. + * You do not need to specify the base and os directories, they are already included. + */ + public function mibDir(?string $dir): SnmpQueryInterface; + + /** + * Output all OIDs numerically + */ + public function numeric(): SnmpQueryInterface; + + /** + * Set option(s) for net-snmp command line. + * Some options may break parsing, but you can manually parse the raw output if needed. + * This will override other options set such as setting numeric. Call with no options to reset to default. + * Try to avoid setting options this way to keep the API generic. + * + * @param array|string $options + * @return $this + */ + public function options($options = []): SnmpQueryInterface; + + /** + * snmpget an OID + * Commonly used to fetch a single or multiple explicit values. + * + * @param array|string $oid + * @return \LibreNMS\Data\Source\SnmpResponse + */ + public function get($oid): SnmpResponse; + + /** + * snmpwalk an OID + * Fetches all OIDs under a given OID, commonly used with tables. + * + * @param array|string $oid + * @return \LibreNMS\Data\Source\SnmpResponse + */ + public function walk($oid): SnmpResponse; + + /** + * snmpnext for the given oid + * snmpnext retrieves the first oid after the given oid. + * + * @param array|string $oid + * @return \LibreNMS\Data\Source\SnmpResponse + */ + public function next($oid): SnmpResponse; + + /** + * Translate an OID. + * Call numeric method prior output numeric OID. + */ + public function translate(string $oid, ?string $mib = null): SnmpResponse; +} diff --git a/LibreNMS/Modules/Core.php b/LibreNMS/Modules/Core.php index 3fcec43360..ea8d0683d2 100644 --- a/LibreNMS/Modules/Core.php +++ b/LibreNMS/Modules/Core.php @@ -1,5 +1,5 @@ . * - * @package LibreNMS * @link https://www.librenms.org - * @copyright 2020 Tony Murray + * + * @copyright 2021 Tony Murray * @author Tony Murray */ namespace LibreNMS\Modules; +use App\Models\Device; use Illuminate\Support\Str; use LibreNMS\Config; +use LibreNMS\Interfaces\Module; +use LibreNMS\OS; +use LibreNMS\RRD\RrdDefinition; +use LibreNMS\Util\Time; +use Log; +use SnmpQuery; -class Core +class Core implements Module { + public function discover(OS $os) + { + $snmpdata = SnmpQuery::numeric()->get(['SNMPv2-MIB::sysObjectID.0', 'SNMPv2-MIB::sysDescr.0', 'SNMPv2-MIB::sysName.0']) + ->values(); + + $device = $os->getDevice(); + $device->fill([ + 'sysObjectID' => $snmpdata['.1.3.6.1.2.1.1.2.0'] ?? null, + 'sysName' => strtolower(trim($snmpdata['.1.3.6.1.2.1.1.5.0'] ?? '')), + 'sysDescr' => isset($snmpdata['.1.3.6.1.2.1.1.1.0']) ? str_replace(chr(218), "\n", $snmpdata['.1.3.6.1.2.1.1.1.0']) : null, + ]); + + foreach ($device->getDirty() as $attribute => $value) { + Log::event($value . ' -> ' . $device->$attribute, $device, 'system', 3); + $os->getDeviceArray()[$attribute] = $value; // update device array + } + + // detect OS + $device->os = self::detectOS($device, false); + + if ($device->isDirty('os')) { + Log::event('Device OS changed: ' . $device->getOriginal('os') . ' -> ' . $device->os, $device, 'system', 3); + $os->getDeviceArray()['os'] = $device->os; + + echo 'Changed '; + } + + // Set type to a predefined type for the OS if it's not already set + $loaded_os_type = Config::get("os.$device->os.type"); + if (! $device->getAttrib('override_device_type') && $loaded_os_type != $device->type) { + $device->type = $loaded_os_type; + Log::debug("Device type changed to $loaded_os_type!"); + } + + $device->save(); + + echo 'OS: ' . Config::getOsSetting($device->os, 'text') . " ($device->os)\n\n"; + } + + public function poll(OS $os) + { + global $agent_data; + + $snmpdata = SnmpQuery::numeric() + ->get(['SNMPv2-MIB::sysDescr.0', 'SNMPv2-MIB::sysObjectID.0', 'SNMPv2-MIB::sysUpTime.0', 'SNMPv2-MIB::sysName.0']) + ->values(); + + $device = $os->getDevice(); + $device->fill([ + 'uptime' => $snmpdata['.1.3.6.1.2.1.1.3.0'], + 'sysName' => str_replace("\n", '', strtolower($snmpdata['.1.3.6.1.2.1.1.5.0'])), + 'sysObjectID' => $snmpdata['.1.3.6.1.2.1.1.2.0'], + 'sysDescr' => str_replace(chr(218), "\n", $snmpdata['.1.3.6.1.2.1.1.1.0']), + ]); + + if (! empty($agent_data['uptime'])) { + [$uptime] = explode(' ', $agent_data['uptime']); + $uptime = round((float) $uptime); + echo "Using UNIX Agent Uptime ($uptime)\n"; + } else { + $uptime_data = SnmpQuery::make()->get(['SNMP-FRAMEWORK-MIB::snmpEngineTime.0', 'HOST-RESOURCES-MIB::hrSystemUptime.0'])->values(); + + $uptime = max( + round($device->uptime / 100), + Config::get("os.$device->os.bad_snmpEngineTime") ? 0 : $uptime_data['SNMP-FRAMEWORK-MIB::snmpEngineTime.0'], + Config::get("os.$device->os.bad_hrSystemUptime") ? 0 : round($uptime_data['HOST-RESOURCES-MIB::hrSystemUptime.0'] / 100) + ); + Log::debug("Uptime seconds: $uptime\n"); + } + + if ($uptime != 0 && Config::get("os.$device->os.bad_uptime") !== true) { + if ($uptime < $device->uptime) { + Log::event('Device rebooted after ' . Time::formatInterval($device->uptime) . " -> {$uptime}s", $device, 'reboot', 4, $device->uptime); + } + + app('Datastore')->put($os->getDeviceArray(), 'uptime', [ + 'rrd_def' => RrdDefinition::make()->addDataset('uptime', 'GAUGE', 0), + ], $uptime); + + $os->enableGraph('uptime'); + + echo 'Uptime: ' . Time::formatInterval($uptime) . PHP_EOL; + $device->uptime = $uptime; + + $device->save(); + } + } + + public function cleanup(OS $os) + { + // nothing to cleanup + } + /** * Detect the os of the given device. * - * @param array $device device to check + * @param Device $device device to check * @param bool $fetch fetch sysDescr and sysObjectID fresh from the device * @return string the name of the os * * @throws \Exception */ - public static function detectOS($device, $fetch = true) + public static function detectOS(Device $device, bool $fetch = true): string { if ($fetch) { - // some devices act odd when getting both oids at once - $device['sysDescr'] = snmp_get($device, 'SNMPv2-MIB::sysDescr.0', '-Ovq'); - $device['sysObjectID'] = snmp_get($device, 'SNMPv2-MIB::sysObjectID.0', '-Ovqn'); + // some devices act oddly when getting both OIDs at once + $device->sysDescr = SnmpQuery::device($device)->get('SNMPv2-MIB::sysDescr.0')->value(); + $device->sysObjectID = SnmpQuery::device($device)->numeric()->get('SNMPv2-MIB::sysObjectID.0')->value(); } - d_echo("| {$device['sysDescr']} | {$device['sysObjectID']} | \n"); + Log::debug("| $device->sysDescr | $device->sysObjectID | \n"); $deferred_os = []; $generic_os = [ @@ -96,12 +196,12 @@ class Core * * Appending _except to any condition will invert the match. * - * @param array $device + * @param Device $device * @param array $array Array of items, keys should be sysObjectID, sysDescr, or sysDescr_regex * @param string|array $mibdir MIB directory for evaluated OS * @return bool the result (all items passed return true) */ - protected static function checkDiscovery($device, $array, $mibdir) + protected static function checkDiscovery(Device $device, array $array, $mibdir): bool { // all items must be true foreach ($array as $key => $value) { @@ -126,24 +226,20 @@ class Core return false; } } elseif ($key == 'snmpget') { - $get_value = snmp_get( - $device, - $value['oid'], - $value['options'] ?? '-Oqv', - $value['mib'] ?? null, - $value['mib_dir'] ?? $mibdir - ); + $get_value = SnmpQuery::device($device) + ->options($value['options'] ?? []) + ->mibDir($value['mib_dir'] ?? $mibdir) + ->get(isset($value['mib']) ? "{$value['mib']}::{$value['oid']}" : $value['oid']) + ->value(); if (compare_var($get_value, $value['value'], $value['op'] ?? 'contains') == $check) { return false; } } elseif ($key == 'snmpwalk') { - $walk_value = snmp_walk( - $device, - $value['oid'], - $value['options'] ?? '-Oqv', - $value['mib'] ?? null, - $value['mib_dir'] ?? $mibdir - ); + $walk_value = SnmpQuery::device($device) + ->options($value['options'] ?? []) + ->mibDir($value['mib_dir'] ?? $mibdir) + ->walk(isset($value['mib']) ? "{$value['mib']}::{$value['oid']}" : $value['oid']) + ->raw(); if (compare_var($walk_value, $value['value'], $value['op'] ?? 'contains') == $check) { return false; } @@ -153,7 +249,7 @@ class Core return true; } - protected static function discoveryIsSlow($def) + protected static function discoveryIsSlow($def): bool { foreach ($def['discovery'] as $item) { if (array_key_exists('snmpget', $item) || array_key_exists('snmpwalk', $item)) { diff --git a/LibreNMS/OS.php b/LibreNMS/OS.php index 71605f1d59..7721559f75 100644 --- a/LibreNMS/OS.php +++ b/LibreNMS/OS.php @@ -2,7 +2,7 @@ /** * OS.php * - * Base OS class + * -Description- * * 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 @@ -19,7 +19,7 @@ * * @link https://www.librenms.org * - * @copyright 2017 Tony Murray + * @copyright 2021 Tony Murray * @author Tony Murray */ @@ -39,6 +39,7 @@ use LibreNMS\OS\Traits\HostResources; use LibreNMS\OS\Traits\UcdResources; use LibreNMS\OS\Traits\YamlMempoolsDiscovery; use LibreNMS\OS\Traits\YamlOSDiscovery; +use LibreNMS\Util\StringHelpers; class OS implements ProcessorDiscovery, OSDiscovery, MempoolsDiscovery { @@ -212,7 +213,7 @@ class OS implements ProcessorDiscovery, OSDiscovery, MempoolsDiscovery unset($device['os_group']); } - $class = str_to_class($device['os'], 'LibreNMS\\OS\\'); + $class = StringHelpers::toClass($device['os'], 'LibreNMS\\OS\\'); d_echo('Attempting to initialize OS: ' . $device['os'] . PHP_EOL); if (class_exists($class)) { d_echo("OS initialized: $class\n"); @@ -221,9 +222,9 @@ class OS implements ProcessorDiscovery, OSDiscovery, MempoolsDiscovery } // If not a specific OS, check for a group one. - if ($os_group) { - $class = str_to_class($device['os_group'], 'LibreNMS\\OS\\Shared\\'); - d_echo('Attempting to initialize OS: ' . $device['os_group'] . PHP_EOL); + if ($os_group = Config::get("os.{$device['os']}.group")) { + $class = StringHelpers::toClass($os_group, 'LibreNMS\\OS\\Shared\\'); + d_echo("Attempting to initialize Group OS: $os_group\n"); if (class_exists($class)) { d_echo("OS initialized: $class\n"); diff --git a/LibreNMS/Util/StringHelpers.php b/LibreNMS/Util/StringHelpers.php index 310197c442..97edbf04e4 100644 --- a/LibreNMS/Util/StringHelpers.php +++ b/LibreNMS/Util/StringHelpers.php @@ -125,4 +125,25 @@ class StringHelpers return $string; } + + /** + * Generate a class name from a lowercase string containing - or _ + * Remove - and _ and camel case words + * + * @param string $name The string to convert to a class name + * @param string|null $namespace namespace to prepend to the name for example: LibreNMS\ + * @return string Class name + */ + public static function toClass(string $name, ?string $namespace = null): string + { + $pre_format = str_replace(['-', '_'], ' ', $name); + $class = str_replace(' ', '', ucwords(strtolower($pre_format))); + $class = preg_replace_callback('/^(\d)(.)/', function ($matches) { + $numbers = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']; + + return $numbers[$matches[1]] . strtoupper($matches[2]); + }, $class); + + return $namespace . $class; + } } diff --git a/app/Models/Device.php b/app/Models/Device.php index 3034b2e7eb..423af4a3d1 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -37,34 +37,36 @@ class Device extends BaseModel public $timestamps = false; protected $primaryKey = 'device_id'; protected $fillable = [ - 'hostname', - 'ip', - 'status', - 'status_reason', - 'sysName', - 'sysDescr', - 'sysObjectID', - 'hardware', - 'version', - 'features', - 'serial', - 'icon', - 'overwrite_ip', - 'os', - 'community', - 'port', - 'transport', - 'snmpver', - 'poller_group', - 'port_association_mode', - 'snmp_disable', - // ---- snmpV3 fields ---- + 'authalgo', 'authlevel', 'authname', 'authpass', - 'authalgo', - 'cryptopass', + 'community', 'cryptoalgo', + 'cryptopass', + 'features', + 'hardware', + 'hostname', + 'icon', + 'ip', + 'os', + 'overwrite_ip', + 'poller_group', + 'port', + 'port_association_mode', + 'retries', + 'serial', + 'snmp_disable', + 'snmp_max_repeaters', + 'snmpver', + 'status', + 'status_reason', + 'sysDescr', + 'sysName', + 'sysObjectID', + 'timeout', + 'transport', + 'version', ]; protected $casts = [ diff --git a/includes/common.php b/includes/common.php index 5ec744b99d..aa54006b93 100644 --- a/includes/common.php +++ b/includes/common.php @@ -1075,15 +1075,7 @@ function get_vm_parent_id($device) */ function str_to_class($name, $namespace = null) { - $pre_format = str_replace(['-', '_'], ' ', $name); - $class = str_replace(' ', '', ucwords(strtolower($pre_format))); - $class = preg_replace_callback('/^(\d)(.)/', function ($matches) { - $numbers = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']; - - return $numbers[$matches[1]] . strtoupper($matches[2]); - }, $class); - - return $namespace . $class; + return \LibreNMS\Util\StringHelpers::toClass($name, $namespace); } /** diff --git a/includes/definitions/dell-compellent.yaml b/includes/definitions/dell-compellent.yaml index 406e0706c9..329fd69cc0 100644 --- a/includes/definitions/dell-compellent.yaml +++ b/includes/definitions/dell-compellent.yaml @@ -28,7 +28,6 @@ discovery: 'FreeBSD' snmpget: oid: 1.3.6.1.4.1.674.11000.2000.500.1.2.1.0 - mib: DELL-STORAGE-SC-MIB op: starts value: 'Dell-Compellent' - diff --git a/includes/discovery/core.inc.php b/includes/discovery/core.inc.php index 65abed540e..7f7b8e429f 100644 --- a/includes/discovery/core.inc.php +++ b/includes/discovery/core.inc.php @@ -1,44 +1,10 @@ fill([ - 'sysObjectID' => $snmpdata['.1.3.6.1.2.1.1.2.0'] ?? null, - 'sysName' => strtolower(trim($snmpdata['.1.3.6.1.2.1.1.5.0'] ?? '')), - 'sysDescr' => isset($snmpdata['.1.3.6.1.2.1.1.1.0']) ? str_replace(chr(218), "\n", $snmpdata['.1.3.6.1.2.1.1.1.0']) : null, -]); - -foreach ($deviceModel->getDirty() as $attribute => $value) { - Log::event($value . ' -> ' . $deviceModel->$attribute, $deviceModel, 'system', 3); - $device[$attribute] = $value; // update device array -} - -// detect OS -$deviceModel->os = Core::detectOS($device, false); - -if ($deviceModel->isDirty('os')) { - Log::event('Device OS changed: ' . $deviceModel->getOriginal('os') . ' -> ' . $deviceModel->os, $deviceModel, 'system', 3); - $device['os'] = $deviceModel->os; - - echo 'Changed '; -} - -// Set type to a predefined type for the OS if it's not already set -$loaded_os_type = Config::get("os.{$device['os']}.type"); -if (! $deviceModel->getAttrib('override_device_type') && $loaded_os_type != $deviceModel->type) { - $deviceModel->type = $loaded_os_type; - Log::debug("Device type changed to $loaded_os_type!"); -} - -$deviceModel->save(); +// start assuming no os +(new \LibreNMS\Modules\Core())->discover(Generic::make($device)); +// then create with actual OS $os = OS::make($device); - -echo 'OS: ' . Config::getOsSetting($device['os'], 'text') . " ({$device['os']})\n\n"; - -unset($snmpdata, $attribute, $value, $deviceModel); diff --git a/includes/functions.php b/includes/functions.php index 68f90017fd..ab20b38597 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -572,7 +572,7 @@ function createHost( $port_assoc_mode = get_port_assoc_mode_id($port_assoc_mode); } - $device = [ + $device = new Device(array_merge([ 'hostname' => $host, 'overwrite_ip' => $overwrite_ip, 'sysName' => $additional['sysName'] ?? $host, @@ -587,22 +587,18 @@ function createHost( 'status_reason' => '', 'port_association_mode' => $port_assoc_mode, 'snmp_disable' => $additional['snmp_disable'] ?? 0, - ]; - - $device = array_merge($device, $v3); // merge v3 settings + ], $v3)); if ($force_add !== true) { - $device['os'] = Core::detectOS($device); + $device->os = Core::detectOS($device); - $snmphost = snmp_get($device, 'sysName.0', '-Oqv', 'SNMPv2-MIB'); - if (host_exists($host, $snmphost)) { - throw new HostExistsException("Already have host $host ($snmphost) due to duplicate sysName"); + $device->sysName = SnmpQuery::device($device)->get('SNMPv2-MIB::sysName.0')->value(); + if (host_exists($host, $device->sysName)) { + throw new HostExistsException("Already have host $host ({$device->sysName}) due to duplicate sysName"); } } - - $deviceModel = Device::create($device); - if ($deviceModel->device_id) { - return $deviceModel->device_id; + if ($device->save()) { + return $device->device_id; } throw new \Exception('Failed to add host to the database, please run ./validate.php'); diff --git a/includes/init.php b/includes/init.php index d6282e24cb..663fafd7d2 100644 --- a/includes/init.php +++ b/includes/init.php @@ -60,11 +60,7 @@ require_once $install_dir . '/includes/dbFacile.php'; require_once $install_dir . '/includes/datastore.inc.php'; require_once $install_dir . '/includes/billing.php'; require_once $install_dir . '/includes/syslog.php'; -if (module_selected('mocksnmp', $init_modules)) { - require_once $install_dir . '/tests/mocks/mock.snmp.inc.php'; -} elseif (! in_array($install_dir . '/tests/mocks/mock.snmp.inc.php', get_included_files())) { - require_once $install_dir . '/includes/snmp.inc.php'; -} +require_once $install_dir . '/includes/snmp.inc.php'; require_once $install_dir . '/includes/services.inc.php'; require_once $install_dir . '/includes/functions.php'; require_once $install_dir . '/includes/rewrites.php'; diff --git a/includes/polling/core.inc.php b/includes/polling/core.inc.php index 463a2defcf..cb82a4f851 100644 --- a/includes/polling/core.inc.php +++ b/includes/polling/core.inc.php @@ -11,57 +11,4 @@ * See COPYING for more details. */ -use LibreNMS\Config; -use LibreNMS\RRD\RrdDefinition; -use LibreNMS\Util\Time; - -$snmpdata = snmp_get_multi_oid($device, ['sysUpTime.0', 'sysName.0', 'sysObjectID.0', 'sysDescr.0'], '-OQnUt', 'SNMPv2-MIB'); - -$poll_device['sysUptime'] = $snmpdata['.1.3.6.1.2.1.1.3.0']; -$poll_device['sysName'] = str_replace("\n", '', strtolower($snmpdata['.1.3.6.1.2.1.1.5.0'])); -$poll_device['sysObjectID'] = $snmpdata['.1.3.6.1.2.1.1.2.0']; -$poll_device['sysDescr'] = str_replace(chr(218), "\n", $snmpdata['.1.3.6.1.2.1.1.1.0']); - -if (! empty($agent_data['uptime'])) { - [$uptime] = explode(' ', $agent_data['uptime']); - $uptime = round($uptime); - echo "Using UNIX Agent Uptime ($uptime)\n"; -} else { - $uptime_data = snmp_get_multi($device, ['snmpEngineTime.0', 'hrSystemUptime.0'], '-OQnUst', 'HOST-RESOURCES-MIB:SNMP-FRAMEWORK-MIB'); - - $uptime = max( - round($poll_device['sysUptime'] / 100), - Config::get("os.{$device['os']}.bad_snmpEngineTime") ? 0 : $uptime_data[0]['snmpEngineTime'], - Config::get("os.{$device['os']}.bad_hrSystemUptime") ? 0 : round($uptime_data[0]['hrSystemUptime'] / 100) - ); - d_echo("Uptime seconds: $uptime\n"); -} - -if ($uptime != 0 && Config::get("os.{$device['os']}.bad_uptime") !== true) { - if ($uptime < $device['uptime']) { - log_event('Device rebooted after ' . Time::formatInterval($device['uptime']) . " -> {$uptime}s", $device, 'reboot', 4, $device['uptime']); - } - - $tags = [ - 'rrd_def' => RrdDefinition::make()->addDataset('uptime', 'GAUGE', 0), - ]; - data_update($device, 'uptime', $tags, $uptime); - - $os->enableGraph('uptime'); - - echo 'Uptime: ' . Time::formatInterval($uptime) . PHP_EOL; - - $update_array['uptime'] = $uptime; - $device['uptime'] = $uptime; -}//end if - -// Save results of various polled values to the database -foreach (['sysObjectID', 'sysName', 'sysDescr'] as $elem) { - if ($poll_device[$elem] != $device[$elem]) { - $update_array[$elem] = $poll_device[$elem]; - $device[$elem] = $poll_device[$elem]; - log_event("$elem -> " . $poll_device[$elem], $device, 'system', 3); - } -} - -unset($snmpdata, $uptime_data, $uptime, $tags, $poll_device); +(new \LibreNMS\Modules\Core())->poll($os); diff --git a/includes/polling/unix-agent.inc.php b/includes/polling/unix-agent.inc.php index 0c4bc06ee2..a837c9541e 100644 --- a/includes/polling/unix-agent.inc.php +++ b/includes/polling/unix-agent.inc.php @@ -63,6 +63,8 @@ if ($device['os_group'] == 'unix' || $device['os'] == 'windows') { 'gpsd', ]; + global $agent_data; + $agent_data = []; foreach (explode('<<<', $agent_raw) as $section) { [$section, $data] = explode('>>>', $section); [$sa, $sb] = explode('-', $section, 2); diff --git a/scripts/new-os.php b/scripts/new-os.php index bbc28c6926..b155b1533a 100755 --- a/scripts/new-os.php +++ b/scripts/new-os.php @@ -35,7 +35,7 @@ sysObjectID: $full_sysObjectID "); - $os = Core::detectOS($device); + $os = Core::detectOS(new \App\Models\Device($device)); $continue = 'n'; if ($os != 'generic') { $continue = get_user_input("We already detect this device as OS $os type, do you want to continue to add sensors? (Y/n)"); diff --git a/tests/Mocks/SnmpQueryMock.php b/tests/Mocks/SnmpQueryMock.php new file mode 100644 index 0000000000..ec25dedbff --- /dev/null +++ b/tests/Mocks/SnmpQueryMock.php @@ -0,0 +1,307 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2021 Tony Murray + * @author Tony Murray + */ + +namespace LibreNMS\Tests\Mocks; + +use App\Models\Device; +use DeviceCache; +use Exception; +use Illuminate\Support\Str; +use LibreNMS\Data\Source\SnmpQuery; +use LibreNMS\Data\Source\SnmpQueryInterface; +use LibreNMS\Data\Source\SnmpResponse; +use LibreNMS\Device\YamlDiscovery; +use Log; + +class SnmpQueryMock implements SnmpQueryInterface +{ + /** + * @var array + */ + private static $cache; + + /** + * @var Device + */ + private $device; + /** + * @var string + */ + private $context; + /** + * @var string|null + */ + private $mibDir; + /** + * @var bool + */ + private $numeric = false; + /** + * @var array|mixed + */ + private $options = []; + + public static function make(): SnmpQueryInterface + { + $new = new static; + $new->device = DeviceCache::getPrimary(); + + return $new; + } + + public function device(Device $device): SnmpQueryInterface + { + $this->device = $device; + + return $this; + } + + public function deviceArray(array $device): SnmpQueryInterface + { + $this->device = new Device($device); + + return $this; + } + + public function context(string $context): SnmpQueryInterface + { + $this->context = $context; + + return $this; + } + + public function translate(string $oid, ?string $mib = null): SnmpResponse + { + // call real snmptranslate + $options = $this->options; + if ($this->numeric) { + $options[] = '-On'; + } + + return SnmpQuery::make() + ->mibDir($this->mibDir) + ->options($options) + ->translate($oid, $mib); + } + + public function numeric(): SnmpQueryInterface + { + $this->numeric = true; + + return $this; + } + + public function options($options = []): SnmpQueryInterface + { + $this->options = $options; + + return $this; + } + + public function mibDir(?string $dir): SnmpQueryInterface + { + $this->mibDir = $dir; + + return $this; + } + + public function get($oid): SnmpResponse + { + $community = $this->device->community; + $num_oid = $this->translateNumber($oid); + $data = $this->getSnmprec($community)[$num_oid] ?? [0, '']; + + Log::debug("[SNMP] snmpget $community $num_oid: "); + + return new SnmpResponse($this->outputLine($oid, $num_oid, $data[0], $data[1])); + } + + public function walk($oid): SnmpResponse + { + $community = $this->device->community; + $num_oid = $this->translateNumber($oid); + $dev = $this->getSnmprec($community); + + $output = ''; + foreach ($dev as $key => $data) { + if (Str::startsWith($key, $num_oid)) { + $output .= $this->outputLine($oid, $num_oid, $data[0], $data[1]); + } + } + + Log::debug("[SNMP] snmpwalk $community $num_oid"); + + return new SnmpResponse($output); + } + + public function next($oid): SnmpResponse + { + $community = $this->device->community; + $num_oid = $this->translateNumber($oid); + $dev = $this->getSnmprec($community); + + Log::debug("[SNMP] snmpnext $community $num_oid: "); + while (Str::contains($num_oid, '.')) { + foreach ($dev as $key => $data) { + if (Str::startsWith($key, $num_oid)) { + return new SnmpResponse($this->outputLine($oid, $num_oid, $data[0], $data[1])); + } + } + + $num_oid = substr($num_oid, 0, strrpos($num_oid, '.')); + } + + return new SnmpResponse(''); + } + + private function cacheSnmprec(string $file): void + { + if (isset(self::$cache[$file])) { + return; + } + self::$cache[$file] = []; + + $data = file_get_contents(base_path("/tests/snmpsim/$file.snmprec")); + $line = strtok($data, "\r\n"); + while ($line !== false) { + [$oid, $type, $data] = explode('|', $line, 3); + if ($type == '4') { + $data = trim($data); + } elseif ($type == '6') { + $data = trim($data, '.'); + } elseif ($type == '4x') { + // MacAddress type is stored as hex string, but we don't understand mibs + if (Str::startsWith($oid, [ + '1.3.6.1.2.1.2.2.1.6', // IF-MIB::ifPhysAddress + '1.3.6.1.2.1.17.1.1.0', // BRIDGE-MIB::dot1dBaseBridgeAddress.0 + '1.3.6.1.4.1.890.1.5.13.13.8.1.1.20', // IES5206-MIB::slotModuleMacAddress + ])) { + $data = \LibreNMS\Util\Rewrite::readableMac($data); + } else { + $data = hex2str($data); + } + } + + self::$cache[$file][$oid] = [$type, $data]; + $line = strtok("\r\n"); + } + } + + /** + * Get all data of the specified $community from the snmprec cache + * + * @param string $community snmp community to return + * @return array array of the data containing: [$oid][$type, $data] + * + * @throws Exception this $community is not cached + */ + private function getSnmprec(string $community): array + { + if (! isset(self::$cache[$community])) { + $this->cacheSnmprec($community); + } + + if (isset(self::$cache[$community])) { + return self::$cache[$community]; + } + + throw new Exception("SNMPREC: community $community not cached"); + } + + private function outputLine(string $oid, string $num_oid, string $type, string $data): string + { + if ($type == 6) { + $data = $this->numeric ? ".$data" : $this->translate($data, $this->extractMib($oid))->value(); + } + + if ($this->numeric) { + return "$num_oid = $data"; + } + + if (! empty($oid) && YamlDiscovery::oidIsNumeric($oid)) { + $oid = $this->translate($oid)->value(); + } + + return "$oid = $data"; + } + + /** + * Get the numeric oid of an oid + * The leading dot is ommited by default to be compatible with snmpsim + * + * @param string $oid the oid to tranlslate + * @param string $mib mib to use + * @return string the oid in numeric format (1.3.4.5) + * + * @throws Exception Could not translate the oid + */ + private function translateNumber($oid, $mib = null) + { + // optimizations (35s -> 1.6s on my laptop) + switch ($oid) { + case 'SNMPv2-MIB::sysDescr.0': + return '1.3.6.1.2.1.1.1.0'; + case 'SNMPv2-MIB::sysObjectID.0': + return '1.3.6.1.2.1.1.2.0'; + case 'ENTITY-MIB::entPhysicalDescr.1': + return '1.3.6.1.2.1.47.1.1.1.1.2.1'; + case 'SML-MIB::product-Name.0': + return '1.3.6.1.4.1.2.6.182.3.3.1.0'; + case 'ENTITY-MIB::entPhysicalMfgName.1': + return '1.3.6.1.2.1.47.1.1.1.1.12.1'; + case 'GAMATRONIC-MIB::psUnitManufacture.0': + return '1.3.6.1.4.1.6050.1.1.2.0'; + case 'SYNOLOGY-SYSTEM-MIB::systemStatus.0': + return '1.3.6.1.4.1.6574.1.1.0'; + } + + if (YamlDiscovery::oidIsNumeric($oid)) { + return ltrim($oid, '.'); + } + + $options = ['-IR']; + if ($mib) { + $options[] = "-m $mib"; + } + + $number = SnmpQuery::make()->mibDir($this->mibDir) + ->options(array_merge($options, $this->options))->numeric()->translate($oid)->value(); + + if (empty($number)) { + throw new Exception('Could not translate oid: ' . $oid . PHP_EOL); + } + + return ltrim($number, '.'); + } + + private function extractMib(string $oid): ?string + { + if (Str::contains($oid, '::')) { + return explode('::', $oid, 2)[0]; + } + + return null; + } +} diff --git a/tests/OSDiscoveryTest.php b/tests/OSDiscoveryTest.php index 095150eb0d..ac989bbf21 100644 --- a/tests/OSDiscoveryTest.php +++ b/tests/OSDiscoveryTest.php @@ -25,9 +25,12 @@ namespace LibreNMS\Tests; +use App\Models\Device; use Illuminate\Support\Str; use LibreNMS\Config; +use LibreNMS\Data\Source\SnmpQuery; use LibreNMS\Modules\Core; +use LibreNMS\Tests\Mocks\SnmpQueryMock; use LibreNMS\Util\Debug; use LibreNMS\Util\OS; @@ -64,6 +67,10 @@ class OSDiscoveryTest extends TestCase */ public function testOSDetection($os_name) { + if (! getenv('SNMPSIM')) { + $this->app->bind(SnmpQuery::class, SnmpQueryMock::class); + } + $glob = Config::get('install_dir') . "/tests/snmpsim/$os_name*.snmprec"; $files = array_map(function ($file) { return basename($file, '.snmprec'); @@ -104,6 +111,8 @@ class OSDiscoveryTest extends TestCase */ private function checkOS($expected_os, $filename = null) { + $start = microtime(true); + $community = $filename ?: $expected_os; Debug::set(); Debug::setVerbose(); @@ -112,6 +121,7 @@ class OSDiscoveryTest extends TestCase $output = ob_get_contents(); ob_end_clean(); + $this->assertLessThan(5, microtime(true) - $start, "OS $expected_os took longer than 5s to detect"); $this->assertEquals($expected_os, $os, "Test file: $community.snmprec\n$output"); } @@ -119,12 +129,11 @@ class OSDiscoveryTest extends TestCase * Generate a fake $device array * * @param string $community The snmp community to set - * @return array resulting device array + * @return Device resulting device array */ - private function genDevice($community) + private function genDevice($community): Device { - return [ - 'device_id' => 1, + return new Device([ 'hostname' => $this->getSnmpsim()->getIP(), 'snmpver' => 'v2c', 'port' => $this->getSnmpsim()->getPort(), @@ -133,9 +142,7 @@ class OSDiscoveryTest extends TestCase 'snmp_max_repeaters' => 10, 'community' => $community, 'os' => 'generic', - 'os_group' => '', - 'attribs' => [], - ]; + ]); } /** diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 201baa8039..eb749a78e1 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -30,10 +30,6 @@ $install_dir = realpath(__DIR__ . '/..'); $init_modules = ['web', 'discovery', 'polling', 'nodb']; -if (! getenv('SNMPSIM')) { - $init_modules[] = 'mocksnmp'; -} - require $install_dir . '/includes/init.php'; chdir($install_dir); diff --git a/tests/data/ios.json b/tests/data/ios.json index 2afd54037d..767ce54220 100644 --- a/tests/data/ios.json +++ b/tests/data/ios.json @@ -13235,7 +13235,7 @@ { "sysName": "", "sysObjectID": ".1.3.6.1.4.1.9.1.359", - "sysDescr": "Cisco Internetwork Operating System Software\nIOS (tm) C2950 Software (C2950-I6K2L2Q4-M), Version 12.1(22)EA14, RELEASE SOFTWARE (fc1)\r\nTechnical Support: http://www.cisco.com/techsupport\r\nCopyright (c) 1986-2010 by cisco Systems, Inc.\r\nCompiled Tue 26-O", + "sysDescr": "Cisco Internetwork Operating System Software \r\nIOS (tm) C2950 Software (C2950-I6K2L2Q4-M), Version 12.1(22)EA14, RELEASE SOFTWARE (fc1)\r\nTechnical Support: http://www.cisco.com/techsupport\r\nCopyright (c) 1986-2010 by cisco Systems, Inc.\r\nCompiled Tue 26-O", "sysContact": "", "version": "12.1(22)EA14", "hardware": "catalyst2950t24", diff --git a/tests/data/ios_c3560e.json b/tests/data/ios_c3560e.json index ab50a9fb9d..35ed4de44e 100644 --- a/tests/data/ios_c3560e.json +++ b/tests/data/ios_c3560e.json @@ -23,7 +23,7 @@ { "sysName": "", "sysObjectID": ".1.3.6.1.4.1.9.1.1226", - "sysDescr": "Cisco IOS Software, C3560E Software (C3560E-UNIVERSALK9-M), Version 15.0(2)SE7, RELEASE SOFTWARE (fc1)\nTechnical Support: http://www.cisco.com/techsupport\r\nCopyright (c) 1986-2014 by Cisco Systems, Inc.\r\nCompiled Thu 23-Oct-14 13:27 by prod_rel_team", + "sysDescr": "Cisco IOS Software, C3560E Software (C3560E-UNIVERSALK9-M), Version 15.0(2)SE7, RELEASE SOFTWARE (fc1)\r\nTechnical Support: http://www.cisco.com/techsupport\r\nCopyright (c) 1986-2014 by Cisco Systems, Inc.\r\nCompiled Thu 23-Oct-14 13:27 by prod_rel_team", "sysContact": null, "version": "15.0(2)SE7", "hardware": "cat3560x24", diff --git a/tests/data/iosxe.json b/tests/data/iosxe.json index 0eecf89099..d544fe6e7c 100644 --- a/tests/data/iosxe.json +++ b/tests/data/iosxe.json @@ -387,7 +387,7 @@ { "sysName": "", "sysObjectID": ".1.3.6.1.4.1.9.1.1116", - "sysDescr": "Cisco IOS Software, ASR1000 Software (PPC_LINUX_IOSD-ADVENTERPRISEK9-M), Version 15.5(3)S1a, RELEASE SOFTWARE (fc1)\nTechnical Support: http://www.cisco.com/techsupport\r\nCopyright (c) 1986-2015 by Cisco Systems, Inc.\r\nCompiled Wed 04-Nov-15 17:40 by mcpre", + "sysDescr": "Cisco IOS Software, ASR1000 Software (PPC_LINUX_IOSD-ADVENTERPRISEK9-M), Version 15.5(3)S1a, RELEASE SOFTWARE (fc1)\r\nTechnical Support: http://www.cisco.com/techsupport\r\nCopyright (c) 1986-2015 by Cisco Systems, Inc.\r\nCompiled Wed 04-Nov-15 17:40 by mcpre", "sysContact": "", "version": "15.5(3)S1a", "hardware": "ciscoASR1002F", diff --git a/tests/data/iosxe_c9400.json b/tests/data/iosxe_c9400.json index 8ee0bdee37..085a81e70c 100644 --- a/tests/data/iosxe_c9400.json +++ b/tests/data/iosxe_c9400.json @@ -5,7 +5,7 @@ { "sysName": "", "sysObjectID": ".1.3.6.1.4.1.9.1.1116", - "sysDescr": "Cisco IOS Software, ASR1000 Software (PPC_LINUX_IOSD-ADVENTERPRISEK9-M), Version 15.5(3)S1a, RELEASE SOFTWARE (fc1)\nTechnical Support: http://www.cisco.com/techsupport\r\nCopyright (c) 1986-2015 by Cisco Systems, Inc.\r\nCompiled Wed 04-Nov-15 17:40 by mcpre", + "sysDescr": "Cisco IOS Software, ASR1000 Software (PPC_LINUX_IOSD-ADVENTERPRISEK9-M), Version 15.5(3)S1a, RELEASE SOFTWARE (fc1)\r\nTechnical Support: http://www.cisco.com/techsupport\r\nCopyright (c) 1986-2015 by Cisco Systems, Inc.\r\nCompiled Wed 04-Nov-15 17:40 by mcpre", "sysContact": "", "version": "15.5(3)S1a", "hardware": "C9404R", diff --git a/tests/data/iosxr.json b/tests/data/iosxr.json index e11d3284c0..df2ca7a52d 100644 --- a/tests/data/iosxr.json +++ b/tests/data/iosxr.json @@ -28500,7 +28500,7 @@ { "sysName": "", "sysObjectID": ".1.3.6.1.4.1.9.1.1018", - "sysDescr": "Cisco IOS XR Software (Cisco ASR9K Series), Version 5.3.4[Default]\nCopyright (c) 2017 by Cisco Systems, Inc.", + "sysDescr": "Cisco IOS XR Software (Cisco ASR9K Series), Version 5.3.4[Default]\r\nCopyright (c) 2017 by Cisco Systems, Inc.", "sysContact": "", "version": "5.3.4", "hardware": "ASR9K Series", diff --git a/tests/data/vxoa.json b/tests/data/vxoa.json index 06a4a5e2c1..797f56c563 100644 --- a/tests/data/vxoa.json +++ b/tests/data/vxoa.json @@ -5,7 +5,7 @@ { "sysName": "", "sysObjectID": ".1.3.6.1.4.1.23867.1.2.48", - "sysDescr": "Silver Peak Systems, Inc. ECS\nLinux hostname 2.6.38.6-rc1 #1 VXOA 8.1.9.8_77260 SMP Tue Dec 31 14:19:17 PST 2019 x86_64", + "sysDescr": "Silver Peak Systems, Inc. ECS\r\nLinux hostname 2.6.38.6-rc1 #1 VXOA 8.1.9.8_77260 SMP Tue Dec 31 14:19:17 PST 2019 x86_64", "sysContact": "", "version": "8.1.9.8_77260", "hardware": "ECS", diff --git a/tests/mocks/mock.snmp.inc.php b/tests/mocks/mock.snmp.inc.php deleted file mode 100644 index 6f995ced74..0000000000 --- a/tests/mocks/mock.snmp.inc.php +++ /dev/null @@ -1,297 +0,0 @@ -. - * - * @link https://www.librenms.org - * - * @copyright 2016 Tony Murray - * @author Tony Murray - */ - -use Illuminate\Support\Str; -use LibreNMS\Config; - -$snmpMockCache = []; - -/** - * Cache the data from an snmprec file - * in ./tests/snmpsim/ - * - * @param string $file the snmprec file name (excluding .snmprec) - */ -function cache_snmprec($file) -{ - global $snmpMockCache; - if (isset($snmpMockCache[$file])) { - return; - } - $snmpMockCache[$file] = []; - - $data = file_get_contents(Config::get('install_dir') . "/tests/snmpsim/$file.snmprec"); - $line = strtok($data, "\r\n"); - while ($line !== false) { - [$oid, $type, $data] = explode('|', $line, 3); - if ($type == '4') { - $data = trim($data); - } elseif ($type == '6') { - $data = trim($data, '.'); - } elseif ($type == '4x') { - // MacAddress type is stored as hex string, but we don't understand mibs - if (Str::startsWith($oid, [ - '1.3.6.1.2.1.2.2.1.6', // IF-MIB::ifPhysAddress - '1.3.6.1.2.1.17.1.1.0', // BRIDGE-MIB::dot1dBaseBridgeAddress.0 - '1.3.6.1.4.1.890.1.5.13.13.8.1.1.20', // IES5206-MIB::slotModuleMacAddress - ])) { - $data = \LibreNMS\Util\Rewrite::readableMac($data); - } else { - $data = hex2str($data); - } - } - - $snmpMockCache[$file][$oid] = [$type, $data]; - $line = strtok("\r\n"); - } -} - -/** - * Get all data of the specified $community from the snmprec cache - * - * @param string $community snmp community to return - * @return array array of the data containing: [$oid][$type, $data] - * - * @throws Exception this $community is not cached - */ -function snmprec_get($community) -{ - global $snmpMockCache; - cache_snmprec($community); - d_echo($snmpMockCache); - - if (isset($snmpMockCache[$community])) { - return $snmpMockCache[$community]; - } - - throw new Exception("SNMPREC: community $community not cached"); -} - -/** - * Get an $oid from the specified $community - * - * @param string $community the community to fetch data from - * @param string $oid numeric oid of data to fetch - * @return array array of the data containing: [$type, $data] - * - * @throws Exception this $oid is not cached - */ -function snmprec_get_oid($community, $oid) -{ - global $snmpMockCache; - cache_snmprec($community); - - if (isset($snmpMockCache[$community]) && isset($snmpMockCache[$community][$oid])) { - return $snmpMockCache[$community][$oid]; - } - - throw new Exception("SNMPREC: oid $community:$oid not cached"); -} - -/** - * Get the numeric oid of an oid - * The leading dot is ommited by default to be compatible with snmpsim - * - * @param string $oid the oid to tranlslate - * @param string $mib mib to use - * @param string $mibdir mib dir to look for mib in - * @return string the oid in numeric format (.1.3.4.5) - * - * @throws Exception Could not translate the oid - */ -function snmp_translate_number($oid, $mib = null, $mibdir = null) -{ - // optimizations (35s -> 1.6s on my laptop) - if ($oid == 'SNMPv2-MIB::sysDescr.0') { - return '1.3.6.1.2.1.1.1.0'; - } - if ($oid == 'SNMPv2-MIB::sysObjectID.0') { - return '1.3.6.1.2.1.1.2.0'; - } - if ($oid == 'ENTITY-MIB::entPhysicalDescr.1') { - return '1.3.6.1.2.1.47.1.1.1.1.2.1'; - } - if ($oid == 'SML-MIB::product-Name.0') { - return '1.3.6.1.4.1.2.6.182.3.3.1.0'; - } - if ($oid == 'ENTITY-MIB::entPhysicalMfgName.1') { - return '1.3.6.1.2.1.47.1.1.1.1.12.1'; - } - if ($oid == 'GAMATRONIC-MIB::psUnitManufacture.0') { - return '1.3.6.1.4.1.6050.1.1.2.0'; - } - if ($oid === 'SYNOLOGY-SYSTEM-MIB::systemStatus.0') { - return '1.3.6.1.4.1.6574.1.1.0'; - } - // end optimizations - - if (preg_match('/^[\.\d]*$/', $oid)) { - return ltrim($oid, '.'); - } - - $cmd = "snmptranslate -IR -On '$oid'"; - $cmd .= ' -M ' . (isset($mibdir) ? Config::get('mib_dir') . ':' . Config::get('mib_dir') . "/$mibdir" : Config::get('mib_dir')); - if (isset($mib) && $mib) { - $cmd .= " -m $mib"; - } - - $number = shell_exec($cmd); - - if (empty($number)) { - throw new Exception('Could not translate oid: ' . $oid . PHP_EOL . 'Tried: ' . $cmd); - } - - return trim($number, ". \n\r"); -} - -function snmp_translate_type($oid, $mib = null, $mibdir = null) -{ - $cmd = "snmptranslate -IR -Td $oid"; - $cmd .= ' -M ' . (isset($mibdir) ? Config::get('mib_dir') . ':' . Config::get('mib_dir') . "/$mibdir" : Config::get('mib_dir')); - if (isset($mib) && $mib) { - $cmd .= " -m $mib"; - } - - $result = shell_exec($cmd); - - if (empty($result)) { - throw new Exception('Could not translate oid: ' . $oid . PHP_EOL . 'Tried: ' . $cmd); - } - - if (Str::contains($result, 'OCTET STRING')) { - return 4; - } - if (Str::contains($result, 'Integer32')) { - return 2; - } - if (Str::contains($result, 'NULL')) { - return 5; - } - if (Str::contains($result, 'OBJECT IDENTIFIER')) { - return 6; - } - if (Str::contains($result, 'IpAddress')) { - return 64; - } - if (Str::contains($result, 'Counter32')) { - return 65; - } - if (Str::contains($result, 'Gauge32')) { - return 66; - } - if (Str::contains($result, 'TimeTicks')) { - return 67; - } - if (Str::contains($result, 'Opaque')) { - return 68; - } - if (Str::contains($result, 'Counter64')) { - return 70; - } - - throw new Exception('Unknown type'); -} - -// Mocked functions - -function snmp_get($device, $oid, $options = null, $mib = null, $mibdir = null) -{ - $community = $device['community']; - $num_oid = snmp_translate_number($oid, $mib, $mibdir); - - try { - $data = snmprec_get_oid($community, $num_oid); - - $result = $data[1]; - if ($data[0] == 6) { - $result = '.' . $data[1]; - } - - d_echo("[SNMP] snmpget $community $oid ($num_oid): $result\n"); - - return $result; - } catch (Exception $e) { - 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 = []; - 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']; - $dev = snmprec_get($community); - $num_oid = snmp_translate_number($oid, $mib, $mibdir); - - $output = ''; - foreach ($dev as $key => $data) { - if (Str::startsWith($key, $num_oid)) { - if ($data[0] == 6) { - $output .= '.' . $data[1] . PHP_EOL; - } else { - $output .= $data[1] . PHP_EOL; - } - } - } - - d_echo("[SNMP] snmpwalk $community $num_oid: "); - if (empty($output)) { - d_echo("no data\n"); - // does this match the behavior of the real snmp_walk()? - return false; - } else { - d_echo($output); - - return $output; - } -}