initial commit; version 22.5.12042

This commit is contained in:
2022-12-12 23:28:25 -05:00
commit af1b03d79f
17653 changed files with 22692970 additions and 0 deletions

View File

@ -0,0 +1,893 @@
<?php
/**
* Observium
*
* This file is part of Observium.
*
* @package observium
* @subpackage entities
* @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2021 Observium Limited
*
*/
function discover_counter_definition($device, $mib, $entry)
{
echo($entry['oid']. ' [');
// Just append mib name to definition entry, for simple pass to external functions
if (empty($entry['mib']))
{
$entry['mib'] = $mib;
}
// Check that types listed in skip_if_valid_exist have already been found
if (discovery_check_if_type_exist($entry, 'counter')) { echo '!]'; return; }
// Check array requirements list
if (discovery_check_requires_pre($device, $entry, 'counter')) { echo '!]'; return; }
// Validate if Oid exist for current mib (in case when used generic definitions, ie edgecore)
if (empty($entry['oid_num']))
{
// Use snmptranslate if oid_num not set
$entry['oid_num'] = snmp_translate($entry['oid'], $mib);
if (empty($entry['oid_num']))
{
echo("]");
print_debug("Oid [".$entry['oid']."] not exist for mib [$mib]. Counter skipped.");
return;
}
} else {
$entry['oid_num'] = rtrim($entry['oid_num'], '.');
}
// Fetch table or Oids
$table_oids = array('oid', 'oid_descr', 'oid_scale', 'oid_unit', 'oid_class', 'oid_add',
'oid_limit_low', 'oid_limit_low_warn', 'oid_limit_high_warn', 'oid_limit_high', 'oid_limit_warn',
'oid_limit_nominal', 'oid_limit_delta_warn', 'oid_limit_delta', 'oid_limit_scale',
'oid_extra', 'oid_entPhysicalIndex');
$counter_array = discover_fetch_oids($device, $mib, $entry, $table_oids);
$counters = array(); // Reset per-class counters for each MIB
$counters_count = count($counter_array);
foreach ($counter_array as $index => $counter)
{
$options = array();
if (isset($entry['class']))
{
// Hardcoded counter class
$class = $entry['class'];
} else {
// If no 'class' hardcoded, see if we can get class from the map_class via oid_class
if (isset($entry['oid_class']) && isset($counter[$entry['oid_class']]))
{
if (isset($entry['map_class'][$counter[$entry['oid_class']]]))
{
$class = $entry['map_class'][$counter[$entry['oid_class']]];
} else {
print_debug('Value from oid_class (' . $counter[$entry['oid_class']] . ') does not match any configured values in map_class!');
continue; // Break foreach. Next counter!
}
} else {
print_debug('No class hardcoded, but no oid_class (' . $entry['oid_class'] . ') found in table walk!');
continue; // Break foreach. Next counter!
}
}
$dot_index = strlen($index) ? '.' . $index : '';
$oid_num = $entry['oid_num'] . $dot_index;
// echo PHP_EOL; print_vars($entry); echo PHP_EOL; print_vars($counter); echo PHP_EOL; print_vars($descr); echo PHP_EOL;
// %i% can be used in description, a counter is kept per counter class
$counters[$class]++;
// Generate specific keys used during rewrites
$counter['class'] = nicecase($class); // Class in descr
$counter['index'] = $index; // Index in descr
foreach (explode('.', $index) as $k => $i)
{
$counter['index'.$k] = $i; // Index parts
}
$counter['i'] = $counters[$class]; // i++ counter in descr (per counter class)
// Check valid exist with entity tags
if (discovery_check_if_type_exist($entry, 'counter', $counter)) { continue; }
// Check array requirements list
if (discovery_check_requires($device, $entry, $counter, 'counter')) { continue; }
$value = snmp_fix_numeric($counter[$entry['oid']]);
if (!is_numeric($value))
{
print_debug("Excluded by current value ($value) is not numeric.");
continue;
}
// Check for min/max values, when counters report invalid data as counter does not exist
if (isset($entry['min']) && $value <= $entry['min'])
{
print_debug("Excluded by current value ($value) is equals or below min (".$entry['min'].").");
continue;
}
elseif (isset($entry['max']) && $value >= $entry['max'])
{
print_debug("Excluded by current value ($value) is equals or above max (".$entry['max'].").");
continue;
}
elseif (isset($entry['invalid']) && in_array($value, (array)$entry['invalid']))
{
print_debug("Excluded by current value ($value) in invalid range [".implode(', ', (array)$entry['invalid'])."].");
continue;
}
// Scale
$scale = entity_scale_definition($device, $entry, $counter);
// Limits
$options = array_merge($options, entity_limits_definition($device, $entry, $counter, $scale));
// Unit
if (isset($entry['unit'])) { $options['counter_unit'] = $entry['unit']; }
if (isset($entry['oid_unit']) && isset($counter[$entry['oid_unit']]))
{
// Translate unit from specific Oid
$unit = $counter[$entry['oid_unit']];
if (isset($entry['map_unit'][$unit]))
{
$options['counter_unit'] = $entry['map_unit'][$unit];
}
}
// Rule-based entity linking.
if ($measured = entity_measured_match_definition($device, $entry, $counter, 'counter'))
{
$options = array_merge($options, $measured);
$counter = array_merge($counter, $measured); // append to $counter for %descr% tags, ie %port_label%
}
// End rule-based entity linking
elseif (isset($entry['entPhysicalIndex']))
{
// Just set physical index
$options['entPhysicalIndex'] = array_tag_replace($counter, $entry['entPhysicalIndex']);
}
// Add extra value, dirty hack for DANTHERM-COOLING-MIB:
// heaterOpertdurHour.0 = INTEGER: 13 Hours
// heaterOpertdurMin.0 = INTEGER: 59
if (isset($entry['oid_add']) && isset($counter[$entry['oid_add']]))
{
$options['value_add'] = snmp_fix_numeric($counter[$entry['oid_add']]);
$options['oid_add'] = snmp_translate($entry['oid_add'] . $dot_index, $mib);
if (isset($entry['scale_add']))
{
$options['scale_add'] = $entry['scale_add'];
}
}
// Generate Description
$descr = entity_descr_definition('counter', $entry, $counter, $counters_count);
// Rename old (converted) RRDs to definition format
if (isset($entry['rename_rrd']))
{
$options['rename_rrd'] = $entry['rename_rrd'];
}
elseif (isset($entry['rename_rrd_full']))
{
$options['rename_rrd_full'] = $entry['rename_rrd_full'];
}
discover_counter($device, $class, $mib, $entry['oid'], $oid_num, $index, $descr, $scale, $value, $options);
}
echo '] ';
}
// TESTME needs unit testing
/**
* Discover a new counter on a device
*
* This function adds a status counter to a device, if it does not already exist.
* Data on the counter is updated if it has changed, and an event is logged with regards to the changes.
*
* Status counters are handed off to discover_status().
* Current counter values are rectified in case they are broken (added spaces, etc).
*
* @param array $device Device array counter is being discovered on
* @param string $class Class of counter (voltage, temperature, etc.)
* @param string $mib SNMP MIB name
* @param string $object SNMP Named Oid of counter (without index)
* @param string $oid SNMP Numeric Oid of counter (without index)
* @param string $index SNMP index of counter
* @param string $counter_descr Description of counter
* @param int $scale Scale of counter (0.1 for 1:10 scale, 10 for 10:1 scale, etc)
* @param string $value Current counter value
* @param array $options Options (counter_unit, limit_auto, limit*, poller_type, scale, measured_*)
* @return bool
*/
function discover_counter($device, $class, $mib, $object, $oid, $index, $counter_descr, $scale = 1, $value = NULL, $options = []) {
//echo 'MIB:'; print_vars($mib);
$poller_type = isset($options['poller_type']) ? $options['poller_type'] : 'snmp';
// Class for counter is free text string (not limited by known classes)
$class = safe_empty($class) ? 'counter' : strtolower($class);
// Use MIB & Object or Numeric Oid?
$use_mib_object = $mib && $object;
$counter_deleted = 0;
// Init main
$param_main = [ 'oid' => 'counter_oid', 'counter_descr' => 'counter_descr', 'scale' => 'counter_multiplier',
'counter_deleted' => 'counter_deleted', 'mib' => 'counter_mib', 'object' => 'counter_object' ];
// Init numeric values
if (!is_numeric($scale) || $scale == 0) { $scale = 1; }
// Skip discovery counter if value not numeric or null (default)
if (!safe_empty($value)) {
// Some retarded devices report data with spaces and commas
// STRING: " 20,4"
$value = snmp_fix_numeric($value);
}
if (is_numeric($value)) {
$value = scale_value($value, $scale);
// $value *= $scale; // Scale before unit conversion
$value = value_to_si($value, $options['counter_unit'], $class); // Convert if not SI unit
// Extra add value
if (isset($options['value_add']) && is_numeric($options['value_add'])) {
$value += scale_value($options['value_add'], $options['scale_add']);
}
} else {
print_debug("Counter skipped by not numeric value: '$value', '$counter_descr'");
if (strlen($value))
{
print_debug("Perhaps this is named status, use discover_status() instead.");
}
return FALSE;
}
$param_limits = array('limit_high' => 'counter_limit', 'limit_high_warn' => 'counter_limit_warn',
'limit_low' => 'counter_limit_low', 'limit_low_warn' => 'counter_limit_low_warn');
foreach ($param_limits as $key => $column) {
// Set limits vars and unit convert if required
$$key = (is_numeric($options[$key]) ? value_to_si($options[$key], $options['counter_unit'], $class) : NULL);
}
// Set by which param use limits
switch (strtolower($options['limit_by'])) {
case 's':
case 'sec':
case 'second':
$limit_by = 'sec';
break;
case 'm':
case 'min':
case 'minute':
$limit_by = 'min';
break;
case 'h':
case 'hour':
$limit_by = 'hour';
break;
case 'val':
case 'value':
$limit_by = 'value';
break;
default:
//case 'poll':
//case '5min':
$limit_by = '5min';
break;
}
// Auto calculate high/low limits if not passed (for counter must be explicit passed)
//$limit_auto = !isset($options['limit_auto']) || (bool)$options['limit_auto'];
$limit_auto = isset($options['limit_auto']) && (bool)$options['limit_auto'];
// Init optional
$param_opt = array('entPhysicalIndex', 'entPhysicalClass', 'entPhysicalIndex_measured', 'measured_class', 'measured_entity', 'counter_unit');
foreach ($param_opt as $key) {
$$key = ($options[$key] ? $options[$key] : NULL);
}
print_debug("Discover counter: [class: $class, device: ".$device['hostname'].", oid: $oid, index: $index, descr: $counter_descr, scale: $scale, limits: ($limit_low, $limit_low_warn, $limit_high_warn, $limit_high), CURRENT: $value, $entPhysicalIndex, $entPhysicalClass");
// Check counter ignore filters
if (entity_descr_check($counter_descr, 'counter')) { return FALSE; }
//foreach ($config['ignore_counter'] as $bi) { if (strcasecmp($bi, $counter_descr) == 0) { print_debug("Skipped by equals: $bi, $counter_descr "); return FALSE; } }
//foreach ($config['ignore_counter_string'] as $bi) { if (stripos($counter_descr, $bi) !== FALSE) { print_debug("Skipped by strpos: $bi, $counter_descr "); return FALSE; } }
//foreach ($config['ignore_counter_regexp'] as $bi) { if (preg_match($bi, $counter_descr) > 0) { print_debug("Skipped by regexp: $bi, $counter_descr "); return FALSE; } }
if (!is_null($limit_low_warn) && !is_null($limit_high_warn) && ($limit_low_warn > $limit_high_warn)) {
// Fix high/low thresholds (i.e. on negative numbers)
list($limit_high_warn, $limit_low_warn) = array($limit_low_warn, $limit_high_warn);
}
print_debug_vars($limit_high);
print_debug_vars($limit_high_warn);
print_debug_vars($limit_low_warn);
print_debug_vars($limit_low);
if ($use_mib_object) {
$where = '`device_id` = ? AND `counter_class` = ? AND `counter_mib` = ? AND `counter_object` = ? AND `counter_index` = ? AND `poller_type`= ?';
$params = [$device['device_id'], $class, $mib, $object, $index, $poller_type];
} else {
// Rare case, when MIB and Object unknown
$where = '`device_id` = ? AND `counter_class` = ? AND `counter_oid` = ? AND `counter_index` = ? AND `poller_type`= ?';
$params = [$device['device_id'], $class, $oid, $index, $poller_type];
}
if (!dbExist('counters', $where, $params)) {
if (!$limit_high) { $limit_high = sensor_limit_high($class, $value, $limit_auto); }
if (!$limit_low) { $limit_low = sensor_limit_low($class, $value, $limit_auto); }
if (!is_null($limit_low) && !is_null($limit_high) && ($limit_low > $limit_high)) {
// Fix high/low thresholds (i.e. on negative numbers)
list($limit_high, $limit_low) = array($limit_low, $limit_high);
print_debug("High/low limits swapped.");
}
$counter_insert = [
'poller_type' => $poller_type, 'device_id' => $device['device_id'],
'counter_class' => $class, 'counter_index' => $index
];
foreach ($param_main as $key => $column) {
$counter_insert[$column] = $$key;
}
foreach ($param_limits as $key => $column) {
// Convert strings/numbers to (float) or to array('NULL')
$$key = is_numeric($$key) ? (float)$$key : array('NULL');
$counter_insert[$column] = $$key;
}
$counter_insert['counter_limit_by'] = $limit_by;
foreach ($param_opt as $key) {
if (is_null($$key)) { $$key = array('NULL'); }
$counter_insert[$key] = $$key;
}
$counter_insert['counter_value'] = $value;
$counter_insert['counter_polled'] = time();
$counter_id = dbInsert($counter_insert, 'counters');
// Extra oid add
if ($counter_id && isset($options['oid_add'])) {
set_entity_attrib('counter', $counter_id, 'oid_add', $options['oid_add'], $device['device_id']);
if (isset($options['scale_add'])) {
set_entity_attrib('counter', $counter_id, 'scale_add', $options['scale_add'], $device['device_id']);
}
}
print_debug("( $counter_id inserted )");
echo('+');
log_event("Counter added: $class $mib::$object.$index $counter_descr", $device, 'counter', $counter_id);
} else {
$counter_entry = dbFetchRow("SELECT * FROM `counters` WHERE " . $where, $params);
$counter_id = $counter_entry['counter_id'];
// Limits
if (!$counter_entry['counter_custom_limit'])
{
if (!is_numeric($limit_high))
{
if ($counter_entry['counter_limit'] !== '')
{
// Calculate a reasonable limit
$limit_high = sensor_limit_high($class, $value, $limit_auto);
} else {
// Use existing limit. (this is wrong! --mike)
$limit_high = $counter_entry['counter_limit'];
}
}
if (!is_numeric($limit_low))
{
if ($counter_entry['counter_limit_low'] !== '')
{
// Calculate a reasonable limit
$limit_low = sensor_limit_low($class, $value, $limit_auto);
} else {
// Use existing limit. (this is wrong! --mike)
$limit_low = $counter_entry['counter_limit_low'];
}
}
// Fix high/low thresholds (i.e. on negative numbers)
if (!is_null($limit_low) && !is_null($limit_high) && ($limit_low > $limit_high))
{
list($limit_high, $limit_low) = array($limit_low, $limit_high);
print_debug("High/low limits swapped.");
}
// Update limits
$update = array();
$update_msg = array();
$debug_msg = 'Current counter value: "'.$value.'", scale: "'.$scale.'"'.PHP_EOL;
foreach ($param_limits as $key => $column)
{
// $key - param name, $$key - param value, $column - column name in DB for $key
$debug_msg .= ' '.$key.': "'.$counter_entry[$column].'" -> "'.$$key.'"'.PHP_EOL;
//convert strings/numbers to identical type (float) or to array('NULL') for correct comparison
$$key = is_numeric($$key) ? (float)$$key : array('NULL');
$counter_entry[$column] = is_numeric($counter_entry[$column]) ? (float)$counter_entry[$column] : array('NULL');
if (float_cmp($$key, $counter_entry[$column], 0.01) !== 0) // FIXME, need compare autogenerated and hard passed limits by different ways
{
$update[$column] = $$key;
$update_msg[] = $key.' -> "'.(is_array($$key) ? 'NULL' : $$key).'"';
}
}
if ($counter_entry['counter_limit_by'] != $limit_by)
{
$update['counter_limit_by'] = $limit_by;
$update_msg[] = 'limit_by -> "'.$limit_by.'"';
}
if (count($update))
{
echo("L");
print_debug($debug_msg);
log_event('Counter updated (limits): '.implode(', ', $update_msg), $device, 'counter', $counter_entry['counter_id']);
$updated = dbUpdate($update, 'counters', '`counter_id` = ?', array($counter_entry['counter_id']));
}
}
$update = array();
foreach ($param_main as $key => $column)
{
if (float_cmp($$key, $counter_entry[$column]) !== 0)
{
$update[$column] = $$key;
}
}
foreach ($param_opt as $key)
{
if ($$key != $counter_entry[$key])
{
$update[$key] = $$key;
}
}
if (count($update))
{
$updated = dbUpdate($update, 'counters', '`counter_id` = ?', array($counter_entry['counter_id']));
// Extra oid add
if (isset($options['oid_add']))
{
set_entity_attrib('counter', $counter_entry['counter_id'], 'oid_add', $options['oid_add'], $device['device_id']);
if (isset($options['scale_add']))
{
set_entity_attrib('counter', $counter_entry['counter_id'], 'scale_add', $options['scale_add'], $device['device_id']);
}
}
echo('U');
log_event("Counter updated: $class $mib::$object.$index $counter_descr", $device, 'counter', $counter_entry['counter_id']);
} else {
if (isset($options['oid_add']) &&
!dbExist('entity_attribs', '`entity_type` = ? AND `entity_id` = ? AND `attrib_type` = ?', array('counter', $counter_entry['counter_id'], 'oid_add')))
{
set_entity_attrib('counter', $counter_entry['counter_id'], 'oid_add', $options['oid_add'], $device['device_id']);
if (isset($options['scale_add']))
{
set_entity_attrib('counter', $counter_entry['counter_id'], 'scale_add', $options['scale_add'], $device['device_id']);
}
}
echo('.');
}
}
// Rename old (converted) RRDs to definition format
// Allow with changing class or without
if (isset($options['rename_rrd']) || isset($options['rename_rrd_full'])) {
$rrd_tags = array('index' => $index, 'mib' => $mib, 'object' => $object, 'oid' => $object);
if (isset($options['rename_rrd'])) {
$options['rename_rrd'] = array_tag_replace($rrd_tags, $options['rename_rrd']);
$old_rrd = 'counter-' . $class . '-' . $options['rename_rrd'];
} elseif (isset($options['rename_rrd_full'])) {
$options['rename_rrd_full'] = array_tag_replace($rrd_tags, $options['rename_rrd_full']);
$old_rrd = 'counter-' . $options['rename_rrd_full'];
}
$new_rrd = "counter-" . $class . "-" . $mib . "-" . $object . "-" . $index . ".rrd";
rename_rrd($device, $old_rrd, $new_rrd);
}
$GLOBALS['valid']['counter'][$class][$mib][$object][$index] = 1;
return $counter_id;
//return TRUE;
}
// Poll a counter
function poll_counter($device, &$oid_cache)
{
global $config, $agent_sensors, $ipmi_counters, $graphs, $table_rows;
$sql = "SELECT * FROM `counters`";
$sql .= " WHERE `device_id` = ? AND `counter_deleted` = ?";
$sql .= ' ORDER BY `counter_oid`'; // This fix polling some OIDs (when not ordered)
//print_debug_vars($GLOBALS['cache']['entity_attribs']);
foreach (dbFetchRows($sql, array($device['device_id'], '0')) as $counter_db)
{
$counter_poll = array();
$counter_attribs = get_entity_attribs('counter', $counter_db);
//print_debug_vars($GLOBALS['cache']['entity_attribs']);
//print_debug_vars($counter_attribs);
$class = $counter_db['counter_class'];
// Counter not have type attribute, this need for compat with agent or ipmi
$type = $counter_db['counter_mib'] . '-' . $counter_db['counter_object'];
//print_cli_heading("Counter: ".$counter_db['counter_descr'], 3);
if (OBS_DEBUG)
{
echo("Checking (" . $counter_db['poller_type'] . ") $class " . $counter_db['counter_descr'] . " ");
print_debug_vars($counter_db, 1);
}
if ($counter_db['poller_type'] === "snmp")
{
$counter_db['counter_oid'] = '.' . ltrim($counter_db['counter_oid'], '.'); // Fix first dot in oid for caching
// Take value from $oid_cache if we have it, else snmp_get it
if (isset($oid_cache[$counter_db['counter_oid']]))
{
$oid_cache_tmp = $oid_cache[$counter_db['counter_oid']]; // keep original value, while cache entry can reused
$oid_cache[$counter_db['counter_oid']] = snmp_fix_numeric($oid_cache[$counter_db['counter_oid']], $counter_db['counter_unit']);
}
if (is_numeric($oid_cache[$counter_db['counter_oid']]))
{
print_debug("value taken from oid_cache");
$counter_poll['counter_value'] = $oid_cache[$counter_db['counter_oid']];
$oid_cache[$counter_db['counter_oid']] = $oid_cache_tmp; // restore original cached value
} else {
// Get by numeric oid
$counter_poll['counter_value'] = snmp_get_oid($device, $counter_db['counter_oid'], 'SNMPv2-MIB');
$counter_poll['counter_value'] = snmp_fix_numeric($counter_poll['counter_value'], $counter_db['counter_unit']);
}
$unit = $counter_db['counter_unit'];
// Extra add
if (isset($counter_attribs['oid_add']))
{
$counter_poll['counter_value_add'] = snmp_fix_numeric(snmp_get_oid($device, $counter_attribs['oid_add'], 'SNMPv2-MIB'));
}
}
elseif ($counter_db['poller_type'] === "agent")
{
if (isset($agent_sensors))
{
$counter_poll['counter_value'] = $agent_sensors[$class][$type][$counter_db['counter_index']]['current'];
} else {
print_warning("No agent counter data available.");
continue;
}
}
elseif ($counter_db['poller_type'] === "ipmi")
{
if (isset($ipmi_counters))
{
$counter_poll['counter_value'] = snmp_fix_numeric($ipmi_counters[$class][$type][$counter_db['counter_index']]['current']);
$unit = $ipmi_counters[$class][$type][$counter_db['counter_index']]['unit'];
} else {
print_warning("No IPMI counter data available.");
continue;
}
} else {
print_warning("Unknown counter poller type.");
continue;
}
$counter_polled_time = time(); // Store polled time for current counter
$counter_polled_period = $counter_polled_time - $counter_db['counter_polled'];
if (isset($counter_db['counter_multiplier']) && $counter_db['counter_multiplier'] != 0)
{
//$counter_poll['counter_value'] *= $counter_db['counter_multiplier'];
$counter_poll['counter_value'] = scale_value($counter_poll['counter_value'], $counter_db['counter_multiplier']);
}
// Unit conversion to SI (if required)
$counter_poll['counter_value'] = value_to_si($counter_poll['counter_value'], $counter_db['counter_unit'], $class);
// Extra add
if (isset($counter_attribs['oid_add']) && is_numeric($counter_poll['counter_value_add']))
{
print_debug("Extra value add: ".$counter_poll['counter_value']." + (".$counter_poll['counter_value_add']." * ".$counter_attribs['scale_add'].") =");
$counter_poll['counter_value'] += scale_value($counter_poll['counter_value_add'], $counter_attribs['scale_add']);
print_debug($counter_poll['counter_value']);
}
// Rate /s
$value_diff = int_sub($counter_poll['counter_value'], $counter_db['counter_value']);
$counter_poll['counter_rate'] = float_div($value_diff, $counter_polled_period);
$counter_poll['counter_rate_min'] = float_div($value_diff, ($counter_polled_period / 60));
$counter_poll['counter_rate_5min'] = float_div($value_diff, ($counter_polled_period / 300)); // This is mostly same as count per poll period
print_debug('Rate /sec: (' . $counter_poll['counter_value'] . ' - ' . $counter_db['counter_value'] . '(='.$value_diff.')) / ' . $counter_polled_period . ' = ' . $counter_poll['counter_rate']);
print_debug('Rate /min: ' . $counter_poll['counter_rate_min']);
print_debug('Rate /5min: ' . $counter_poll['counter_rate_5min']);
// Rate /h (more complex since counters grow up very rarely
$counter_poll['counter_history'] = $counter_db['counter_history'] != '' ? json_decode($counter_db['counter_history'], TRUE) : [];
// Now find first polled time around 3600s (1h)
foreach ($counter_poll['counter_history'] as $polled_time => $value)
{
$diff = $counter_polled_time - $polled_time;
if ($diff < (3600 + ($config['rrd']['step'] / 2))) // 3600 + 150 (possible change step in future)
{
if ($diff < 3300)
{
// If not have full hour history, use approximate to hour rate
$period = $diff / 3600; // Period in hours (around 1)
$counter_poll['counter_rate_hour'] = int_sub($counter_poll['counter_value'], $value) / $period;
print_debug("Hour rate by approximate: ".$counter_poll['counter_value']." - $value / $period");
} else {
$counter_poll['counter_rate_hour'] = int_sub($counter_poll['counter_value'], $value); // Keep this value as integer, since we keep in history only 1 hour
print_debug("Hour rate by history: ".$counter_poll['counter_value']." - $value");
}
break;
}
// Clear old entries
unset($counter_poll['counter_history'][$polled_time]);
}
// Just if initially not exist history
if (!isset($counter_poll['counter_rate_hour']))
{
$counter_poll['counter_rate_hour'] = $counter_poll['counter_rate_5min'] * 12;
print_debug("Hour rate initially: ".$counter_poll['counter_rate_5min']." * 12");
}
print_debug('Rate /hour: ' . $counter_poll['counter_rate_hour']);
// Append last value to history and json it
$counter_poll['counter_history'][$counter_polled_time] = $counter_poll['counter_value'];
print_debug_vars($counter_poll['counter_history']);
$counter_poll['counter_history'] = json_encode($counter_poll['counter_history']);
print_debug_vars($counter_poll, 1);
//print_cli_data("Value", $counter_poll['counter_value'] . "$unit ", 3);
// FIXME this block and the other block below it are kinda retarded. They should be merged and simplified.
if ($counter_db['counter_disable'])
{
$counter_poll['counter_event'] = 'ignore';
$counter_poll['counter_status'] = 'Counter disabled.';
} else {
// Select param for calculate limit events
switch ($counter_db['counter_limit_by'])
{
case 'sec':
$limit_by = 'counter_rate';
$limit_unit = "$unit/sec";
break;
case 'min':
$limit_by = 'counter_rate_min';
$limit_unit = "$unit/min";
break;
case '5min':
$limit_by = 'counter_rate_5min';
$limit_unit = "$unit/5min";
break;
case 'hour':
$limit_by = 'counter_rate_hour';
$limit_unit = "$unit/hour";
break;
case 'value':
$limit_by = 'counter_value';
$limit_unit = $unit;
break;
}
$counter_poll['counter_event'] = check_thresholds($counter_db['counter_limit_low'], $counter_db['counter_limit_low_warn'],
$counter_db['counter_limit_warn'], $counter_db['counter_limit'],
$counter_poll[$limit_by]);
if ($counter_poll['counter_event'] === 'alert')
{
$counter_poll['counter_status'] = 'Counter critical thresholds exceeded.';
}
elseif ($counter_poll['counter_event'] === 'warning')
{
$counter_poll['counter_status'] = 'Counter warning thresholds exceeded.';
} else {
$counter_poll['counter_status'] = '';
}
// Reset Alert if counter ignored
if ($counter_poll['counter_event'] !== 'ok' && $counter_db['counter_ignore'])
{
$counter_poll['counter_event'] = 'ignore';
$counter_poll['counter_status'] = 'Counter thresholds exceeded, but ignored.';
}
}
// If last change never set, use current time
if (empty($counter_db['counter_last_change']))
{
$counter_db['counter_last_change'] = $counter_polled_time;
}
if ($counter_poll['counter_event'] != $counter_db['counter_event'])
{
// Counter event changed, log and set counter_last_change
$counter_poll['counter_last_change'] = $counter_polled_time;
if ($counter_db['counter_event'] === 'ignore')
{
print_message("[%yCounter Ignored%n]", 'color');
}
elseif (is_numeric($counter_db['counter_limit_low']) &&
$counter_db[$limit_by] >= $counter_db['counter_limit_low'] &&
$counter_poll[$limit_by] < $counter_db['counter_limit_low'])
{
// If old value greater than low limit and new value less than low limit
$msg = ucfirst($class) . " Alarm: " . $device['hostname'] . " " . $counter_db['counter_descr'] . " is under threshold: " . $counter_poll[$limit_by] . "$limit_unit (< " . $counter_db['counter_limit_low'] . "$limit_unit)";
log_event(ucfirst($class) . ' ' . $counter_db['counter_descr'] . " under threshold: " . $counter_poll[$limit_by] . " $limit_unit (< " . $counter_db['counter_limit_low'] . " $limit_unit)", $device, 'counter', $counter_db['counter_id'], 'warning');
}
elseif (is_numeric($counter_db['counter_limit']) &&
$counter_db[$limit_by] <= $counter_db['counter_limit'] &&
$counter_poll[$limit_by] > $counter_db['counter_limit'])
{
// If old value less than high limit and new value greater than high limit
$msg = ucfirst($class) . " Alarm: " . $device['hostname'] . " " . $counter_db['counter_descr'] . " is over threshold: " . $counter_poll[$limit_by] . "$limit_unit (> " . $counter_db['counter_limit'] . "$limit_unit)";
log_event(ucfirst($class) . ' ' . $counter_db['counter_descr'] . " above threshold: " . $counter_poll[$limit_by] . " $limit_unit (> " . $counter_db['counter_limit'] . " $limit_unit)", $device, 'counter', $counter_db['counter_id'], 'warning');
}
} else {
// If counter not changed, leave old last_change
$counter_poll['counter_last_change'] = $counter_db['counter_last_change'];
}
// Send statistics array via AMQP/JSON if AMQP is enabled globally and for the ports module
if ($config['amqp']['enable'] == TRUE && $config['amqp']['modules']['counters'])
{
$json_data = array('value' => $counter_poll['counter_value']);
messagebus_send(array('attribs' => array('t' => $counter_polled_time,
'device' => $device['hostname'],
'device_id' => $device['device_id'],
'e_type' => 'counter',
'e_class' => $counter_db['counter_class'],
'e_type' => $type,
'e_index' => $counter_db['counter_index']),
'data' => $json_data));
}
// Add table row
$type = $counter_db['counter_mib'] . '::' . $counter_db['counter_object'] . '.' . $counter_db['counter_index'];
$format = (string)$config['counter_types'][$counter_db['counter_class']]['format'];
$table_rows[] = array($counter_db['counter_descr'],
$counter_db['counter_class'],
$type,
$counter_poll['counter_value'] . $unit,
format_value($counter_poll['counter_rate'], $format) . '/s | ' . format_value($counter_poll['counter_rate_min'], $format) . '/min | ' .
format_value($counter_poll['counter_rate_5min'], $format) . '/5min | ' . format_value($counter_poll['counter_rate_hour'], $format) . '/h',
$counter_poll['counter_event'],
format_unixtime($counter_poll['counter_last_change']),
$counter_db['poller_type']);
// Update StatsD/Carbon
if ($config['statsd']['enable'] == TRUE)
{
StatsD::gauge(str_replace(".", "_", $device['hostname']) . '.' . 'counter' . '.' . $counter_db['counter_class'] . '.' . $type . '.' . $counter_db['counter_index'], $counter_poll['counter_value']);
}
// Update RRD (Counter store both rate(counter) and value(sensor)
//$rrd_file = get_counter_rrd($device, $counter_db);
//rrdtool_create($device, $rrd_file, "DS:counter:GAUGE:600:-20000:U");
//rrdtool_update($device, $rrd_file, "N:" . $counter_poll['counter_value']);
$ds = [
'sensor' => $counter_poll['counter_value'],
// RRD COUNTER must be integer
'counter' => (is_numeric($counter_poll['counter_value']) ? round($counter_poll['counter_value'], 0) : 0)
];
rrdtool_update_ng($device, 'counter', $ds, $counter_db['counter_id']);
// Enable graph
$graphs[$counter_db['counter_class']] = TRUE;
// Check alerts
$metrics = array();
$metrics['counter_value'] = $counter_poll['counter_value'];
$metrics['counter_rate'] = $counter_poll['counter_rate'];
$metrics['counter_rate_min'] = $counter_poll['counter_rate_min'];
$metrics['counter_rate_5min'] = $counter_poll['counter_rate_5min'];
$metrics['counter_rate_hour'] = $counter_poll['counter_rate_hour'];
$metrics['counter_event'] = $counter_poll['counter_event'];
$metrics['counter_event_uptime'] = $counter_polled_time - $counter_poll['counter_last_change'];
$metrics['counter_status'] = $counter_poll['counter_status'];
check_entity('counter', $counter_db, $metrics);
// Add to MultiUpdate SQL State
$GLOBALS['multi_update_db'][] = array(
'counter_id' => $counter_db['counter_id'], // UNIQUE index
//'device_id' => $counter_db['device_id'], // Required
'counter_value' => $counter_poll['counter_value'],
'counter_rate' => $counter_poll['counter_rate'],
'counter_rate_5min' => $counter_poll['counter_rate_5min'],
'counter_rate_hour' => $counter_poll['counter_rate_hour'],
'counter_history' => $counter_poll['counter_history'],
'counter_event' => $counter_poll['counter_event'],
'counter_status' => $counter_poll['counter_status'],
'counter_last_change' => $counter_poll['counter_last_change'],
'counter_polled' => $counter_polled_time);
}
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function check_valid_counter($device, $poller_type = 'snmp') {
$valid = &$GLOBALS['valid']['counter'];
$entries = dbFetchRows("SELECT * FROM `counters` WHERE `device_id` = ? AND `poller_type` = ? AND `counter_deleted` = '0'", array($device['device_id'], $poller_type));
foreach ($entries as $entry) {
$index = $entry['counter_index'];
$class = $entry['counter_class'];
$object = $entry['counter_object'];
$mib = strlen($entry['counter_mib']) ? $entry['counter_mib'] : '__';
if (!$valid[$class][$mib][$object][$index] ||
$valid[$class][$mib][$object][$index] > 1) {// Duplicate entry
echo("-");
print_debug("Status deleted: $mib::$object.$index");
//dbDelete('counter', "`counter_id` = ?", array($entry['counter_id']));
dbUpdate(array('counter_deleted' => '1'), 'counters', '`counter_id` = ?', array($entry['counter_id']));
foreach (get_entity_attribs('counter', $entry['counter_id']) as $attrib_type => $value) {
del_entity_attrib('counter', $entry['counter_id'], $attrib_type);
}
log_event("Counter deleted: ".$entry['counter_class']." ". $entry['counter_index']." ".$entry['counter_descr'], $device, 'counter', $entry['counter_id']);
} else {
// Increase counter as hint for find duplicates
$valid[$class][$mib][$object][$index]++;
}
}
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_counter_rrd($device, $counter)
{
global $config;
# For IPMI/agent, counters tend to change order, and there is no index, so we prefer to use the description as key here.
if ((isset($config['os'][$device['os']]['sensor_descr']) && $config['os'][$device['os']]['sensor_descr']) || // per os definition
(isset($config['mibs'][$counter['counter_mib']]['sensor_descr']) && $config['mibs'][$counter['counter_mib']]['sensor_descr']) || // per mib definition
($counter['poller_type'] != "snmp" && $counter['poller_type'] != ''))
{
$index = $counter['counter_descr'];
} else {
$index = $counter['counter_index'];
}
if ($counter['poller_type'] != "snmp" && $counter['poller_type'] != '')
{
$rrd_file = "counter-" . $counter['counter_class'] . "-" . $counter['poller_type'] . "-" . $index . ".rrd";
}
elseif ($counter['counter_mib'] == '' || $counter['counter_object'] == '')
{
// Seems as numeric Oid polling (not known MIB & Oid)
// counter_oid is full numeric oid with index
$rrd_file = "counter-" . $counter['counter_class'] . "-" . $counter['counter_oid'] . ".rrd";
} else {
$rrd_file = "counter-" . $counter['counter_class'] . "-" . $counter['counter_mib'] . "-" . $counter['counter_object'] . "-" . $index . ".rrd";
}
return($rrd_file);
}
// EOF

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,987 @@
<?php
/**
* Observium
*
* This file is part of Observium.
*
* @package observium
* @subpackage entities
* @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2020 Observium Limited
*
*/
function discover_ip_address_definition($device, $mib, $entry)
{
echo(' ['); // FIXME
// Check that types listed in skip_if_valid_exist have already been found
//if (discovery_check_if_type_exist($entry, 'ip-address')) { echo '!]'; return; }
// Check array requirements list
//if (discovery_check_requires_pre($device, $entry, 'ip-address')) { echo '!]'; return; }
// Fetch table or Oids
$table_oids = array('oid_address', 'oid_ifIndex', 'oid_prefix', 'oid_mask', 'oid_gateway', // 'oid_version',
'oid_origin', 'oid_type', 'oid_vrf', 'oid_mac', 'oid_extra');
$ip_array = discover_fetch_oids($device, $mib, $entry, $table_oids);
// Just append mib name to definition entry, for simple pass to external functions
if (empty($entry['mib'])) {
$entry['mib'] = $mib;
}
// Index delimiter, for split index parts
// I.e. in HUAWEI-IF-EXT-MIB, CISCOSB-IPv6
$index_delimiter = is_flag_set(OBS_SNMP_INDEX_PARTS, $entry['snmp_flags']) ? '->' : '.';
// not indexed part, see CPI-UNIFIED-MIB
if (is_flag_set(OBS_SNMP_NOINDEX, $entry['snmp_flags']) &&
isset($ip_array['']) && count($ip_array) > 1) {
$ip_array_extra = $ip_array[''];
unset($ip_array['']);
}
//$ips_count = count($ip_array);
foreach ($ip_array as $index => $ip_address) {
// add index and index parts for tags replace
$ip_address['index'] = $index;
foreach (explode($index_delimiter, $index) as $i => $part) {
$ip_address['index' . $i] = $part;
}
// append extra (not indexed) array
if (isset($ip_array_extra)) {
$ip_address = array_merge($ip_address, $ip_array_extra);
}
print_debug_vars($ip_address);
// ifIndex
$ifIndex = set_value_param_definition('ifIndex', $entry, $ip_address);
if (str_contains($ifIndex, '%')) {
// I.e. in CISCOSB-IPv6, for addresses with ifIndex parts:
// ipv6z->ff:02:00:00:00:00:00:00:00:00:00:01:ff:a3:3f:49%100000
list(, $ifIndex) = explode('%', $ifIndex);
}
// Rule-based entity linking
if (!is_numeric($ifIndex) &&
$measured = entity_measured_match_definition($device, $entry, $ip_address, 'ip-address')) {
$ifIndex = $measured['ifIndex'];
//print_debug_vars($measured);
}
// Link by MAC
if (!is_numeric($ifIndex) || $ifIndex == 0) {
$mac = set_value_param_definition('mac', $entry, $ip_address);
if (strlen($mac) && $port_id = get_port_id_by_mac($device, $mac)) {
$port = get_port_by_id_cache($port_id);
$ifIndex = $port['ifIndex'];
}
}
if (!is_numeric($ifIndex)) {
print_debug("Excluded by unknown ifIndex [$ifIndex]");
continue;
}
// Set base ip array
$data = [ 'ifIndex' => $ifIndex ];
// IP address
$ip = set_value_param_definition('address', $entry, $ip_address);
if (str_contains($ip, '%')) {
// I.e. in CISCOSB-IPv6, for addresses with ifIndex parts:
// ipv6z->ff:02:00:00:00:00:00:00:00:00:00:01:ff:a3:3f:49%100000
list($ip) = explode('%', $ip);
}
// IP address with prefix
if (str_contains($ip, '/')) {
// I.e. VIPTELA-OPER-VPN address+prefix: 10.123.10.69/32
list($ip, $prefix) = explode('/', $ip);
if (is_numeric($prefix)) {
$data['prefix'] = $prefix;
} else {
$data['mask'] = $prefix;
}
}
$ip = hex2ip($ip);
$data['ip'] = $ip;
// Other ip params: origin, type, prefix, mask
foreach ([ 'origin', 'type', 'prefix', 'mask', 'gateway', 'vrf' ] as $param) {
if (!isset($data[$param]) && $value = set_value_param_definition($param, $entry, $ip_address)) {
if ($param === 'prefix' && !is_numeric($value)) {
// Always explode prefix part from oid value, ie:
// cIpAddressPrefix.ipv6."20:01:04:70:00:15:00:bb:00:00:00:00:00:00:00:02" = cIpAddressPfxOrigin.450.ipv6."20:01:04:70:00:15:00:bb:00:00:00:00:00:00:00:00".64
$tmp_prefix = explode('.', $value);
$value = end($tmp_prefix);
}
$data[$param] = $value;
}
}
// Check array requirements list
if (discovery_check_requires($device, $entry, array_merge($ip_address, $data), 'ip-address')) { continue; }
discover_add_ip_address($device, $mib, $data);
}
echo '] ';
}
function discover_add_ip_address($device, $mib, $entry) {
global $ip_data;
$ip = $entry['ip'];
if (isset($entry['prefix']) && ($entry['prefix'] === 'zeroDotZero' || safe_empty($entry['prefix']))) {
unset($entry['prefix']);
}
// IP version
$ip_version = get_ip_version($ip);
$ip_version = 'ipv' . $ip_version;
// ifIndex
if ($entry['ifIndex'] == 0 && !isset($ip_data[$ip_version][0])) {
// When used system table/oids without known ifIndex,
// try to find correct ifIndex and update entry
// ie: SWSYSTEM-MIB, ENVIROMUX16D
foreach ($ip_data[$ip_version] as $ind => $tmp) {
if (isset($tmp[$ip])) {
print_debug("Found ifIndex $ind for IP $ip");
$entry['ifIndex'] = $ind;
break;
}
}
}
$ifIndex = $entry['ifIndex'];
switch ($ip_version) {
case 'ipv4':
// IPv4
$prefix = get_ip_prefix($entry);
if (!is_ipv4_valid($ip, $prefix)) {
print_debug("Address '$ip/$prefix' skipped as invalid.");
return;
}
$entry['prefix'] = $prefix;
break;
case 'ipv6':
// IPv6
$prefix = get_ip_prefix($entry);
if (!is_ipv6_valid($ip, $prefix)) {
print_debug("Address '$ip/$prefix' skipped as invalid.");
return;
}
$entry['prefix'] = $prefix;
break;
default:
print_debug("Excluded by unknown IP address [$ip]");
return;
}
// Always re-detect unicast type, while device can report it incorrectly
if (empty($entry['type']) || $entry['type'] === 'unicast') {
$entry['type'] = get_ip_type("$ip/$prefix");
}
if (!isset($ip_data[$ip_version][$ifIndex][$ip])) {
$ip_data[$ip_version][$ifIndex][$ip] = $entry;
print_debug("Added $ip");
print_debug_vars($entry);
echo '.';
} else {
// Compare both and merge best
$old_entry = $ip_data[$ip_version][$ifIndex][$ip];
if (isset($old_entry['prefix']) && $old_entry['prefix'] === 'zeroDotZero') {
unset($old_entry['prefix']);
}
$check_prefix = $old_entry['prefix'] < 3 ||
($ip_version === 'ipv4' && $old_entry['prefix'] == '32') ||
($ip_version === 'ipv6' && $old_entry['prefix'] == '128');
$updated = [];
foreach (array_keys($entry) as $param) {
//print_debug_vars($old_entry);
//print_debug_vars($entry);
if (!isset($old_entry[$param]) || safe_empty($old_entry[$param])) {
$ip_data[$ip_version][$ifIndex][$ip][$param] = $entry[$param];
$updated[] = $param;
} elseif (($param === 'type' && $old_entry[$param] === 'anycast' && strlen($entry[$param])) ||
($param === 'prefix' && $check_prefix && $entry[$param] > 0)) {
$ip_data[$ip_version][$ifIndex][$ip][$param] = $entry[$param];
$updated[] = $param;
}
}
if (count($updated)) {
print_debug("Already exist $ip, updated params [".implode(', ', $updated)."]");
} else {
print_debug("Already exist $ip, skip");
}
//print_debug_vars($ip_data[$ip_version][$ifIndex][$ip]);
//print_debug_vars($entry);
}
}
/**
* Convert IPv4 netmask to CIDR
*
* @param string $netmask
*
* @return string|null
*/
function netmask2cidr($netmask) {
$addr = Net_IPv4::parseAddress("1.2.3.4/$netmask");
return is_intnum($addr->bitmask) ? $addr->bitmask : NULL;
}
/**
* Convert CIDR to IPv4 netmask
* @param $cidr
*
* @return string|null
*/
function cidr2netmask($cidr) {
if (!is_intnum($cidr) || $cidr < 0 || $cidr > 32) {
return NULL;
}
return long2ip(ip2long("255.255.255.255") << (32 - $cidr));
}
function get_ip_prefix($entry) {
if (!is_array($entry)) {
// Convert ip/prefix string to common array
$address = $entry;
$entry = [];
list($entry['ip'], $prefix) = explode('/', $address);
if (!safe_empty($prefix)) {
if (is_numeric($prefix)) {
$entry['prefix'] = $prefix;
} else {
$entry['mask'] = $prefix;
}
}
}
print_debug_vars($entry);
switch (get_ip_version($entry['ip'])) {
case 4:
if (!safe_empty($entry['prefix']) && $entry['prefix'] !== 'zeroDotZero') {
$prefix = netmask2cidr($entry['prefix']);
} else { //if (!safe_empty($entry['mask']) && $entry['mask'] !== 'zeroDotZero') {
$prefix = netmask2cidr($entry['mask']);
}
if (!is_intnum($prefix)) {
if (isset($entry['gateway']) && get_ip_version($entry['gateway']) === 4) {
// Derp way for detect prefix by gateway (limit by /24 - /32)
$prefix = 24;
while ($prefix < 32) {
$net = Net_IPv4::parseAddress($entry['ip'].'/'.$prefix);
if (Net_IPv4::ipInNetwork($entry['gateway'], $net->network.'/'.$prefix)) {
// Gateway IP in network, stop loop
print_debug("Prefix '$prefix' detected by IP '${entry['ip']}' and Gateway '${entry['gateway']}'.");
break;
}
$prefix++;
}
// Still not found prefix, try increase now /23 - /8
if ($prefix === 32 && $entry['ip'] !== $entry['gateway']) {
$tmp_prefix = 23;
while ($tmp_prefix >= 8) {
$net = Net_IPv4::parseAddress($entry['ip'].'/'.$tmp_prefix);
if (Net_IPv4::ipInNetwork($entry['gateway'], $net->network.'/'.$tmp_prefix)) {
// Gateway IP in network, stop loop
$prefix = $tmp_prefix;
print_debug("Prefix '$prefix' detected by IP '${entry['ip']}' and Gateway '${entry['gateway']}'.");
break;
}
$tmp_prefix--;
}
}
} else {
$prefix = 32;
}
}
return (int) $prefix;
case 6:
if (!safe_empty($entry['prefix']) && $entry['prefix'] !== 'zeroDotZero') {
$prefix = $entry['prefix'];
}
if (!is_intnum($prefix) || $prefix < 0 || $prefix > 128) {
$prefix = 128;
}
return (int) $prefix;
}
// Incorrect IP
print_debug("Incorrect: ${entry['ip']}");
return NULL;
}
/**
* Returns IP version for string or FALSE if string not an IP
*
* Examples:
* get_ip_version('127.0.0.1') === 4
* get_ip_version('::1') === 6
* get_ip_version('my_hostname') === FALSE
*
* @param string $address IP address string
* @return mixed IP version or FALSE if passed incorrect address
*/
function get_ip_version($address) {
if (str_contains($address, '/')) {
// Dump condition,
// IPs with CIDR not correct for us here
return FALSE;
}
$address_version = FALSE;
if (preg_match('%^'.OBS_PATTERN_IPV4.'$%', $address)) {
$address_version = 4;
} elseif (preg_match('%^'.OBS_PATTERN_IPV6.'$%i', $address)) {
$address_version = 6;
}
return $address_version;
}
/**
* Function for compress IPv6 address and keep as is IPv4 (already compressed)
*
* @param string|array $address IPv4 or IPv6 address
* @param bool $force If true, an already compressed IP address will be compressed again
*
* @return string|array Compressed address string
*/
function ip_compress($address, $force = TRUE) {
// Recursive compress for arrays
if (is_array($address)) {
$array = [];
foreach ($address as $entry) {
$array[] = ip_compress($entry, $force);
}
return $array;
}
list($ip, $net) = explode('/', $address);
if (get_ip_version($ip) === 6) {
$address = Net_IPv6::compress($ip, $force);
if (is_numeric($net) && ($net >= 0) && ($net <= 128)) {
$address .= '/'.$net;
}
}
return $address;
}
/**
* Function for uncompress IPv6 address and keep as is IPv4
*
* @param string|array $address IPv4 or IPv6 address
* @param bool $leading_zeros If true, the uncompressed address has a fixed length
*
* @return string|array Uncompressed address string
*/
function ip_uncompress($address, $leading_zeros = TRUE) {
// Recursive uncompress for arrays
if (is_array($address)) {
$array = [];
foreach ($address as $entry) {
$array[] = ip_uncompress($entry, $leading_zeros);
}
return $array;
}
list($ip, $net) = explode('/', $address);
if (get_ip_version($ip) === 6) {
$address = Net_IPv6::uncompress($ip, $leading_zeros);
if (is_numeric($net) && ($net >= 0) && ($net <= 128)) {
$address .= '/'.$net;
}
}
return $address;
}
/**
* Return safe key for use in arrays by ip address (replaced with __ if IP is empty or 0.0.0.0/::0)
* Additionally, passed hostname and/or IPv6 address compressed.
* @param string $hostname Hostname (or IP address)
* @param string|null $ip IPv4/IPv6 addresss
*
* @return string Return compressed IP address or __ if IP is empty
*/
function safe_ip_hostname_key(&$hostname, &$ip = NULL)
{
$hostname = strtolower($hostname);
// Hostname is IP address
if ($hostname_ip_version = get_ip_version($hostname))
{
$hostname = ip_compress($hostname);
if (empty($ip))
{
// If hostname is IP, set remote_ip same
$ip = $hostname;
}
}
$ip_type = get_ip_type($ip);
if ($ip_type && $ip_type != 'unspecified')
{
$ip = ip_compress($ip);
$ip_key = $ip;
} else {
$ip_key = '__';
}
return $ip_key;
}
/**
* Check if a given IPv4 address (and prefix length) is valid.
*
* @param string $ipv4_address IPv4 Address
* @param string $ipv4_prefixlen IPv4 Prefix length (optional, either 24 or 255.255.255.0)
*
* @return bool Returns TRUE if address is valid, FALSE if not valid.
*/
// TESTME needs unit testing
function is_ipv4_valid($ipv4_address, $ipv4_prefixlen = NULL) {
if (str_contains($ipv4_address, '/')) {
list($ipv4_address, $ipv4_prefixlen) = explode('/', $ipv4_address);
}
$ip_full = $ipv4_address . '/' . $ipv4_prefixlen;
// False if invalid IPv4 syntax
if (strlen((string)$ipv4_prefixlen) &&
!preg_match('%^'.OBS_PATTERN_IPV4_NET.'$%', $ip_full)) {
// Address with prefix
return FALSE;
}
if (!preg_match('%^'.OBS_PATTERN_IPV4.'$%i', $ipv4_address)) {
// Address without prefix
return FALSE;
}
$ipv4_type = get_ip_type($ip_full);
// False if unspecified
$ignore_type = [ 'unspecified' ];
// It this address types (broadcast) removed from config by user, allow to discover it too
// Do not ignore link-local for IPv4, this is not same as for IPv6
// if (in_array('link-local', $GLOBALS['config']['ip-address']['ignore_type']))
// {
// $ignore_type[] = 'link-local';
// }
if (in_array('broadcast', $GLOBALS['config']['ip-address']['ignore_type'])) {
$ignore_type[] = 'broadcast';
}
if (in_array($ipv4_type, $ignore_type)) {
print_debug("Address $ip_full marked as invalid by type [$ipv4_type].");
return FALSE;
}
return TRUE;
}
/**
* Check if a given IPv6 address (and prefix length) is valid.
* Link-local addresses are considered invalid.
*
* @param string $ipv6_address IPv6 Address
* @param string $ipv6_prefixlen IPv6 Prefix length (optional)
*
* @return bool Returns TRUE if address is valid, FALSE if not valid.
*/
// TESTME needs unit testing
function is_ipv6_valid($ipv6_address, $ipv6_prefixlen = NULL) {
if (str_contains($ipv6_address, '/')) {
list($ipv6_address, $ipv6_prefixlen) = explode('/', $ipv6_address);
}
$ip_full = $ipv6_address . '/' . $ipv6_prefixlen;
// False if invalid IPv6 syntax
if (strlen((string)$ipv6_prefixlen) &&
!preg_match('%^'.OBS_PATTERN_IPV6_NET.'$%i', $ip_full)) {
// Address with prefix
return FALSE;
}
if (!preg_match('%^'.OBS_PATTERN_IPV6.'$%i', $ipv6_address)) {
// Address without prefix
return FALSE;
}
$ipv6_type = get_ip_type($ip_full);
// False if link-local, unspecified
$ignore_type = [ 'unspecified' ];
// It this address types (link-local, broadcast) removed from config by user, allow to discover it too
if (in_array('link-local', $GLOBALS['config']['ip-address']['ignore_type'])) {
$ignore_type[] = 'link-local';
}
if (in_array('broadcast', $GLOBALS['config']['ip-address']['ignore_type'])) {
$ignore_type[] = 'broadcast';
}
if (in_array($ipv6_type, $ignore_type)) {
print_debug("Address $ip_full marked as invalid by type [$ipv6_type].");
return FALSE;
}
return TRUE;
}
/**
* Detect IP type.
* Based on https://www.ripe.net/manage-ips-and-asns/ipv6/ipv6-address-types/ipv6-address-types
*
* Known types:
* - unspecified : ::/128, 0.0.0.0
* - loopback : ::1/128, 127.0.0.1
* - ipv4mapped : only for IPv6 ::ffff/96 (::ffff:192.0.2.47)
* - private : fc00::/7 (fdf8:f53b:82e4::53),
* 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
* - link-local : fe80::/10 (fe80::200:5aee:feaa:20a2),
* 169.254.0.0/16
* - teredo : only for IPv6 2001:0000::/32 (2001:0000:4136:e378:8000:63bf:3fff:fdd2)
* - benchmark : 2001:0002::/48 (2001:0002:6c::430),
* 198.18.0.0/15
* - orchid : only for IPv6 2001:0010::/28 (2001:10:240:ab::a)
* - 6to4 : 2002::/16 (2002:cb0a:3cdd:1::1),
* 192.88.99.0/24
* - documentation : 2001:db8::/32 (2001:db8:8:4::2),
* 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24
* - global-unicast : only for IPv6 2000::/3
* - multicast : ff00::/8 (ff01:0:0:0:0:0:0:2), 224.0.0.0/4
* - unicast : all other
*
* @param string $address IPv4 or IPv6 address string
* @return string IP type
*/
function get_ip_type($address)
{
global $config;
list($ip, $bits) = explode('/', trim($address)); // Remove subnet/mask if exist
$ip_version = get_ip_version($ip);
switch ($ip_version)
{
case 4:
// Detect IPv4 broadcast
if (strlen($bits))
{
$ip_parse = Net_IPv4::parseAddress($address);
if ($ip == $ip_parse->broadcast && $ip_parse->bitmask < 31) // Do not set /31 and /32 as broadcast!
{
$ip_type = 'broadcast';
break;
}
}
// no break here!
case 6:
$ip_type = ($ip_version == 4) ? 'unicast': 'reserved'; // Default for any valid address
foreach ($config['ip_types'] as $type => $entry)
{
if (isset($entry['networks']) && match_network($ip, $entry['networks'], TRUE))
{
// Stop loop if IP founded in networks
$ip_type = $type;
break;
}
}
break;
default:
// Not valid IP address
return FALSE;
}
return $ip_type;
}
/**
* Parse string for valid IP/Network queries.
*
* Valid queries example:
* - 10.0.0.0/8 - exactly IPv4 network, matches to 10.255.0.2, 10.0.0.0, 10.255.255.255
* - 10.12.0.3/8 - same as previous, NOTE network detected by prefix: 10.0.0.0/8
* - 10.12.0.3 - single IPv4 address
* - *.12.0.3, 10.*.3 - * matching by any string in address
* - ?.12.0.3, 10.?.?.3 - ? matching by single char
* - 10.12 - match by part of address, matches to 10.12.2.3, 10.122.3.3, 1.2.110.120
* - 1762::b03:1:ae00/119 - exactly IPv6 network, matches to 1762::b03:1:ae00, 1762::B03:1:AF18, 1762::b03:1:afff
* - 1762:0:0:0:0:B03:1:AF18/119 - same as previous, NOTE network detected by prefix: 1762::b03:1:ae00/119
* - 1762:0:0:0:0:B03:1:AF18 - single IPv6 address
* - *::B03:1:AF18, 1762::*:AF18 - * matching by any string in address
* - ?::B03:1:AF18, 1762::?:AF18 - ? matching by single char
* - 1762:b03 - match by part of address, matches to 1:AF18:1762::B03, 1762::b03:1:ae00
*
* Return array contain this params:
* 'query_type' - which query type required.
* Possible: single (single IP address),
* network (addresses inside network),
* like, %like% (part of address with masks *, ?)
* 'ip_version' - numeric IP version (4, 6)
* 'ip_type' - ipv4 or ipv6
* 'address' - string with passed IP address without prefixes or address part
* 'prefix' - detected network prefix
* 'network' - detected network with prefix
* 'network_start' - first address of network
* 'network_end' - last address of network (broadcast)
*
* @param string $network Network/IP query string
* @return array Array with parsed network params
*/
function parse_network($network)
{
$network = trim($network);
$array = array(
'query_type' => 'network', // Default query type by valid network with prefix
);
if (preg_match('%^'.OBS_PATTERN_IPV4_NET.'$%', $network, $matches))
{
// Match by valid IPv4 network
$array['ip_version'] = 4;
$array['ip_type'] = 'ipv4';
$array['address'] = $matches['ipv4']; // Same as IP
//$array['prefix'] = $matches['ipv4_prefix'];
// Convert Cisco Inverse netmask to normal mask
if (isset($matches['ipv4_inverse_mask']))
{
$matches['ipv4_mask'] = inet_pton($matches['ipv4_inverse_mask']);
$matches['ipv4_mask'] = inet_ntop(~$matches['ipv4_mask']); // Binary inverse and back to IP string
$matches['ipv4_network'] = $matches['ipv4'] . '/' . $matches['ipv4_mask'];
}
if ($matches['ipv4_prefix'] == '32' || $matches['ipv4_mask'] == '255.255.255.255')
{
$array['prefix'] = '32';
$array['network_start'] = $array['address'];
$array['network_end'] = $array['address'];
$array['network'] = $matches['ipv4_network']; // Network with prefix
$array['query_type'] = 'single'; // Single IP query
} else {
$address = Net_IPv4::parseAddress($matches['ipv4_network']);
//print_vars($address);
$array['prefix'] = $address->bitmask . '';
$array['network_start'] = $address->network;
$array['network_end'] = $address->broadcast;
$array['network'] = $array['network_start'] . '/' . $array['prefix']; // Network with prefix
}
}
elseif (preg_match('%^'.OBS_PATTERN_IPV6_NET.'$%i', $network, $matches))
{
// Match by valid IPv6 network
$array['ip_version'] = 6;
$array['ip_type'] = 'ipv6';
$array['address'] = $matches['ipv6']; // Same as IP
$array['prefix'] = $matches['ipv6_prefix'];
if ($array['prefix'] == 128)
{
$array['network_start'] = $array['address'];
$array['network_end'] = $array['address'];
$array['network'] = $matches['ipv6_network']; // Network with prefix
$array['query_type'] = 'single'; // Single IP query
} else {
$address = Net_IPv6::parseAddress($array['address'], $array['prefix']);
//print_vars($address);
$array['network_start'] = $address['start'];
$array['network_end'] = $address['end'];
$array['network'] = $array['network_start'] . '/' . $array['prefix']; // Network with prefix
}
}
elseif ($ip_version = get_ip_version($network))
{
// Single IPv4/IPv6
if ($ip_version === 6 && preg_match('/::ffff:(\d+\.\d+\.\d+\.\d+)/', $network, $matches))
{
// IPv4 mapped to IPv6, like ::ffff:192.0.2.128
// See: http://jira.observium.org/browse/OBSERVIUM-1274
$network = $matches[1];
$ip_version = 4;
}
$array['ip_version'] = $ip_version;
$array['address'] = $network;
//$array['prefix'] = $matches['ipv6_prefix'];
if ($ip_version === 4)
{
$array['ip_type'] = 'ipv4';
$array['prefix'] = '32';
} else {
$array['ip_type'] = 'ipv6';
$array['prefix'] = '128';
}
$array['network_start'] = $array['address'];
$array['network_end'] = $array['address'];
$array['network'] = $network . '/' . $array['prefix']; // Add prefix
$array['query_type'] = 'single'; // Single IP query
}
elseif (preg_match('/^[\d\.\?\*]+$/', $network))
{
// Match IPv4 by mask
$array['ip_version'] = 4;
$array['ip_type'] = 'ipv4';
$array['address'] = $network;
if (str_contains_array($network, [ '?', '*' ])) {
// If network contains * or !
$array['query_type'] = 'like';
} else {
// All other cases
$array['query_type'] = '%like%';
}
}
elseif (preg_match('/^[abcdef\d\:\?\*]+$/i', $network))
{
// Match IPv6 by mask
$array['ip_version'] = 6;
$array['ip_type'] = 'ipv6';
$array['address'] = $network;
if (str_contains_array($network, [ '?', '*' ])) {
// If network contains * or !
$array['query_type'] = 'like';
} else {
// All other cases
$array['query_type'] = '%like%';
}
} else {
// Not valid network string passed
return FALSE;
}
// Add binary addresses for single and network queries
switch ($array['query_type'])
{
case 'single':
$array['address_binary'] = inet_pton($array['address']);
break;
case 'network':
$array['network_start_binary'] = inet_pton($array['network_start']);
$array['network_end_binary'] = inet_pton($array['network_end']);
break;
}
return $array;
}
/**
* Determines whether or not the supplied IP address is within the supplied network (IPv4 or IPv6).
*
* @param string $ip IP Address
* @param string $nets IPv4/v6 networks
* @param bool $first FIXME
*
* @return bool Returns TRUE if address is found in supplied network, FALSE if it is not.
*/
// TESTME needs unit testing
function match_network($ip, $nets, $first = FALSE)
{
$return = FALSE;
$ip_version = get_ip_version($ip);
if ($ip_version)
{
foreach ((array)$nets as $net)
{
$revert = (bool) preg_match('/^\!/', $net); // NOT match network
if ($revert)
{
$net = preg_replace('/^\!/', '', $net);
}
if ($ip_version === 4)
{
if (strpos($net, '.') === FALSE) { continue; } // NOT IPv4 net, skip
if (strpos($net, '/') === FALSE) { $net .= '/32'; } // NET without mask as single IP
$ip_in_net = Net_IPv4::ipInNetwork($ip, $net);
} else {
//print_vars($ip); echo(' '); print_vars($net); echo(PHP_EOL);
if (strpos($net, ':') === FALSE) { continue; } // NOT IPv6 net, skip
if (strpos($net, '/') === FALSE) { $net .= '/128'; } // NET without mask as single IP
$ip_in_net = Net_IPv6::isInNetmask($ip, $net);
}
if ($revert && $ip_in_net) { return FALSE; } // Return FALSE if IP found in network where should NOT match
if ($first && $ip_in_net) { return TRUE; } // Return TRUE if IP found in first match
$return = $return || $ip_in_net;
}
}
return $return;
}
/**
* Convert HEX encoded IP value to pretty IP string
*
* Examples:
* IPv4 "C1 9C 5A 26" => "193.156.90.38"
* IPv4 "J}4:" => "74.125.52.58"
* IPv6 "20 01 07 F8 00 12 00 01 00 00 00 00 00 05 02 72" => "2001:07f8:0012:0001:0000:0000:0005:0272"
* IPv6 "20:01:07:F8:00:12:00:01:00:00:00:00:00:05:02:72" => "2001:07f8:0012:0001:0000:0000:0005:0272"
*
* @param string $ip_hex HEX encoded IP address
*
* @return string IP address or original input string if not contains IP address
*/
function hex2ip($ip_hex) {
//$ip = trim($ip_hex, "\"\t\n\r\0\x0B"); // Strange case, cleaned incorrectly
$ip = trim($ip_hex, "\"\t\n\r\0");
// IPv6z, ie: 2a:02:a0:10:80:03:00:00:00:00:00:00:00:00:00:01%503316482
if (str_contains_array($ip, '%')) {
list($ip) = explode('%', $ip);
}
$len = strlen($ip);
if ($len === 5 && $ip[0] === ' ') {
$ip = substr($ip, 1);
$len = 4;
}
if ($len === 4) {
// IPv4 hex string converted to SNMP string
$ip = str2hex($ip);
$len = strlen($ip);
}
$ip = str_replace(' ', '', $ip);
if ($len > 8) {
// For IPv6
$ip = str_replace(':', '', $ip);
$len = strlen($ip);
}
if (!ctype_xdigit($ip)) {
return $ip_hex;
}
if ($len === 6) {
// '90 7F 8A ', should be '90 7F 8A 00 ' ?
$ip .= '00';
$len = 8;
}
//print_cli("IP: '$ip', LEN: $len\n");
switch ($len) {
case 8:
// IPv4
$ip_array = array();
foreach (str_split($ip, 2) as $entry) {
$ip_array[] = hexdec($entry);
}
$separator = '.';
break;
case 16:
// Cisco incorrect IPv4 (54 2E 68 02 FF FF FF FF)
$ip_array = array();
foreach (str_split($ip, 2) as $i => $entry)
{
if ($i == 4) { break; }
$ip_array[] = hexdec($entry);
}
$separator = '.';
break;
case 32:
// IPv6
$ip_array = str_split(strtolower($ip), 4);
$separator = ':';
break;
default:
// Try convert hex string to string
$ip = snmp_hexstring($ip_hex);
if (get_ip_version($ip)) {
return $ip;
}
return $ip_hex;
}
$ip = implode($separator, $ip_array);
return $ip;
}
/**
* Convert IP string to HEX encoded value.
*
* Examples:
* IPv4 "193.156.90.38" => "C1 9C 5A 26"
* IPv6 "2001:07f8:0012:0001:0000:0000:0005:0272" => "20 01 07 f8 00 12 00 01 00 00 00 00 00 05 02 72"
* IPv6 "2001:7f8:12:1::5:0272" => "20 01 07 f8 00 12 00 01 00 00 00 00 00 05 02 72"
*
* @param string $ip IP address string
* @param string $separator Separator for HEX parts
*
* @return string HEX encoded address
*/
function ip2hex($ip, $separator = ' ')
{
$ip_hex = trim($ip, " \"\t\n\r\0\x0B");
$ip_version = get_ip_version($ip_hex);
if ($ip_version === 4)
{
// IPv4
$ip_array = array();
foreach (explode('.', $ip_hex) as $entry)
{
$ip_array[] = zeropad(dechex($entry));
}
}
elseif ($ip_version === 6)
{
// IPv6
$ip_hex = str_replace(':', '', Net_IPv6::uncompress($ip_hex, TRUE));
$ip_array = str_split($ip_hex, 2);
} else {
return $ip;
}
$ip_hex = implode($separator, $ip_array);
return $ip_hex;
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function snmp2ipv6($ipv6_snmp)
{
$ipv6 = explode('.',$ipv6_snmp);
// Workaround stupid Microsoft bug in Windows 2008 -- this is fixed length!
// < fenestro> "because whoever implemented this mib for Microsoft was ignorant of RFC 2578 section 7.7 (2)"
if (count($ipv6) == 17 && $ipv6[0] == 16)
{
array_shift($ipv6);
}
for ($i = 0;$i <= 15;$i++) { $ipv6[$i] = zeropad(dechex($ipv6[$i])); }
for ($i = 0;$i <= 15;$i+=2) { $ipv6_2[] = $ipv6[$i] . $ipv6[$i+1]; }
return implode(':',$ipv6_2);
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function ipv62snmp($ipv6)
{
$ipv6_ex = explode(':',Net_IPv6::uncompress($ipv6));
for ($i = 0;$i < 8;$i++) { $ipv6_ex[$i] = zeropad($ipv6_ex[$i],4); }
$ipv6_ip = implode('',$ipv6_ex);
for ($i = 0;$i < 32;$i+=2) $ipv6_split[] = hexdec(substr($ipv6_ip,$i,2));
return implode('.',$ipv6_split);
}
// EOF

View File

@ -0,0 +1,942 @@
<?php
/**
* Observium
*
* This file is part of Observium.
*
* @package observium
* @subpackage entities
* @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2021 Observium Limited
*
*/
// Get port id by ip address (using cache)
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_id_by_ip_cache($device, $ip)
{
global $cache;
$ip_version = get_ip_version($ip);
if (is_array($device) && isset($device['device_id']))
{
$device_id = $device['device_id'];
}
elseif (is_numeric($device))
{
$device_id = $device;
}
if (!isset($device_id) || !$ip_version)
{
print_error("Invalid arguments passed into function get_port_id_by_ip_cache(). Please report to developers.");
return FALSE;
}
if ($ip_version == 6)
{
$ip = Net_IPv6::uncompress($ip, TRUE);
}
if (isset($cache['port_ip'][$device_id][$ip]))
{
return $cache['port_ip'][$device_id][$ip];
}
$ips = dbFetchRows('SELECT `port_id`, `ifOperStatus`, `ifAdminStatus` FROM `ipv'.$ip_version.'_addresses`
LEFT JOIN `ports` USING(`port_id`)
WHERE `deleted` = 0 AND `device_id` = ? AND `ipv'.$ip_version.'_address` = ?', array($device_id, $ip));
if (count($ips) === 1)
{
// Simple
$port = current($ips);
//return $port['port_id'];
} else {
foreach ($ips as $entry)
{
if ($entry['ifAdminStatus'] === 'up' && $entry['ifOperStatus'] === 'up')
{
// First UP entry
$port = $entry;
break;
}
elseif ($entry['ifAdminStatus'] === 'up')
{
// Admin up, but port down or other state
$ips_up[] = $entry;
} else {
// Admin down
$ips_down[] = $entry;
}
}
if (!isset($port))
{
if ($ips_up)
{
$port = current($ips_up);
} else {
$port = current($ips_down);
}
}
}
$cache['port_ip'][$device_id][$ip] = $port['port_id'] ? $port['port_id'] : FALSE;
return $cache['port_ip'][$device_id][$ip];
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_id_by_mac($device, $mac)
{
if (is_array($device) && isset($device['device_id']))
{
$device_id = $device['device_id'];
}
elseif (is_numeric($device))
{
$device_id = $device;
}
$remote_mac = mac_zeropad($mac);
if ($remote_mac && $remote_mac != '000000000000')
{
return dbFetchCell("SELECT `port_id` FROM `ports` WHERE `deleted` = '0' AND `ifPhysAddress` = ? AND `device_id` = ? LIMIT 1", [ $remote_mac, $device_id ]);
}
return FALSE;
}
function get_port_by_ent_index($device, $entPhysicalIndex, $allow_snmp = FALSE) {
$mib = 'ENTITY-MIB';
if (!is_numeric($entPhysicalIndex) ||
!is_numeric($device['device_id']) ||
!is_device_mib($device, $mib)) {
return FALSE;
}
$allow_snmp = $allow_snmp || is_cli(); // Allow snmpwalk queries in poller/discovery or if in wui passed TRUE!
if (isset($GLOBALS['cache']['snmp'][$mib][$device['device_id']])) {
// Cached
$entity_array = $GLOBALS['cache']['snmp'][$mib][$device['device_id']];
if (safe_empty($entity_array)) {
// Force DB queries
$allow_snmp = FALSE;
}
} elseif ($allow_snmp) {
// Inventory module disabled, this DB empty, try to cache
$entity_array = [];
$oids = array('entPhysicalDescr', 'entPhysicalName', 'entPhysicalClass', 'entPhysicalContainedIn', 'entPhysicalParentRelPos');
if (is_device_mib($device, 'ARISTA-ENTITY-SENSOR-MIB')) {
$oids[] = 'entPhysicalAlias';
}
foreach ($oids as $oid) {
$entity_array = snmpwalk_cache_oid($device, $oid, $entity_array, snmp_mib_entity_vendortype($device, 'ENTITY-MIB'));
if (!snmp_status()) { break; }
}
$entity_array = snmpwalk_cache_twopart_oid($device, 'entAliasMappingIdentifier', $entity_array, 'ENTITY-MIB:IF-MIB');
if (safe_empty($entity_array)) {
// Force DB queries
$allow_snmp = FALSE;
}
$GLOBALS['cache']['snmp'][$mib][$device['device_id']] = $entity_array;
} else {
// Or try to use DB
}
//print_debug_vars($entity_array);
$sensor_index = $entPhysicalIndex; // Initial ifIndex
$sensor_name = '';
do {
if ($allow_snmp) {
// SNMP (discovery)
$sensor_port = $entity_array[$sensor_index];
} else {
// DB (web)
$sensor_port = dbFetchRow('SELECT * FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalIndex` = ? AND `deleted` IS NULL', array($device['device_id'], $sensor_index));
}
print_debug_vars($sensor_index, 1);
print_debug_vars($sensor_port, 1);
if ($sensor_port['entPhysicalClass'] === 'sensor') {
// Need to store initial sensor name, for multi-lane ports
$sensor_name = $sensor_port['entPhysicalName'];
}
if ($sensor_port['entPhysicalClass'] === 'port') {
// Port found, get mapped ifIndex
unset($entAliasMappingIdentifier);
foreach (array(0, 1, 2) as $i) {
if (isset($sensor_port[$i]['entAliasMappingIdentifier'])) {
$entAliasMappingIdentifier = $sensor_port[$i]['entAliasMappingIdentifier'];
break;
}
}
if (isset($entAliasMappingIdentifier) && str_contains($entAliasMappingIdentifier, 'fIndex')) {
list(, $ifIndex) = explode('.', $entAliasMappingIdentifier);
$port = get_port_by_index_cache($device['device_id'], $ifIndex);
if (is_array($port)) {
// Hola, port really found
print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port['port_id']);
return $port;
}
} elseif (!$allow_snmp && $sensor_port['ifIndex']) {
// ifIndex already stored by inventory module
$ifIndex = $sensor_port['ifIndex'];
$port = get_port_by_index_cache($device['device_id'], $ifIndex);
print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port['port_id']);
return $port;
} else {
// This is another case for Cisco IOSXR, when have incorrect entAliasMappingIdentifier association,
// https://jira.observium.org/browse/OBS-3654
$port_id = get_port_id_by_ifDescr($device['device_id'], $sensor_port['entPhysicalName']);
if (is_numeric($port_id)) {
// Hola, port really found
$port = get_port_by_id($port_id);
$ifIndex = $port['ifIndex'];
print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port_id);
return $port;
}
}
break; // Exit do-while
} elseif ($device['os'] === 'arista_eos' &&
$sensor_port['entPhysicalClass'] === 'container' && !safe_empty($sensor_port['entPhysicalAlias'])) {
// Arista not have entAliasMappingIdentifier, but used entPhysicalAlias as ifDescr
$port_id = get_port_id_by_ifDescr($device['device_id'], $sensor_port['entPhysicalAlias']);
if (is_numeric($port_id)) {
// Hola, port really found
$port = get_port_by_id($port_id);
$ifIndex = $port['ifIndex'];
print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port_id);
return $port; // Exit do-while
}
if ($port_id = get_port_id_by_ifDescr($device['device_id'], $sensor_port['entPhysicalAlias'] . '/1')) {
// Multi-lane Tranceivers
$port = get_port_by_id($port_id);
$port['sensor_multilane'] = TRUE;
$ifIndex = $port['ifIndex'];
print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port_id);
return $port; // Exit do-while
}
$sensor_index = $sensor_port['entPhysicalContainedIn']; // Next ifIndex
} elseif ($sensor_index == $sensor_port['entPhysicalContainedIn']) {
break; // Break if current index same as next to avoid loop
} elseif ($sensor_port['entPhysicalClass'] === 'module' &&
(isset($sensor_port[0]['entAliasMappingIdentifier']) ||
isset($sensor_port[1]['entAliasMappingIdentifier']) ||
isset($sensor_port[2]['entAliasMappingIdentifier']))) {
// Cisco IOSXR 6.5.x ASR 9900 platform && NCS 5500
$sensor_index = $sensor_port['entPhysicalContainedIn']; // Next ifIndex
// By first try if entAliasMappingIdentifier correct
unset($entAliasMappingIdentifier);
foreach (array(0, 1, 2) as $i) {
if (isset($sensor_port[$i]['entAliasMappingIdentifier'])) {
$entAliasMappingIdentifier = $sensor_port[$i]['entAliasMappingIdentifier'];
break;
}
}
if (isset($entAliasMappingIdentifier) && str_contains($entAliasMappingIdentifier, 'fIndex')) {
list(, $ifIndex) = explode('.', $entAliasMappingIdentifier);
$port = get_port_by_index_cache($device['device_id'], $ifIndex);
if (is_array($port)) {
// Hola, port really found
print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port['port_id']);
return $port;
}
}
// This case for Cisco IOSXR ASR 9900 platform, when have incorrect entAliasMappingIdentifier association,
// https://jira.observium.org/browse/OBS-3147
$port_id = FALSE;
if (str_contains($sensor_port['entPhysicalName'], '-PORT-')) {
// Second, try detect port by entPhysicalDescr/entPhysicalName
if (str_starts($sensor_port['entPhysicalDescr'], [ '10GBASE', '10GE' ]) ||
str_icontains_array($sensor_port['entPhysicalDescr'], [ ' 10GBASE', ' 10GE', ' 10G ' ])) {
$ifDescr_base = 'TenGigE';
} elseif (str_starts($sensor_port['entPhysicalDescr'], [ '25GBASE', '25GE' ]) ||
str_icontains_array($sensor_port['entPhysicalDescr'], [ ' 25GBASE', ' 25GE', ' 25G ' ])) {
$ifDescr_base = 'TwentyFiveGigE';
} elseif (str_starts($sensor_port['entPhysicalDescr'], [ '40GBASE', '40GE' ]) ||
str_icontains_array($sensor_port['entPhysicalDescr'], [ ' 40GBASE', ' 40GE', ' 40G ' ])) {
$ifDescr_base = 'FortyGigE';
} elseif (str_starts($sensor_port['entPhysicalDescr'], [ '100GBASE', '100GE' ]) ||
str_icontains_array($sensor_port['entPhysicalDescr'], [ ' 100GBASE', ' 100GE', ' 100G ' ])) {
// Ie:
// Cisco CPAK 100GBase-SR4, 100m, MMF
// 100GBASE-ER4 CFP2 Module for SMF (<40 km)
// Non-Cisco QSFP28 100G ER4 Pluggable Optics Module
$ifDescr_base = 'HundredGigE';
}
$ifDescr_num = str_replace('-PORT-', '/', $sensor_port['entPhysicalName']);
$port_id = get_port_id_by_ifDescr($device['device_id'], $ifDescr_base . $ifDescr_num);
if (!is_numeric($port_id)) {
// FIXME, I think first node number '0/' should be detected by some how
$port_id = get_port_id_by_ifDescr($device['device_id'], $ifDescr_base . '0/' . $ifDescr_num);
}
} elseif (str_contains_array($sensor_port['entPhysicalName'], [ 'TenGigE', 'TwentyFiveGigE', 'FortyGigE', 'HundredGigE', 'Ethernet' ])) {
// Same as previous, but entPhysicalName contain correct ifDescr, ie:
// NCS platform: FortyGigE0/0/0/20
// NXOS platform: "Ethernet1/1(volt)
list($ifDescr, ) = explode('(', $sensor_port['entPhysicalName'], 2);
$port_id = get_port_id_by_ifDescr($device['device_id'], $ifDescr);
}
if (is_numeric($port_id)) {
// Hola, port really found
$port = get_port_by_id($port_id);
$ifIndex = $port['ifIndex'];
print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port_id);
return $port;
}
} elseif ((($sensor_port['entPhysicalClass'] === 'sensor' && $sensor_port['entPhysicalContainedIn'] == 0) ||
($sensor_port['entPhysicalClass'] === 'module' && $sensor_port['entPhysicalIsFRU'] === 'true')) &&
str_starts($sensor_port['entPhysicalName'], [ 'TenGigE', 'TwentyFiveGigE', 'FortyGigE', 'HundredGigE', 'Ethernet' ])) {
$sensor_index = $sensor_port['entPhysicalContainedIn']; // Next ifIndex
// entPhysicalName contain correct ifDescr, ie:
// NCS platform: FortyGigE0/0/0/20
// NXOS 6.x platform: "Ethernet1/1(volt)
list($ifDescr, ) = explode('(', $sensor_port['entPhysicalName'], 2);
if ($port_id = get_port_id_by_ifDescr($device['device_id'], $ifDescr)) {
// Hola, port really found
$port = get_port_by_id($port_id);
$ifIndex = $port['ifIndex'];
print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port_id);
return $port;
}
} else {
$sensor_index = $sensor_port['entPhysicalContainedIn']; // Next ifIndex
// See: http://jira.observium.org/browse/OBS-2295
// IOS-XE and IOS-XR can store in module index both: sensors and port
$sensor_transceiver = $sensor_port['entPhysicalClass'] === 'sensor' &&
str_icontains_array($sensor_port['entPhysicalName'] . $sensor_port['entPhysicalDescr'] . $sensor_port['entPhysicalVendorType'], [ 'transceiver', '-PORT-' ]);
// This is multi-lane optical transceiver, ie 100G, 40G, multiple sensors for each port
$sensor_multilane = $sensor_port['entPhysicalClass'] === 'container' &&
(in_array($sensor_port['entPhysicalVendorType'], [ 'cevContainer40GigBasePort', 'cevContainerCXP', 'cevContainerCPAK' ]) || // Known Cisco specific containers
str_contains_array($sensor_port['entPhysicalName'] . $sensor_port['entPhysicalDescr'], [ 'Optical' ])); // Pluggable Optical Module Container
if ($sensor_transceiver) {
$tmp_index = dbFetchCell('SELECT `entPhysicalIndex` FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalContainedIn` = ? AND `entPhysicalClass` = ? AND `deleted` IS NULL', array($device['device_id'], $sensor_index, 'port'));
if (is_numeric($tmp_index) && $tmp_index > 0) {
// If port index found, try this entPhysicalIndex in next round
$sensor_index = $tmp_index;
}
} elseif ($sensor_multilane) {
$entries = dbFetchRows('SELECT `entPhysicalIndex`, `entPhysicalName` FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalContainedIn` = ? AND `entPhysicalClass` = ? AND `deleted` IS NULL', array($device['device_id'], $sensor_index, 'port'));
print_debug("Multi-Lane entries:");
print_debug_vars($entries, 1);
if (count($entries) > 1 &&
preg_match('/(?<start>\D{2})(?<num>\d+(?:\/\d+)+).*Lane\s*(?<lane>\d+)/', $sensor_name, $matches)) { // detect port numeric part and lane
// There is each Line associated with breakout port, mostly is QSFP+ 40G
// FortyGigE0/0/0/0-Tx Lane 1 Power -> 0/RP0-TenGigE0/0/0/0/1
// FortyGigE0/0/0/0-Tx Lane 2 Power -> 0/RP0-TenGigE0/0/0/0/2
$lane_num = $matches['start'] . $matches['num'] . '/' . $matches['lane']; // FortyGigE0/0/0/0-Tx Lane 1 -> gE0/0/0/0/1
foreach ($entries as $entry) {
if (str_ends($entry['entPhysicalName'], $lane_num)) {
$sensor_index = $entry['entPhysicalIndex'];
break;
}
}
} elseif (is_numeric($entries[0]['entPhysicalIndex']) && $entries[0]['entPhysicalIndex'] > 0) {
// Single multi-lane port association, ie 100G
$sensor_index = $entries[0]['entPhysicalIndex'];
}
}
}
// NOTE for self: entPhysicalParentRelPos >= 0 because on iosxr trouble
} while ($sensor_port['entPhysicalClass'] !== 'port' && $sensor_port['entPhysicalContainedIn'] > 0 &&
($sensor_port['entPhysicalParentRelPos'] >= 0 || $device['os'] === 'arista_eos'));
return NULL;
}
// Get port array by ID (using cache)
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_by_id_cache($port_id) {
return get_entity_by_id_cache('port', $port_id);
}
// Get port array by ID (with port state)
// NOTE get_port_by_id(ID) != get_port_by_id_cache(ID)
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_by_id($port_id)
{
if (is_numeric($port_id))
{
//$port = dbFetchRow("SELECT * FROM `ports` LEFT JOIN `ports-state` ON `ports`.`port_id` = `ports-state`.`port_id` WHERE `ports`.`port_id` = ?", array($port_id));
$port = dbFetchRow("SELECT * FROM `ports` WHERE `ports`.`port_id` = ?", array($port_id));
}
if (is_array($port))
{
//$port['port_id'] = $port_id; // It corrects the situation, when `ports-state` is empty
humanize_port($port);
return $port;
}
return FALSE;
}
// Get port array by ifIndex (using cache)
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_by_index_cache($device, $ifIndex, $deleted = 0) {
global $cache;
if (is_array($device) && isset($device['device_id'])) {
$device_id = $device['device_id'];
} elseif (is_numeric($device)) {
$device_id = $device;
}
if (!isset($device_id) || !is_intnum($ifIndex)) {
print_error("Invalid arguments passed into function get_port_by_index_cache(). Please report to developers.");
return FALSE;
}
if (OBS_PROCESS_NAME === 'poller' && !isset($cache['port_index'][$device_id]) && !$deleted) {
// Pre-cache all ports in poller for speedup db queries
foreach (dbFetchRows('SELECT * FROM `ports` WHERE `device_id` = ? AND `deleted` = ?', [ $device_id, 0 ]) as $entity) {
if (is_numeric($entity['port_id'])) {
// Cache ifIndex to port ID translations
$cache['port_index'][$device_id][$entity['ifIndex']] = $entity['port_id'];
// Same caching as in get_entity_by_id_cache()
humanize_port($entity);
entity_rewrite('port', $entity);
$cache['port'][$entity['port_id']] = $entity;
}
}
}
if (isset($cache['port_index'][$device_id][$ifIndex]) && is_numeric($cache['port_index'][$device_id][$ifIndex])) {
$id = $cache['port_index'][$device_id][$ifIndex];
} else {
$deleted = $deleted ? 1 : 0; // Just convert boolean to 0 or 1
$id = dbFetchCell("SELECT `port_id` FROM `ports` WHERE `device_id` = ? AND `ifIndex` = ? AND `deleted` = ? LIMIT 1", [ $device_id, $ifIndex, $deleted ]);
if (!$deleted && is_numeric($id)) {
// Cache port IDs (except deleted)
$cache['port_index'][$device_id][$ifIndex] = $id;
}
}
$port = get_entity_by_id_cache('port', $id);
if (is_array($port)) { return $port; }
return FALSE;
}
// Get port array by ifIndex
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_by_ifIndex($device_id, $ifIndex) {
if (is_array($device_id)) {
$device_id = $device_id['device_id'];
}
$port = dbFetchRow("SELECT * FROM `ports` WHERE `device_id` = ? AND `ifIndex` = ? LIMIT 1", array($device_id, $ifIndex));
if (is_array($port)) {
humanize_port($port);
return $port;
}
return FALSE;
}
// Get port ID by ifDescr (i.e. 'TenGigabitEthernet1/1') or ifName (i.e. 'Te1/1')
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_id_by_ifDescr($device_id, $ifDescr, $deleted = 0) {
if (is_array($device_id)) {
$device_id = $device_id['device_id'];
}
$port_id = dbFetchCell("SELECT `port_id` FROM `ports` WHERE `device_id` = ? AND (`ifDescr` = ? OR `ifName` = ? OR `port_label_short` = ?) AND `deleted` = ? LIMIT 1", [ $device_id, $ifDescr, $ifDescr, $ifDescr, $deleted ]);
if (is_numeric($port_id)) {
return $port_id;
}
return FALSE;
}
// Get port ID by ifAlias (interface description)
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_id_by_ifAlias($device_id, $ifAlias, $deleted = 0) {
if (is_array($device_id)) {
$device_id = $device_id['device_id'];
}
$port_id = dbFetchCell("SELECT `port_id` FROM `ports` WHERE `device_id` = ? AND `ifAlias` = ? AND `deleted` = ? LIMIT 1", array($device_id, $ifAlias, $deleted));
if (is_numeric($port_id)) {
return $port_id;
}
return FALSE;
}
// Get port ID by customer params (see http://www.observium.org/wiki/Interface_Description_Parsing)
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_id_by_customer($customer)
{
$where = ' WHERE 1';
if (is_array($customer))
{
foreach ($customer as $var => $value)
{
if ($value != '')
{
switch ($var)
{
case 'device':
case 'device_id':
$where .= generate_query_values($value, 'device_id');
break;
case 'type':
case 'descr':
case 'circuit':
case 'speed':
case 'notes':
$where .= generate_query_values($value, 'port_descr_'.$var);
break;
}
}
}
} else {
return FALSE;
}
$query = 'SELECT `port_id` FROM `ports` ' . $where . ' ORDER BY `ifOperStatus` DESC';
$ids = dbFetchColumn($query);
//print_vars($ids);
switch (count($ids))
{
case 0:
return FALSE;
case 1:
return $ids[0];
break;
default:
foreach ($ids as $port_id)
{
$port = get_port_by_id_cache($port_id);
$device = device_by_id_cache($port['device_id']);
if ($device['disabled'] || !$device['status'])
{
continue; // switch to next ID
}
break;
}
return $port_id;
}
return FALSE;
}
function get_device_ids_by_customer($type, $customer)
{
if ($customer === '' || is_null($customer)) { return NULL; }
// Recursive merge
if (is_array($customer))
{
$ids = [];
foreach ($customer as $entry)
{
if ($entry_ids = get_device_ids_by_customer($type, $entry))
{
$ids = array_merge($ids, $entry_ids);
}
}
return $ids;
}
$where = ' WHERE 1';
switch ($type)
{
case 'device':
case 'device_id':
$where .= generate_query_values($customer, 'device_id');
break;
case 'type':
case 'descr':
case 'circuit':
case 'speed':
case 'notes':
$where .= generate_query_values($customer, 'port_descr_'.$type);
break;
default:
$where .= generate_query_values($customer, 'port_descr_descr');
}
$query = 'SELECT DISTINCT `device_id` FROM `ports` ' . $where;
return dbFetchColumn($query);
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function is_port_valid($port, $device) {
global $config;
// Ignore non standard ifOperStatus
// See http://tools.cisco.com/Support/SNMP/do/BrowseOID.do?objectInput=ifOperStatus
$valid_ifOperStatus = [ 'testing', 'dormant', 'down', 'lowerLayerDown', 'unknown', 'up', 'monitoring' ];
if (isset($port['ifOperStatus']) && strlen($port['ifOperStatus']) &&
!in_array($port['ifOperStatus'], $valid_ifOperStatus)) {
print_debug("ignored (by ifOperStatus = notPresent or invalid value).");
return FALSE;
}
// Per os definition
$def = isset($config['os'][$device['os']]['ports_ignore']) ? $config['os'][$device['os']]['ports_ignore'] : [];
// Ignore ports with empty ifType
if (isset($def['allow_empty'])) {
$ports_allow_empty = (bool) $def['allow_empty'];
unset($def['allow_empty']);
} else {
$ports_allow_empty = FALSE;
}
if (!isset($port['ifType']) && !$ports_allow_empty) {
/* Some devices (ie D-Link) report ports without any useful info, example:
[74] => Array
(
[ifName] => po22
[ifInMulticastPkts] => 0
[ifInBroadcastPkts] => 0
[ifOutMulticastPkts] => 0
[ifOutBroadcastPkts] => 0
[ifLinkUpDownTrapEnable] => enabled
[ifHighSpeed] => 0
[ifPromiscuousMode] => false
[ifConnectorPresent] => false
[ifAlias] => po22
[ifCounterDiscontinuityTime] => 0:0:00:00.00
)
*/
print_debug("ignored (by empty ifType).");
return FALSE;
}
// This happens on some liebert UPS devices or when device have memory leak (ie Eaton Powerware)
if (isset($config['os'][$device['os']]['ifType_ifDescr']) &&
$config['os'][$device['os']]['ifType_ifDescr'] && $port['ifIndex']) {
$len = strlen($port['ifDescr']);
$type = rewrite_iftype($port['ifType']);
if ($type && ($len === 0 || $len > 255 ||
isHexString($port['ifDescr']) ||
preg_match('/(.)\1{4,}/', $port['ifDescr']))) {
$port['ifDescr'] = $type . ' ' . $port['ifIndex'];
}
}
//$if = ($config['os'][$device['os']]['ifname'] ? $port['ifName'] : $port['ifDescr']);
$valid_ifDescr = !safe_empty($port['ifDescr']);
$valid_ifName = !safe_empty($port['ifName']);
// Ignore ports with empty ifName and ifDescr (while not possible store in db)
if (!$valid_ifDescr && !$valid_ifName) {
print_debug("ignored (by empty ifDescr and ifName).");
return FALSE;
}
// ifName not same as ifDescr (speedup label checks)
$nosame_ifName = $port['ifDescr'] !== $port['ifName'];
// Complex Oid based checks
foreach ($def as $i => $array) {
if (!is_numeric($i)) { continue; } // Ignore old definitions
$count = safe_count($array); // count required Oids
// Oids: ifType, ifAlias, ifDescr, ifName, label
$found = 0;
$table_rows = [];
foreach ($array as $oid => $entry) {
switch (strtolower($oid)) {
case 'type':
case 'iftype':
if (str_contains_array($port['ifType'], $entry)) {
$table_rows[] = [ 'ifType', $GLOBALS['str_last_needle'], $port['ifType'] ];
$found++;
}
break;
// This defs always regex!
case 'descr':
case 'ifdescr':
if (!$valid_ifDescr) { break; }
foreach ((array)$entry as $pattern) {
if (preg_match($pattern, $port['ifDescr'])) {
$table_rows[] = [ 'ifDescr', $pattern, $port['ifDescr'] ];
$found++;
break;
}
}
break;
case 'name':
case 'ifname':
if (!$valid_ifName) { break; }
foreach ((array)$entry as $pattern) {
if (preg_match($pattern, $port['ifName'])) {
$table_rows[] = [ 'ifName', $pattern, $port['ifName'] ];
$found++;
break;
}
}
break;
case 'label':
if ($valid_ifDescr) {
foreach ((array)$entry as $pattern) {
if (preg_match($pattern, $port['ifDescr'])) {
$table_rows[] = [ 'ifDescr', $pattern, $port['ifDescr'] ];
$found++;
break;
}
}
}
if ($valid_ifName && $nosame_ifName) {
foreach ((array)$entry as $pattern) {
if (preg_match($pattern, $port['ifName'])) {
$table_rows[] = [ 'ifName', $pattern, $port['ifName'] ];
$found++;
break;
}
}
}
break;
case 'alias':
case 'ifalias':
foreach ((array)$entry as $pattern) {
if (preg_match($pattern, $port['ifAlias'])) {
$table_rows[] = [ 'ifAlias', $pattern, $port['ifAlias'] ];
$found++;
break;
}
}
break;
}
}
if ($count && $found === $count) {
// Show matched Oids
print_debug("ignored (by Oids):");
$table_headers = [ '%WOID%n', '%WMatched definition%n', '%WValue%n' ];
print_cli_table($table_rows, $table_headers);
return FALSE;
}
}
/* Global Configs */
// Ignore ports by ifAlias
if (isset($config['bad_ifalias_regexp'])) {
foreach ((array)$config['bad_ifalias_regexp'] as $bi) {
if (preg_match($bi, $port['ifAlias'])) {
print_debug("ignored (by ifAlias): ".$port['ifAlias']." [ $bi ]");
return FALSE;
}
}
}
// Ignore ports by ifName/ifDescr (do not forced as case insensitive)
if (isset($config['bad_if_regexp'])) {
foreach ((array)$config['bad_if_regexp'] as $bi) {
if ($valid_ifDescr && preg_match($bi, $port['ifDescr'])) {
print_debug("ignored (by ifDescr regexp): ".$port['ifDescr']." [ $bi ]");
return FALSE;
}
if ($valid_ifName && $nosame_ifName && preg_match($bi, $port['ifName'])) {
print_debug("ignored (by ifName regexp): ".$port['ifName']." [ $bi ]");
return FALSE;
}
}
}
// FIXME. Prefer regexp
if ($valid_ifDescr && str_icontains_array($port['ifDescr'], (array)$config['bad_if'])) {
$bi = $GLOBALS['str_last_needle'];
print_debug("ignored (by ifDescr): ".$port['ifDescr']." [ $bi ]");
return FALSE;
}
if ($valid_ifName && $nosame_ifName && str_icontains_array($port['ifName'], (array)$config['bad_if'])) {
$bi = $GLOBALS['str_last_needle'];
print_debug("ignored (by ifName): ".$port['ifName']." [ $bi ]");
return FALSE;
}
// Ignore ports by ifType
if (isset($config['bad_iftype']) && str_contains_array($port['ifType'], (array)$config['bad_iftype'])) {
$bi = $GLOBALS['str_last_needle'];
print_debug("ignored (by ifType): ".$port['ifType']." [ $bi ]");
return FALSE;
}
return TRUE;
}
// Delete port from database and associated rrd files
// DOCME needs phpdoc block
// TESTME needs unit testing
function delete_port($int_id, $delete_rrd = TRUE) {
global $config;
$port = dbFetchRow("SELECT * FROM `ports`
LEFT JOIN `devices` USING (`device_id`)
WHERE `port_id` = ?", array($int_id));
$ret = "> Deleted interface from ".$port['hostname'].": id=$int_id (".$port['ifDescr'].")\n";
// Remove entities from common tables
$deleted_entities = array();
foreach ($config['entity_tables'] as $table)
{
$where = '`entity_type` = ?' . generate_query_values($int_id, 'entity_id');
$table_status = dbDelete($table, $where, array('port'));
if ($table_status) { $deleted_entities['port'] = 1; }
}
if (count($deleted_entities))
{
$ret .= ' * Deleted common entity entries linked to port.' . PHP_EOL;
}
// FIXME, move to definitions
$port_tables = array('eigrp_ports', 'ipv4_addresses', 'ipv6_addresses',
'ip_mac', 'juniAtmVp', 'mac_accounting', 'ospf_nbrs', 'ospf_ports',
'ports_adsl', 'ports_cbqos', 'ports_vlans', 'pseudowires', 'vlans_fdb',
'neighbours', 'ports');
$deleted_tables = [];
foreach ($port_tables as $table)
{
$table_status = dbDelete($table, "`port_id` = ?", array($int_id));
if ($table_status) { $deleted_tables[] = $table; }
}
$table_status = dbDelete('ports_stack', "`port_id_high` = ? OR `port_id_low` = ?", array($port['ifIndex'], $port['ifIndex']));
if ($table_status) { $deleted_tables[] = 'ports_stack'; }
$table_status = dbDelete('entity_permissions', "`entity_type` = 'port' AND `entity_id` = ?", array($int_id));
if ($table_status) { $deleted_tables[] = 'entity_permissions'; }
$table_status = dbDelete('alert_table', "`entity_type` = 'port' AND `entity_id` = ?", array($int_id));
if ($table_status) { $deleted_tables[] = 'alert_table'; }
$table_status = dbDelete('group_table', "`entity_type` = 'port' AND `entity_id` = ?", array($int_id));
if ($table_status) { $deleted_tables[] = 'group_table'; }
$ret .= ' * Deleted interface entries from tables: '.implode(', ', $deleted_tables).PHP_EOL;
if ($delete_rrd) {
$rrd_types = array('adsl', 'dot3', 'fdbcount', 'poe', NULL);
$deleted_rrds = [];
foreach ($rrd_types as $type) {
$rrdfile = get_port_rrdfilename($port, $type, TRUE);
if (is_file($rrdfile)) {
unlink($rrdfile);
$deleted_rrds[] = $rrdfile;
}
}
$ret .= ' * Deleted interface RRD files: ' . implode(', ', $deleted_rrds) . PHP_EOL;
}
return $ret;
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function port_html_class($ifOperStatus, $ifAdminStatus, $encrypted = FALSE)
{
$ifclass = "interface-upup";
if ($ifAdminStatus == "down") { $ifclass = "gray"; }
elseif ($ifAdminStatus == "up")
{
if ($ifOperStatus == "down") { $ifclass = "red"; }
elseif ($ifOperStatus == "lowerLayerDown") { $ifclass = "orange"; }
elseif ($ifOperStatus == "monitoring") { $ifclass = "green"; }
//elseif ($encrypted === '1') { $ifclass = "olive"; }
elseif ($encrypted) { $ifclass = "olive"; }
elseif ($ifOperStatus == "up") { $ifclass = ""; }
else { $ifclass = "purple"; }
}
return $ifclass;
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_port_rrdindex($port)
{
global $config;
if (isset($port['device_id']))
{
$device_id = $port['device_id'];
} else {
// In poller, device_id not always passed
$port_tmp = get_port_by_id_cache($port['port_id']);
$device_id = $port_tmp['device_id'];
}
$device = device_by_id_cache($device_id);
if (isset($config['os'][$device['os']]['port_rrd_identifier']))
{
$device_identifier = strtolower($config['os'][$device['os']]['port_rrd_identifier']);
} else {
$device_identifier = 'ifindex';
}
// default to ifIndex
$this_port_identifier = $port['ifIndex'];
if ($device_identifier == "ifname" && $port['ifName'] != "")
{
$this_port_identifier = strtolower(str_replace("/", "-", $port['ifName']));
}
return $this_port_identifier;
}
// CLEANME DEPRECATED
function get_port_rrdfilename($port, $suffix = NULL, $fullpath = FALSE)
{
$this_port_identifier = get_port_rrdindex($port);
if ($suffix == "")
{
$filename = "port-" . $this_port_identifier . ".rrd";
} else {
$filename = "port-" . $this_port_identifier . "-" . $suffix . ".rrd";
}
if ($fullpath)
{
if (isset($port['device_id']))
{
$device_id = $port['device_id'];
} else {
// In poller, device_id not always passed
$port_tmp = get_port_by_id_cache($port['port_id']);
$device_id = $port_tmp['device_id'];
}
$device = device_by_id_cache($device_id);
$filename = get_rrd_path($device, $filename);
}
return $filename;
}
// EOF

View File

@ -0,0 +1,821 @@
<?php
/**
* Observium
*
* This file is part of Observium.
*
* @package observium
* @subpackage entities
* @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2022 Observium Limited
*
*/
/**
* Validate BGP peer
*
* @param array $peer BGP peer array from discovery or polling process
* @param array $device Common device array
* @return boolean TRUE if peer array valid
*/
function is_bgp_peer_valid($peer, $device) {
$valid = TRUE;
if (isset($peer['admin_status']) && empty($peer['admin_status'])) {
$valid = FALSE;
print_debug("Peer ignored (by empty Admin Status).");
}
if ($valid && !(is_numeric($peer['as']) && $peer['as'] != 0)) {
$valid = FALSE;
print_debug("Peer ignored (by invalid AS number '".$peer['as']."').");
}
if ($valid && !get_ip_version($peer['ip'])) {
$valid = FALSE;
print_debug("Peer ignored (by invalid Remote IP '".$peer['ip']."').");
}
return $valid;
}
/**
* Detect is BGP AS number in private range, see:
* https://tools.ietf.org/html/rfc6996
* https://tools.ietf.org/html/rfc7300
*
* @param string|int $as AS number
* @return boolean TRUE if AS number in private range
*/
function is_bgp_as_private($as) {
$as = bgp_asdot_to_asplain($as); // Convert ASdot to ASplain
// Note 65535 and 5294967295 not really Private ASNs,
// this is Reserved for use by Well-known Communities
$private = ($as >= 64512 && $as <= 65535) || // 16-bit private ASn
($as >= 4200000000 && $as <= 5294967295); // 32-bit private ASn
return $private;
}
/**
* Convert AS number from asplain to asdot format (for 32bit ASn).
*
* @param string|int|array $as AS number in plain or dot format
* @return string|array AS number in dot format (for 32bit ASn)
*/
function bgp_asplain_to_asdot($as) {
if (is_array($as)) {
// Recursive for arrays
$return = [];
foreach ($as as $entry) {
$return[] = bgp_asplain_to_asdot($entry);
}
return $return;
}
if (str_contains($as, '.') || // Already asdot format
($as < 65536)) { // 16bit ASn no need to formatting
return $as;
}
$as2 = $as % 65536;
$as1 = ($as - $as2) / 65536;
return (int)$as1 . '.' . (int)$as2;
}
/**
* Convert AS number from asdot to asplain format (for 32bit ASn).
*
* @param string|int|array $as AS number in plain or dot format
* @return string|array AS number in plain format (for 32bit ASn)
*/
function bgp_asdot_to_asplain($as) {
if (is_array($as)) {
// Recursive for arrays
$return = [];
foreach ($as as $entry) {
$return[] = bgp_asdot_to_asplain($entry);
}
return $return;
}
if (!str_contains($as, '.')) { // Already asplain format
return $as;
}
list($as1, $as2) = explode('.', $as, 2);
$as = $as1 * 65536 + $as2;
return (string) $as;
}
/**
* @param array $device
*
* @return array
*/
function get_bgp_localas_array($device) {
global $config, $attribs;
if (OBS_PROCESS_NAME === 'poller') {
// In poller return cached array
if (isset($attribs['bgp_localas'])) {
print_debug("Get Cached BGP LocalAs array.");
$return = safe_json_decode($attribs['bgp_localas']);
print_debug_vars($return);
return $return;
}
if (safe_empty($device['bgpLocalAs']) || $device['bgpLocalAs'] == 0) {
// Old empty peers (before rewrite or caching)
print_debug("BGP not discovered on device.");
return [];
}
} else {
$db_bgp_localas = get_entity_attrib('device', $device['device_id'], 'bgp_localas');
}
// use correct MIBs order:
// first always CISCO-BGP4-MIB (before BGP4-MIB)
// second BGP4-MIB, then all others
$bgp_mibs = [];
foreach (get_device_mibs_permitted($device) as $mib) {
// check bgp definitions
if (!isset($config['mibs'][$mib]['bgp'])) { continue; }
if ($mib === 'CISCO-BGP4-MIB') {
array_unshift($bgp_mibs , $mib);
} elseif ($mib === 'BGP4-MIB') {
if ($bgp_mibs[0] === 'CISCO-BGP4-MIB') {
// put BGP4-MIB after CISCO-BGP4-MIB
array_splice($bgp_mibs, 1, 0, $mib);
} else {
array_unshift($bgp_mibs, $mib);
}
} else {
$bgp_mibs[] = $mib;
}
}
print_debug_vars($bgp_mibs);
if (empty($bgp_mibs)) {
if (!safe_empty($db_bgp_localas)) {
// Clean cache when BGP removed
del_entity_attrib('device', $device['device_id'], 'bgp_localas');
}
return [];
}
// Common LocalAs before all
$bgp4_local_as = snmp_get_oid($device, 'bgpLocalAs.0', 'BGP4-MIB');
$bgp4_mib = is_numeric($bgp4_local_as);
$entries = [];
foreach ($bgp_mibs as $mib) {
$def = $config['mibs'][$mib]['bgp'];
// Main
if ($mib === 'CISCO-BGP4-MIB') {
// Cisco can report Afi/Safi but without cbgpLocalAs.0 and cbgpPeer2RemoteAs,
// Not sure, probably check cbgpPeerAcceptedPrefixes / cbgpPeer2AcceptedPrefixes here
if ($local_as = snmp_get_oid($device, 'cbgpLocalAs.0', $mib)) {
$ctest = snmp_getnext_oid($device, 'cbgpPeer2AcceptedPrefixes', $mib);
$cisco_version = safe_empty($ctest) ? 1 : 2;
$entries[] = [ 'mib' => $mib, 'oid' => 'cbgpLocalAs.0', 'LocalAs' => snmp_dewrap32bit($local_as), 'cisco_version' => $cisco_version ];
} else {
$local_as = snmp_getnext_oid($device, 'cbgpPeer2LocalAs', $mib);
// cbgpPeer2LocalAs in many devices is zero, but peers exist
if (is_numeric($local_as)) {
$entries[] = [ 'mib' => $mib, 'oid' => 'cbgpPeer2LocalAs', 'LocalAs' => snmp_dewrap32bit($local_as), 'cisco_version' => 2 ];
} elseif ($bgp4_mib) {
// Only check AcceptedPrefixes
$ctest = snmp_getnext_oid($device, 'cbgpPeer2AcceptedPrefixes', $mib);
$cisco_version = 2;
if (!snmp_status()) {
$ctest = snmp_getnext_oid($device, 'cbgpPeerAcceptedPrefixes', $mib);
$cisco_version = 1;
// CISCO-BGP4-MIB not exist
if (!snmp_status()) { continue; }
}
$entries[] = [ 'mib' => $mib, 'cisco_version' => $cisco_version ];
}
}
} elseif ($mib === 'BGP4-MIB') {
if ($bgp4_mib) {
$entries[] = [ 'mib' => $mib, 'oid' => $def['oids']['LocalAs']['oid'], 'LocalAs' => snmp_dewrap32bit($bgp4_local_as) ];
}
} elseif (isset($def['oids']['LocalAs']['oid'])) {
// Vendor definitions
$local_as = snmp_get_oid($device, $def['oids']['LocalAs']['oid'], $mib);
if (is_numeric($local_as)) {
$entries[] = [ 'mib' => $mib, 'oid' => $def['oids']['LocalAs']['oid'], 'LocalAs' => snmp_dewrap32bit($local_as) ];
}
} elseif (isset($def['oids']['LocalAs']['oid_next'])) {
// Vendor definitions
$local_as = snmp_getnext_oid($device, $def['oids']['LocalAs']['oid_next'], $mib);
if (is_numeric($local_as)) {
$entries[] = [ 'mib' => $mib, 'oid' => $def['oids']['LocalAs']['oid_next'], 'LocalAs' => snmp_dewrap32bit($local_as) ];
}
} elseif ($bgp4_mib) {
// When MIB specific LocalAs not exist, but BGP4-MIB already return valid LocasAs
// append this mib to entries, ie HUAWEI-BGP-VPN-MIB, CUMULUS-BGPUN-MIB
snmp_getnext_oid($device, $def['oids']['PeerRemoteAs']['oid'], $mib);
if (snmp_status()) {
$entries[] = [ 'mib' => $mib ];
}
}
// Check in Virtual Routing
$check_vrfs = (safe_empty($device['snmp_context']) && // Device not already with context
isset($config['os'][$device['os']]['snmp']['virtual']) && $config['os'][$device['os']]['snmp']['virtual'] && // Context permitted for os
$vrf_contexts = safe_json_decode(get_entity_attrib('device', $device, 'vrf_contexts')));
if ($check_vrfs) {
$bgp4_mib_vrf = [];
foreach ($vrf_contexts as $vrf_name => $snmp_virtual) {
print_debug("Check LocalAs in Virtual Routing: $vrf_name...");
$device_vrf = snmp_virtual_device($device, $snmp_virtual);
// Common LocalAs before all
$bgp4_local_as = snmp_get_oid($device_vrf, 'bgpLocalAs.0', 'BGP4-MIB');
$bgp4_mib_vrf[$vrf_name] = is_numeric($bgp4_local_as);
if ($mib === 'CISCO-BGP4-MIB') {
// Cisco can report Afi/Safi but without cbgpLocalAs.0 and cbgpPeer2RemoteAs,
// Not sure, probably check cbgpPeerAcceptedPrefixes / cbgpPeer2AcceptedPrefixes here
if ($local_as = snmp_get_oid($device_vrf, 'cbgpLocalAs.0', $mib)) {
$ctest = snmp_getnext_oid($device_vrf, 'cbgpPeer2AcceptedPrefixes', $mib);
$cisco_version = safe_empty($ctest) ? 1 : 2;
$entries[] = [ 'mib' => $mib, 'oid' => 'cbgpLocalAs.0', 'LocalAs' => snmp_dewrap32bit($local_as), 'cisco_version' => $cisco_version,
'virtual_name' => $vrf_name, 'snmp_context' => $snmp_virtual ];
} else {
$local_as = snmp_getnext_oid($device_vrf, 'cbgpPeer2LocalAs', $mib);
// cbgpPeer2LocalAs in many devices is zero, but peers exist
if (is_numeric($local_as)) {
$entries[] = [ 'mib' => $mib, 'oid' => 'cbgpPeer2LocalAs', 'LocalAs' => snmp_dewrap32bit($local_as), 'cisco_version' => 2,
'virtual_name' => $vrf_name, 'snmp_context' => $snmp_virtual ];
} elseif ($bgp4_mib_vrf[$vrf_name]) {
// Only check AcceptedPrefixes
$ctest = snmp_getnext_oid($device_vrf, 'cbgpPeer2AcceptedPrefixes', $mib);
$cisco_version = 2;
if (!snmp_status()) {
$ctest = snmp_getnext_oid($device_vrf, 'cbgpPeerAcceptedPrefixes', $mib);
$cisco_version = 1;
// CISCO-BGP4-MIB not exist
if (!snmp_status()) { continue; }
}
$entries[] = [ 'mib' => $mib, 'cisco_version' => $cisco_version,
'virtual_name' => $vrf_name, 'snmp_context' => $snmp_virtual ];
}
}
} elseif ($mib === 'BGP4-MIB') {
if ($bgp4_mib_vrf[$vrf_name]) {
$entries[] = [ 'mib' => $mib, 'oid' => $def['oids']['LocalAs']['oid'], 'LocalAs' => snmp_dewrap32bit($bgp4_local_as),
'virtual_name' => $vrf_name, 'snmp_context' => $snmp_virtual ];
}
} elseif (isset($def['oids']['LocalAs']['oid'])) {
$local_as = snmp_get_oid($device_vrf, $def['oids']['LocalAs']['oid'], $mib);
if (is_numeric($local_as)) {
$entries[] = [ 'mib' => $mib, 'oid' => $def['oids']['LocalAs']['oid'], 'LocalAs' => snmp_dewrap32bit($local_as),
'virtual_name' => $vrf_name, 'snmp_context' => $snmp_virtual ];
}
} elseif (isset($def['oids']['LocalAs']['oid_next'])) {
$local_as = snmp_getnext_oid($device_vrf, $def['oids']['LocalAs']['oid_next'], $mib);
if (is_numeric($local_as)) {
$entries[] = [ 'mib' => $mib, 'oid' => $def['oids']['LocalAs']['oid_next'], 'LocalAs' => snmp_dewrap32bit($local_as),
'virtual_name' => $vrf_name, 'snmp_context' => $snmp_virtual ];
}
} elseif (isset($bgp4_mib_vrf[$vrf_name])) {
// When MIB specific LocalAs not exist, but BGP4-MIB already return valid LocasAs
// append this mib to entries, ie HUAWEI-BGP-VPN-MIB
snmp_getnext_oid($device_vrf, $def['oids']['PeerRemoteAs']['oid'], $mib);
if (snmp_status()) {
$entries[] = [ 'mib' => $mib, 'virtual_name' => $vrf_name, 'snmp_context' => $snmp_virtual ];
}
}
}
}
}
print_debug_vars($entries);
// Cache entries (in discovery)
if (safe_count($entries)) {
$attrib = safe_json_encode($entries);
if ($attrib !== $db_bgp_localas) {
set_entity_attrib('device', $device['device_id'], 'bgp_localas', safe_json_encode($entries));
if (OBS_PROCESS_NAME === 'poller') {
$attribs['bgp_localas'] = safe_json_encode($entries);
}
}
} elseif (!safe_empty($db_bgp_localas)) {
// Clean cache when BGP removed
del_entity_attrib('device', $device['device_id'], 'bgp_localas');
}
return $entries;
}
/**
* Convert BGP peer index to vendor MIB specific entries
*
* @param array $peer Array with walked peer oids
* @param string $index Peer index
* @param string $mib MIB name
*/
function parse_bgp_peer_index(&$peer, $index, $mib = 'BGP4V2-MIB') {
global $config;
$address_types = $config['mibs']['INET-ADDRESS-MIB']['rewrite']['InetAddressType'];
$index_parts = explode('.', $index);
switch ($mib) {
case 'BGP4-MIB':
// bgpPeerRemoteAddr
$peer_ip = $index;
if (safe_count($index_parts) > 4) {
// Aruba case:
// BGP4-MIB::bgpPeerRemoteAddr.4.18.1.23.109 = IpAddress: 18.1.23.109
$ip_len = array_shift($index_parts);
$peer_ip = implode('.', $index_parts);
}
if (get_ip_version($peer_ip)) {
$peer['bgpPeerRemoteAddr'] = $peer_ip;
}
break;
case 'ARISTA-BGP4V2-MIB':
// 1. aristaBgp4V2PeerInstance
$peer['aristaBgp4V2PeerInstance'] = array_shift($index_parts);
// 2. aristaBgp4V2PeerRemoteAddrType
$peer_addr_type = array_shift($index_parts);
if (safe_empty($peer['aristaBgp4V2PeerRemoteAddrType'])) {
$peer['aristaBgp4V2PeerRemoteAddrType'] = $peer_addr_type;
}
if (isset($address_types[$peer['aristaBgp4V2PeerRemoteAddrType']])) {
$peer['aristaBgp4V2PeerRemoteAddrType'] = $address_types[$peer['aristaBgp4V2PeerRemoteAddrType']];
}
// 3. length of the IP address
$ip_len = array_shift($index_parts);
// 4. IP address
$ip_parts = array_slice($index_parts, 0, $ip_len);
// 5. aristaBgp4V2PeerRemoteAddr
$peer_ip = implode('.', $ip_parts);
if ((int)$ip_len === 16) {
$peer_ip = snmp2ipv6($peer_ip);
}
if ($peer_addr_type = get_ip_version($peer_ip)) {
$peer['aristaBgp4V2PeerRemoteAddr'] = $peer_ip;
$peer['aristaBgp4V2PeerRemoteAddrType'] = 'ipv' . $peer_addr_type; // FIXME. not sure, but seems as Arista use only ipv4/ipv6 for afi
}
break;
case 'BGP4V2-MIB':
case 'FOUNDRY-BGP4V2-MIB': // BGP4V2-MIB draft
// 1. bgp4V2PeerInstance
$peer['bgp4V2PeerInstance'] = array_shift($index_parts);
// 2. bgp4V2PeerLocalAddrType
$local_addr_type = array_shift($index_parts);
if (safe_empty($peer['bgp4V2PeerLocalAddrType'])) {
$peer['bgp4V2PeerLocalAddrType'] = $local_addr_type;
}
if (isset($address_types[$peer['bgp4V2PeerLocalAddrType']])) {
$peer['bgp4V2PeerLocalAddrType'] = $address_types[$peer['bgp4V2PeerLocalAddrType']];
}
// 3. length of the local IP address
$ip_len = array_shift($index_parts);
// 4. IP address
$ip_parts = array_slice($index_parts, 0, $ip_len);
// 5. bgp4V2PeerLocalAddr
$local_ip = implode('.', $ip_parts);
if ((int)$ip_len === 16) {
$local_ip = snmp2ipv6($local_ip);
}
if (get_ip_version($local_ip)) {
$peer['bgp4V2PeerLocalAddr'] = $local_ip;
}
// Get second part of index
$index_parts = array_slice($index_parts, $ip_len);
$peer_addr_type = array_shift($index_parts);
if (safe_empty($peer['bgp4V2PeerRemoteAddrType'])) {
$peer['bgp4V2PeerRemoteAddrType'] = $peer_addr_type;
}
if (isset($address_types[$peer['bgp4V2PeerRemoteAddrType']])) {
$peer['bgp4V2PeerRemoteAddrType'] = $address_types[$peer['bgp4V2PeerRemoteAddrType']];
}
// 6. length of the IP address
$ip_len = array_shift($index_parts);
// 7. IP address
$ip_parts = array_slice($index_parts, 0, $ip_len);
// 8. bgp4V2PeerRemoteAddr
$peer_ip = implode('.', $ip_parts);
if ((int)$ip_len === 16) {
$peer_ip = snmp2ipv6($peer_ip);
}
if ($peer_addr_type = get_ip_version($peer_ip)) {
$peer['bgp4V2PeerRemoteAddr'] = $peer_ip;
$peer['bgp4V2PeerRemoteAddrType'] = 'ipv' . $peer_addr_type;
}
break;
case 'HUAWEI-BGP-VPN-MIB':
// HUAWEI-BGP-VPN-MIB::hwBgpPeerVrfName.0.ipv4.unicast.ipv4."10.100.0.5" = STRING: "Public"
// HUAWEI-BGP-VPN-MIB::hwBgpPeerVrfName.0.1.1.1.4.10.100.0.5 = STRING: "Public"
// 1. hwBgpPeerInstanceId
$peer['hwBgpPeerInstanceId'] = array_shift($index_parts);
// 2. hwBgpPeerAddrFamilyAfi
$afi = array_shift($index_parts);
$afis = [ 1 => 'ipv4', 2 => 'ipv6', 25 => 'vpls', 196 => 'l2vpn' ]; // Huawei specific AFI numbers (HWBgpAfi)
if (isset($afis[$afi])) {
$peer['hwBgpPeerAddrFamilyAfi'] = $afis[$afi];
} else {
// or use common afis
$peer['hwBgpPeerAddrFamilyAfi'] = $config['routing_afis'][$afi];
}
// 3. hwBgpPeerAddrFamilySafi
$safi = array_shift($index_parts);
$safis = [ 1 => 'unicast', 2 => 'multicast', 4 => 'mpls', 5 => 'mcast-vpn', 65 => 'vpls', 66 => 'mdt', 128 => 'vpn', 132 => 'route-target' ]; // Huawei specific SAFI numbers (HWBgpSafi)
if (isset($safis[$safi])) {
$peer['hwBgpPeerAddrFamilySafi'] = $safis[$safi];
} else {
// or use common safi
$peer['hwBgpPeerAddrFamilySafi'] = $config['routing_safis'][$safi];
}
// 4. hwBgpPeerRemoteAddrType (hwBgpPeerType)
$peer_addr_type = array_shift($index_parts);
if (isset($address_types[$peer_addr_type])) {
$peer['hwBgpPeerRemoteAddrType'] = $address_types[$peer_addr_type];
}
// 5. hwBgpPeerRemoteAddr
$ip_len = array_shift($index_parts);
$ip_parts = $index_parts;
$remote_ip = implode('.', $ip_parts);
if ((int)$ip_len === 16) {
$remote_ip = snmp2ipv6($remote_ip);
}
if ($peer_addr_type = get_ip_version($remote_ip)) {
$peer['hwBgpPeerRemoteAddr'] = $remote_ip;
$peer['hwBgpPeerRemoteIdentifier'] = $peer['hwBgpPeerRemoteAddr'];
if (safe_empty($peer['hwBgpPeerRemoteAddrType'])) {
$peer['hwBgpPeerRemoteAddrType'] = 'ipv' . $peer_addr_type;
}
}
// 6. hwBgpPeerAdminStatus
if (!isset($peer['hwBgpPeerAdminStatus'])) {
// Always set this Oid to start, while not really exist and while peer entry exist in this table
$peer['hwBgpPeerAdminStatus'] = 'start';
}
break;
case 'BGP4-V2-MIB-JUNIPER':
// 1. jnxBgpM2PeerRoutingInstance
$peer['jnxBgpM2PeerRoutingInstance'] = array_shift($index_parts);
// 2. jnxBgpM2PeerLocalAddrType
$local_addr_type = array_shift($index_parts);
if (safe_empty($peer['jnxBgpM2PeerLocalAddrType'])) {
$peer['jnxBgpM2PeerLocalAddrType'] = $local_addr_type;
}
if (isset($address_types[$peer['jnxBgpM2PeerLocalAddrType']])) {
$peer['jnxBgpM2PeerLocalAddrType'] = $address_types[$peer['jnxBgpM2PeerLocalAddrType']];
}
// 3. length of the local IP address
$ip_len = str_contains($peer['jnxBgpM2PeerLocalAddrType'], 'ipv6') ? 16 : 4;
// 4. IP address
$ip_parts = array_slice($index_parts, 0, $ip_len);
// 5. jnxBgpM2PeerLocalAddr
$local_ip = implode('.', $ip_parts);
if ((int)$ip_len === 16) {
$local_ip = snmp2ipv6($local_ip);
}
if (get_ip_version($local_ip)) {
$peer['jnxBgpM2PeerLocalAddr'] = $local_ip;
}
// Get second part of index
$index_parts = array_slice($index_parts, $ip_len);
// 6. jnxBgpM2PeerRemoteAddrType
$peer_addr_type = array_shift($index_parts);
if (safe_empty($peer['jnxBgpM2PeerRemoteAddrType'])) {
$peer['jnxBgpM2PeerRemoteAddrType'] = $peer_addr_type;
}
if (isset($address_types[$peer['jnxBgpM2PeerRemoteAddrType']])) {
$peer['jnxBgpM2PeerRemoteAddrType'] = $address_types[$peer['jnxBgpM2PeerRemoteAddrType']];
}
// 7. length of the remote IP address
$ip_len = str_contains($peer['jnxBgpM2PeerRemoteAddrType'], 'ipv6') ? 16 : 4;
// 8. IP address
$ip_parts = array_slice($index_parts, 0, $ip_len);
// 9. jnxBgpM2PeerRemoteAddr
$peer_ip = implode('.', $ip_parts);
if ((int)$ip_len === 16) {
$peer_ip = snmp2ipv6($peer_ip);
}
if (get_ip_version($peer_ip)) {
$peer['jnxBgpM2PeerRemoteAddr'] = $peer_ip;
}
break;
case 'DELLEMC-OS10-BGP4V2-MIB':
// 1. os10bgp4V2PeerInstance
$peer['os10bgp4V2PeerInstance'] = array_shift($index_parts);
// 2. os10bgp4V2PeerRemoteAddrType
$peer_addr_type = array_shift($index_parts);
if (safe_empty($peer['os10bgp4V2PeerRemoteAddrType'])) {
$peer['os10bgp4V2PeerRemoteAddrType'] = $peer_addr_type;
}
if (isset($address_types[$peer['os10bgp4V2PeerRemoteAddrType']])) {
$peer['os10bgp4V2PeerRemoteAddrType'] = $address_types[$peer['os10bgp4V2PeerRemoteAddrType']];
}
// 3. length of the remote IP address
$ip_len = array_shift($index_parts);
// 4. IP address
$ip_parts = array_slice($index_parts, 0, $ip_len);
// 5. os10bgp4V2PeerRemoteAddr
$peer_ip = implode('.', $ip_parts);
if ((int)$ip_len === 16) {
$peer_ip = snmp2ipv6($peer_ip);
}
if (get_ip_version($peer_ip)) {
$peer['os10bgp4V2PeerRemoteAddr'] = $peer_ip;
}
break;
case 'FORCE10-BGP4-V2-MIB':
// 1. f10BgpM2PeerInstance
$peer['f10BgpM2PeerInstance'] = array_shift($index_parts);
// 2. f10BgpM2PeerLocalAddrType
$local_addr_type = array_shift($index_parts);
if (safe_empty($peer['f10BgpM2PeerLocalAddrType'])) {
$peer['f10BgpM2PeerLocalAddrType'] = $local_addr_type;
}
if (isset($address_types[$peer['f10BgpM2PeerLocalAddrType']])) {
$peer['f10BgpM2PeerLocalAddrType'] = $address_types[$peer['f10BgpM2PeerLocalAddrType']];
}
// 3. length of the local IP address
$ip_len = str_contains($peer['f10BgpM2PeerLocalAddrType'], 'ipv6') ? 16 : 4;
// 4. IP address
$ip_parts = array_slice($index_parts, 0, $ip_len);
// 5. f10BgpM2PeerLocalAddr
$local_ip = implode('.', $ip_parts);
if ((int)$ip_len === 16) {
$local_ip = snmp2ipv6($local_ip);
}
if (get_ip_version($local_ip)) {
$peer['f10BgpM2PeerLocalAddr'] = $local_ip;
}
// Get second part of index
$index_parts = array_slice($index_parts, $ip_len);
// 6. f10BgpM2PeerRemoteAddrType
$peer_addr_type = array_shift($index_parts);
if (safe_empty($peer['f10BgpM2PeerRemoteAddrType'])) {
$peer['f10BgpM2PeerRemoteAddrType'] = $peer_addr_type;
}
if (isset($address_types[$peer['f10BgpM2PeerRemoteAddrType']])) {
$peer['f10BgpM2PeerRemoteAddrType'] = $address_types[$peer['f10BgpM2PeerRemoteAddrType']];
}
// 7. length of the remote IP address
$ip_len = str_contains($peer['f10BgpM2PeerRemoteAddrType'], 'ipv6') ? 16 : 4;
// 8. IP address
$ip_parts = array_slice($index_parts, 0, $ip_len);
// 9. f10BgpM2PeerRemoteAddr
$peer_ip = implode('.', $ip_parts);
if ((int)$ip_len === 16) {
$peer_ip = snmp2ipv6($peer_ip);
}
if (get_ip_version($peer_ip)) {
$peer['f10BgpM2PeerRemoteAddr'] = $peer_ip;
}
break;
case 'VIPTELA-OPER-BGP':
// 1. bgpBgpNeighborVpnId
$peer['bgpBgpNeighborVpnId'] = array_shift($index_parts);
// 2. hwBgpPeerRemoteAddr
$ip_len = safe_count($index_parts);
$peer_ip = implode('.', $index_parts);
if ((int)$ip_len === 16) {
$peer_ip = snmp2ipv6($peer_ip);
}
if ($peer_addr_type = get_ip_version($peer_ip)) {
$peer['bgpBgpNeighborPeerAddr'] = $peer_ip;
$peer['bgpBgpNeighborPeerAddrType'] = 'ipv' . $peer_addr_type;
}
// 6. hwBgpPeerAdminStatus
if (!isset($peer['bgpBgpNeighborAdminState'])) {
// Always set this Oid to start, while not really exist and while peer entry exist in this table
$peer['bgpBgpNeighborAdminState'] = in_array($peer['bgpBgpNeighborState'], [ 'clearing', 'deleted' ]) ? 'stop' : 'start';
}
break;
}
}
function discovery_bgp_afisafi($device, $peer, $afi, $safi, &$af_list) {
global $table_rows;
$index = $peer['index'];
$peer_id = $peer['id'];
$peer_ip = $peer['ip'];
$peer_as = $peer['as'];
if (isset($GLOBALS['config']['routing_afis'][$afi])) {
$afi = $GLOBALS['config']['routing_afis'][$afi]['name'];
}
if (isset($GLOBALS['config']['routing_safis'][$safi])) {
$safi = $GLOBALS['config']['routing_safis'][$safi]['name'];
}
print_debug("INDEX: $index, AS: $peer_as, IP: $peer_ip, AFI: $afi, SAFI: $safi");
print_debug_vars($peer);
$af_list[$peer_id][$afi][$safi] = 1;
if (!safe_empty($table_rows[$peer_ip][4])) {
$table_rows[$peer_ip][4] .= ', ';
}
$table_rows[$peer_ip][4] .= $afi . '.' . $safi;
if (!dbExist('bgpPeers_cbgp', '`device_id` = ? AND `bgpPeer_id` = ? AND `afi` = ? AND `safi` = ?', [ $device['device_id'], $peer_id, $afi, $safi ])) {
$params = [ 'bgpPeer_id' => $peer_id, 'device_id' => $device['device_id'], 'bgpPeerIndex' => $index, 'afi' => $afi, 'safi' => $safi ];
dbInsert($params, 'bgpPeers_cbgp');
} elseif (!safe_empty($index)) {
// Update Index
dbUpdate([ 'bgpPeerIndex' => $index ], 'bgpPeers_cbgp', 'device_id = ? AND `bgpPeer_id` = ? AND `afi` = ? AND `safi` = ?', [ $device['device_id'], $peer_id, $afi, $safi ]);
}
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_astext($asn) {
global $config, $cache;
// Fetch pre-set AS text from config first
if (isset($config['astext'][$asn])) {
return $config['astext'][$asn];
}
// Not preconfigured, check cache before doing a new DNS request
if (!isset($cache['astext'][$asn])) {
$result = dns_get_record("AS$asn.asn.cymru.com", DNS_TXT);
print_debug_vars($result);
$txt = explode('|', $result[0]['txt']);
$cache['astext'][$asn] = trim(str_replace('"', '', $txt[4]));
}
return $cache['astext'][$asn];
}
function discover_vrf($device, $vrf)
{
global $cache_discovery, $valid, $table_rows;
$module = 'vrf';
if (empty($vrf['vrf_name'])) { return; }
$vrf_name = $vrf['vrf_name'];
// Pre-cache VRFs from DB
if (!isset($cache_discovery['vrf_db']))
{
$cache_discovery['vrf_db'] = [];
foreach (dbFetchRows("SELECT * FROM `vrfs` WHERE `device_id` = ?", [ $device['device_id'] ]) as $entry)
{
// Strange case with duplicate entries: https://jira.observium.org/browse/OBS-3600
if (isset($cache_discovery['vrf_db'][$entry['vrf_name']])) {
print_debug("Duplicate VRF entry in DB found: ".$entry['vrf_name']);
print_debug_vars($entry);
dbDelete('vrfs', '`vrf_id` = ?', [ $entry['vrf_id'] ]);
continue;
}
$cache_discovery['vrf_db'][$entry['vrf_name']] = $entry;
}
}
$params_main = [ 'vrf_mib', 'vrf_name', 'vrf_descr', 'vrf_rd' ];
$params_state = [ 'vrf_admin_status', 'vrf_oper_status',
'vrf_active_ports', 'vrf_total_ports',
'vrf_added_routes', 'vrf_deleted_routes', 'vrf_total_routes' ];
$insert_array = [ 'device_id' => $device['device_id'] ];
foreach ($params_main as $param)
{
$insert_array[$param] = isset($vrf[$param]) ? $vrf[$param] : '';
}
// Set added/changed params
$current_time = (int)time();
$param = 'vrf_added';
$insert_array[$param] = isset($vrf[$param]) && $vrf[$param] < $current_time ? (int)$vrf[$param] : $device['last_rebooted'];
if (!isset($cache_discovery['vrf_db'][$vrf['vrf_name']]))
{
// Added
$param = 'vrf_last_change';
$insert_array[$param] = isset($vrf[$param]) && $vrf[$param] < $current_time ? (int)$vrf[$param] : $current_time;
// When insert, also add state params
foreach ($params_state as $param)
{
if (isset($vrf[$param]))
{
// When not set, use db default
$insert_array[$param] = $vrf[$param];
}
}
$vrf_id = dbInsert($insert_array, 'vrfs');
$GLOBALS['module_stats'][$module]['added']++; //echo "+";
} else {
// Compare/update
$update_array = [];
$entry = $cache_discovery['vrf_db'][$vrf['vrf_name']];
$vrf_id = $entry['vrf_id'];
foreach ($params_main as $param)
{
if ($insert_array[$param] !== $entry[$param])
{
$update_array[$param] = $insert_array[$param];
}
}
// Update old entries (after migrate)
if (empty($entry['vrf_added']))
{
// State params
foreach ($params_state as $param)
{
if (isset($vrf[$param]))
{
// When not set, use db default
$update_array[$param] = $vrf[$param];
}
}
$update_array['vrf_added'] = $insert_array['vrf_added'];
$update_array['vrf_last_change'] = isset($vrf['vrf_last_change']) && $vrf['vrf_last_change'] < $current_time ? (int)$vrf['vrf_last_change'] : $current_time;
} else {
if (count($update_array)) {
$update_array['vrf_last_change'] = isset($vrf['vrf_last_change']) && $vrf['vrf_last_change'] < $current_time ? (int)$vrf['vrf_last_change'] : $current_time;
}
// For old entries only validate added/changed times (more than 60 sec)
/*foreach ([ 'vrf_added', 'vrf_last_change' ] as $param) {
foreach ([ 'vrf_last_change' ] as $param) {
if (abs($insert_array[$param] - $entry[$param]) > 60)
{
$update_array[$param] = $insert_array[$param];
print_debug("$param: ".$insert_array[$param]." - ".$entry[$param]." = ".($insert_array[$param] - $entry[$param]));
}
}
*/
}
if (safe_count($update_array))
{
dbUpdate($update_array, 'vrfs', '`vrf_id` = ?', [ $vrf_id ]);
$GLOBALS['module_stats'][$module]['updated']++;
} else {
$GLOBALS['module_stats'][$module]['unchanged']++;
}
}
$valid['vrf'][$vrf_name] = $vrf_id;
// VRF ports
if (safe_count($vrf['ifIndex']))
{
$db_update = [];
foreach ($vrf['ifIndex'] as $ifIndex)
{
if ($port = get_port_by_index_cache($device, $ifIndex))
{
$db_update[] = [ 'port_id' => $port['port_id'], 'ifIndex' => $port['ifIndex'], 'device_id' => $device['device_id'], 'ifVrf' => $vrf_id ];
$valid['vrf-ports'][$vrf_name][$port['port_id']] = $vrf_id;
}
}
dbUpdateMulti($db_update, 'ports', [ 'ifVrf' ]);
}
}
// EOF

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,849 @@
<?php
/**
* Observium
*
* This file is part of Observium.
*
* @package observium
* @subpackage entities
* @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2021 Observium Limited
*
*/
/// MOVEME. states.inc.php
function get_bits_state_array($hex, $mib = NULL, $object = NULL, $bits_def = NULL)
{
global $config;
// Fetch validate BITS definition
if (is_array($bits_def))
{
// Use passed bits definitions
$def = $bits_def;
}
else if (strlen($mib) && strlen($object) &&
isset($config['mibs'][$mib]['states_bits'][$object]))
{
$def = $config['mibs'][$mib]['states_bits'][$object];
}
if (empty($def))
{
print_debug("Incorrect BITS state definition passed.");
return NULL;
}
print_debug_vars($def);
//$bit_array = array_reverse(str_split(hex2binmap($hex)));
$bit_array = str_split(hex2binmap($hex));
print_debug_vars($bit_array);
$state_array = [];
foreach ($bit_array as $bit => $value)
{
if ($value)
{
$state_array[$bit] = $def[$bit]['name'];
}
}
return $state_array;
}
/**
* Return normalized state array by type, mib and value (numeric or string)
*
* @param string $type State type, in MIBs definition array $config['mibs'][$mib]['states'][$type]
* @param string $value Polled value for status
* @param string $mib MIB name
* @param string $event_map Polled value for event (this used only for statuses with event condition values)
* @param string $poller_type Poller type (snmp, ipmi, agent)
*
* @return array State array
*/
function get_state_array($type, $value, $mib = '', $event_map = NULL, $poller_type = 'snmp') {
global $config;
$state_array = [ 'value' => FALSE ];
switch ($poller_type) {
case 'agent':
case 'ipmi':
$state = state_string_to_numeric($type, $value, $mib, $poller_type);
if ($state !== FALSE) {
$state_array['value'] = $state; // Numeric value
if (isset(array_values($config[$poller_type]['states'][$type])[0]['match'])) {
// String value
$match_events = [
0 => 'exclude',
1 => 'ok',
2 => 'warning',
3 => 'alert',
4 => 'ignore',
];
$state_array['name'] = trim($value);
$state_array['event'] = $match_events[$state];
} else {
$state_array['name'] = $config[$poller_type]['states'][$type][$state]['name']; // Named value
$state_array['event'] = $config[$poller_type]['states'][$type][$state]['event']; // Event type
}
$state_array['mib'] = $mib;
}
break;
default: // SNMP
$value = trim($value);
$state = state_string_to_numeric($type, $value, $mib);
if ($state !== FALSE) {
if (safe_empty($mib)) {
$mib = state_type_to_mib($type);
}
$state_array['value'] = $state; // Numeric value
$state_def = $config['mibs'][$mib]['states'][$type];
$def_key = array_key_first($state_def);
if (isset($state_def[$def_key]['match'])) {
// String value
$match_events = [
0 => 'exclude',
1 => 'ok',
2 => 'warning',
3 => 'alert',
4 => 'ignore',
];
$state_array['name'] = $value;
if (isset($state_def[$def_key]['name'])) {
// Rare case, for convert numeric states to named, see: GEIST-MIB-V3::waterSensorDampness
foreach ($state_def as $index => $content) {
if (preg_match($content['match'], $value)) {
//return $match_events[$content['event']];
$state_array['name'] = $content['name'];
}
}
}
$state_array['event'] = $match_events[$state];
} else {
// Named value
$state_array['name'] = $config['mibs'][$mib]['states'][$type][$state]['name'];
if (isset($config['mibs'][$mib]['states'][$type][$state]['event_map'])) {
// For events based on additional Oid value, see:
// PowerNet-MIB::emsInputContactStatusInputContactState.1 = INTEGER: contactOpenEMS(2)
// PowerNet-MIB::emsInputContactStatusInputContactNormalState.1 = INTEGER: normallyOpenEMS(2)
// Find event associated event with event_value
$state_array['event'] = $config['mibs'][$mib]['states'][$type][$state]['event_map'][$event_map];
} else {
// Normal static events
$state_array['event'] = $config['mibs'][$mib]['states'][$type][$state]['event']; // Event type
}
}
// Force discovery by event
if (isset($config['mibs'][$mib]['states'][$type][$state]['discovery'])) {
$state_array['discovery'] = $config['mibs'][$mib]['states'][$type][$state]['discovery'];
}
$state_array['mib'] = $mib; // MIB name
}
}
return $state_array;
}
/**
* Converts named oid values to numerical interpretation based on oid descriptions and stored in definitions
*
* @param string $type Sensor type which has definitions in $config['mibs'][$mib]['states'][$type]
* @param mixed $value Value which must be converted
* @param string $mib MIB name
* @param string $poller_type Poller type
*
* @return integer Note, if definition not found or incorrect value, returns FALSE
*/
function state_string_to_numeric($type, $value, $mib = '', $poller_type = 'snmp') {
switch ($poller_type) {
case 'agent':
case 'ipmi':
if (!isset($GLOBALS['config'][$poller_type]['states'][$type])) {
return FALSE;
}
$state_def = $GLOBALS['config'][$poller_type]['states'][$type];
break;
default:
if (safe_empty($mib)) {
$mib = state_type_to_mib($type);
}
$state_def = $GLOBALS['config']['mibs'][$mib]['states'][$type];
}
if (!is_array($state_def)) {
return FALSE;
}
//$state_match = isset(array_values($state_def)[0]['match']);
if (isset($state_def[array_key_first($state_def)]['match'])) {
// different type of statuses - string statuses, not enum
// Match by regex
// See QSAN-SNMP-MIB definitions and others
// 0 -> exclude, 1 -> ok, 2 -> warning, 3 -> alert, 4 -> ignore
$match_events = [
'exclude' => 0,
'ok' => 1,
'warning' => 2,
'alert' => 3,
'ignore' => 4,
];
foreach ($state_def as $index => $content) {
if (preg_match($content['match'], trim($value))) {
return $match_events[$content['event']];
}
}
return 1; // by default ok
}
if (is_intnum($value)) {
// Return value if already numeric
if (isset($state_def[$value])) {
return (int)$value;
}
return FALSE;
}
foreach ($state_def as $index => $content) {
if (strcasecmp($content['name'], trim($value)) == 0) { return $index; }
}
// 0x01
if (str_starts($value, '0x')) {
$int = (int) hexdec($value);
if (isset($state_def[$int])) {
return $int;
}
}
return FALSE;
}
/**
* Helper function for get MIB name by status type.
* Currently we use unique status types over all MIBs
*
* @param string $state_type Unique status type
*
* @return string MIB name corresponding to this type
*/
function state_type_to_mib($state_type)
{
// By first cache all type -> mib from definitions
if (!isset($GLOBALS['cache']['state_type_mib']))
{
$GLOBALS['cache']['state_type_mib'] = array();
// $config['mibs'][$mib]['states']['dskf-mib-hum-state'][0] = array('name' => 'error', 'event' => 'alert');
foreach ($GLOBALS['config']['mibs'] as $mib => $entries)
{
if (!isset($entries['states'])) { continue; }
foreach ($entries['states'] as $type => $entry)
{
if (isset($GLOBALS['cache']['state_type_mib'][$type]))
{
// Disabling because it's annoying for now - pending some rewriting.
//print_warning('Warning, status type name "'.$type.'" for MIB "'.$mib.'" also exist in MIB "'.$GLOBALS['cache']['state_type_mib'][$type].'". Type name MUST be unique!');
}
$GLOBALS['cache']['state_type_mib'][$type] = $mib;
}
}
}
//print_vars($GLOBALS['cache']['state_type_mib']);
return $GLOBALS['cache']['state_type_mib'][$state_type];
}
function discover_status_definition($device, $mib, $entry)
{
echo($entry['oid']. ' [');
// Just append mib name to definition entry, for simple pass to external functions
if (empty($entry['mib']))
{
$entry['mib'] = $mib;
}
// Check that types listed in skip_if_valid_exist have already been found
if (discovery_check_if_type_exist($entry, 'status')) { echo '!]'; return; }
// Check array requirements list
if (discovery_check_requires_pre($device, $entry, 'status')) { echo '!]'; return; }
// Validate if Oid exist for current mib (in case when used generic definitions, ie edgecore)
if (empty($entry['oid_num']))
{
// Use snmptranslate if oid_num not set
$entry['oid_num'] = snmp_translate($entry['oid'], $mib);
if (empty($entry['oid_num']))
{
echo("]");
print_debug("Oid [".$entry['oid']."] not exist for mib [$mib]. Status skipped.");
return;
}
} else {
$entry['oid_num'] = rtrim($entry['oid_num'], '.');
}
// Fetch table or Oids
$table_oids = array('oid', 'oid_descr', 'oid_class', 'oid_map', 'oid_extra', 'oid_entPhysicalIndex');
$status_array = discover_fetch_oids($device, $mib, $entry, $table_oids);
$i = 0; // Used in descr as $i++
$status_count = count($status_array);
foreach ($status_array as $index => $status)
{
$options = array();
$dot_index = strlen($index) ? '.' . $index : '';
$oid_num = $entry['oid_num'] . $dot_index;
//echo PHP_EOL; print_vars($entry); echo PHP_EOL; print_vars($status); echo PHP_EOL; print_vars($descr); echo PHP_EOL;
// %i% can be used in description
$i++;
if (isset($entry['oid_class']) && strlen($status[$entry['oid_class']]))
{
if (isset($entry['map_class'][$status[$entry['oid_class']]]))
{
// Try override measured class by map
$class = $entry['map_class'][$status[$entry['oid_class']]];
if ($class === 'exclude' || !$class)
{
print_debug("Excluded by empty class map value.");
continue; // trigger for exclude statuses
}
} else {
$class = $status[$entry['oid_class']];
}
} else {
$class = $entry['measured'];
}
// Generate specific keys used during rewrites
$status['class'] = nicecase($class); // Class in descr
$status['index'] = $index; // Index in descr
foreach (explode('.', $index) as $k => $i)
{
$status['index'.$k] = $i; // Index parts
}
$status['i'] = $i; // i++ counter in descr
// Check valid exist with entity tags
if (discovery_check_if_type_exist($entry, 'status', $status)) { continue; }
// Check array requirements list
if (discovery_check_requires($device, $entry, $status, 'status')) { continue; }
$value = $status[$entry['oid']];
if (!strlen($value))
{
print_debug("Excluded by empty current value.");
continue;
}
$options = array('entPhysicalClass' => $class);
// Definition based events
if (isset($entry['oid_map']) && $status[$entry['oid_map']])
{
$options['status_map'] = $status[$entry['oid_map']];
}
// Rule-based entity linking.
if ($measured = entity_measured_match_definition($device, $entry, $status, 'status')) {
$options = array_merge($options, $measured);
$status = array_merge($status, $measured); // append to $status for %descr% tags, ie %port_label%
if (empty($class)) {
$options['entPhysicalClass'] = $measured['measured'];
}
} elseif (isset($entry['entPhysicalIndex'])) {
// Just set physical index
$options['entPhysicalIndex'] = array_tag_replace($status, $entry['entPhysicalIndex']);
}
// Generate Description
$descr = entity_descr_definition('status', $entry, $status, $status_count);
// Rename old (converted) RRDs to definition format
if (isset($entry['rename_rrd'])) {
$options['rename_rrd'] = $entry['rename_rrd'];
} elseif (isset($entry['rename_rrd_full'])) {
$options['rename_rrd_full'] = $entry['rename_rrd_full'];
}
discover_status_ng($device, $mib, $entry['oid'], $oid_num, $index, $entry['type'], $descr, $value, $options);
}
echo '] ';
}
// Compatibility wrapper!
function discover_status($device, $numeric_oid, $index, $type, $status_descr, $value = NULL, $options = array(), $poller_type = NULL) {
if (isset($poller_type)) { $options['poller_type'] = $poller_type; }
return discover_status_ng($device, '', '', $numeric_oid, $index, $type, $status_descr, $value, $options);
}
// TESTME needs unit testing
/**
* Discover a new status sensor on a device - called from discover_sensor()
*
* This function adds a status sensor to a device, if it does not already exist.
* Data on the sensor is updated if it has changed, and an event is logged with regards to the changes.
*
* @param array $device Device array status sensor is being discovered on
* @param string $mib SNMP MIB name
* @param string $object SNMP Named Oid of sensor (without index)
* @param string $oid SNMP Numeric Oid of sensor (without index)
* @param string $index SNMP index of status sensor
* @param string $type Type of status sensor (used as key in $config['status_states'])
* @param string $status_descr Description of status sensor
* @param string $value Current value of status sensor
* @param array $options Options
*
* @return bool
*/
function discover_status_ng($device, $mib, $object, $oid, $index, $type, $status_descr, $value = NULL, $options = []) {
global $config;
$poller_type = (isset($options['poller_type']) ? $options['poller_type'] : 'snmp');
$status_deleted = 0;
// Init main
$param_main = [
'oid' => 'status_oid', 'status_descr' => 'status_descr', 'status_deleted' => 'status_deleted',
'index' => 'status_index', 'mib' => 'status_mib', 'object' => 'status_object'
];
// Init optional
$param_opt = [
'entPhysicalIndex', 'entPhysicalClass', 'entPhysicalIndex_measured',
'measured_class', 'measured_entity', 'measured_entity_label', 'status_map'
];
foreach ($param_opt as $key) {
$$key = $options[$key] ?: NULL;
}
$state_array = get_state_array($type, $value, $mib, $status_map, $poller_type);
$state = $state_array['value'];
if ($state === FALSE) {
print_debug("Skipped by unknown state value: $value, $status_descr ");
return FALSE;
}
if ($state_array['event'] === 'exclude') {
print_debug("Skipped by 'exclude' event value: ".$config['status_states'][$type][$state]['name'].", $status_descr ");
return FALSE;
}
$value = $state;
$index = (string)$index; // Convert to string, for correct compare
print_debug("Discover status: [device: ".$device['hostname'].", oid: $oid, index: $index, type: $type, descr: $status_descr, CURRENT: $value, $entPhysicalIndex, $entPhysicalClass]");
// Check status ignore filters
if (entity_descr_check($status_descr, 'status')) { return FALSE; }
//foreach ($config['ignore_sensor'] as $bi) { if (strcasecmp($bi, $status_descr) == 0) { print_debug("Skipped by equals: $bi, $status_descr "); return FALSE; } }
//foreach ($config['ignore_sensor_string'] as $bi) { if (stripos($status_descr, $bi) !== FALSE) { print_debug("Skipped by strpos: $bi, $status_descr "); return FALSE; } }
//foreach ($config['ignore_sensor_regexp'] as $bi) { if (preg_match($bi, $status_descr) > 0) { print_debug("Skipped by regexp: $bi, $status_descr "); return FALSE; } }
$new_definition = $poller_type === 'snmp' && !safe_empty($mib) && !safe_empty($object);
if ($new_definition) {
$where = ' WHERE `device_id` = ? AND `status_mib` = ? AND `status_object` = ? AND `status_type` = ? AND `status_index` = ? AND `poller_type`= ?';
$params = array($device['device_id'], $mib, $object, $type, $index, $poller_type);
$status_exist = dbExist('status', $where, $params);
// Check if old format of status was exist, then rename rrd
if (!$status_exist) {
$old_where = ' WHERE `device_id` = ? AND `status_type` = ? AND `status_index` = ? AND `poller_type`= ?';
$old_index = $object . '.' . $index;
$old_params = array($device['device_id'], $type, $old_index, $poller_type);
if ($status_exist = dbExist('status', $old_where, $old_params)) {
$where = $old_where;
$params = $old_params;
// Rename old rrds without mib & object to new rrd name style
if (!isset($options['rename_rrd'])) {
$options['rename_rrd'] = $type . "-" . $old_index;
}
}
}
} else {
// Old format of definitions
$where = ' WHERE `device_id` = ? AND `status_type` = ? AND `status_index` = ? AND `poller_type`= ?';
$params = array($device['device_id'], $type, $index, $poller_type);
$status_exist = dbExist('status', $where, $params);
}
//if (dbFetchCell('SELECT COUNT(*) FROM `status`' . $where, $params) == '0')
if (!$status_exist) {
$status_insert = [
'poller_type' => $poller_type,
'device_id' => $device['device_id'],
'status_index' => $index,
'status_type' => $type,
//'status_id' => $status_id,
'status_value' => $value,
'status_polled' => time(), //array('NOW()'), // this field is INT(11)
'status_event' => $state_array['event'],
'status_name' => $state_array['name']
];
foreach ($param_main as $key => $column) {
$status_insert[$column] = $$key;
}
foreach ($param_opt as $key)
{
if (is_null($$key)) { $$key = array('NULL'); } // If param null, convert to array(NULL)
$status_insert[$key] = $$key;
}
$status_id = dbInsert($status_insert, 'status');
print_debug("( $status_id inserted )");
echo('+');
log_event("Status added: $entPhysicalClass $type $index $status_descr", $device, 'status', $status_id);
} else {
$status_entry = dbFetchRow('SELECT * FROM `status`' . $where, $params);
$status_id = $status_entry['status_id'];
print_debug_vars($status_entry);
$update = array();
foreach ($param_main as $key => $column)
{
if ($$key != $status_entry[$column])
{
$update[$column] = $$key;
}
}
foreach ($param_opt as $key) {
if ($$key != $status_entry[$key]) {
$update[$key] = !is_null($$key) ? $$key : [ 'NULL' ];
}
}
print_debug_vars($update);
if (count($update)) {
$updated = dbUpdate($update, 'status', '`status_id` = ?', array($status_entry['status_id']));
echo('U');
log_event("Status updated: $entPhysicalClass $type $index $status_descr", $device, 'status', $status_entry['status_id']);
} else {
echo('.');
}
}
// Rename old (converted) RRDs to definition format
// Allow with changing class or without
if (isset($options['rename_rrd_full'])) {
// Compatibility with sensor option
$options['rename_rrd'] = $options['rename_rrd_full'];
}
if (isset($options['rename_rrd'])) {
$rrd_tags = array('index' => $index, 'type' => $type, 'mib' => $mib, 'object' => $object, 'oid' => $object);
$options['rename_rrd'] = array_tag_replace($rrd_tags, $options['rename_rrd']);
$old_rrd = 'status-' . $options['rename_rrd'];
//$new_rrd = 'status-'.$type.'-'.$index;
$new_entry = ['status_descr' => $status_descr, 'status_mib' => $mib, 'status_object' => $object,
'status_type' => $type, 'status_index' => $index, 'poller_type' => $poller_type];
$new_rrd = get_status_rrd($device, $new_entry);
rename_rrd($device, $old_rrd, $new_rrd);
}
if ($new_definition) {
$GLOBALS['valid']['status'][$mib][$object][$index] = 1;
} else {
// without $mib/$object
$GLOBALS['valid']['status']['__'][$type][$index] = 1;
}
return $status_id;
//return TRUE;
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function check_valid_status($device, $valid, $poller_type = 'snmp') {
$entries = dbFetchRows("SELECT * FROM `status` WHERE `device_id` = ? AND `poller_type` = ? AND `status_deleted` = ?", [ $device['device_id'], $poller_type, 0 ]);
if (!safe_empty($entries)) {
foreach ($entries as $entry) {
$index = $entry['status_index'];
$type = $entry['status_type'];
if ($poller_type === 'snmp') {
if (!safe_empty($entry['status_mib']) && !safe_empty($entry['status_object'])) {
// New definitions use Object instead type
$mib = $entry['status_mib'];
$type = $entry['status_object'];
} else {
$mib = '__';
}
} else {
// For ipmi and unix-agent
$mib = 'state';
}
if (!$valid[$mib][$type][$index]) {
echo("-");
print_debug("Status deleted: $index -> $type");
//dbDelete('status', "`status_id` = ?", array($entry['status_id']));
dbUpdate([ 'status_deleted' => '1' ], 'status', '`status_id` = ?', [ $entry['status_id'] ]);
foreach (get_entity_attribs('status', $entry['status_id']) as $attrib_type => $value) {
del_entity_attrib('status', $entry['status_id'], $attrib_type);
}
log_event("Status deleted: ".$entry['status_class']." ".$entry['status_type']." ". $entry['status_index']." ".$entry['status_descr'], $device, 'status', $entry['status_id']);
}
}
}
}
function poll_status($device, &$oid_cache)
{
global $config, $agent_sensors, $ipmi_sensors, $graphs, $table_rows;
$sql = "SELECT * FROM `status`";
//$sql .= " LEFT JOIN `status-state` USING(`status_id`)";
$sql .= " WHERE `device_id` = ? AND `status_deleted` = ?";
$sql .= ' ORDER BY `status_oid`'; // This fix polling some OIDs (when not ordered)
foreach (dbFetchRows($sql, array($device['device_id'], '0')) as $status_db)
{
//print_cli_heading("Status: ".$status_db['status_descr']. "(".$status_db['poller_type'].")", 3);
print_debug("Checking (" . $status_db['poller_type'] . ") " . $status_db['status_descr'] . " ");
// $status_poll = $status_db; // Cache non-humanized status array for use as new status state
if ($status_db['poller_type'] === "snmp")
{
$status_db['status_oid'] = '.' . ltrim($status_db['status_oid'], '.'); // Fix first dot in oid for caching
// Check if a specific poller file exists for this status, else collect via SNMP.
$file = $config['install_dir']."/includes/polling/status/".$status_db['status_type'].".inc.php";
if (is_file($file))
{
include($file);
} else {
// Take value from $oid_cache if we have it, else snmp_get it
if (isset($oid_cache[$status_db['status_oid']]))
{
print_debug("value taken from oid_cache");
$status_value = $oid_cache[$status_db['status_oid']];
} else {
$status_value = snmp_get_oid($device, $status_db['status_oid'], 'SNMPv2-MIB');
}
//$status_value = snmp_fix_numeric($status_value); // Do not use fix, this broke not-enum (string) statuses
}
}
elseif ($status_db['poller_type'] === "agent")
{
if (isset($agent_sensors['state']))
{
$status_value = $agent_sensors['state'][$status_db['status_type']][$status_db['status_index']]['current'];
} else {
print_warning("No agent status data available.");
continue;
}
}
elseif ($status_db['poller_type'] === "ipmi")
{
if (isset($ipmi_sensors['state']))
{
$status_value = $ipmi_sensors['state'][$status_db['status_type']][$status_db['status_index']]['current'];
} else {
print_warning("No IPMI status data available.");
continue;
}
} else {
print_warning("Unknown status poller type.");
continue;
}
$status_polled_time = time(); // Store polled time for current status
// Write new value and humanize (for alert checks)
$state_array = get_state_array($status_db['status_type'], $status_value, $status_db['status_mib'], $status_db['status_map'], $status_db['poller_type']);
$status_value = $state_array['value']; // Override status_value by numeric for "pseudo" (string) statuses
$status_poll['status_value'] = $state_array['value'];
$status_poll['status_name'] = $state_array['name'];
if ($status_db['status_ignore'] || $status_db['status_disable'])
{
$status_poll['status_event'] = 'ignore';
} else {
$status_poll['status_event'] = $state_array['event'];
}
// Force ignore state if measured entity is in Shutdown state
$measured_class = $status_db['measured_class'];
if ($status_poll['status_event'] === 'alert' && is_numeric($status_db['measured_entity']) &&
isset($config['sensors'][$measured_class]['ignore_shutdown']) && $config['sensors'][$measured_class]['ignore_shutdown'])
{
$measured_entity = get_entity_by_id_cache($measured_class, $status_db['measured_entity']);
print_debug_vars($measured_entity);
// Currently only for ports
if (isset($measured_entity['ifAdminStatus']) && $measured_entity['ifAdminStatus'] === 'down')
{
$status_poll['status_event'] = 'ignore';
}
}
// If last change never set, use current time
if (empty($status_db['status_last_change']))
{
$status_db['status_last_change'] = $status_polled_time;
}
if ($status_poll['status_event'] != $status_db['status_event'])
{
// Status event changed, log and set status_last_change
$status_poll['status_last_change'] = $status_polled_time;
if ($status_poll['status_event'] === 'ignore')
{
print_message("[%ystatus Ignored%n]", 'color');
}
elseif ($status_db['status_event'] != '')
{
// If old state not empty and new state not equals to new state
$msg = 'Status ' . ucfirst($status_poll['status_event']) . ': ' . $device['hostname'] . ' ' . $status_db['status_descr'] .
' entered ' . strtoupper($status_poll['status_event']) . ' state: ' . $status_poll['status_name'] .
' (previous: ' . $status_db['status_name'] . ')';
if (isset($config['entity_events'][$status_poll['status_event']]))
{
$severity = $config['entity_events'][$status_poll['status_event']]['severity'];
} else {
$severity = 'informational';
}
log_event($msg, $device, 'status', $status_db['status_id'], $severity);
// Trick for fast rediscover sensors if associated status changed
// See in MIKROTIK-MIB::mtxrPOEStatus definition
$old_state_array = get_state_array($status_db['status_type'], $status_db['status_value'], $status_db['status_mib'], $status_db['status_map'], $status_db['poller_type']);
if (isset($old_state_array['discovery']) && is_module_enabled($device, $old_state_array['discovery'], 'discovery'))
{
force_discovery($device, $old_state_array['discovery']);
print_debug("Module ${old_state_array['discovery']} force for discovery by changed status type ${status_db['status_mib']}::${status_db['status_object']}");
}
}
} else {
// If status not changed, leave old last_change
$status_poll['status_last_change'] = $status_db['status_last_change'];
}
print_debug_vars($status_poll);
// Send statistics array via AMQP/JSON if AMQP is enabled globally and for the ports module
if ($config['amqp']['enable'] == TRUE && $config['amqp']['modules']['status'])
{
$json_data = array('value' => $status_value);
messagebus_send(array('attribs' => array('t' => time(), 'device' => $device['hostname'], 'device_id' => $device['device_id'],
'e_type' => 'status', 'e_type' => $status_db['status_type'], 'e_index' => $status_db['status_index']), 'data' => $json_data));
}
// Update StatsD/Carbon
if ($config['statsd']['enable'] == TRUE)
{
StatsD::gauge(str_replace(".", "_", $device['hostname']).'.'.'status'.'.'.$status_db['status_class'].'.'.$status_db['status_type'].'.'.$status_db['status_index'], $status_value);
}
// Update RRD - FIXME - can't convert to NG because filename is dynamic! new function should return index instead of filename.
$rrd_file = get_status_rrd($device, $status_db);
rrdtool_create($device, $rrd_file, "DS:status:GAUGE:600:-20000:U");
rrdtool_update($device, $rrd_file,"N:$status_value");
// Enable graph
$graphs[$status_db['status']] = TRUE;
// Check alerts
$metrics = array();
$metrics['status_value'] = $status_value;
$metrics['status_name'] = $status_poll['status_name'];
$metrics['status_name_uptime'] = $status_polled_time - $status_poll['status_last_change'];
$metrics['status_event'] = $status_poll['status_event'];
//print_cli_data("Event (State)", $status_poll['status_event'] ." (".$status_poll['status_name'].")", 3);
$table_rows[] = array($status_db['status_descr'], $status_db['status_type'], $status_db['status_index'] ,$status_db['poller_type'],
$status_poll['status_name'], $status_poll['status_event'], format_unixtime($status_poll['status_last_change']));
check_entity('status', $status_db, $metrics);
// Add to MultiUpdate SQL State
$GLOBALS['multi_update_db'][] = array(
'status_id' => $status_db['status_id'], // UNIQUE index
'status_value' => $status_value,
'status_name' => $status_poll['status_name'],
'status_event' => $status_poll['status_event'],
'status_last_change' => $status_poll['status_last_change'],
'status_polled' => $status_polled_time);
//dbUpdate(array('status_value' => $status_value,
// 'status_name' => $status_poll['status_name'],
// 'status_event' => $status_poll['status_event'],
// 'status_last_change' => $status_poll['status_last_change'],
// 'status_polled' => $status_polled_time),
// 'status', '`status_id` = ?', array($status_db['status_id']));
}
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_status_rrd($device, $status)
{
global $config;
# For IPMI, sensors tend to change order, and there is no index, so we prefer to use the description as key here.
if ((isset($config['os'][$device['os']]['sensor_descr']) && $config['os'][$device['os']]['sensor_descr']) || // per os definition
(isset($config['mibs'][$status['status_mib']]['sensor_descr']) && $config['mibs'][$status['status_mib']]['sensor_descr']) || // per mib definition
($status['poller_type'] != "snmp" && $status['poller_type'] != ''))
{
$index = $status['status_descr'];
} else {
$index = $status['status_index'];
}
if (strlen($status['status_mib']) && strlen($status['status_object']))
{
// for discover_status_ng(), note here is just status index
$rrd_file = "status-" . $status['status_mib'] . "-" . $status['status_object'] . "-" . $index . ".rrd";
} else {
// for discover_status(), note index == "%object%.%index%"
$rrd_file = "status-" . $status['status_type'] . "-" . $index . ".rrd";
}
return($rrd_file);
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_status_by_id($status_id)
{
if (is_numeric($status_id))
{
$status = dbFetchRow("SELECT * FROM `status` WHERE `status_id` = ?", array($status_id));
}
if (is_array($status))
{
return $status;
} else {
return FALSE;
}
}
// EOF

View File

@ -0,0 +1,248 @@
<?php
/**
* Observium
*
* This file is part of Observium.
*
* @package observium
* @subpackage discovery
* @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2021 Observium Limited
*
*/
//MOVEME. to includes/entities/wifi.inc.php
// Discover an entity by populating/updating a database table and returning an id
/* I think this is still experimental by adama, pls see discover_entity_definition() common as other entities
function discover_entity($device_id, $entity_type, $data)
{
if (is_array($GLOBALS['config']['entities'][$entity_type])) {
$def = $GLOBALS['config']['entities'][$entity_type];
$index = $data[$def['table_fields']['index']];
$params = $def['params'];
if (isset($params['table_fields']['index']) && is_array())
{
} elseif (isset($params['table_fields']['index'])) {
} else {
}
if (is_array($GLOBALS['cache'][$def['table']][$index])) {
echo 'Exists';
$db = $GLOBALS['cache'][$def['table']][$index];
$id = $db[$def['table_fields']['id']];
echo 'exists:'.$id.PHP_EOL;
$update = array();
foreach ($params as $param)
{
if ($data[$param] != $db[$param]) { $update[$param] = $data[$param]; }
}
if (count($update))
{
dbUpdate($update, $def['table'], '`'.$def['table_fields']['id'].'` = ?', array($id));
echo('U');
} else {
echo('.');
}
} else {
echo 'Doesnt Exist';
$insert = array();
$insert['device_id'] = $device_id;
foreach ($params as $param)
{
$insert[$param] = $data[$param];
if ($data[$param] == NULL) { $insert[$param] = array('NULL'); }
}
$id = dbInsert($insert, $def['table']);
echo("+");
$params[$def['table_fields']['id']] = $id;
// Populate cache with this entry. Maybe we need it.
$GLOBALS['cache'][$def['table']][$index] = $params;
}
} else {
print_error("Entity Type does not exist. This is a relatively serious error.");
return FALSE;
}
return $id;
}
*/
/**
* Discover WIFI Access Point. Returns ap_id.
*
* @param array|integer $device
* @param array $ap
*
* @return integer
*/
function discover_wifi_ap($device, $ap) {
$device_id = is_array($device) ? $device['device_id'] : $device;
$params = [ 'ap_mib', 'ap_index',
'ap_number', 'ap_name', 'ap_address', 'ap_serial', 'ap_model', 'ap_location', 'ap_fingerprint', // not required
'ap_status', 'ap_admin_status' ];
if (is_array($GLOBALS['cache']['wifi_aps'][$ap['ap_index']])) {
/* Only insert new APs, for polling ability
// Database entry exists. Lets update it!
$ap_db = $GLOBALS['cache']['wifi_aps'][$ap['ap_index']];
$ap_id = $ap_db['wifi_ap_id'];
echo 'exists:'.$ap_id.PHP_EOL;
$update = [];
foreach ($params as $param) {
if ($ap[$param] != $ap_db[$param]) {
if (safe_empty($ap[$param])) {
$update[$param] = [ 'NULL' ];
} else {
$update[$param] = $ap[$param];
}
}
}
if (count($update)) {
dbUpdate($update, 'wifi_aps', '`wifi_ap_id` = ?', array($ap_db['wifi_ap_id']));
echo('U');
} else {
echo('.');
}
*/
$ap_id = $GLOBALS['cache']['wifi_aps'][$ap['ap_index']]['wifi_ap_id'];
if ($GLOBALS['cache']['wifi_aps'][$ap['ap_index']]['deleted']) {
dbUpdate([ 'deleted' => 0 ], 'wifi_aps', '`wifi_ap_id` = ?', [ $ap_id ]);
$GLOBALS['cache']['wifi_aps'][$ap['ap_index']]['deleted'] = 0;
}
} else {
// Database entry doesn't exist. Lets create it!
$insert = [];
$insert['device_id'] = $device_id;
foreach ($params as $param) {
$insert[$param] = $ap[$param];
if (safe_empty($ap[$param])) {
$insert[$param] = array('NULL');
}
}
print_debug_vars($insert);
$ap_id = dbInsert($insert, 'wifi_aps');
echo("+");
$params['wifi_ap_id'] = $ap_id;
// Populate cache with this entry. Maybe we need it.
$GLOBALS['cache']['wifi_aps'][$ap['ap_index']] = $params;
}
$GLOBALS['valid']['wifi']['aps'][$ap['ap_index']] = $ap_id;
return $ap_id;
}
function discover_wifi_wlan($device_id, $wlan)
{
$params = array('wlan_admin_status', 'wlan_beacon_period', 'wlan_bssid', 'wlan_bss_type', 'wlan_channel', 'wlan_dtim_period', 'wlan_frag_thresh',
'wlan_index', 'wlan_igmp_snoop', 'wlan_name', 'wlan_prot_mode', 'wlan_radio_mode', 'wlan_rts_thresh',
'wlan_ssid', 'wlan_ssid_bcast', 'wlan_vlan_id');
if (is_array($GLOBALS['cache']['wifi_wlans'][$wlan['wlan_index']]))
{
// Database entry exists. Lets update it!
$wlan_db = $GLOBALS['cache']['wifi_wlans'][$wlan['wlan_index']];
$wlan_id = $wlan_db['wlan_id'];
$update = array();
foreach ($params as $param)
{
if ($wlan[$param] != $wlan_db[$param]) { $update[$param] = $wlan[$param]; }
}
if (count($update))
{
dbUpdate($update, 'wifi_wlans', '`wlan_id` = ?', array($wlan_db['wlan_id']));
echo('U');
} else {
echo('.');
}
} else {
// Database entry doesn't exist. Lets create it!
$insert = array();
$insert['device_id'] = $device_id;
foreach ($params as $param)
{
$insert[$param] = $wlan[$param];
if (is_null($wlan[$param])) { $insert[$param] = array('NULL'); }
}
$wlan_id = dbInsert($insert, 'wifi_wlans');
echo("+");
}
return $wlan_id;
}
function discover_wifi_radio($device_id, $radio) {
$params = [ 'radio_ap', 'radio_mib', 'radio_number', 'radio_util', 'radio_type', 'radio_status',
'radio_clients', 'radio_txpower', 'radio_channel', 'radio_mac', 'radio_protection', 'radio_bsstype' ];
if (is_array($GLOBALS['cache']['wifi_radios'][$radio['radio_ap']][$radio['radio_number']])) {
$radio_db = $GLOBALS['cache']['wifi_radios'][$radio['radio_ap']][$radio['radio_number']];
}
if (!isset($radio_db['wifi_radio_id']))
{
$insert = array();
$insert['device_id'] = $device_id;
foreach ($params as $param)
{
$insert[$param] = $radio[$param];
if (is_null($radio[$param])) { $insert[$param] = array('NULL'); }
}
$wifi_radio_id = dbInsert($insert, 'wifi_radios');
echo("+");
} else {
$update = array();
foreach ($params as $param)
{
if ($radio[$param] != $radio_db[$param]) { $update[$param] = $radio[$param]; }
}
if (count($update))
{
dbUpdate($update, 'wifi_radios', '`wifi_radio_id` = ?', array($radio_db['wifi_radio_id']));
echo('U');
} else {
echo('.');
}
}
$GLOBALS['valid']['wifi']['radio'][$radio['radio_mib']][$wifi_radio_id] = 1;
}
// EOF