$config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $remote_ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' // 'ok','no_xdp','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' ]; set_autodiscovery($hostname, 'no_xdp', $insert); return FALSE; } // By first detect hostname is IP or domain name (IPv4/6 == 4/6, hostname == FALSE) if ($ip_version = get_ip_version($hostname)) { // Hostname is IPv4/IPv6 $use_ip = TRUE; $hostname = ip_compress($hostname); $ip = $hostname; } else { $use_ip = FALSE; $ip = is_valid_hostname($hostname) ? gethostbyname6($hostname) : FALSE; $hostname_mydomain = $hostname . '.' . $config['mydomain']; if (!empty($config['mydomain']) && !$ip && is_valid_hostname($hostname_mydomain, TRUE) && $ip_dns = gethostbyname6($hostname_mydomain)) { $hostname = $hostname_mydomain; $ip = $ip_dns; } if ($remote_ip_type === 'unspecified' && $ip) { // When discovery protocol returns valid FQDN hostname but invalid IP (0.0.0.0), try use dns IP // See: https://jira.observium.org/browse/OBS-4117 $remote_ip = $ip; $remote_ip_type = get_ip_type($remote_ip); } if ($remote_ip_type === 'unspecified') { // 0.0.0.0, :: // In case when passed valid hostname and invalid IP, do not autodiscovery anyway print_debug("$hostname passed with invalid IP ($ip_key), not permitted for autodiscovery."); $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $remote_ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' ]; set_autodiscovery($hostname, 'no_ip_permit', $insert); return FALSE; } // Determine v4 vs v6 if ($ip) { // DNS correct, but different from discovered by protocol if ($remote_ip_type && $remote_ip != $ip) { if (str_contains($ip, ':') || !match_network($ip, $config['autodiscovery']['ip_nets'])) { // Force autodiscovery by IP only if hostname resolved to IPv6 print_debug("Host $hostname resolved as $ip, but not same as discovered by protocol $remote_ip. Try autodiscover by IP."); $use_ip = TRUE; $ip = $remote_ip; } else { // NOTE. Possible case when remote hostname resolved to loopback interface, // but remote protocol reports IP from physical interface.. Currently just notify this case print_debug("Host $hostname resolved as $ip, but not same as discovered by protocol $remote_ip."); } } else { print_debug("Host $hostname resolved as $ip"); } } elseif ($remote_ip_type) { // No DNS records print_debug("Host $hostname not resolved, try autodiscover by IP."); $use_ip = TRUE; $ip = $remote_ip; } else { print_debug("Host $hostname not resolved, autodiscovery fails."); $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, //'remote_ip' => $ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' ]; set_autodiscovery($hostname, 'no_dns', $insert); return FALSE; } } if (isset($config['autodiscovery']['ignore_ip_types']) && !in_array(get_ip_type($ip), $config['autodiscovery']['ignore_ip_types'])) { print_debug("IP $ip ($hostname) not permitted inside \$config['autodiscovery']['ignore_ip_types'] in config."); $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' ]; set_autodiscovery($hostname, 'no_ip_permit', $insert); return FALSE; } if (!match_network($ip, $config['autodiscovery']['ip_nets'])) { print_debug("IP $ip ($hostname) not permitted inside \$config['autodiscovery']['ip_nets'] in config."); $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' ]; set_autodiscovery($hostname, 'no_ip_permit', $insert); return FALSE; } print_debug("Host $hostname ($ip) founded inside configured nets, trying to add:"); $ip_version = get_ip_version($ip); // By first check if pingable $dns_options = $ip_version == 6 ? 'ipv6' : 'all'; // Exclude IPv4 $ping_skip = isset($config['autodiscovery']['ping_skip']) && $config['autodiscovery']['ping_skip']; $pingable = is_pingable($ip, $dns_options, $ping_skip); if (!$pingable) { print_debug("Host $hostname not pingable. You can try set in config: \$config['autodiscovery']['ping_skip'] = TRUE;"); $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' ]; set_autodiscovery($hostname, 'no_ping', $insert); return FALSE; } // Check if a device duplicated by IP $ip_hex = binary_to_hex(inet_pton($ip)); $db = dbFetchRow('SELECT `hostname` FROM `ipv' . $ip_version . '_addresses` LEFT JOIN `devices` USING(`device_id`) WHERE `disabled` = 0 AND `ipv' . $ip_version . '_binary` = ? LIMIT 1', [ [$ip_hex] ]); if ($db) { print_debug('Already have device ' . $db['hostname'] . " with IP $ip"); $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' // 'ok','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' ]; set_autodiscovery($hostname, 'duplicated', $insert); return FALSE; } // Detect snmp transport, net-snmp needs udp6 for ipv6 $snmp_transport = $ip_version == 4 ? 'udp' : 'udp6'; $snmp_port = 161; // Detect snmp auth $new_device = detect_device_snmpauth($ip, $snmp_port, $snmp_transport); if (!$new_device) { print_debug("Host $hostname not snmpable by known snmp auth params."); $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' // 'ok','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' ]; set_autodiscovery($hostname, 'no_snmp', $insert); return FALSE; } if ($use_ip) { $check_hostname = FALSE; // When hostname detected by sysName or by PTR, recheck that is allowed by config // Detect FQDN hostname // by sysName $snmphost = snmp_get_oid($new_device, 'sysName.0', 'SNMPv2-MIB'); if ($snmphost) { // Add "mydomain" configuration if this resolves, converts switch1 -> switch1.mydomain.com if (!empty($config['mydomain']) && is_domain_resolves($snmphost . '.' . $config['mydomain'], $dns_options)) { $snmphost .= '.' . $config['mydomain']; } $snmp_ip = gethostbyname6($snmphost, $dns_options); } if ($snmp_ip == $ip) { $hostname = $snmphost; $check_hostname = TRUE; } else { // by PTR $ptr = gethostbyaddr6($ip); if ($ptr) { // Add "mydomain" configuration if this resolves, converts switch1 -> switch1.mydomain.com if (!empty($config['mydomain']) && is_domain_resolves($ptr . '.' . $config['mydomain'], $dns_options)) { $ptr .= '.' . $config['mydomain']; } $ptr_ip = gethostbyname6($ptr, $dns_options); } if ($ptr && $ptr_ip == $ip) { $hostname = $ptr; $check_hostname = TRUE; } elseif ($config['autodiscovery']['require_hostname']) { print_debug("Device IP $ip does not seem to have FQDN."); $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' // 'ok','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' ]; set_autodiscovery($hostname, 'no_fqdn', $insert); return FALSE; } else { // Hostname as IP string $hostname = ip_compress($ip); // Always use compressed IPv6 name } } print_debug("Device IP $ip linked to FQDN name: $hostname"); // When hostname detected by sysName or by PTR, recheck that is allowed by config (only hostnames) if ($check_hostname && is_bad_xdp($hostname)) { $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $remote_ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' // 'ok','no_xdp','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' ]; set_autodiscovery($hostname, 'no_xdp', $insert); return FALSE; } } $new_device['hostname'] = $hostname; // Check if we already have a same device if (check_device_duplicated($new_device)) { // When detected a duplicate device, this mean it already SNMPable and not need check next auth! print_debug("Already have device $hostname with IP $ip"); $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' // 'ok','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' ]; set_autodiscovery($hostname, 'duplicated', $insert); return FALSE; } // Add a new device to db $snmp = [ 'community' => $new_device['snmp_community'], 'version' => $new_device['snmp_version'], 'port' => $new_device['snmp_port'], 'transport' => $new_device['snmp_transport'] ]; if ($new_device['snmp_version'] === 'v3') { $snmp['snmp_authlevel'] = $new_device['snmp_authlevel']; $snmp['snmp_authname'] = $new_device['snmp_authname']; $snmp['snmp_authpass'] = $new_device['snmp_authpass']; $snmp['snmp_authalgo'] = $new_device['snmp_authalgo']; $snmp['snmp_cryptopass'] = $new_device['snmp_cryptopass']; $snmp['snmp_cryptoalgo'] = $new_device['snmp_cryptoalgo']; } //$remote_device_id = createHost($new_device['hostname'], $new_device['snmp_community'], $new_device['snmp_version'], $new_device['snmp_port'], $new_device['snmp_transport'], $snmp); $remote_device_id = create_device($new_device['hostname'], $snmp); if ($remote_device_id) { // Set skip pings option for remote device if ($ping_skip) { set_entity_attrib('device', $remote_device_id, 'ping_skip', 1); } //$remote_device = device_by_id_cache($remote_device_id, 1); if ($port) { humanize_port($port); log_event("Device autodiscovered through $protocol on " . $device['hostname'] . " (port " . $port['port_label'] . ")", $remote_device_id, 'port', $port['port_id']); } else { log_event("Device autodiscovered through $protocol on " . $device['hostname'], $remote_device_id, $protocol); } $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, 'remote_device_id' => $remote_device_id, 'protocol' => $protocol, //'last_reason' => 'no_dns' // 'ok','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' ]; set_autodiscovery($hostname, 'ok', $insert); return $remote_device_id; } $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, //'remote_device_id' => NULL, 'protocol' => $protocol, //'last_reason' => 'no_dns' // 'ok','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' ]; set_autodiscovery($hostname, 'no_db', $insert); return FALSE; } function set_autodiscovery($hostname, $reason = 'unknown', $options = []) { global $cache; if (isset($options['remote_address'])) { $options['remote_ip'] = $options['remote_address']; } $ip_key = safe_ip_hostname($hostname, $options['remote_ip']); // Cache autodiscovery entry $db_entry = get_autodiscovery_entry($hostname, $options['remote_ip'], $options['device_id']); $insert = [ 'poller_id' => $GLOBALS['config']['poller_id'], //'device_id' => $device['device_id'], 'remote_hostname' => $hostname, //'remote_ip' => $ip, //'remote_device_id' => NULL, //'protocol' => $protocol, 'last_reason' => $reason ]; foreach ([ 'device_id', 'remote_ip', 'remote_device_id', 'protocol' ] as $param) { if (!safe_empty($options[$param])) { $insert[$param] = $options[$param]; } } // Set cache key, for use in check_autodiscovery() //$cache['autodiscovery_remote_device_id'][$hostname][$ip_key] = $insert['remote_device_id']; $db_update = []; //$db_params = [ 'device_id', 'remote_ip', 'remote_device_id', 'protocol', 'last_reason' ]; $db_params = ['device_id', 'remote_ip', 'remote_device_id', 'last_reason']; // do not change protocol when update if ($reason === 'ok') { $db_params[] = 'protocol'; } // BGP and OSPF (and others not XDP protocols not pass hostname, do not update it) $hostname_update = !in_array(strtolower($insert['protocol']), [ 'bgp', 'ospf' ]); if (is_array($db_entry)) { // already discovered $autodiscovery_id = $db_entry['autodiscovery_id']; foreach ($db_params as $param) { // Skip hostname update for non-xdp protocols if ($param === 'remote_hostname' && !$hostname_update) { continue; } if ($db_entry[$param] != $insert[$param]) { $db_update[$param] = !safe_empty($insert[$param]) ? $insert[$param] : [ 'NULL' ]; } } if (empty($db_update)) { // not changed, but force update for increase last_checked dbUpdate([ 'last_checked' => [ 'CURRENT_TIMESTAMP()' ] ], 'autodiscovery', '`autodiscovery_id` = ?', [ $autodiscovery_id ]); // Clear cache entry unset($cache['autodiscovery'][$hostname][$ip_key]); return $autodiscovery_id; } } /* if (isset($cache['autodiscovery'][$hostname][$ip_key])) { // already discovered $db_entry = $cache['autodiscovery'][$hostname][$ip_key]; $db_id = $db_entry['autodiscovery_id']; foreach ($db_params as $param) { if ($db_entry[$param] != $insert[$param] && strlen($insert[$param])) { $db_update[$param] = $insert[$param]; } } if (!count($db_update)) { // not changed, but force update for increase last_checked dbUpdate([ 'last_checked' => [ 'CURRENT_TIMESTAMP()' ] ], 'autodiscovery', '`autodiscovery_id` = ?', [ $db_id ]); // Clear cache entry unset($cache['autodiscovery'][$hostname][$ip_key]); return $db_id; } } elseif (isset($cache['autodiscovery'][$hostname]['__']) && $ip_key != '__') { // already discovered, but without ip $db_entry = $cache['autodiscovery'][$hostname]['__']; $db_id = $db_entry['autodiscovery_id']; foreach ($db_params as $param) { if ($db_entry[$param] != $insert[$param] && strlen($insert[$param])) { $db_update[$param] = $insert[$param]; } } } elseif ($ip_key != '__' && isset($cache['autodiscovery_ip'][$ip_key])) { // FIXME. Host already discovered by IP, but we not check it this.. print_debug("DEVEL. Host already discovered by IP, but we not check it this.."); } elseif ($hostname_ip_version = get_ip_version($hostname)) { // FIXME. Try if hostname passed as IP $hostname = ip_compress($hostname); if (isset($cache['autodiscovery_ip'][$hostname])) { print_debug("DEVEL. Try if hostname passed as IP.."); } } */ if (!safe_empty($db_update)) { dbUpdate($db_update, 'autodiscovery', '`autodiscovery_id` = ?', [ $autodiscovery_id ]); print_debug("AUTODISCOVERY UPDATED"); // Clear cache entry unset($cache['autodiscovery'][$hostname][$ip_key]); } else { $insert['added'] = [ 'NOW()' ]; $autodiscovery_id = dbInsert($insert, 'autodiscovery'); print_debug("AUTODISCOVERY INSERTED"); } return $autodiscovery_id; } function check_autodiscovery($hostname, $ip = NULL) { global $config, $cache; if (get_ip_type($ip) === 'unspecified' && is_valid_hostname($hostname, TRUE) && $ip_dns = gethostbyname6($hostname)) { // When discovery protocol return valid FQDN hostname but invalid IP (0.0.0.0), try use dns IP // See: https://jira.observium.org/browse/OBS-4117 $ip = $ip_dns; } $ip_key = safe_ip_hostname($hostname, $ip); // Invalid hostname && IP $valid_hostname = is_valid_hostname($hostname); if (!$valid_hostname && empty($ip)) { print_debug("Invalid hostname $hostname and empty IP, skipped."); return FALSE; } // Cache autodiscovery entry get_autodiscovery_entry($hostname, $ip); if (isset($cache['autodiscovery'][$hostname][$ip_key])) { // already discovered $db_entry = $cache['autodiscovery'][$hostname][$ip_key]; //$remote_device_id = $db_entry['remote_device_id']; print_debug("AUTODISCOVERY DEVEL: hostname & ip DB found"); } elseif (isset($cache['autodiscovery'][$hostname]['0.0.0.0']) && $ip_key !== '0.0.0.0') { // already discovered, but without ip $db_entry = $cache['autodiscovery'][$hostname]['0.0.0.0']; //$remote_device_id = $db_entry['remote_device_id']; print_debug("AUTODISCOVERY DEVEL: hostname DB found"); } // if (isset($cache['autodiscovery_remote_device_id'][$hostname]) && // array_key_exists($ip_key, $cache['autodiscovery_remote_device_id'][$hostname])) // { // print_debug("Hostname $hostname ($ip) already checked by autodiscovery."); // return $cache['autodiscovery_remote_device_id'][$hostname][$ip_key]; // } if ($db_entry) { print_debug_vars($db_entry); switch ($db_entry['last_reason']) { // 'ok','no_xdp','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' case 'ok': // Already added device, no need for discovery, also checked by get_autodiscovery_device_id() print_debug("Remote device already discovered, no need for discovery again."); return FALSE; case 'duplicated': print_debug("Remote device passed mostly checks, but detected as duplicated."); return FALSE; //case 'no_db': // break; default: // All other reasons check last_checked_unixtime not more than 24 hours $interval = get_time() - $db_entry['last_checked_unixtime']; if ($interval <= $config['autodiscovery']['recheck_interval']) { $interval = format_uptime($interval); $recheck_interval = format_uptime($config['autodiscovery']['recheck_interval']); print_debug("Remote device checked $interval ago (less than $recheck_interval)"); //return FALSE; } } } return TRUE; } function get_autodiscovery_entry($hostname, $ip = NULL, $exclude_device_id = NULL) { global $cache; // ip key for empty or unspecified always 0.0.0.0 $ip_key = safe_ip_hostname($hostname, $ip); if (!isset($cache['autodiscovery'][$hostname][$ip_key])) { $valid_hostname = is_valid_hostname($hostname); if (!$valid_hostname && empty($ip)) { // Not valid hostname & ip, do not query db $cache['autodiscovery'][$hostname][$ip_key] = NULL; return NULL; } $where = [ '`poller_id` = ?' ]; $params = [ $GLOBALS['config']['poller_id'] ]; if ($ip == $hostname || !$valid_hostname) { // Hostname and IP same, search by IP // print_vars($ip); // print_vars($ip_key); // print_vars($hostname); $where[] = '`remote_ip` = ?'; $params[] = $ip; } elseif (empty($ip)) { // Undefined IP (always use NULL in db) $where[] = '`remote_hostname` = ?'; $where[] = '`remote_ip` IS NULL'; $params[] = $hostname; //$params[] = '0.0.0.0'; } else { // Search by valid hostname and ip $where[] = '`remote_hostname` = ?'; $where[] = '`remote_ip` = ?'; $params[] = $hostname; $params[] = $ip; } // Exclude local device if (is_numeric($exclude_device_id)) { $where[] = '(`remote_device_id` IS NULL OR `remote_device_id` != ?)'; $params[] = $exclude_device_id; } $sql = 'SELECT `autodiscovery`.*, UNIX_TIMESTAMP(`last_checked`) AS `last_checked_unixtime` FROM `autodiscovery`'; if ($entry = dbFetchRow($sql . generate_where_clause($where), $params)) { $cache['autodiscovery'][$hostname][$ip_key] = $entry; } } return $cache['autodiscovery'][$hostname][$ip_key]; } // Note return numeric device_id if already found, if not found: FALSE (for cached results) or NULL for not cached function get_autodiscovery_device_id($device, $hostname, $ip = NULL, $mac = NULL) { global $config, $cache; $ip_key = safe_ip_hostname($hostname, $ip); if (empty($ip) && is_valid_hostname($hostname, TRUE) && $ip_dns = gethostbyname6($hostname)) { // When discovery protocol return valid FQDN hostname but invalid IP (0.0.0.0), try use dns IP // See: https://jira.observium.org/browse/OBS-4117 $ip = $ip_dns; $ip_key = safe_ip_hostname($hostname, $ip); } // Check if cached if (isset($cache['autodiscovery_remote_device_id'][$hostname]) && array_key_exists($ip_key, $cache['autodiscovery_remote_device_id'][$hostname])) { print_debug("AUTODISCOVERY DEVEL: remote_device_id from cache"); // Set to false, for prevent caching with NULL if (empty($cache['autodiscovery_remote_device_id'][$hostname][$ip_key])) { return FALSE; } return $cache['autodiscovery_remote_device_id'][$hostname][$ip_key]; } $ip_type = get_ip_type($ip); // Always check by mac for private IPs $check_mac = $ip_type === 'private' && !safe_empty($mac); // Check previous autodiscovery rounds as mostly correct! if (!$check_mac) { print_debug("AUTODISCOVERY DEVEL: skipped for Private IPs [$ip] and checks by MAC address [$mac]."); $autodiscovery_entry = get_autodiscovery_entry($hostname, $ip, $device['device_id']); if (isset($autodiscovery_entry['last_reason']) && $autodiscovery_entry['last_reason'] === 'ok') { $remote_device_id = $autodiscovery_entry['remote_device_id']; } } // Try to find remote host by remote chassis mac address from DB if (!$remote_device_id) { $remote_device_id = get_device_id_by_mac($mac, $device['device_id']); } // We can also use IP address to find remote device. if (!$remote_device_id && $ip_type && !in_array($ip_type, [ 'unspecified', 'loopback' ])) { // 'link-local' ? $peer_where = generate_query_values_and($device['device_id'], 'device_id', '!='); // Additional filter for exclude self IPs // Fetch all devices with peer IP and filter by UP if ($ids = get_entity_ids_ip_by_network('device', $ip, $peer_where)) { $remote_device_id = $ids[0]; if (count($ids) > 1) { // If multiple same IPs found, get first NOT disabled or down foreach ($ids as $id) { $tmp_device = device_by_id_cache($id); if (!$tmp_device['disabled'] && $tmp_device['status']) { $remote_device_id = $id; break; } } } elseif ($check_mac) { // Do not set remote device by private ip when really should be found by MAC address $remote_device_id = NULL; } } } // Check if device already exist by hostname if (!$remote_device_id) { $remote_device_id = get_device_id_by_hostname($hostname); // Try with mydomain option $hostname_mydomain = $hostname . '.' . $config['mydomain']; if (!$remote_device_id && $config['mydomain'] && is_valid_hostname($hostname_mydomain, TRUE) && $remote_device_id = get_device_id_by_hostname($hostname_mydomain)) { $hostname = $hostname_mydomain; } // If hostname is FQDN, also try by sysName $hostname_valid = is_valid_hostname($hostname, TRUE); if (!$remote_device_id && $hostname_valid) { $remote_device_id = dbFetchCell("SELECT `device_id` FROM `devices` WHERE `sysName` = ? AND `disabled` = ?", [ $hostname, 0 ]); } // Last chance if hostname not FQDN and not an default sysname if (!$remote_device_id && !$hostname_valid && strlen($hostname) > 6 && !in_array(strtolower($hostname), $config['devices']['ignore_sysname'], TRUE)) { $remote_devices = dbFetchRows("SELECT * FROM `devices` WHERE `sysName` = ? AND `disabled` = ?", [ $hostname, 0 ]); $remote_count = safe_count($remote_devices); if ($remote_count === 1) { // Use only unique sysName! $remote_device_id = $remote_devices[0]['device_id']; } elseif ($remote_count > 1) { print_debug("Founded multiple devices with not unique sysName:\n"); print_debug_vars($remote_devices); } } } // This is dumb, but I not remember if all functions return null :/ if ($remote_device_id === FALSE) { $remote_device_id = NULL; } if ($remote_device_id) { // Founded remote device in local DB $insert = [ //'poller_id' => $config['poller_id'], 'device_id' => $device['device_id'], //'remote_hostname' => $hostname, 'remote_ip' => $ip, 'remote_device_id' => $remote_device_id, 'protocol' => 'XDP', //'last_reason' => 'no_dns' // 'ok','no_fqdn','no_dns','no_ip_permit','no_ping','no_snmp','no_db','duplicated','unknown' ]; set_autodiscovery($hostname, 'ok', $insert); } $cache['autodiscovery_remote_device_id'][$hostname][$ip_key] = $remote_device_id; return $remote_device_id; } /** * @param array $device * @param array|null $options * * @return false|float */ function discover_device($device, $options = NULL) { global $config, $valid, $cache_discovery; // Initialise variables $valid = []; // Reset $valid array $cache_discovery = []; // Specific discovery cache for exchange snmpwalk data between modules (memory/storage/sensors/etc) $attribs = get_entity_attribs('device', $device['device_id']); $device['attribs'] = $attribs; // better here for access inside functions. $device_start = utime(); // Start counting device poll time // Check if device discovery already running $pid_info = check_process_run($device); if ($pid_info) { // Process ID exist in DB print_message("%rAnother " . $pid_info['process_name'] . " process (PID: " . $pid_info['PID'] . ", UID: " . $pid_info['UID'] . ", STARTED: " . $pid_info['STARTED'] . ") already running for device " . $device['hostname'] . " (" . $device['device_id'] . ").%n", 'color'); return FALSE; } add_process_info($device); // Store process info print_cli_heading($device['hostname'] . " [" . $device['device_id'] . "]", 1); $detect_os = TRUE; // Set TRUE or FALSE for module 'os' (exclude double os detection) if ($device['os'] === 'generic' || (isset($options['h']) && $options['h'] === 'new')) { // verify if OS has changed $detect_os = FALSE; $os_changed = check_device_os_changed($device); // Set device sysObjectID when device just added (required for some cases before other discovery/polling) $sysObjectID = snmp_cache_sysObjectID($device); if ($device['sysObjectID'] != $sysObjectID) { dbUpdate(['sysObjectID' => $sysObjectID], 'devices', '`device_id` = ?', [ $device['device_id'] ]); $device['sysObjectID'] = $sysObjectID; } } elseif (is_null($device['sysObjectID'])) { // Set device sysObjectID when device just added (required for some cases before other discovery/polling) $sysObjectID = snmp_cache_sysObjectID($device); $device['sysObjectID'] = $sysObjectID; } if (OBS_DEBUG > 1) { // Cache cleanup for a new device $device_discovery_cache_keys = array_keys($GLOBALS['cache']); print_vars($device_discovery_cache_keys); // Show a full list permitted MIBs print_vars(get_device_mibs_permitted($device)); } print_cli_data("OS Type", $device['os'], 1); if ($config['os'][$device['os']]['group']) { $device['os_group'] = $config['os'][$device['os']]['group']; print_cli_data("OS Group", $device['os_group'], 1); } print_cli_data("SNMP Version", $device['snmp_version'], 1); print_cli_data("Last discovery", $device['last_discovered'], 1); print_cli_data("Last duration", $device['last_discovered_timetaken'] . " seconds", 1); echo(PHP_EOL); $modules = get_discovery_modules($device, $options, $modules_forced); foreach ($modules as $module => $module_status) { if (!(in_array($module, $modules_forced) || is_module_enabled($device, $module))) { continue; } //if ($attribs['discover_'.$module] || ( $module_status && !isset($attribs['discover_'.$module]))) $m_start = utime(); $GLOBALS['module_stats'][$module] = []; $valid[$module] = []; print_cli_heading("Module Start: %R" . $module); include("includes/discovery/$module.inc.php"); $GLOBALS['module_stats'][$module]['time'] = elapsed_time($m_start, 4); print_module_stats($device, $module); echo(PHP_EOL); //print_cli_heading("Module End: %R".$module.""); } // Modules enabled stats: $modules_stat = $GLOBALS['cache']['devices']['discovery_modules'][$device['device_id']]; if (safe_count($modules_stat['excluded'])) { print_cli_data("Modules Excluded", implode(", ", $modules_stat['excluded']), 1); } if (safe_count($modules_stat['disabled'])) { print_cli_data("Modules Disabled", implode(", ", $modules_stat['disabled']), 1); } if (safe_count($modules_stat['enabled'])) { print_cli_data("Modules Enabled", implode(", ", $modules_stat['enabled']), 1); } // Set type to a predefined type for the OS if it's not already set if ($device['type'] === "unknown" || $device['type'] == "") { if ($config['os'][$device['os']]['type']) { $device['type'] = $config['os'][$device['os']]['type']; } } $device_time = elapsed_time($device_start, 4); $update_array = [ 'last_discovered' => [ 'NOW()' ], 'type' => $device['type'], 'last_discovered_timetaken' => $device_time, 'force_discovery' => 0 ]; // Store device stats and history data (only) if we're not doing a single-module poll if (!$options['m']) { // Fetch previous device state (do not use $device array here, for exclude update history collisions) $old_device_state = dbFetchCell('SELECT `device_state` FROM `devices` WHERE `device_id` = ?;', [$device['device_id']]); $old_device_state = safe_unserialize($old_device_state); // Add first entry $discovery_history = [ (int)$device_start => $device_time ]; // start => duration // Adds and keeps not more than 100 last entries if (isset($old_device_state['discovery_history'])) { print_debug_vars($old_device_state['discovery_history']); $discovery_history = array_slice($discovery_history + $old_device_state['discovery_history'], 0, 100, TRUE); } print_debug_vars($discovery_history); // Keep per module perf stats $discovery_mod_perf = []; foreach ($GLOBALS['module_stats'] as $module => $entry) { $discovery_mod_perf[$module] = $entry['time']; } $device_state = $old_device_state; // Keep old devices state (from poller) $device_state['discovery_history'] = $discovery_history; $device_state['discovery_mod_perf'] = $discovery_mod_perf; unset($discovery_history, $old_device_state); $update_array['device_state'] = serialize($device_state); // Not worth putting discovery data into rrd. It's not done every 5 minutes :) } dbUpdate($update_array, 'devices', '`device_id` = ?', [ $device['device_id'] ]); // Clean force discovery if (isset($attribs['force_discovery_modules'])) { del_entity_attrib('device', $device['device_id'], 'force_discovery_modules'); } print_cli_heading($device['hostname'] . " [" . $device['device_id'] . "] completed discovery modules at " . date("Y-m-d H:i:s"), 1); print_cli_data("Discovery time", $device_time . " seconds", 1); echo(PHP_EOL); // Clean if (OBS_DEBUG > 1) { $device_discovery_cache_keys = array_diff(array_keys($GLOBALS['cache']), $device_discovery_cache_keys); print_vars($device_discovery_cache_keys); //print_vars($GLOBALS['cache']); } del_process_info($device); // Remove process info // Clean cache vars unset($cache_discovery, $GLOBALS['cache']['snmp'], $GLOBALS['cache_snmp'][$device['device_id']]); return $device_time; } function get_discovery_modules($device, $options = NULL, &$modules_forced = []) { global $config; $modules = []; // Either only run the modules specified on the commandline, or run all modules in config. $modules_forced = []; if ($options['m']) { foreach (explode(",", $options['m']) as $module) { if (!isset($config['discovery_modules'][$module])) { continue; } // unknown module $modules[$module] = TRUE; $modules_forced[] = $module; } } elseif ($device['force_discovery'] && $options['h'] === 'new' && isset($device['attribs']['force_discovery_modules'])) { // Forced discovery-specific modules foreach (safe_json_decode($device['attribs']['force_discovery_modules']) as $module) { $modules[$module] = TRUE; } log_event('Forced discovery module(s): ' . implode(', ', array_keys($modules)), $device, 'device', $device['device_id'], 'debug'); } else { $modules = $config['discovery_modules']; } // Use os specific modules order //print_vars($modules); if (isset($config['os'][$device['os']]['discovery_order'])) { //print_vars($config['os'][$device['os']]['discovery_order']); foreach ($config['os'][$device['os']]['discovery_order'] as $module => $module_order) { if (array_key_exists($module, $modules)) { $module_status = $modules[$module]; switch ($module_order) { case 'last': // add to end of modules list unset($modules[$module]); $modules[$module] = $module_status; break; case 'first': // add to begin of a module list, but not before os/system $new_modules = []; if ($modules['os']) { $new_modules['os'] = $modules['os']; unset($modules['os']); } if ($modules['system']) { $new_modules['system'] = $modules['system']; unset($modules['system']); } $new_modules[$module] = $module_status; unset($modules[$module]); $modules = $new_modules + $modules; break; default: // add into specific place (after module name in $module_order) // yes, this is hard and magic if (array_key_exists($module_order, $modules)) { unset($modules[$module]); $new_modules = []; foreach ($modules as $new_module => $new_status) { array_shift($modules); $new_modules[$new_module] = $new_status; if ($new_module == $module_order) { $new_modules[$module] = $module_status; break; } } $modules = array_merge($new_modules, (array)$modules); } } } } //print_vars($modules); } return $modules; } // TESTME needs unit testing // FIXME don't pass valid, use this as a global variable /** * Discover a new virtual machine on a device * * This function adds a virtual machine to a device, if it does not already exist. * Data on the VM is updated if it has changed, and an event is logged with regards to the changes. * If the VM has a valid hostname, Observium attempts to discover this as a new device (calling discover_new_device). * * Valid array keys for the $options array: type, id, name, os, memory (in bytes), cpucount, status, source (= snmp, agent, etc) * * @param array &$valid * @param array $device * @param array $options */ function discover_virtual_machine(&$valid, $device, $options = []) { print_debug('Discover VM: ' . $options['type'] . '/' . $options['source'] . ' (' . $options['id'] . ') ' . $options['name'] . ' CPU: ' . $options['cpucount'] . ' RAM: ' . $options['memory'] . ' Status: ' . $options['status']); $options['protocol'] = $options['protocol'] ?? $options['type']; if (!dbExist('vminfo', '`device_id` = ? AND `vm_uuid` = ? AND `vm_name` = ? AND `vm_type` = ? AND `vm_source` = ?', [$device['device_id'], $options['id'], $options['name'], $options['type'], $options['source']])) { $vm_insert = ['device_id' => $device['device_id'], 'vm_type' => $options['type'], 'vm_uuid' => $options['id'], 'vm_name' => $options['name'], 'vm_guestos' => $options['os'], 'vm_memory' => $options['memory'] / 1024 / 1024, 'vm_cpucount' => $options['cpucount'], 'vm_state' => $options['status'], 'vm_source' => $options['source']]; $vm_id = dbInsert($vm_insert, 'vminfo'); //echo('\ndevice_id` = ? AND `vm_uuid` = ? AND `vm_name` = ? AND `vm_type` = ? AND `vm_source` = ?\n'); //var_dump([$device['device_id'], $options['id'], $options['name'], $options['type'], $options['source']]); //var_dump($vm_insert); echo('+'); log_event("Virtual Machine added: " . $options['name'] . ' (' . format_bi($options['memory']) . 'B RAM, ' . $options['cpucount'] . ' CPU)', $device, 'virtualmachine', $vm_id); if (is_valid_hostname($options['name']) && in_array($options['status'], ['running', 'powered on', 'poweredOn'])) { // Try to discover this VM as a new device, if it's actually running. discover_new_device() will check for valid hostname, duplicates, etc. // Libvirt, Proxmox (= QEMU-powered) return "running"; VMWare returns "powered on" (or "poweredOn" in older versions). autodiscovery_device($options['name'], NULL, $options['protocol'], $options['os'], $device); } } else { $vm = dbFetchRow("SELECT * FROM `vminfo` WHERE `device_id` = ? AND `vm_uuid` = ? AND `vm_name` = ? AND `vm_type` = ? AND `vm_source` = ?", [ $device['device_id'], $options['id'], $options['name'], $options['type'], $options['source']]); // Define the fields to check in pairs (vm field => options field) $fieldsToCheck = [ 'vm_state' => 'status', // 'vm_name' => 'name', 'vm_cpucount' => 'cpucount', 'vm_guestos' => 'os', 'vm_memory' => 'memory' // Memory will be divided by 1024^2 in the check ]; // Initialize an empty array to store mismatched fields $mismatches = []; //var_dump($vm); //var_dump($options); // Iterate over the fields and compare vm to options foreach ($fieldsToCheck as $vmField => $optionsField) { $vmValue = $vm[$vmField]; $optionsValue = $options[$optionsField]; // For 'vm_memory', convert memory from bytes to MB if ($vmField === 'vm_memory') { $optionsValue /= 1024 * 1024; // Convert options['memory'] from bytes to MB } // If values don't match, add the field to mismatches if ($vmValue != $optionsValue) { $mismatches[] = $vmField; echo("Mismatch: $vmfield || Old: $vmValue || New: $optionsValue\n"); } } // If there are mismatches, perform the update if (!empty($mismatches)) { //print_vars($mismatches); $update = [ 'vm_state' => $options['status'], 'vm_guestos' => $options['os'], //'vm_name' => $options['name'], 'vm_memory' => $options['memory'] / 1024 / 1024, 'vm_cpucount' => $options['cpucount'] ]; dbUpdate($update, 'vminfo', "device_id = ? AND vm_type = ? AND vm_name = ? AND vm_uuid = ? AND vm_source = ?", [$device['device_id'], $options['type'], $options['name'], $options['id'], $options['source']]); echo('U'); /// FIXME eventlog changed fields } else { echo('.'); } } $valid['vm'][$options['type']][(string)$options['id']] = 1; } /** * Discover a new application on a device * * This function returns an app_id for the application code to use. * If the app+instance combination already exists in the database, the current id will be returned. * If not, a new row will be created and the newly created id will be returned. * * @param array $device * @param string $type * @param string $instance * * @return integer */ function discover_app($device, $type, $instance = NULL) { if ($instance == NULL) { $app_data = dbFetchRow("SELECT * FROM `applications` WHERE `device_id` = ? AND `app_type` = ? AND `app_instance` IS NULL", [$device['device_id'], $type]); } else { $app_data = dbFetchRow("SELECT * FROM `applications` WHERE `device_id` = ? AND `app_type` = ? AND `app_instance` = ?", [$device['device_id'], $type, $instance]); } if ($app_data == FALSE) { $app_insert = ['device_id' => $device['device_id'], 'app_type' => $type, 'app_instance' => ($instance == NULL ? ['NULL'] : $instance)]; echo('+'); return dbInsert($app_insert, 'applications'); } else { echo('.'); return $app_data['app_id']; } } /** * Fetch table or oids inside sensor/status definition discovery * * @param array $device Device array * @param string $mib MIB name * @param array $def Definition entry * @param array $table_oids List of required Oids to fetch * * @return array */ function discover_fetch_oids($device, $mib, $def, $table_oids) { // Individual indexes (not sure how unify for all entities) $entity_get = isset($GLOBALS['config']['mibs'][$mib]['sensors_walk']) && !$GLOBALS['config']['mibs'][$mib]['sensors_walk']; $indexes_get = isset($def['indexes']) && ($entity_get || $def['type'] === 'static' || $def['type'] === 'indexes'); // type static for processors $oids_walk = (isset($def['table_walk']) && !$def['table_walk']) || !isset($def['table']); if ($indexes_get) { // Get separate Oids by snmpget for cases, when device have troubles with walking (ie FIREBRICK-MIB) $array = fetch_def_oids_indexes($device, $mib, $def, $table_oids); } elseif ($oids_walk) { // Walk individual OIDs separately $array = fetch_def_oids_walks($device, $mib, $def, $table_oids); } else { // Walk the whole table $flags = get_def_snmp_flags($def); $array = []; foreach ((array)$def['table'] as $table) { $array = snmp_cache_table($device, $table, $array, $mib, NULL, $flags); } // Append oid_extra if (isset($def['oid_extra']) && in_array('oid_extra', $table_oids, TRUE)) { foreach ((array)$def['oid_extra'] as $get_oid) { $array = snmp_cache_table($device, $get_oid, $array, $mib, NULL, $flags); } } } // Populate entPhysical using the value of supplied oid_entPhysicalIndex. // Will populate with values from entPhysicalContainedIn if entPhysical_parent set. fetch_def_oids_entphysical($device, $def, $array); print_debug_vars($array); return $array; } function get_def_snmp_flags($def) { // SNMP flags $flags = OBS_SNMP_ALL_NUMERIC_INDEX | OBS_SNMP_DISPLAY_HINT | OBS_SNMP_CONCAT; // Allow custom definition flags if (isset($def['snmp_flags'])) { if (is_flag_set(OBS_SNMP_TABLE, $def['snmp_flags'])) { // If table output is used, exclude numeric index $flags ^= OBS_SNMP_NUMERIC_INDEX; } return $flags | $def['snmp_flags']; } // Force UTF decode? //$flags = $flags | OBS_SNMP_HEX | OBS_DECODE_UTF8; return $flags; } function fetch_def_oids_indexes($device, $mib, $def, $table_oids) { // Get separate Oids by snmpget for cases, when device have troubles with walking (ie FIREBRICK-MIB) // Create a list of required Oids $get_oids = []; foreach ($table_oids as $table_oid) { if (isset($def[$table_oid])) { // See WIPIPE-MIB $oids_tmp = ($table_oid === 'oids') ? array_keys($def[$table_oid]) : (array)$def[$table_oid]; //$get_oids = array_merge($get_oids, $oids_tmp); $get_oids[] = $oids_tmp; } } $get_oids = array_merge([], ...$get_oids); unset($oids_tmp); // Get individual Oids $i = 0; $array = []; foreach (array_keys($def['indexes']) as $index) { $get_oid = implode(".$index ", $get_oids) . ".$index"; // Generate multi get list of indexed oids $array = snmp_get_multi_oid($device, $get_oid, $array, $mib, NULL, get_def_snmp_flags($def)); if ($i === 0 && !snmp_status()) { break; } // break on first index loop of incorrect answer $i++; } return $array; } function fetch_def_oids_walks($device, $mib, $def, $table_oids) { // Walk individual OIDs separately $flags = get_def_snmp_flags($def); $array = []; foreach ($table_oids as $table_oid) { if (isset($def[$table_oid])) { $oids_tmp = ($table_oid === 'oids') ? array_keys($def[$table_oid]) : (array)$def[$table_oid]; foreach ($oids_tmp as $get_oid) { // Do not walk limit/unit/scale oids with passed indexes (they should be fetched by get) if (str_contains_array($table_oid, [ 'limit', 'unit', 'scale', 'total', 'count' ]) && str_contains($get_oid, '.')) { continue; } $array = snmp_cache_table($device, $get_oid, $array, $mib, NULL, $flags); //print_vars($array); } } } return $array; } function fetch_def_oids_entphysical($device, $def, &$array) { if (!isset($def['oid_entPhysicalIndex']) && !isset($def['entPhysicalIndex'])) { return; } $oids_tmp = []; // Fetch entPhysicalIndex by defined oid or just use tag, mostly common is %index% $use_oid = isset($def['oid_entPhysicalIndex']); foreach ($array as $index => $array_tmp) { // Get entPhysicalIndex if ($use_oid) { $entPhysicalIndex = $array_tmp[$def['oid_entPhysicalIndex']] ?? NULL; } else { $array_tmp['index'] = $index; $entPhysicalIndex = array_tag_replace($array_tmp, $def['entPhysicalIndex']); } if (is_numeric($entPhysicalIndex)) { if (isset($def['entPhysical_parent']) && $def['entPhysical_parent']) { $oidlist = [ 'entPhysicalContainedIn' ]; } else { $oidlist = [ 'entPhysicalContainedIn', 'entPhysicalDescr', 'entPhysicalAlias', 'entPhysicalName', 'entPhysicalClass' ]; } foreach ($oidlist as $oid_tmp) { $oids_tmp[] = $oid_tmp . '.' . $entPhysicalIndex; } } } $entPhysicalTable = snmp_get_multi_oid($device, $oids_tmp, [], 'ENTITY-MIB'); foreach ($array as $index => $array_tmp) { // Get entPhysicalIndex if ($use_oid) { $entPhysicalIndex = $array_tmp[$def['oid_entPhysicalIndex']] ?? NULL; } else { $array_tmp['index'] = $index; $entPhysicalIndex = array_tag_replace($array_tmp, $def['entPhysicalIndex']); } if (is_array($entPhysicalTable[$entPhysicalIndex])) { $array[$index] = array_merge($array_tmp, $entPhysicalTable[$entPhysicalIndex]); } } if (isset($def['entPhysical_parent']) && $def['entPhysical_parent']) { $oids_tmp = []; foreach ($entPhysicalTable as $entPhysicalIndex => $entPhysicalEntry) { foreach (['entPhysicalDescr', 'entPhysicalAlias', 'entPhysicalName'] as $oid_tmp) { $oids_tmp[] = $oid_tmp . '.' . $entPhysicalEntry['entPhysicalContainedIn']; } } $entPhysicalTable = snmp_get_multi_oid($device, $oids_tmp, $entPhysicalTable, 'ENTITY-MIB'); foreach ($array as $index => $array_tmp) { if (is_array($entPhysicalTable[$array_tmp['entPhysicalContainedIn']])) { $array[$index] = array_merge($array_tmp, $entPhysicalTable[$array_tmp['entPhysicalContainedIn']]); } } } } function fetch_oids_lldp_addr($device, &$lldp_array) { if (empty($lldp_array)) { return; } // lldpRemManAddrTable // Case 1: // LLDP-MIB::lldpRemManAddrSubtype.120.30001.1582.1.4.10.133.3.10 = INTEGER: ipV4(1) // LLDP-MIB::lldpRemManAddr.120.30001.1582.1.4.10.133.3.10 = Hex-STRING: 0A 85 03 0A // LLDP-MIB::lldpRemManAddrIfSubtype.120.30001.1582.1.4.10.133.3.10 = INTEGER: ifIndex(2) // LLDP-MIB::lldpRemManAddrIfId.120.30001.1582.1.4.10.133.3.10 = INTEGER: 2009 // LLDP-MIB::lldpRemManAddrOID.120.30001.1582.1.4.10.133.3.10 = OID: SNMPv2-SMI::zeroDotZero.0 // Case 2: // LLDP-MIB::lldpRemManAddrIfSubtype.1173570000.129.2.1.4.10.0.10.5 = INTEGER: unknown(1) // LLDP-MIB::lldpRemManAddrIfSubtype.1173834000.4.6.0.6.132.181.156.89.235.128 = INTEGER: unknown(1) // LLDP-MIB::lldpRemManAddrIfId.1173570000.129.2.1.4.10.0.10.5 = INTEGER: 0 // LLDP-MIB::lldpRemManAddrIfId.1173834000.4.6.0.6.132.181.156.89.235.128 = INTEGER: 0 // LLDP-MIB::lldpRemManAddrOID.1173570000.129.2.1.4.10.0.10.5 = OID: SNMPv2-SMI::enterprises.14823.2.2.1.2.1.1 // LLDP-MIB::lldpRemManAddrOID.1173834000.4.6.0.6.132.181.156.89.235.128 = OID: SNMPv2-SMI::enterprises.14823.2.2.1.2.1.7 // Case 3: // LLDP-MIB::lldpRemManAddrIfId.0.2.1.1.4.10.137.41.4 = INTEGER: 0 // LLDP-MIB::lldpRemManAddrIfId.0.3.1.1.4.10.137.41.57 = INTEGER: 2009 // LLDP-MIB::lldpRemManAddrIfId.0.47.1.1.4.10.137.41.19 = INTEGER: 2009 // LLDP-MIB::lldpRemManAddrIfId.0.49.3.1.4.10.129.2.171 = INTEGER: 34 // LLDP-MIB::lldpRemManAddrIfId.0.49.3.2.16.42.2.32.40.255.0.0.0.0.0.0.1.0.0.1.113 = INTEGER: 34 // LLDP-MIB::lldpRemManAddrIfId.0.49.4.1.4.10.129.2.171 = INTEGER: 19 // LLDP-MIB::lldpRemManAddrIfId.0.49.4.2.16.42.2.32.40.255.0.0.0.0.0.0.1.0.0.1.113 = INTEGER: 19 // LLDP-MIB::lldpRemManAddrIfId.0.53.2.1.4.10.129.2.171 = INTEGER: 23 // LLDP-MIB::lldpRemManAddrIfId.0.53.2.2.16.42.2.32.40.255.0.0.0.0.0.0.1.0.0.1.113 = INTEGER: 23 // Case 4: // LLDP-MIB::lldpRemManAddrSubtype.22 = INTEGER: ipV4(1) // LLDP-MIB::lldpRemManAddrSubtype.86 = INTEGER: ipV6(2) // LLDP-MIB::lldpRemManAddr.22 = STRING: "10.31.0.2" // LLDP-MIB::lldpRemManAddr.86 = STRING: "fe80::d6ca:6dff:fe8e:8b3f" // Case 5 (multiple IP addresses): // lldpRemManAddrIfId.31300.2.2.1.4.192.168.13.1 = 5 // lldpRemManAddrIfId.31300.2.2.2.16.32.1.4.112.0.40.11.253.0.0.0.0.0.0.0.0 = 5 // lldpRemManAddrIfId.31300.2.2.2.16.254.128.0.0.0.0.0.0.198.173.52.255.254.216.108.126 = 5 $lldp_addr = snmpwalk_cache_oid($device, 'lldpRemManAddrIfId', [], "LLDP-MIB", NULL, OBS_SNMP_ALL_NUMERIC_INDEX); $lldp_addr = snmpwalk_cache_oid($device, 'lldpRemManAddr', $lldp_addr, "LLDP-MIB", NULL, OBS_SNMP_ALL_NUMERIC_INDEX); foreach ($lldp_addr as $index => $entry) { if (isset($lldp_array[$index])) { $lldp_array[$index] = array_merge($lldp_array[$index], $entry); if (isset($entry['lldpRemManAddr'])) { $addr = hex2ip($entry['lldpRemManAddr']); $lldp_array[$index]['lldpRemMan'][$addr] = $entry; // For multiple entries } continue; } $index_array = explode('.', $index); // LLDP index $lldpRemTimeMark = array_shift($index_array); $lldpRemLocalPortNum = array_shift($index_array); $lldpRemIndex = array_shift($index_array); $lldp_index = "$lldpRemTimeMark.$lldpRemLocalPortNum.$lldpRemIndex"; if (!isset($lldp_array[$lldp_index])) { continue; } $lldp_array[$lldp_index] = array_merge($lldp_array[$lldp_index], $entry); // Already exist Oid, just merge // if (isset($entry['lldpRemManAddr'])) { // continue; // } // Convert from index part $lldpAddressFamily = array_shift($index_array); if ($lldpAddressFamily == 1 && safe_count($index_array) === 4) { // Incorrect indexes // lldpRemManAddrIfId.7800.28.1.1.10.99.0.10 = 2 // lldpRemManAddrIfId.32900.4.1.2.42.14.17.6 = 2 $len = 4; } else { $len = array_shift($index_array); } $addr = implode('.', $index_array); if (isset($entry['lldpRemManAddr'])) { // Already exist Oid, just merge $addr = hex2ip($entry['lldpRemManAddr']); //continue; } elseif ($lldpAddressFamily == 1 || $len == 4) { // IPv4, ie: 4.10.129.2.171 $lldp_array[$lldp_index]['lldpRemManAddr'] = $addr; } elseif ($lldpAddressFamily == 2 || $len == 16) { // IPv6, ie: 16.42.2.32.40.255.0.0.0.0.0.0.1.0.0.1.113 $addr = snmp2ipv6($addr); $lldp_array[$lldp_index]['lldpRemManAddr'] = $addr; } elseif ($lldpAddressFamily == 0 && $len == 6) { // Hrm, I really not know what is this, ie, seems as MAC address: // 6 132.181.156.89.235.128 84:B5:9C:59:EB:80 continue; } $lldp_array[$lldp_index]['lldpRemMan'][$addr] = $entry; // For multiple entries } // Clean IP, select best foreach ($lldp_array as $lldp_index => $lldp) { if (!isset($lldp['lldpRemMan'])) { continue; } $lldp['lldpRemMan'] = array_keys($lldp['lldpRemMan']); if (safe_count($lldp['lldpRemMan']) > 1) { // Multiple IP addresses.. detect best? foreach ($lldp['lldpRemMan'] as $addr) { $addr_version = get_ip_version($addr); $addr_type = get_ip_type($addr); if (in_array($addr_type, [ 'unspecified', 'loopback', 'reserved', 'multicast' ])) { continue; } if ($addr_version == 6 && $addr_type === 'link-local') { continue; } if ($addr_type === 'unicast') { // Prefer IPv4/IPv6 unicast $lldp_array[$lldp_index]['lldpRemManAddr'] = $addr; break; } if ($addr_version == 4) { // Than prefer IPv4 $lldp_array[$lldp_index]['lldpRemManAddr'] = $addr; break; } $lldp_array[$lldp_index]['lldpRemManAddr'] = $addr; } print_debug("Multiple remote IP addresses detect, selected: $addr"); print_debug_vars($lldp); } $lldp_array[$lldp_index]['lldpRemMan'] = $lldp['lldpRemMan']; if (isset($lldp_array[$lldp_index]['lldpRemManAddr'])) { $lldp_array[$lldp_index]['lldpRemManAddr'] = hex2ip($lldp_array[$lldp_index]['lldpRemManAddr']); } } } // DOCME needs phpdoc block // TESTME needs unit testing function check_valid_virtual_machines($device, $valid, $source) { $entries = dbFetchRows("SELECT * FROM `vminfo` WHERE `device_id` = ? AND `vm_source` = ?", [$device['device_id'], $source]); if (is_array($entries) && count($entries)) { foreach ($entries as $entry) { $id = $entry['vm_uuid']; $type = $entry['vm_type']; if (!$valid['vm'][$type][(string)$id]) { echo("-"); print_debug("Virtual Machine deleted: $id -> $type"); dbDelete('vminfo', "`vm_id` = ?", [$entry['vm_id']]); log_event("Virtual Machine deleted: " . $entry['name'] . " " . $entry['vm_type'] . " " . $entry['vm_uuid'], $device, 'virtualmachine', $entry['vm_uuid']); } else { echo('.'); } } } } // DOCME needs phpdoc block // TESTME needs unit testing function discover_juniAtmVp(&$valid, $port_id, $vp_id, $vp_descr) { global $config; //if (dbFetchCell("SELECT COUNT(*) FROM `juniAtmVp` WHERE `port_id` = ? AND `vp_id` = ?", array($port_id, $vp_id)) == "0") if (!dbExist('juniAtmVp', '`port_id` = ? AND `vp_id` = ?', [$port_id, $vp_id])) { $inserted = dbInsert(['port_id' => $port_id, 'vp_id' => $vp_id, 'vp_descr' => $vp_descr], 'juniAtmVp'); //FIXME vv no $device in front of 'juniAtmVp' - will not log correctly! log_event("Juniper ATM VP Added: port $port_id vp $vp_id descr $vp_descr", 'juniAtmVp', $inserted); } else { echo('.'); } $valid[$port_id][$vp_id] = 1; } // DOCME needs phpdoc block // TESTME needs unit testing function discover_neighbour($port, $protocol, $neighbour) { print_debug("Discover neighbour: proto[" . $protocol . "], port_id[" . $port['port_id'] . "], " . print_r($neighbour, TRUE)); if (!is_numeric($port['port_id'])) { print_debug("Local port unknown, skip neighbour."); return NULL; } //elseif (is_bad_xdp($neighbour['remote_hostname'], $neighbour['remote_platform']) || empty($neighbour['remote_hostname'])) if (safe_empty($neighbour['remote_hostname']) || is_bad_xdp($neighbour['remote_hostname'])) { // Note in neighbour discovery, ignore only hostname! print_debug("Hostname ignored, skip neighbour."); return NULL; } // Autodiscovery id $hostname = $neighbour['remote_hostname']; safe_ip_hostname($hostname, $neighbour['remote_address']); $autodiscovery_entry = get_autodiscovery_entry($hostname, $neighbour['remote_address'], $port['device_id']); $neighbour['autodiscovery_id'] = $autodiscovery_entry['autodiscovery_id']; $neighbour['protocol'] = $protocol; $neighbour['active'] = '1'; // Get the remote device id if we've not been told it if (isset($neighbour['remote_port_id']) && safe_empty($neighbour['remote_device_id'])) { $neighbour['remote_device_id'] = get_device_id_by_port_id($neighbour['remote_port_id']); } $params = ['protocol', 'remote_device_id', 'remote_port_id', 'remote_hostname', 'remote_port', 'remote_platform', 'remote_version', 'remote_address', 'autodiscovery_id', 'active']; $neighbour_db = dbFetchRow("SELECT `neighbours`.*, UNIX_TIMESTAMP(`last_change`) AS `last_change_unixtime` FROM `neighbours` WHERE `port_id` = ? AND `protocol` = ? AND `remote_hostname` = ? AND `remote_port` = ?", [$port['port_id'], $protocol, $neighbour['remote_hostname'], $neighbour['remote_port']]); if (!isset($neighbour_db['neighbour_id'])) { $update = ['port_id' => $port['port_id'], 'device_id' => $port['device_id']]; foreach ($params as $param) { $update[$param] = $neighbour[$param]; if (is_null($neighbour[$param])) { $update[$param] = ['NULL']; } } // Last change as unixtime // FIXME. Need convert in db schema last_change to unixtime if (is_numeric($neighbour['last_change']) && $neighbour['last_change'] > OBS_MIN_UNIXTIME) { $update['last_change'] = ['FROM_UNIXTIME(' . $neighbour['last_change'] . ')']; } $id = dbInsert($update, 'neighbours'); $GLOBALS['module_stats']['neighbours']['added']++; //echo('+'); } else { $update = []; foreach ($params as $param) { if ($neighbour[$param] != $neighbour_db[$param]) { $update[$param] = $neighbour[$param]; if (is_null($neighbour[$param])) { $update[$param] = ['NULL']; } } } // Last change as unixtime // FIXME. Need convert in db schema last_change to unixtime if (is_numeric($neighbour['last_change']) && $neighbour['last_change'] > OBS_MIN_UNIXTIME && abs($neighbour['last_change'] - $neighbour_db['last_change_unixtime']) > 90) { $update['last_change'] = ['FROM_UNIXTIME(' . $neighbour['last_change'] . ')']; } if (count($update)) { dbUpdate($update, 'neighbours', '`neighbour_id` = ?', [$neighbour_db['neighbour_id']]); $GLOBALS['module_stats']['neighbours']['updated']++; //echo('U'); } else { $GLOBALS['module_stats']['neighbours']['unchanged']++; //echo('.'); } } $valid_host_key = $neighbour['remote_hostname']; if (strlen($neighbour['remote_address'])) { $valid_host_key .= '-' . $neighbour['remote_address']; } $GLOBALS['valid']['neighbours'][$port['port_id']][$valid_host_key][$neighbour['remote_port']] = 1; } // DOCME needs phpdoc block // TESTME needs unit testing // FIXME don't pass valid, use this as a global variable function discover_storage(&$valid, $device, $storage_index, $storage_type, $storage_mib, $storage_descr, $storage_units, $storage_size, $storage_used, $options = []) { // options && limits $option = 'storage_hc'; $$option = isset($options[$option]) ? $options[$option] : 0; $option = 'storage_ignore'; $$option = isset($options[$option]) ? $options[$option] : 0; if (isset($options['limit_high'])) { $storage_crit_limit = $options['limit_high']; } if (isset($options['limit_high_warn'])) { $storage_warn_limit = $options['limit_high_warn']; } print_debug($device['device_id'] . " -> $storage_index, $storage_type, $storage_mib, $storage_descr, $storage_units, $storage_size, $storage_used, $storage_hc"); //$storage_mib = strtolower($storage_mib); // Check storage ignore filters if (entity_descr_check($storage_descr, 'storage')) { return FALSE; } //foreach ($config['ignore_mount'] as $bi) { if (strcasecmp($bi, $storage_descr) == 0) { print_debug("Skipped by equals: $bi, $storage_descr "); return FALSE; } } //foreach ($config['ignore_mount_string'] as $bi) { if (stripos($storage_descr, $bi) !== FALSE) { print_debug("Skipped by strpos: $bi, $storage_descr "); return FALSE; } } //foreach ($config['ignore_mount_regexp'] as $bi) { if (preg_match($bi, $storage_descr) > 0) { print_debug("Skipped by regexp: $bi, $storage_descr "); return FALSE; } } // Search duplicates for same mib/descr if (in_array($storage_descr, array_values((array)$valid[$storage_mib]))) { print_debug("Skipped by already exist: $storage_descr "); return FALSE; } $params = ['storage_index', 'storage_mib', 'storage_type', 'storage_descr', 'storage_hc', 'storage_ignore', 'storage_crit_limit', 'storage_warn_limit', 'storage_units']; $params_state = ['storage_size', 'storage_used', 'storage_free', 'storage_perc']; // This is changeable params, not required for update $device_id = $device['device_id']; $storage_db = dbFetchRow("SELECT * FROM `storage` WHERE `device_id` = ? AND `storage_index` = ? AND `storage_mib` = ?", [$device_id, $storage_index, $storage_mib]); if (!isset($storage_db['storage_id'])) { // FIXME. Ignore 0 storage size? $storage_free = $storage_size - $storage_used; $storage_perc = round(float_div($storage_used, $storage_size) * 100, 2); $update = ['device_id' => $device_id]; foreach (array_merge($params, $params_state) as $param) { $update[$param] = ($$param === NULL ? ['NULL'] : $$param); } $id = dbInsert($update, 'storage'); //$update_state = array('storage_id' => $id); //foreach ($params_state as $param) { $update_state[$param] = $$param; } //dbInsert($update_state, 'storage-state'); $GLOBALS['module_stats']['storage']['added']++; //echo('+'); log_event("Storage added: index $storage_index, mib $storage_mib, descr $storage_descr", $device, 'storage', $id); } else { $update = []; foreach ($params as $param) { if ($$param != $storage_db[$param]) { $update[$param] = ($$param === NULL ? ['NULL'] : $$param); } } if (count($update)) { //if (isset($update['storage_descr'])) //{ // // Rename storage rrds, because its filename based on description // $old_rrd = $config['rrd_dir'] . '/' . $device['hostname'] . '/' . safename('storage-' . $storage_db['storage_mib'] . '-' . $storage_db['storage_descr'] . '.rrd'); // $new_rrd = $config['rrd_dir'] . '/' . $device['hostname'] . '/' . safename('storage-' . $storage_db['storage_mib'] . '-' . $storage_descr . '.rrd'); // if (is_file($old_rrd) && !is_file($new_rrd)) { rename($old_rrd, $new_rrd); print_warning("Moved RRD"); } //} dbUpdate($update, 'storage', '`storage_id` = ?', [$storage_db['storage_id']]); $GLOBALS['module_stats']['storage']['updated']++; //echo('U'); log_event("Storage updated: index $storage_index, mib $storage_mib, descr $storage_descr", $device, 'storage', $storage_db['storage_id']); } else { $GLOBALS['module_stats']['storage']['unchanged']++; //echo('.'); } } print_debug_vars($update); if ($storage_ignore) { $GLOBALS['module_stats']['storage']['ignored']++; } $valid[$storage_mib][$storage_index] = $storage_descr; } // DOCME needs phpdoc block // TESTME needs unit testing // FIXME don't pass valid, use this as a global variable // FIXME move all the other stuff over to $options too // $options accepts: // - table function discover_mempool(&$valid, $device, $mempool_index, $mempool_mib, $mempool_descr, $mempool_multiplier, $mempool_total, $mempool_used, $mempool_hc = 0, $options = []) { global $config; print_debug($device['device_id'] . " -> $mempool_index, $mempool_mib::" . $options['table'] . ", $mempool_descr, $mempool_multiplier, $mempool_total, $mempool_used"); // Check mempool ignore filters if (entity_descr_check($mempool_descr, 'mempool')) { return FALSE; } //foreach ($config['ignore_mempool'] as $bi) { if (strcasecmp($bi, $mempool_descr) == 0) { print_debug("Skipped by equals: $bi, $mempool_descr "); return FALSE; } } //foreach ($config['ignore_mempool_string'] as $bi) { if (stripos($mempool_descr, $bi) !== FALSE) { print_debug("Skipped by strpos: $bi, $mempool_descr "); return FALSE; } } //foreach ($config['ignore_mempool_regexp'] as $bi) { if (preg_match($bi, $mempool_descr) > 0) { print_debug("Skipped by regexp: $bi, $mempool_descr "); return FALSE; } } // Init $options -> regular variables until all others have been converted to be inside $options (FIXME) $param_opt = ['table' => 'mempool_table']; foreach ($param_opt as $key => $varname) { $$varname = $options[$key] ?: NULL; } // Limit mempool_multiplier input to fit the MySQL field (FLOAT(14,5)) $mempool_multiplier = round($mempool_multiplier, 5); $params = ['mempool_index', 'mempool_mib', 'mempool_descr', 'mempool_multiplier', 'mempool_hc', 'mempool_table']; $params_state = ['mempool_total', 'mempool_used', 'mempool_free', 'mempool_perc']; if (!is_numeric($mempool_total) || $mempool_total <= 0 || !is_numeric($mempool_used)) { print_debug("Skipped by not numeric or too small mempool total ($mempool_total) or used ($mempool_used)"); return FALSE; } if (!$mempool_multiplier) { $mempool_multiplier = 1; } if (!(isset($options['table']) && isset($config['mibs'][$mempool_mib]['mempool'][$options['table']]))) { // Scale for non-definitions based $mempool_total *= $mempool_multiplier; $mempool_used *= $mempool_multiplier; } $mempool_db = dbFetchRow("SELECT * FROM `mempools` WHERE `device_id` = ? AND `mempool_index` = ? AND `mempool_mib` = ?", [$device['device_id'], $mempool_index, $mempool_mib]); if (!isset($mempool_db['mempool_id'])) { $mempool_free = $mempool_total - $mempool_used; $mempool_perc = round(float_div($mempool_used, $mempool_total) * 100, 2); $update = ['device_id' => $device['device_id']]; foreach ($params as $param) { $update[$param] = $$param; } foreach ($params_state as $param) { $update[$param] = $$param; } $update['mempool_polled'] = time(); $id = dbInsert($update, 'mempools'); //$update_state = array('mempool_id' => $id, 'mempool_polled' => time()); //foreach ($params_state as $param) { $update_state[$param] = $$param; } //dbInsert($update_state, 'mempools-state'); $GLOBALS['module_stats']['mempools']['added']++; //echo('+'); log_event("Memory pool added: mib $mempool_mib" . ($mempool_table ? "::$mempool_table" : '') . ", index $mempool_index, descr $mempool_descr", $device, 'mempool', $id); } else { $update = []; foreach ($params as $param) { if ($$param != $mempool_db[$param]) { $update[$param] = $$param; } } if (count($update)) { dbUpdate($update, 'mempools', '`mempool_id` = ?', [$mempool_db['mempool_id']]); $GLOBALS['module_stats']['mempools']['updated']++; //echo('U'); log_event("Memory pool updated: mib $mempool_mib" . ($mempool_table ? "::$mempool_table" : '') . ", index $mempool_index, descr $mempool_descr", $device, 'mempool', $mempool_db['mempool_id']); } else { $GLOBALS['module_stats']['mempools']['unchanged']++; //echo('.'); } } $valid[$mempool_mib][$mempool_index] = 1; } // DOCME needs phpdoc block // TESTME needs unit testing // Valid params: description, type (fuser, toner, etc), index, oid, mib, level, capacity, capacity_oid, colour function discover_printersupply($device, $options = []) { // Check toner ignore filters if (entity_descr_check($options['description'], 'printersupply')) { return FALSE; } if (discovery_check_if_type_exist($options, 'printersupply')) { return FALSE; } //foreach ($config['ignore_toner'] as $bi) { if (strcasecmp($bi, $options['description']) == 0) { print_debug("Skipped by equals: $bi, " . $options['description']); return FALSE; } } //foreach ($config['ignore_toner_string'] as $bi) { if (stripos($options['description'], $bi) !== FALSE) { print_debug("Skipped by strpos: $bi, " . $options['description']); return FALSE; } } //foreach ($config['ignore_toner_regexp'] as $bi) { if (preg_match($bi, $options['description']) > 0) { print_debug("Skipped by regexp: $bi, " . $options['description']); return FALSE; } } //if (dbFetchCell("SELECT COUNT(`supply_id`) FROM `printersupplies` WHERE `device_id` = ? AND `supply_index` = ?", array($device['device_id'], $options['index'])) == '0') if (!dbExist('printersupplies', '`device_id` = ? AND `supply_index` = ?', [$device['device_id'], $options['index']])) { $supply_insert = ['device_id' => $device['device_id'], 'supply_index' => $options['index'], 'supply_mib' => $options['mib'], 'supply_descr' => $options['description'], 'supply_capacity' => $options['capacity'], 'supply_capacity_oid' => $options['capacity_oid'], 'supply_oid' => $options['oid'], 'supply_value' => $options['level'], 'supply_type' => $options['type'], 'supply_colour' => $options['colour']]; $supply_id = dbInsert($supply_insert, 'printersupplies'); $GLOBALS['module_stats']['printersupplies']['added']++; //echo('+'); log_event("Printer supply added: " . $options['description'] . " (" . nicecase($options['type']) . ", index " . $options['index'] . ")", $device, 'printersupply', $supply_id); } else { $supply = dbFetchRow("SELECT * FROM `printersupplies` WHERE `device_id` = ? AND `supply_index` = ?", [$device['device_id'], $options['index']]); $field_map = [ 'description' => 'supply_descr', 'type' => 'supply_type', 'mib' => 'supply_mib', 'colour' => 'supply_colour', 'capacity' => 'supply_capacity', 'oid' => 'supply_oid', 'capacity_oid' => 'supply_capacity_oid' ]; $update = []; foreach ($field_map as $param => $db_param) { if ($options[$param] != $supply[$db_param]) { $update[$db_param] = $options[$param]; } } if (count($update)) { dbUpdate($update, 'printersupplies', '`supply_id` = ?', [$supply['supply_id']]); $GLOBALS['module_stats']['printersupplies']['updated']++; //echo('U'); log_event("Printer supply updated: " . $options['description'] . " (" . nicecase($options['type']) . ", index " . $options['index'] . ")", $device, 'printersupply', $supply['supply_id']); } else { $GLOBALS['module_stats']['printersupplies']['unchanged']++; //echo('.'); } } $GLOBALS['valid']['printersupply'][$options['type']][$options['mib']][$options['index']] = 1; } // DOCME needs phpdoc block // TESTME needs unit testing function check_valid_printer_supplies($device) { $valid = $GLOBALS['valid']['printersupply']; foreach (dbFetchRows("SELECT * FROM `printersupplies` WHERE `device_id` = ?", [$device['device_id']]) as $entry) { $index = $entry['supply_index']; $type = $entry['supply_type']; $mib = $entry['supply_mib']; if (!$valid[$type][$mib][(string)$index]) { $GLOBALS['module_stats']['printersupplies']['deleted']++; // echo("-"); print_debug("Printer supply deleted: $index -> $mib -> $type"); dbDelete('printersupplies', "`supply_id` = ?", [$entry['supply_id']]); log_event("Printer supply deleted: " . $entry['supply_descr'] . " (" . nicecase($entry['supply_type']) . ", index $index)", $device, 'toner', $entry['supply_id']); } } } // DOCME needs phpdoc block // TESTME needs unit testing function discover_inventory($device, $index, $inventory_tmp, $mib) { $entPhysical_oids = [ 'entPhysicalDescr', 'entPhysicalClass', 'entPhysicalName', 'entPhysicalHardwareRev', 'entPhysicalFirmwareRev', 'entPhysicalSoftwareRev', 'entPhysicalAlias', 'entPhysicalAssetID', 'entPhysicalIsFRU', 'entPhysicalModelName', 'entPhysicalVendorType', 'entPhysicalSerialNum', 'entPhysicalContainedIn', 'entPhysicalParentRelPos', 'entPhysicalMfgName', 'ifIndex', 'inventory_mib' ]; $numeric_oids = [ 'entPhysicalContainedIn', 'entPhysicalParentRelPos', 'ifIndex' ]; // DB type 'int' if (!is_array($inventory_tmp) || !is_numeric($index)) { return FALSE; } $inventory = [ 'entPhysicalIndex' => $index ]; $inventory_tmp['inventory_mib'] = $mib; // Rewrites $rewrites = $GLOBALS['config']['rewrites']['inventory']; foreach ($entPhysical_oids as $oid) { $inventory[$oid] = str_replace(['"', "'"], '', snmp_fix_string($inventory_tmp[$oid])); if (isset($rewrites[$oid][$inventory[$oid]])) { $inventory[$oid] = $rewrites[$oid][$inventory[$oid]]; } } if (!$inventory['entPhysicalModelName'] && isset($rewrites['entPhysicalVendorTypes'][$inventory['entPhysicalVendorType']])) { $inventory['entPhysicalModelName'] = $rewrites['entPhysicalVendorTypes'][$inventory['entPhysicalVendorType']]; } elseif (!isset($inventory['entPhysicalModelName'])) { $inventory['entPhysicalModelName'] = $inventory['entPhysicalName']; } if (safe_empty($inventory['entPhysicalName']) && $inventory['entPhysicalDescr']) { // JunOS EVO return empty entPhysicalName $inventory['entPhysicalName'] = $inventory['entPhysicalDescr']; } $query = 'SELECT * FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalIndex` = ? AND `inventory_mib` = ?'; $inventory_db = dbFetchRow($query, [ $device['device_id'], $index, $mib ]); if (!is_array($inventory_db)) { // Compatibility, try with empty mib name $inventory_db = dbFetchRow($query, [ $device['device_id'], $index, '' ]); } if (!is_array($inventory_db)) { $inventory['device_id'] = $device['device_id']; $id = dbInsert($inventory, 'entPhysical'); print_debug('Inventory added: class ' . $inventory['entPhysicalClass'] . ', name ' . $inventory['entPhysicalName'] . ', index ' . $index); $GLOBALS['module_stats']['inventory']['added']++; //echo('+'); } else { $update = []; foreach ($entPhysical_oids as $oid) { if ($inventory[$oid] != $inventory_db[$oid]) { if (in_array($oid, $numeric_oids) && $inventory[$oid] == '') { $update[$oid] = ['NULL']; } else { $update[$oid] = $inventory[$oid]; } } } // Reset deleted date if not empty print_debug_vars($inventory_db); if (!empty($inventory_db['deleted'])) { $update['deleted'] = ['NULL']; } if (safe_count($update)) { print_debug_vars($update); $id = $inventory_db['entPhysical_id']; dbUpdate($update, 'entPhysical', '`entPhysical_id` = ?', [$id]); print_debug('Inventory updated: class ' . $inventory['entPhysicalClass'] . ', name ' . $inventory['entPhysicalName'] . ', index ' . $index); $GLOBALS['module_stats']['inventory']['updated']++; } else { $GLOBALS['module_stats']['inventory']['unchanged']++; } } $GLOBALS['valid']['inventory'][$mib][$index] = 1; } // DOCME needs phpdoc block // TESTME needs unit testing function check_valid_inventory($device) { $query = 'SELECT * FROM `entPhysical` WHERE `device_id` = ?'; $entries = dbFetchRows($query, [$device['device_id']]); foreach ($entries as $entry) { $index = $entry['entPhysicalIndex']; $mib = $entry['inventory_mib']; //if (!$array[$index] && $entry['deleted'] == NULL) if (!isset($GLOBALS['valid']['inventory'][$mib][$index]) && empty($entry['deleted'])) { dbUpdate(['deleted' => ['NOW()']], 'entPhysical', "`entPhysical_id` = ?", [$entry['entPhysical_id']]); // dbDelete('entPhysical', "`entPhysical_id` = ?", array($entry['entPhysical_id'])); print_debug('Inventory deleted: class ' . $entry['entPhysicalClass'] . ', name ' . $entry['entPhysicalName'] . ', index ' . $index); $GLOBALS['module_stats']['inventory']['deleted']++; //echo('-'); } } } // DOCME needs phpdoc block // TESTME needs unit testing function is_bad_xdp($hostname, $platform = '') { global $config; // Ignore Hostname string if (is_array($config['xdp']['ignore_hostname']) && str_contains_array($hostname, $config['xdp']['ignore_hostname'])) { return TRUE; } // Ignore Hostname regex if (is_array($config['xdp']['ignore_hostname_regex'])) { foreach ($config['xdp']['ignore_hostname_regex'] as $pattern) { if (preg_match($pattern, $hostname)) { return TRUE; } } } if (strlen($platform)) { // Ignore Platform string if (is_array($config['xdp']['ignore_platform']) && str_icontains_array($platform, $config['xdp']['ignore_platform'])) { return TRUE; } // Ignore Platform regex if (is_array($config['xdp']['ignore_platform_regex'])) { foreach ($config['xdp']['ignore_platform_regex'] as $pattern) { if (preg_match($pattern, $platform)) { return TRUE; } } } } return FALSE; } // DOCME needs phpdoc block // TESTME needs unit testing // FIXME don't pass valid, use this as a global variable function discover_lsp(&$valid, $device, $lsp_index, $lsp_mib, $lsp_name, $lsp_state, $lsp_uptime, $lsp_total_uptime, $lsp_primary_uptime, $lsp_proto, $lsp_transitions, $lsp_path_changes, $lsp_bandwidth, $lsp_octets, $lsp_packets, $lsp_polled) { print_debug($device['device_id'] . " -> $lsp_index, $lsp_mib, $lsp_name, $lsp_state, $lsp_uptime, $lsp_total_uptime, $lsp_primary_uptime, $lsp_proto, " . "$lsp_transitions, $lsp_path_changes, $lsp_bandwidth, $lsp_octets, $lsp_packets, $lsp_polled"); $lsp_mib = strtolower($lsp_mib); // Check lsp ignore filters if (entity_descr_check($lsp_name, 'lsp')) { return FALSE; } //foreach ($config['ignore_lsp'] as $bi) { if (strcasecmp($bi, $lsp_name) == 0) { print_debug("Skipped by equals: $bi, $lsp_name "); return FALSE; } } //foreach ($config['ignore_lsp_string'] as $bi) { if (stripos($lsp_name, $bi) !== FALSE) { print_debug("Skipped by strpos: $bi, $lsp_name "); return FALSE; } } //foreach ($config['ignore_lsp_regexp'] as $bi) { if (preg_match($bi, $lsp_name) > 0) { print_debug("Skipped by regexp: $bi, $lsp_name "); return FALSE; } } // Search duplicates for same mib/descr if (!safe_empty($valid[$lsp_mib]) && in_array($lsp_name, array_values($valid[$lsp_mib]))) { print_debug("Skipped by already exist: $lsp_name "); return FALSE; } $params = ['lsp_index', 'lsp_mib', 'lsp_name', 'lsp_proto']; $params_state = ['lsp_polled', 'lsp_state', 'lsp_uptime', 'lsp_total_uptime', 'lsp_primary_uptime', 'lsp_transitions', 'lsp_path_changes', 'lsp_bandwidth', 'lsp_octets', 'lsp_packets']; // This is changable params, not required for update $device_id = $device['device_id']; $lsp_db = dbFetchRow("SELECT * FROM `lsp` WHERE `device_id` = ? AND `lsp_index` = ? AND `lsp_mib` = ?", [$device_id, $lsp_index, $lsp_mib]); if (!isset($lsp_db['lsp_id'])) { $update = ['device_id' => $device_id]; foreach (array_merge($params, $params_state) as $param) { $update[$param] = ($$param === NULL ? ['NULL'] : $$param); } $id = dbInsert($update, 'lsp'); $GLOBALS['module_stats']['lsp']['added']++; log_event("$lsp_proto LSP added: index $lsp_index, mib $lsp_mib, name $lsp_name", $device, 'lsp', $id); } else { $update = []; foreach ($params as $param) { if ($$param != $lsp_db[$param]) { $update[$param] = ($$param === NULL ? ['NULL'] : $$param); } } if (count($update)) { dbUpdate($update, 'lsp', '`lsp_id` = ?', [$lsp_db['lsp_id']]); $GLOBALS['module_stats']['lsp']['updated']++; log_event("$lsp_proto LSP updated: index $lsp_index, mib $lsp_mib, name $lsp_name", $device, 'lsp', $lsp_db['lsp_id']); } else { $GLOBALS['module_stats']['lsp']['unchanged']++; } } print_debug_vars($update); $valid[$lsp_mib][$lsp_index] = $lsp_name; } /** * Process a single node from the lshw output and discover its inventory. * * This function processes a single node from the lshw output, creates an * inventory entry from the node's attributes, and then calls the * discover_inventory() function to add or update the entry in the database. * It also processes any child nodes in a recursive manner. * * FIXME : This can probably be massively extended and improved. This just a PoC. * entPhysical table should be generified and more fields added. * * @param array $device The device for which the inventory is being processed. * @param array $node The lshw node to process. * @param int $parent_index The index of the parent node (default: 0). */ function process_lshw_node($device, $node, $parent_index = 0) { $mib = 'lshw'; $inventory_tmp = [ 'entPhysicalDescr' => $node['description'] ?? '', 'entPhysicalClass' => $node['class'] ?? '', 'entPhysicalName' => $node['product'] ?? '', 'entPhysicalModelName' => $node['version'] ?? '', 'entPhysicalSerialNum' => $node['serial'] ?? '', 'entPhysicalContainedIn' => $parent_index, 'entPhysicalHardwareRev' => $node['configuration']['version'] ?? '', 'entPhysicalFirmwareRev' => $node['configuration']['firmware'] ?? '', 'entPhysicalSoftwareRev' => $node['configuration']['driver'] ?? '', //'entPhysicalAlias' => $node['logicalname'] ?? '', 'entPhysicalAssetID' => $node['physid'] ?? '', 'entPhysicalIsFRU' => (isset($node['claimed']) && $node['claimed'] === 'true') ? 1 : 2, 'entPhysicalMfgName' => $node['vendor'] ?? '', //'entPhysicalParentRelPos' => $node['slot'] ?? '', ]; // Generate a unique, reproducible ID for the node $unique_str = implode('|', [ $node['id'] ?? '', $node['description'] ?? '', $node['class'] ?? '', $node['product'] ?? '', $node['version'] ?? '', ]); $node_id = crc32($unique_str); //print_vars($node); //print_vars($inventory_tmp); discover_inventory($device, $node_id, $inventory_tmp, $mib); echo "."; if (isset($node['children'])) { foreach ($node['children'] as $child) { process_lshw_node($device, $child, $node_id); } } } // EOF