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; } }