mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-25 12:37:47 +09:00
* fix(core): correct client state and declare public method contracts Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(security): isolate Midwinter external homepage popup Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(client): isolate menu state and simplify validated JavaScript guards Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(auth): use certificate expiry truth in rendering assertions Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(js): remove unused local storage while retaining initializer calls Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(js): encode inline translated values through the shared JSON boundary Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * style(js): wrap encoded translation literals without changing messages Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(client): remove unused checkbox reads from graph resizing Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(client): declare repeated function locals once Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(ui): run column width regression in browser CI Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
804 lines
27 KiB
PHP
804 lines
27 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| 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 2 |
|
|
| 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. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
| This code is designed, written, and maintained by the Cacti Group. See |
|
|
| about.php and/or the AUTHORS file for specific developer information. |
|
|
+-------------------------------------------------------------------------+
|
|
| http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
class Net_Ping {
|
|
public socket $socket;
|
|
public array $host;
|
|
public int $port;
|
|
public mixed $ping_status;
|
|
public string $ping_response;
|
|
public mixed $snmp_status;
|
|
public string $snmp_response;
|
|
public string $request;
|
|
public int $request_len;
|
|
public mixed $reply;
|
|
public int $timeout;
|
|
public int $retries;
|
|
public int $precision;
|
|
public int $time;
|
|
public float $timer_start_time;
|
|
public string $sqn;
|
|
public int $avail_method;
|
|
public int $ping_type;
|
|
|
|
public function __construct() {
|
|
$this->port = 33439;
|
|
}
|
|
|
|
public function __destruct() {
|
|
}
|
|
|
|
public function close_socket() : void {
|
|
@socket_shutdown($this->socket, 2);
|
|
socket_close($this->socket);
|
|
}
|
|
|
|
public function start_time() : void {
|
|
$this->timer_start_time = microtime(true);
|
|
}
|
|
|
|
public function get_time(int $acc = 2) : mixed {
|
|
// format start time
|
|
$start_time = $this->timer_start_time;
|
|
// get and format end time
|
|
$end_time = microtime(true);
|
|
|
|
return number_format($end_time - $start_time, $acc);
|
|
}
|
|
|
|
public function build_udp_packet() : void {
|
|
$data = 'cacti-monitoring-system'; // the actual test data
|
|
|
|
// now lets build the actual UDP packet
|
|
$this->request = chr(0) . chr(1) . chr(0) . $data . chr(0);
|
|
$this->request_len = strlen($this->request);
|
|
}
|
|
|
|
public function ping_error_handler(int $errno, string $errmsg, string $filename, int $linenum, array $vars = []) : bool {
|
|
return true;
|
|
}
|
|
|
|
public function set_ping_error_handler() : void {
|
|
set_error_handler([$this, 'ping_error_handler']);
|
|
}
|
|
|
|
public function restore_cacti_error_handler() : void {
|
|
restore_error_handler();
|
|
}
|
|
|
|
public function build_icmp_packet() : void {
|
|
$seq_low = random_int(0,255);
|
|
$seq_high = random_int(0,255);
|
|
|
|
$data = 'cacti-monitoring-system'; // the actual test data
|
|
$type = "\x08"; // 8 echo message; 0 echo reply message
|
|
$code = "\x00"; // always 0 for this program
|
|
$chksm = "\x00\x00"; // generate checksum for icmp request
|
|
$id = chr($seq_high) . chr($seq_low);
|
|
$sqn = chr($seq_high) . chr($seq_low);
|
|
$this->sqn = $sqn;
|
|
|
|
// now lets build the actual icmp packet
|
|
$this->request = $type . $code . $chksm . $id . $sqn . $data;
|
|
$chksm = $this->get_checksum($this->request);
|
|
|
|
$this->request = $type . $code . $chksm . $id . $sqn . $data;
|
|
$this->request_len = strlen($this->request);
|
|
}
|
|
|
|
public function get_checksum(string $data) : string {
|
|
if (strlen($data) % 2) {
|
|
$data .= "\x00";
|
|
}
|
|
|
|
$bit = unpack('n*', $data);
|
|
$sum = array_sum($bit);
|
|
|
|
while ($sum >> 16) {
|
|
$sum = ($sum >> 16) + ($sum & 0xffff);
|
|
}
|
|
|
|
return pack('n*', ~$sum);
|
|
}
|
|
|
|
public function ping_icmp() : bool {
|
|
// ping me
|
|
if ($this->host['hostname']) {
|
|
// initialize variables
|
|
$this->ping_status = 'down';
|
|
$this->ping_response = __('ICMP Ping timed out');
|
|
|
|
// establish timeout variables
|
|
$to_sec = floor($this->timeout / 1000);
|
|
$to_usec = ($this->timeout % 1000) * 1000;
|
|
|
|
// clean up hostname if specifying snmp_transport
|
|
$this->host['hostname'] = $this->strip_ip_address($this->host['hostname']);
|
|
|
|
/* determine the host's ip address
|
|
* this prevents from command injection as well*/
|
|
if ($this->is_ipaddress($this->host['hostname'])) {
|
|
$host_ip = $this->host['hostname'];
|
|
} else {
|
|
// again, as a side effect, prevention from command injection
|
|
$host_ip = cacti_gethostbyname($this->host['hostname']);
|
|
|
|
if (!$this->is_ipaddress($host_ip)) {
|
|
cacti_log('WARNING: ICMP Ping Error: cacti_gethostbyname failed for ' . $this->host['hostname']);
|
|
$this->ping_response = 'ICMP Ping Error: cacti_gethostbyname failed for ' . $this->host['hostname'];
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// we have to use the real ping, in cases where windows failed or while using UNIX/Linux
|
|
$pattern = bin2hex('cacti-monitoring-system'); // the actual test data
|
|
|
|
$fping = read_config_option('path_fping');
|
|
|
|
if ($fping != '' && file_exists($fping) && is_executable($fping)) {
|
|
$using_fping = true;
|
|
|
|
if (filter_var($this->host['hostname'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
if (file_exists('/usr/sbin/fping6')) {
|
|
$fping = '/usr/sbin/fping6';
|
|
} elseif (file_exists('/usr/bin/fping6')) {
|
|
$fping = '/usr/bin/fping6';
|
|
} elseif (file_exists('/usr/local/sbin/fping6')) {
|
|
$fping = '/usr/local/sbin/fping6';
|
|
} elseif (file_exists('/usr/local/bin/fping6')) {
|
|
$fping = '/usr/local/bin/fping6';
|
|
}
|
|
}
|
|
|
|
$result = shell_exec(cacti_escapeshellarg($fping) . ' -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1');
|
|
} else {
|
|
$using_fping = false;
|
|
|
|
/**
|
|
* The host timeout is given in ms; recalculate to seconds.
|
|
* All numeric fields are safe. The hostname is quoted via
|
|
* cacti_escapeshellarg() as defense-in-depth alongside the
|
|
* DNS resolution check above.
|
|
*/
|
|
if (substr_count(cacti_strtolower(PHP_OS), 'sun')) {
|
|
$result = shell_exec('ping ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'hpux')) {
|
|
$result = shell_exec('ping -m ' . ceil($this->timeout / 1000) . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'mac')) {
|
|
$result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'freebsd')) {
|
|
if (filter_var($host_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
$result = shell_exec('ping6 -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
} else {
|
|
$result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
}
|
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'darwin')) {
|
|
$result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'bsd')) {
|
|
$result = shell_exec('ping -w ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'aix')) {
|
|
$result = shell_exec('ping -i ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
} elseif (cacti_strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
|
$result = shell_exec('chcp 437 && ping -w ' . $this->timeout . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
} else {
|
|
/**
|
|
* Please know, that when running SELinux, httpd will throw
|
|
* ping: cap_set_proc: Permission denied
|
|
* as it now tries to open an ICMP socket and fails
|
|
* $result will be empty, then.
|
|
*/
|
|
if (filter_var($host_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
$result = shell_exec('ping -6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
|
} else {
|
|
$result = shell_exec('ping -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1');
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($using_fping) {
|
|
/**
|
|
* Prototype fping output below:
|
|
* www.google.com : xmt/rcv/%loss = 1/1/0%, min/avg/max = 25.6/25.6/25.6
|
|
* 172.24.254.2 : xmt/rcv/%loss = 1/1/0%, min/avg/max = 7.01/7.01/7.01
|
|
*/
|
|
$position = strpos((string) $result, 'min/avg/max =');
|
|
|
|
if ($position > 0) {
|
|
$output = substr((string) $result, $position);
|
|
$results = explode('/', $output);
|
|
$this->ping_status = $results[3]; // avg
|
|
$this->ping_response = __('ICMP Ping Success (fping.exe) (%s ms)', $results[1]);
|
|
|
|
return true;
|
|
} else {
|
|
$this->ping_status = 'down';
|
|
$this->ping_response = __('ICMP Ping Timed Out (fping.exe) (' . $this->host['hostname'] . '), Result [' . $result . ']');
|
|
|
|
return false;
|
|
}
|
|
} elseif (str_starts_with(PHP_OS, 'WIN')) {
|
|
/**
|
|
* The native ping command was used for windows platform has output
|
|
* as follows.
|
|
*
|
|
* ping -w 500 -n 3 abc
|
|
*
|
|
* Pinging abc [216.239.38.120] with 32 bytes of data:
|
|
* Reply from 216.239.38.120: bytes=32 time=30ms TTL=53
|
|
* Reply from 216.239.38.120: bytes=32 time=22ms TTL=53
|
|
* Reply from 216.239.38.120: bytes=32 time=25ms TTL=53
|
|
*
|
|
* Ping statistics for 216.239.38.120:
|
|
* Packets: Sent = 3, Received = 3, Lost = 0 (0% loss),
|
|
* Approximate round trip times in milli-seconds:
|
|
* Minimum = 22ms, Maximum = 30ms, Average = 25ms
|
|
*/
|
|
$position = strpos((string) $result, 'Minimum');
|
|
|
|
if ($position > 0) {
|
|
$output = trim(substr((string) $result, $position));
|
|
$pieces = explode(',', $output);
|
|
$results = explode('=', $pieces[2]); // Average
|
|
|
|
$this->ping_status = trim(str_replace('ms', '', $output));
|
|
$this->ping_response = __('ICMP Ping Success (ping.exe) (%s ms)', $this->ping_status);
|
|
|
|
return true;
|
|
} else {
|
|
$this->ping_status = 'down';
|
|
$this->ping_response = __('ICMP Ping Timed Out (ping.exe) (' . $this->host['hostname'] . '), Result [' . $result . ']');
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
$position = strpos((string) $result, 'min/avg/max');
|
|
|
|
if ($position > 0) {
|
|
$output = trim(str_replace(' ms', '', substr((string) $result, $position)));
|
|
$pieces = explode('=', $output);
|
|
$results = explode('/', $pieces[1]);
|
|
|
|
$this->ping_status = $results[1];
|
|
$this->ping_response = __('ICMP Ping Success (%s ms)', $results[1]);
|
|
|
|
return true;
|
|
} else {
|
|
$this->ping_status = 'down';
|
|
$this->ping_response = __('ICMP ping Timed out');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
$this->ping_status = 'down';
|
|
$this->ping_response = __('Destination address not specified');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function seteuid() : int {
|
|
$cacti_user = '';
|
|
|
|
// if we are unix, set the effective userid to root and then create
|
|
if ((CACTI_SERVER_OS == 'unix') &&
|
|
(function_exists('posix_getuid'))) {
|
|
$cacti_user = posix_getuid();
|
|
posix_seteuid(0);
|
|
}
|
|
|
|
return $cacti_user;
|
|
}
|
|
|
|
public function setuid(int $cacti_poller_account) : void {
|
|
// if we are unix, set the effective userid to root and then create
|
|
if ((CACTI_SERVER_OS == 'unix') &&
|
|
(function_exists('posix_getuid'))) {
|
|
posix_seteuid($cacti_poller_account);
|
|
}
|
|
}
|
|
|
|
public function ping_snmp() : mixed {
|
|
// initialize variables
|
|
$this->snmp_status = 'down';
|
|
$this->snmp_response = 'Device did not respond to SNMP';
|
|
$output = '';
|
|
|
|
// get start time
|
|
$this->start_time();
|
|
|
|
// by default, we look at sysUptime
|
|
if ($this->avail_method == AVAIL_SNMP_GET_NEXT) {
|
|
$oid = '.1.3.6.1.2.1.1.3.0';
|
|
} elseif ($this->avail_method == AVAIL_SNMP_GET_SYSDESC) {
|
|
$oid = '.1.3.6.1.2.1.1.1.0';
|
|
} else {
|
|
$oid = '.1.3.6.1.2.1.1.3.0';
|
|
}
|
|
|
|
/* Phase 1 (snmp3_get test): run the SNMPv3 availability check through the
|
|
procedural cacti_snmp_get()/getnext() path (snmp_get_method -> snmp3_get)
|
|
instead of the SNMP class session, so an unsupported privacy protocol is
|
|
caught and can fall back to the Net-SNMP binary rather than raising an
|
|
uncaught ValueError in SNMP::setSecurity(). */
|
|
$result = $this->get_snmp_result($oid);
|
|
|
|
if (!$result && $oid == '.1.3.6.1.2.1.1.3.0') {
|
|
$result = $this->get_snmp_result('.1.3.6.1.6.3.10.2.1.3.0');
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function get_snmp_result(string $oid) : bool {
|
|
$h = $this->host;
|
|
|
|
// getnext does not work in php versions less than 5
|
|
if (($this->avail_method == AVAIL_SNMP_GET_NEXT) &&
|
|
(version_compare('5', phpversion(), '<'))) {
|
|
$output = cacti_snmp_getnext($h['hostname'], $h['snmp_community'], $oid,
|
|
$h['snmp_version'], $h['snmp_username'], $h['snmp_password'],
|
|
$h['snmp_auth_protocol'], $h['snmp_priv_passphrase'], $h['snmp_priv_protocol'],
|
|
$h['snmp_context'], $h['snmp_port'], $h['snmp_timeout'], $this->retries,
|
|
'SNMP', $h['snmp_engine_id']);
|
|
} else {
|
|
$output = cacti_snmp_get($h['hostname'], $h['snmp_community'], $oid,
|
|
$h['snmp_version'], $h['snmp_username'], $h['snmp_password'],
|
|
$h['snmp_auth_protocol'], $h['snmp_priv_passphrase'], $h['snmp_priv_protocol'],
|
|
$h['snmp_context'], $h['snmp_port'], $h['snmp_timeout'], $this->retries,
|
|
'SNMP', $h['snmp_engine_id']);
|
|
}
|
|
|
|
// determine total time +- ~10%
|
|
$this->time = $this->get_time($this->precision);
|
|
|
|
// check result for uptime
|
|
if ($output !== false && $output != 'U' && strlen($output)) {
|
|
// calculate total time
|
|
$this->snmp_status = $this->time * 1000;
|
|
$this->snmp_response = 'Device responded to SNMP';
|
|
|
|
return true;
|
|
} else {
|
|
$this->snmp_status = 'down';
|
|
$this->snmp_response = 'Device did not respond to SNMP';
|
|
|
|
return false;
|
|
}
|
|
} // ping_snmp
|
|
|
|
public function ping_udp() : bool {
|
|
$this->set_ping_error_handler();
|
|
|
|
// hostname must be nonblank
|
|
if ($this->host['hostname'] != '') {
|
|
// initialize variables
|
|
$this->ping_status = 'down';
|
|
$this->ping_response = __('default');
|
|
|
|
// establish timeout variables
|
|
$to_sec = intval(floor($this->timeout / 1000));
|
|
$to_usec = intval(($this->timeout % 1000) * 1000);
|
|
|
|
// clean up hostname if specifying snmp_transport
|
|
$this->host['hostname'] = $this->strip_ip_address($this->host['hostname']);
|
|
|
|
// determine the host's ip address
|
|
if ($this->is_ipaddress($this->host['hostname'])) {
|
|
$host_ip = $this->host['hostname'];
|
|
} else {
|
|
$host_ip = cacti_gethostbyname($this->host['hostname']);
|
|
|
|
if (!$this->is_ipaddress($host_ip)) {
|
|
cacti_log('WARNING: UDP Ping Error: cacti_gethostbyname failed for ' . $this->host['hostname']);
|
|
$this->ping_response = 'UDP Ping Error: cacti_gethostbyname failed for ' . $this->host['hostname'];
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// initialize the socket
|
|
if (filter_var($host_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
if (defined('AF_INET6')) {
|
|
$this->socket = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP);
|
|
} else {
|
|
$this->ping_response = __('IPv6 support seems to be missing!');
|
|
$this->ping_status = 'down';
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
$this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
|
|
}
|
|
|
|
socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $to_sec, 'usec' => $to_usec]);
|
|
socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $to_sec, 'usec' => $to_usec]);
|
|
|
|
socket_connect($this->socket, $host_ip, $this->port);
|
|
|
|
// format packet
|
|
$this->build_udp_packet();
|
|
|
|
$error = '';
|
|
$retry_count = 0;
|
|
|
|
while (true) {
|
|
if ($retry_count >= $this->retries) {
|
|
$this->ping_status = 'down';
|
|
$this->ping_response = __('UDP ping error: %s', $error);
|
|
$this->close_socket();
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return false;
|
|
}
|
|
|
|
// get start time
|
|
$this->start_time();
|
|
|
|
// write to the socket
|
|
socket_write($this->socket, $this->request, $this->request_len);
|
|
|
|
// get the socket response
|
|
$r = [$this->socket];
|
|
$w = [$this->socket];
|
|
$f = [$this->socket];
|
|
|
|
$num_changed_sockets = socket_select($r, $w, $f, $to_sec, $to_usec);
|
|
|
|
if ($num_changed_sockets === false) {
|
|
$error = 'UDP ping: socket_select(), reason: ' . socket_strerror(socket_last_error($this->socket));
|
|
} else {
|
|
switch($num_changed_sockets) {
|
|
case 2: // response received, so host is available
|
|
case 1:
|
|
// get packet response
|
|
// $code = socket_recv($this->socket, $this->reply, 256, 0);
|
|
$code = socket_recv($this->socket, $this->reply, 256, 0);
|
|
|
|
// get the end time after the packet was received
|
|
$this->time = $this->get_time($this->precision);
|
|
|
|
$errno = socket_last_error($this->socket);
|
|
socket_clear_error($this->socket);
|
|
|
|
if (($code == -1 || empty($code)) &&
|
|
($errno == EHOSTUNREACH || $errno == ECONNRESET || $errno == ECONNREFUSED)) {
|
|
// set the return message
|
|
$this->ping_status = $this->time * 1000;
|
|
$this->ping_response = __('UDP Ping Success (%s ms)', $this->time * 1000);
|
|
|
|
$this->close_socket();
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return true;
|
|
} else {
|
|
$error = socket_strerror($errno);
|
|
}
|
|
|
|
break;
|
|
case 0:
|
|
// timeout
|
|
$error = 'timeout';
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
$retry_count++;
|
|
}
|
|
} else {
|
|
$this->ping_response = __('Destination address not specified');
|
|
$this->ping_status = 'down';
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return false;
|
|
}
|
|
} // end ping_udp
|
|
|
|
public function ping_tcp() : bool {
|
|
$this->set_ping_error_handler();
|
|
|
|
// hostname must be nonblank
|
|
if ($this->host['hostname'] != '') {
|
|
// initialize variables
|
|
$this->ping_status = 'down';
|
|
$this->ping_response = __('default');
|
|
|
|
// establish timeout variables
|
|
$to_sec = intval(floor($this->timeout / 1000));
|
|
$to_usec = intval(($this->timeout % 1000) * 1000);
|
|
|
|
// clean up hostname if specifying snmp_transport
|
|
$this->host['hostname'] = $this->strip_ip_address($this->host['hostname']);
|
|
|
|
// determine the host's ip address
|
|
if ($this->is_ipaddress($this->host['hostname'])) {
|
|
$host_ip = $this->host['hostname'];
|
|
} else {
|
|
$host_ip = cacti_gethostbyname($this->host['hostname']);
|
|
|
|
if (!$this->is_ipaddress($host_ip)) {
|
|
cacti_log('WARNING: TCP Ping Error: cacti_gethostbyname failed for ' . $this->host['hostname']);
|
|
$this->ping_response = 'TCP Ping Error: cacti_gethostbyname failed for ' . $this->host['hostname'];
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// initialize the socket
|
|
if (filter_var($host_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
if (defined('AF_INET6')) {
|
|
$this->socket = socket_create(AF_INET6, SOCK_STREAM, SOL_TCP);
|
|
} else {
|
|
$this->ping_response = __('IPv6 support appears to be missing!');
|
|
$this->ping_status = 'down';
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
|
}
|
|
|
|
while (1) {
|
|
// set start time
|
|
$this->start_time();
|
|
|
|
socket_set_block($this->socket);
|
|
socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $to_sec, 'usec' => $to_usec]);
|
|
socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $to_sec, 'usec' => $to_usec]);
|
|
|
|
socket_connect($this->socket, $host_ip, $this->port);
|
|
|
|
$errno = socket_last_error($this->socket);
|
|
|
|
if ($errno > 0) {
|
|
if ($errno == 111 && $this->ping_type == PING_TCP_CLOSED) {
|
|
$this->time = $this->get_time($this->precision);
|
|
$this->ping_status = 'up';
|
|
$this->ping_response = __('TCP Ping Success Connection Refused (%s ms)', $this->time * 1000);
|
|
$this->ping_status = $this->time * 1000;
|
|
} else {
|
|
$this->ping_response = __('TCP Ping Failed: socket_connect(), reason: %s', socket_strerror($errno));
|
|
$this->ping_status = 'down';
|
|
}
|
|
|
|
socket_clear_error($this->socket);
|
|
|
|
$this->close_socket();
|
|
$this->restore_cacti_error_handler();
|
|
|
|
if ($this->ping_status == 'down') {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
$r = [$this->socket];
|
|
$w = [$this->socket];
|
|
$f = [$this->socket];
|
|
|
|
$num_changed_sockets = socket_select($r, $w, $f, $to_sec, $to_usec);
|
|
|
|
if ($num_changed_sockets === false) {
|
|
$this->ping_response = __('TCP Ping Failed: socket_select() failed, reason: %s', socket_strerror(socket_last_error()));
|
|
$this->ping_status = 'down';
|
|
|
|
$this->close_socket();
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return false;
|
|
} else {
|
|
switch($num_changed_sockets) {
|
|
case 2: // response received, so host is available
|
|
case 1:
|
|
// connected, so calculate the total time and return
|
|
$this->time = $this->get_time($this->precision);
|
|
|
|
if (($this->time * 1000) <= $this->timeout) {
|
|
$this->ping_response = __('TCP Ping Success (%s ms)', $this->time * 1000);
|
|
$this->ping_status = $this->time * 1000;
|
|
}
|
|
|
|
$this->close_socket();
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return true;
|
|
case 0:
|
|
// timeout
|
|
$this->ping_response = __('TCP ping timed out');
|
|
$this->ping_status = 'down';
|
|
|
|
$this->close_socket();
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$this->ping_response = __('Destination address not specified');
|
|
$this->ping_status = 'down';
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return false;
|
|
}
|
|
} // end ping_tcp
|
|
|
|
public function ping(int $avail_method = AVAIL_SNMP_AND_PING, int $ping_type = PING_ICMP, int $timeout = 500, int $retries = 3) : bool {
|
|
$this->set_ping_error_handler();
|
|
|
|
// initialize variables
|
|
$ping_ping = true;
|
|
$ping_snmp = true;
|
|
|
|
$this->ping_status = 'down';
|
|
$this->ping_response = __('Ping not performed due to setting.');
|
|
$this->ping_type = $ping_type;
|
|
$this->snmp_status = 'down';
|
|
$this->snmp_response = 'SNMP not performed due to setting or ping result.';
|
|
$this->avail_method = $avail_method;
|
|
|
|
// short circuit for availability none
|
|
if ($avail_method == AVAIL_NONE) {
|
|
$this->ping_status = '0.00';
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return true;
|
|
}
|
|
|
|
$sockets_fallback = false;
|
|
|
|
// AVAIL_NONE already returned above, so it needs no second test here
|
|
if (!function_exists('socket_create')) {
|
|
$avail_method = AVAIL_SNMP;
|
|
$sockets_fallback = true;
|
|
|
|
cacti_log('WARNING: sockets support not enabled in PHP, falling back to SNMP ping');
|
|
}
|
|
|
|
if (($retries <= 0) || ($retries > 5)) {
|
|
$this->retries = 2;
|
|
} else {
|
|
$this->retries = $retries;
|
|
}
|
|
|
|
if ($timeout <= 0) {
|
|
$this->timeout = 500;
|
|
} else {
|
|
$this->timeout = $timeout;
|
|
}
|
|
|
|
// decimal precision is 0.0000
|
|
$this->precision = 5;
|
|
|
|
// snmp pinging has been selected at a minimum
|
|
$ping_result = false;
|
|
$snmp_result = false;
|
|
|
|
// icmp/udp ping test
|
|
if (($avail_method == AVAIL_SNMP_AND_PING) ||
|
|
($avail_method == AVAIL_SNMP_OR_PING) ||
|
|
($avail_method == AVAIL_PING)) {
|
|
if ($ping_type == PING_ICMP) {
|
|
$ping_result = $this->ping_icmp();
|
|
} elseif ($ping_type == PING_UDP) {
|
|
$ping_result = $this->ping_udp();
|
|
} elseif ($ping_type == PING_TCP || $ping_type == PING_TCP_CLOSED) {
|
|
$ping_result = $this->ping_tcp();
|
|
}
|
|
}
|
|
|
|
// snmp test
|
|
if (($avail_method == AVAIL_SNMP) ||
|
|
($avail_method == AVAIL_SNMP_GET_SYSDESC) ||
|
|
($avail_method == AVAIL_SNMP_GET_NEXT) ||
|
|
($avail_method == AVAIL_SNMP_AND_PING) ||
|
|
($avail_method == AVAIL_SNMP_OR_PING)) {
|
|
// If we are in AND mode and already have a failed ping result, we don't need SNMP
|
|
if (!$ping_result && $avail_method == AVAIL_SNMP_AND_PING) {
|
|
$snmp_result = $ping_result;
|
|
} else {
|
|
/* cmd.php and other reduced-host callers can omit these keys entirely,
|
|
so a missing one counts the same as an unconfigured one. */
|
|
$have_snmp = (strlen($this->host['snmp_community'] ?? '') > 0 || ($this->host['snmp_version'] ?? 0) >= 3);
|
|
|
|
if ($have_snmp && !($avail_method == AVAIL_SNMP_OR_PING && $ping_result)) {
|
|
/* Run the real SNMP test whenever a community (or v3) is configured. In OR
|
|
mode that includes the case where the ping failed, so an unreachable
|
|
host is only reported up if SNMP actually answers. */
|
|
$snmp_result = $this->ping_snmp();
|
|
} elseif ($sockets_fallback) {
|
|
/* The fallback rewrote $avail_method to AVAIL_SNMP, so reaching here means
|
|
no community is configured and there is nothing to test against. Do not
|
|
report the device up on the strength of a check that never ran. */
|
|
$snmp_result = false;
|
|
} else {
|
|
/* Nothing left to test: either a v1/v2 host with no snmp_community, or an
|
|
OR-mode host a successful ping has already proven up. */
|
|
$snmp_result = true;
|
|
$this->snmp_status = 0.000;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->restore_cacti_error_handler();
|
|
|
|
return match ($avail_method) {
|
|
AVAIL_SNMP_OR_PING => $snmp_result || $ping_result,
|
|
AVAIL_SNMP_AND_PING => $snmp_result && $ping_result,
|
|
AVAIL_SNMP => $snmp_result,
|
|
AVAIL_SNMP_GET_NEXT => $snmp_result,
|
|
AVAIL_SNMP_GET_SYSDESC => $snmp_result,
|
|
AVAIL_PING => $ping_result,
|
|
default => false,
|
|
};
|
|
} // end_ping
|
|
|
|
public function is_ipaddress(string $ip_address = '') : bool {
|
|
// check for ipv4/v6
|
|
if (function_exists('filter_var')) {
|
|
if (filter_var($ip_address, FILTER_VALIDATE_IP) !== false) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} elseif (inet_pton($ip_address) !== false) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function strip_ip_address(string $ip_address) : string {
|
|
// clean up hostname if specifying snmp_transport
|
|
if (str_contains($ip_address, 'tcp6:')) {
|
|
$ip_address = str_replace('tcp6:', '', cacti_strtolower($ip_address));
|
|
|
|
if (str_contains($ip_address, '[')) {
|
|
$parts = explode(']', $ip_address);
|
|
$ip_address = trim($parts[0], '[');
|
|
}
|
|
} elseif (str_contains($ip_address, 'udp6:')) {
|
|
$ip_address = str_replace('udp6:', '', cacti_strtolower($ip_address));
|
|
|
|
if (str_contains($ip_address, '[')) {
|
|
$parts = explode(']', $ip_address);
|
|
$ip_address = trim($parts[0], '[');
|
|
}
|
|
} elseif (str_contains($ip_address, 'tcp:')) {
|
|
$ip_address = str_replace('tcp:', '', cacti_strtolower($ip_address));
|
|
} elseif (str_contains($ip_address, 'udp:')) {
|
|
$ip_address = str_replace('udp:', '', cacti_strtolower($ip_address));
|
|
}
|
|
|
|
return $ip_address;
|
|
}
|
|
}
|