From 08c4d7f3b3eb40368cf3a6f108d69da6c314cb23 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Sun, 28 Aug 2022 19:47:20 -0500 Subject: [PATCH] Allow SnmpQuery to optionally abort walks if one fails (#14255) * Allow SnmpQuery to optionally abort walks if one fails * style * remove baseline --- LibreNMS/Data/Source/NetSnmpQuery.php | 28 ++++++++-- LibreNMS/Data/Source/SnmpQueryInterface.php | 6 ++ phpstan-baseline.neon | 5 -- tests/Mocks/SnmpQueryMock.php | 62 +++++++++++++++++---- 4 files changed, 79 insertions(+), 22 deletions(-) diff --git a/LibreNMS/Data/Source/NetSnmpQuery.php b/LibreNMS/Data/Source/NetSnmpQuery.php index f64be636f9..49a05718a5 100644 --- a/LibreNMS/Data/Source/NetSnmpQuery.php +++ b/LibreNMS/Data/Source/NetSnmpQuery.php @@ -87,6 +87,10 @@ class NetSnmpQuery implements SnmpQueryInterface * @var \App\Models\Device */ private $device; + /** + * @var bool + */ + private $abort = false; public function __construct() { @@ -159,6 +163,17 @@ class NetSnmpQuery implements SnmpQueryInterface return $this; } + /** + * When walking multiple OIDs, stop if one fails. Used when the first OID indicates if the rest are supported. + * OIDs will be walked in order, so you may want to put your OIDs in a specific order. + */ + public function abortOnFailure(): SnmpQueryInterface + { + $this->abort = true; + + return $this; + } + /** * Do not error on out of order indexes. * Use with caution as we could get stuck in an infinite loop. @@ -330,17 +345,20 @@ class NetSnmpQuery implements SnmpQueryInterface } } - private function execMultiple(string $command, array $oids): ?SnmpResponse + private function execMultiple(string $command, array $oids): SnmpResponse { - $combined = null; + $response = new SnmpResponse(''); foreach ($oids as $oid) { - $response = $this->exec($command, [$oid]); + $response = $response->append($this->exec($command, [$oid])); - $combined = $combined ? $combined->append($response) : $response; + // if abort on failure is set, return after first failure + if ($this->abort && ! $response->isValid()) { + return $response; + } } - return $combined; + return $response; } private function exec(string $command, array $oids): SnmpResponse diff --git a/LibreNMS/Data/Source/SnmpQueryInterface.php b/LibreNMS/Data/Source/SnmpQueryInterface.php index cbd1ab3c46..fa9d8025d0 100644 --- a/LibreNMS/Data/Source/SnmpQueryInterface.php +++ b/LibreNMS/Data/Source/SnmpQueryInterface.php @@ -58,6 +58,12 @@ interface SnmpQueryInterface */ public function mibDir(?string $dir): SnmpQueryInterface; + /** + * When walking multiple OIDs, stop if one fails. Used when the first OID indicates if the rest are supported. + * OIDs will be walked in order, so you may want to put your OIDs in a specific order. + */ + public function abortOnFailure(): SnmpQueryInterface; + /** * Do not error on out of order indexes. * Use with caution as we could get stuck in an infinite loop. diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index aa2d4cb308..f1794b1910 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10835,11 +10835,6 @@ parameters: count: 1 path: tests/MibTest.php - - - message: "#^Property LibreNMS\\\\Tests\\\\Mocks\\\\SnmpQueryMock\\:\\:\\$context is never read, only written\\.$#" - count: 1 - path: tests/Mocks/SnmpQueryMock.php - - message: "#^Method LibreNMS\\\\Tests\\\\OSDiscoveryTest\\:\\:checkOS\\(\\) has no return type specified\\.$#" count: 1 diff --git a/tests/Mocks/SnmpQueryMock.php b/tests/Mocks/SnmpQueryMock.php index 3af1196f3b..b208832834 100644 --- a/tests/Mocks/SnmpQueryMock.php +++ b/tests/Mocks/SnmpQueryMock.php @@ -67,6 +67,10 @@ class SnmpQueryMock implements SnmpQueryInterface * @var array|mixed */ private $options = []; + /** + * @var bool + */ + private $abort = false; public static function make(): SnmpQueryInterface { @@ -114,6 +118,13 @@ class SnmpQueryMock implements SnmpQueryInterface ->translate($oid, $mib); } + public function abortOnFailure(): SnmpQueryInterface + { + $this->abort = true; + + return $this; + } + public function allowUnordered(): SnmpQueryInterface { return $this; @@ -157,7 +168,7 @@ class SnmpQueryMock implements SnmpQueryInterface public function get($oid): SnmpResponse { - $community = $this->device->community; + $community = $this->community(); $num_oid = $this->translateNumber($oid); $data = $this->getSnmprec($community)[$num_oid] ?? [0, '']; @@ -166,27 +177,43 @@ class SnmpQueryMock implements SnmpQueryInterface return new SnmpResponse($this->outputLine($oid, $num_oid, $data[0], $data[1])); } - public function walk($oid): SnmpResponse + /** + * @param array|string $oids + * @return \LibreNMS\Data\Source\SnmpResponse + * + * @throws \Exception + */ + public function walk($oids): SnmpResponse { - $community = $this->device->community; - $num_oid = $this->translateNumber($oid); + $community = $this->community(); $dev = $this->getSnmprec($community); + $response = new SnmpResponse(''); - $output = ''; - foreach ($dev as $key => $data) { - if (Str::startsWith($key, $num_oid)) { - $output .= $this->outputLine($oid, $num_oid, $data[0], $data[1]); + foreach (Arr::wrap($oids) as $oid) { + $num_oid = $this->translateNumber($oid); + + $output = ''; + foreach ($dev as $key => $data) { + if (Str::startsWith($key, $num_oid)) { + $output .= $this->outputLine($oid, $num_oid, $data[0], $data[1]); + } } + + $response = $response->append(new SnmpResponse($output)); + + if ($this->abort && ! $response->isValid()) { + return $response; + } + + Log::debug("[SNMP] snmpwalk $community $num_oid"); } - Log::debug("[SNMP] snmpwalk $community $num_oid"); - - return new SnmpResponse($output); + return $response; } public function next($oid): SnmpResponse { - $community = $this->device->community; + $community = $this->community(); $num_oid = $this->translateNumber($oid); $dev = $this->getSnmprec($community); @@ -324,6 +351,17 @@ class SnmpQueryMock implements SnmpQueryInterface return ltrim($number, '.'); } + private function community(): string + { + $community = $this->device->community; + + if (! empty($this->context)) { + $community .= '_' . $this->context; + } + + return $community; + } + private function extractMib(string $oid): ?string { if (Str::contains($oid, '::')) {