Skip to content

Commit f319d85

Browse files
murrantStyleCIBot
andauthored
Set device availability during fast ping (librenms#18270)
* Set device availability in fast ping Refactor code * Apply fixes from StyleCI * default --------- Co-authored-by: StyleCI Bot <[email protected]>
1 parent 13ab0bb commit f319d85

22 files changed

+296
-275
lines changed

LibreNMS/Alert/RunAlerts.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public function describeAlert($alert)
120120
$obj['proc'] = $alert['proc'];
121121
$obj['status'] = $device->status;
122122
$obj['status_reason'] = $device->status_reason;
123-
if ((new ConnectivityHelper($device))->canPing()) {
123+
if (ConnectivityHelper::pingIsAllowed($device)) {
124124
$last_ping = Rrd::lastUpdate(Rrd::name($device->hostname, 'icmp-perf'));
125125
if ($last_ping) {
126126
$obj['ping_timestamp'] = $last_ping->timestamp;

LibreNMS/Data/Source/Fping.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
namespace LibreNMS\Data\Source;
2828

2929
use App\Facades\LibrenmsConfig;
30+
use LibreNMS\Enum\AddressFamily;
3031
use LibreNMS\Exceptions\FpingUnparsableLine;
3132
use Log;
3233
use Symfony\Component\Process\Process;
@@ -58,16 +59,15 @@ public function __construct()
5859
* Run fping against a hostname/ip in count mode and collect stats.
5960
*
6061
* @param string $host hostname or ip
61-
* @param string $address_family ipv4 or ipv6
62+
* @param AddressFamily $address_family ipv4 or ipv6
6263
* @return FpingResponse
6364
*/
64-
public function ping($host, $address_family = 'ipv4'): FpingResponse
65+
public function ping(string $host, AddressFamily $address_family = AddressFamily::IPv4): FpingResponse
6566
{
66-
if ($address_family == 'ipv6') {
67-
$cmd = $this->fping6_bin === false ? [$this->fping_bin, '-6'] : [$this->fping6_bin];
68-
} else {
69-
$cmd = $this->fping6_bin === false ? [$this->fping_bin, '-4'] : [$this->fping_bin];
70-
}
67+
$cmd = match ($address_family) {
68+
AddressFamily::IPv4 => $this->fping6_bin === false ? [$this->fping_bin, '-4'] : [$this->fping_bin],
69+
AddressFamily::IPv6 => $this->fping6_bin === false ? [$this->fping_bin, '-6'] : [$this->fping6_bin],
70+
};
7171

7272
// build the command
7373
$cmd = array_merge($cmd, [

LibreNMS/Enum/AddressFamily.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace LibreNMS\Enum;
4+
5+
enum AddressFamily: string
6+
{
7+
case IPv4 = 'ipv4';
8+
case IPv6 = 'ipv6';
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace LibreNMS\Enum;
4+
5+
enum AvailabilitySource: string
6+
{
7+
case NONE = '';
8+
case SNMP = 'snmp';
9+
case ICMP = 'icmp';
10+
}

LibreNMS/Polling/ConnectivityHelper.php

Lines changed: 4 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -28,168 +28,16 @@
2828

2929
use App\Facades\LibrenmsConfig;
3030
use App\Models\Device;
31-
use App\Models\DeviceOutage;
32-
use App\Models\Eventlog;
33-
use LibreNMS\Data\Source\Fping;
34-
use LibreNMS\Data\Source\FpingResponse;
35-
use LibreNMS\Enum\MaintenanceStatus;
36-
use LibreNMS\Enum\Severity;
37-
use SnmpQuery;
38-
use Symfony\Component\Process\Process;
3931

4032
class ConnectivityHelper
4133
{
42-
/**
43-
* @var Device
44-
*/
45-
private $device;
46-
/**
47-
* @var bool
48-
*/
49-
private $saveMetrics = false;
50-
/**
51-
* @var string
52-
*/
53-
private $family;
54-
/**
55-
* @var string
56-
*/
57-
private $target;
58-
59-
public function __construct(Device $device)
60-
{
61-
$this->device = $device;
62-
$this->target = $device->overwrite_ip ?: $device->hostname;
63-
}
64-
65-
/**
66-
* After pinging the device, save metrics about the ping response
67-
*/
68-
public function saveMetrics(): void
69-
{
70-
$this->saveMetrics = true;
71-
}
72-
73-
/**
74-
* Check if the device is up.
75-
* Save availability and ping data if enabled with savePingPerf()
76-
*/
77-
public function isUp(): bool
78-
{
79-
$ping_response = $this->isPingable();
80-
81-
// calculate device status
82-
if ($ping_response->success()) {
83-
if (! $this->canSnmp() || $this->isSNMPable()) {
84-
// up
85-
$this->device->status = true;
86-
$this->device->status_reason = '';
87-
} else {
88-
// snmp down
89-
$this->device->status = false;
90-
$this->device->status_reason = 'snmp';
91-
}
92-
} else {
93-
// icmp down
94-
$this->device->status = false;
95-
$this->device->status_reason = 'icmp';
96-
}
97-
98-
if ($this->saveMetrics) {
99-
if ($this->canPing()) {
100-
$ping_response->saveStats($this->device);
101-
}
102-
$this->updateAvailability($this->device->status);
103-
104-
$this->device->save(); // confirm device is saved
105-
}
106-
107-
return $this->device->status;
108-
}
109-
110-
/**
111-
* Check if the device responds to ICMP echo requests ("pings").
112-
*/
113-
public function isPingable(): FpingResponse
34+
public static function snmpIsAllowed(Device $device): bool
11435
{
115-
if (! $this->canPing()) {
116-
return FpingResponse::artificialUp($this->target);
117-
}
118-
119-
$status = app()->make(Fping::class)->ping($this->target, $this->ipFamily());
120-
121-
if ($status->duplicates > 0) {
122-
Eventlog::log('Duplicate ICMP response detected! This could indicate a network issue.', $this->device, 'icmp', Severity::Warning);
123-
$status->ignoreFailure(); // when duplicate is detected fping returns 1. The device is up, but there is another issue. Clue admins in with above event.
124-
}
125-
126-
return $status;
36+
return $device->snmp_disable === false;
12737
}
12838

129-
public function isSNMPable(): bool
39+
public static function pingIsAllowed(Device $device): bool
13040
{
131-
$response = SnmpQuery::device($this->device)->get('SNMPv2-MIB::sysObjectID.0');
132-
133-
return $response->getExitCode() === 0 || $response->isValid();
134-
}
135-
136-
public function traceroute(): array
137-
{
138-
$command = [LibrenmsConfig::get('traceroute', 'traceroute'), '-q', '1', '-w', '1', '-I', $this->target];
139-
if ($this->ipFamily() == 'ipv6') {
140-
$command[] = '-6';
141-
}
142-
143-
$process = new Process($command);
144-
$process->setTimeout(120);
145-
$process->run();
146-
147-
return [
148-
'traceroute' => $process->getOutput(),
149-
'traceroute_output' => $process->getErrorOutput(),
150-
];
151-
}
152-
153-
public function canSnmp(): bool
154-
{
155-
return ! $this->device->snmp_disable;
156-
}
157-
158-
public function canPing(): bool
159-
{
160-
return LibrenmsConfig::get('icmp_check') && ! ($this->device->exists && $this->device->getAttrib('override_icmp_disable') === 'true');
161-
}
162-
163-
public function ipFamily(): string
164-
{
165-
if ($this->family === null) {
166-
$this->family = preg_match('/6$/', $this->device->transport ?? '') ? 'ipv6' : 'ipv4';
167-
}
168-
169-
return $this->family;
170-
}
171-
172-
private function updateAvailability(bool $current_status): void
173-
{
174-
// skip update if we are considering maintenance
175-
if (LibrenmsConfig::get('graphing.availability_consider_maintenance')
176-
&& $this->device->getMaintenanceStatus() !== MaintenanceStatus::NONE) {
177-
return;
178-
}
179-
180-
if ($current_status) {
181-
// Device is up, close any open outages
182-
$this->device->outages()->whereNull('up_again')->get()->each(function (DeviceOutage $outage) {
183-
$outage->up_again = time();
184-
$outage->save();
185-
});
186-
187-
return;
188-
}
189-
190-
// Device is down, only open a new outage if none is currently open
191-
if ($this->device->getCurrentOutage() === null) {
192-
$this->device->outages()->save(new DeviceOutage(['going_down' => time()]));
193-
}
41+
return LibrenmsConfig::get('icmp_check') && ! ($device->exists && $device->getAttrib('override_icmp_disable') === 'true');
19442
}
19543
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace App\Actions\Device;
4+
5+
use App\Models\Device;
6+
use LibreNMS\Enum\AvailabilitySource;
7+
use LibreNMS\Polling\ConnectivityHelper;
8+
9+
class CheckDeviceAvailability
10+
{
11+
public function __construct(
12+
private SetDeviceAvailability $setDeviceAvailability,
13+
private DeviceIsPingable $deviceIsPingable,
14+
private DeviceIsSnmpable $deviceIsSnmpable,
15+
) {
16+
}
17+
18+
public function execute(Device $device, bool $commit = false): bool
19+
{
20+
$ping_response = $this->deviceIsPingable->execute($device);
21+
22+
if ($ping_response->success()) {
23+
$is_up_snmp = ! ConnectivityHelper::snmpIsAllowed($device) || $this->deviceIsSnmpable->execute($device);
24+
$this->setDeviceAvailability->execute($device, $is_up_snmp, AvailabilitySource::SNMP, $commit);
25+
} else { // icmp down
26+
$this->setDeviceAvailability->execute($device, false, AvailabilitySource::ICMP, $commit);
27+
}
28+
29+
if ($commit) {
30+
if (ConnectivityHelper::pingIsAllowed($device)) {
31+
$ping_response->saveStats($device);
32+
}
33+
34+
$device->save(); // confirm device is saved
35+
}
36+
37+
return $device->status;
38+
}
39+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace App\Actions\Device;
4+
5+
use App\Models\Device;
6+
use App\Models\Eventlog;
7+
use LibreNMS\Data\Source\Fping;
8+
use LibreNMS\Data\Source\FpingResponse;
9+
use LibreNMS\Enum\Severity;
10+
use LibreNMS\Polling\ConnectivityHelper;
11+
12+
class DeviceIsPingable
13+
{
14+
public function __construct(
15+
private Fping $fping,
16+
) {
17+
}
18+
19+
public function execute(Device $device): FpingResponse
20+
{
21+
if (! ConnectivityHelper::pingIsAllowed($device)) {
22+
return FpingResponse::artificialUp($device->pollerTarget());
23+
}
24+
25+
$status = $this->fping->ping($device->pollerTarget(), $device->ipFamily());
26+
27+
if ($status->duplicates > 0) {
28+
Eventlog::log('Duplicate ICMP response detected! This could indicate a network issue.', $device, 'icmp', Severity::Warning);
29+
$status->ignoreFailure(); // when duplicate is detected fping returns 1. The device is up, but there is another issue. Clue admins in with above event.
30+
}
31+
32+
return $status;
33+
}
34+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace App\Actions\Device;
4+
5+
use App\Models\Device;
6+
use SnmpQuery;
7+
8+
class DeviceIsSnmpable
9+
{
10+
public function execute(Device $device): bool
11+
{
12+
$response = SnmpQuery::device($device)->get('SNMPv2-MIB::sysObjectID.0');
13+
14+
return $response->getExitCode() === 0 || $response->isValid();
15+
}
16+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace App\Actions\Device;
4+
5+
use App\Models\Device;
6+
use LibreNMS\Enum\AvailabilitySource;
7+
8+
class SetDeviceAvailability
9+
{
10+
public function __construct(
11+
private UpdateDeviceOutage $updateDeviceOutage,
12+
) {
13+
}
14+
15+
/**
16+
* @param Device $device
17+
* @param bool $available
18+
* @param AvailabilitySource $source
19+
* @param bool $commit Save changes to the database
20+
* @return bool true if the status changed
21+
*/
22+
public function execute(Device $device, bool $available, AvailabilitySource $source = AvailabilitySource::NONE, bool $commit = true): bool
23+
{
24+
// if device was down and is now up, if reason was snmp and source is icmp, ignore
25+
if ($available && ! $device->status && $device->status_reason == AvailabilitySource::SNMP->value) {
26+
if ($source == AvailabilitySource::ICMP) {
27+
return false;
28+
}
29+
}
30+
31+
$device->status = $available;
32+
$device->status_reason = $available ? AvailabilitySource::NONE->value : $source->value;
33+
$changed = $device->isDirty('status');
34+
35+
if ($commit) {
36+
$device->save();
37+
$this->updateDeviceOutage->execute($device, $available);
38+
}
39+
40+
return $changed;
41+
}
42+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace App\Actions\Device;
4+
5+
use App\Facades\LibrenmsConfig;
6+
use App\Models\Device;
7+
use LibreNMS\Enum\AddressFamily;
8+
use Symfony\Component\Process\Process;
9+
10+
class TracerouteToDevice
11+
{
12+
public function execute(Device $device): string
13+
{
14+
$command = [LibrenmsConfig::get('traceroute', 'traceroute'), '-q', '1', '-w', '1', '-I', $device->pollerTarget()];
15+
16+
if ($device->ipFamily() == AddressFamily::IPv6) {
17+
$command[] = '-6';
18+
}
19+
20+
$process = new Process($command);
21+
$process->setTimeout(120);
22+
$process->run();
23+
24+
return $process->getOutput() . $process->getErrorOutput();
25+
}
26+
}

0 commit comments

Comments
 (0)