mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Connectivity Helper to check and record device reachability (#13315)
* Fping WIP
* Update availability, move ping rrd update in the same place as db update.
* move classes around
* make device:ping command work
* use new code, remove legacy code
* save metrics boolean prevents all saves
style fixes
* update device array
* style fixes
* Update unit test
* fix whitespace
* Fix Fping stub
* fix backwards if
* fix phpstan complaining
* Fix return type
* add fillable to DeviceOutage model.
* device_outage migration to add id...
* missed line in db_schema.yaml
* 1 billion more comments on the brain damage up/down code
* tests for status and status_reason fields
* fix style again :D
* Duplicate legacy isSNMPable() functionality
but with only one snmp call ever 😎
* Remove unused variable
* fix migrations for sqlite
This commit is contained in:
216
LibreNMS/Polling/ConnectivityHelper.php
Normal file
216
LibreNMS/Polling/ConnectivityHelper.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/*
|
||||
* ConnectivityHelper.php
|
||||
*
|
||||
* Helper to check the connectivity to a device and optionally save metrics about that connectivity
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Polling;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceOutage;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Data\Source\Fping;
|
||||
use LibreNMS\Data\Source\FpingResponse;
|
||||
use LibreNMS\RRD\RrdDefinition;
|
||||
use Log;
|
||||
use NetSnmp;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class ConnectivityHelper
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\Device
|
||||
*/
|
||||
private $device;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $saveMetrics = false;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $family;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $target;
|
||||
|
||||
public function __construct(Device $device)
|
||||
{
|
||||
$this->device = $device;
|
||||
$this->target = $device->overwrite_ip ?: $device->hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* After pinging the device, save metrics about the ping response
|
||||
*/
|
||||
public function saveMetrics(): void
|
||||
{
|
||||
$this->saveMetrics = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the device is up.
|
||||
* Save availability and ping data if enabled with savePingPerf()
|
||||
*/
|
||||
public function isUp(): bool
|
||||
{
|
||||
$previous = $this->device->status;
|
||||
$ping_response = $this->isPingable();
|
||||
|
||||
// calculate device status
|
||||
if ($ping_response->success()) {
|
||||
if (! $this->canSnmp() || $this->isSNMPable()) {
|
||||
// up
|
||||
$this->device->status = true;
|
||||
$this->device->status_reason = '';
|
||||
} else {
|
||||
// snmp down
|
||||
$this->device->status = false;
|
||||
$this->device->status_reason = 'snmp';
|
||||
}
|
||||
} else {
|
||||
// icmp down
|
||||
$this->device->status = false;
|
||||
$this->device->status_reason = 'icmp';
|
||||
}
|
||||
|
||||
if ($this->saveMetrics) {
|
||||
if ($this->canPing()) {
|
||||
$this->savePingStats($ping_response);
|
||||
}
|
||||
$this->updateAvailability($previous, $this->device->status);
|
||||
|
||||
$this->device->save(); // confirm device is saved
|
||||
}
|
||||
|
||||
return $this->device->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the device responds to ICMP echo requests ("pings").
|
||||
*/
|
||||
public function isPingable(): FpingResponse
|
||||
{
|
||||
if (! $this->canPing()) {
|
||||
return FpingResponse::artificialUp();
|
||||
}
|
||||
|
||||
$status = app()->make(Fping::class)->ping(
|
||||
$this->target,
|
||||
Config::get('fping_options.count', 3),
|
||||
Config::get('fping_options.interval', 500),
|
||||
Config::get('fping_options.timeout', 500),
|
||||
$this->ipFamily()
|
||||
);
|
||||
|
||||
if ($status->duplicates > 0) {
|
||||
Log::event('Duplicate ICMP response detected! This could indicate a network issue.', $this->device, 'icmp', 4);
|
||||
$status->exit_code = 0; // when duplicate is detected fping returns 1. The device is up, but there is another issue. Clue admins in with above event.
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function isSNMPable(): bool
|
||||
{
|
||||
$response = NetSnmp::device($this->device)->get('SNMPv2-MIB::sysObjectID.0');
|
||||
|
||||
return $response->getExitCode() === 0 || $response->isValid();
|
||||
}
|
||||
|
||||
public function traceroute(): array
|
||||
{
|
||||
$command = [Config::get('traceroute', 'traceroute'), '-q', '1', '-w', '1', $this->target];
|
||||
if ($this->ipFamily() == 'ipv6') {
|
||||
$command[] = '-6';
|
||||
}
|
||||
|
||||
$process = new Process($command);
|
||||
$process->run();
|
||||
|
||||
return [
|
||||
'traceroute' => $process->getOutput(),
|
||||
'output' => $process->getErrorOutput(),
|
||||
];
|
||||
}
|
||||
|
||||
public function canSnmp(): bool
|
||||
{
|
||||
return ! $this->device->snmp_disable;
|
||||
}
|
||||
|
||||
public function canPing(): bool
|
||||
{
|
||||
return Config::get('icmp_check') && ! ($this->device->exists && $this->device->getAttrib('override_icmp_disable') === 'true');
|
||||
}
|
||||
|
||||
public function ipFamily(): string
|
||||
{
|
||||
if ($this->family === null) {
|
||||
$this->family = preg_match('/6$/', $this->device->transport) ? 'ipv6' : 'ipv4';
|
||||
}
|
||||
|
||||
return $this->family;
|
||||
}
|
||||
|
||||
private function updateAvailability(bool $previous, bool $status): void
|
||||
{
|
||||
if (Config::get('graphing.availability_consider_maintenance') && $this->device->isUnderMaintenance()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for open outage
|
||||
$open_outage = $this->device->outages()->whereNull('up_again')->orderBy('going_down', 'desc')->first();
|
||||
|
||||
if ($status) {
|
||||
if ($open_outage) {
|
||||
$open_outage->up_again = time();
|
||||
$open_outage->save();
|
||||
}
|
||||
} elseif ($previous || $open_outage === null) {
|
||||
// status changed from up to down or there is no open outage
|
||||
// open new outage
|
||||
$this->device->outages()->save(new DeviceOutage(['going_down' => time()]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the ping stats to db and rrd, also updates last_ping_timetaken and saves the device model.
|
||||
*/
|
||||
private function savePingStats(FpingResponse $ping_response): void
|
||||
{
|
||||
$perf = $ping_response->toModel();
|
||||
if (! $ping_response->success() && Config::get('debug.run_trace', false)) {
|
||||
$perf->debug = $this->traceroute();
|
||||
}
|
||||
$this->device->perf()->save($perf);
|
||||
$this->device->last_ping_timetaken = $ping_response->avg_latency ?: $this->device->last_ping_timetaken;
|
||||
$this->device->save();
|
||||
|
||||
app('Datastore')->put($this->device->toArray(), 'ping-perf', [
|
||||
'rrd_def' => RrdDefinition::make()->addDataset('ping', 'GAUGE', 0, 65535),
|
||||
], [
|
||||
'ping' => $ping_response->avg_latency,
|
||||
]);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user