1<?php
2/**
3 * Observium
4 *
5 *   This file is part of Observium.
6 *
7 * @package    observium
8 * @subpackage entities
9 * @copyright  (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited
10 *
11 *
12 */
13
14// Get port id  by ip address (using cache)
15// DOCME needs phpdoc block
16// TESTME needs unit testing
17function get_port_id_by_ip_cache($device, $ip)
18{
19  global $cache;
20
21  $ip_version = get_ip_version($ip);
22
23  if (is_array($device) && isset($device['device_id']))
24  {
25    $device_id = $device['device_id'];
26  }
27  elseif (is_numeric($device))
28  {
29    $device_id = $device;
30  }
31  if (!isset($device_id) || !$ip_version)
32  {
33    print_error("Invalid arguments passed into function get_port_id_by_ip_cache(). Please report to developers.");
34    return FALSE;
35  }
36
37  if ($ip_version == 6)
38  {
39    $ip = Net_IPv6::uncompress($ip, TRUE);
40  }
41
42  if (isset($cache['port_ip'][$device_id][$ip]))
43  {
44    return $cache['port_ip'][$device_id][$ip];
45  }
46
47  $ips = dbFetchRows('SELECT `port_id`, `ifOperStatus`, `ifAdminStatus` FROM `ipv'.$ip_version.'_addresses`
48                      LEFT JOIN `ports` USING(`port_id`)
49                      WHERE `deleted` = 0 AND `device_id` = ? AND `ipv'.$ip_version.'_address` = ?', array($device_id, $ip));
50  if (count($ips) === 1)
51  {
52    // Simple
53    $port = current($ips);
54    //return $port['port_id'];
55  } else {
56    foreach ($ips as $entry)
57    {
58      if ($entry['ifAdminStatus'] == 'up' && $entry['ifOperStatus'] == 'up')
59      {
60        // First UP entry
61        $port = $entry;
62        break;
63      }
64      elseif ($entry['ifAdminStatus'] == 'up')
65      {
66        // Admin up, but port down or other state
67        $ips_up[]   = $entry;
68      } else {
69        // Admin down
70        $ips_down[] = $entry;
71      }
72    }
73    if (!isset($port))
74    {
75      if ($ips_up)
76      {
77        $port = current($ips_up);
78      } else {
79        $port = current($ips_down);
80      }
81    }
82  }
83  $cache['port_ip'][$device_id][$ip] = $port['port_id'] ? $port['port_id'] : FALSE;
84
85  return $cache['port_ip'][$device_id][$ip];
86
87}
88
89function get_port_by_ent_index($device, $entPhysicalIndex, $allow_snmp = FALSE)
90{
91  $mib = 'ENTITY-MIB';
92  if (!is_numeric($entPhysicalIndex) ||
93    !is_numeric($device['device_id']) ||
94    !is_device_mib($device, $mib))
95  {
96    return FALSE;
97  }
98
99  $allow_snmp = $allow_snmp || is_cli(); // Allow snmpwalk queries in poller/discovery or if in wui passed TRUE!
100
101  if (isset($GLOBALS['cache']['snmp'][$mib][$device['device_id']]))
102  {
103    // Cached
104    $entity_array = $GLOBALS['cache']['snmp'][$mib][$device['device_id']];
105    if (empty($entity_array))
106    {
107      // Force DB queries
108      $allow_snmp = FALSE;
109    }
110  }
111  elseif ($allow_snmp)
112  {
113    // Inventory module disabled, this DB empty, try to cache
114    $entity_array = array();
115    $oids = array('entPhysicalDescr', 'entPhysicalName', 'entPhysicalClass', 'entPhysicalContainedIn', 'entPhysicalParentRelPos');
116    if (is_device_mib($device, 'ARISTA-ENTITY-SENSOR-MIB'))
117    {
118      $oids[] = 'entPhysicalAlias';
119    }
120    foreach ($oids as $oid)
121    {
122      $entity_array = snmpwalk_cache_multi_oid($device, $oid, $entity_array, snmp_mib_entity_vendortype($device, 'ENTITY-MIB'));
123      if (!$GLOBALS['snmp_status']) { break; }
124    }
125    $entity_array = snmpwalk_cache_twopart_oid($device, 'entAliasMappingIdentifier', $entity_array, 'ENTITY-MIB:IF-MIB');
126    if (empty($entity_array))
127    {
128      // Force DB queries
129      $allow_snmp = FALSE;
130    }
131    $GLOBALS['cache']['snmp'][$mib][$device['device_id']] = $entity_array;
132  } else {
133    // Or try to use DB
134  }
135
136  //print_debug_vars($entity_array);
137  $sensor_index = $entPhysicalIndex; // Initial ifIndex
138  $sensor_name  = '';
139  do
140  {
141    if ($allow_snmp)
142    {
143      // SNMP (discovery)
144      $sensor_port = $entity_array[$sensor_index];
145    } else {
146      // DB (web)
147      $sensor_port = dbFetchRow('SELECT * FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalIndex` = ?', array($device['device_id'], $sensor_index));
148    }
149    print_debug_vars($sensor_index, 1);
150    print_debug_vars($sensor_port, 1);
151
152    if ($sensor_port['entPhysicalClass'] == 'sensor')
153    {
154      // Need to store initial sensor name, for multi-lane ports
155      $sensor_name = $sensor_port['entPhysicalName'];
156    }
157
158    if ($sensor_port['entPhysicalClass'] === 'port')
159    {
160      // Port found, get mapped ifIndex
161      unset($entAliasMappingIdentifier);
162      foreach (array(0, 1, 2) as $i)
163      {
164        if (isset($sensor_port[$i]['entAliasMappingIdentifier']))
165        {
166          $entAliasMappingIdentifier = $sensor_port[$i]['entAliasMappingIdentifier'];
167          break;
168        }
169      }
170      if (isset($entAliasMappingIdentifier) && str_contains($entAliasMappingIdentifier, 'fIndex'))
171      {
172        list(, $ifIndex) = explode('.', $entAliasMappingIdentifier);
173
174        $port = get_port_by_index_cache($device['device_id'], $ifIndex);
175        if (is_array($port))
176        {
177          // Hola, port really found
178          //$options['entPhysicalIndex_measured'] = $ifIndex;
179          //$options['measured_class']  = 'port';
180          //$options['measured_entity'] = $port['port_id'];
181          print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port['port_id']);
182          return $port;
183        }
184      }
185      elseif (!$allow_snmp && $sensor_port['ifIndex'])
186      {
187        $ifIndex = $sensor_port['ifIndex'];
188        $port = get_port_by_index_cache($device['device_id'], $ifIndex);
189        print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port['port_id']);
190        return $port;
191      }
192
193      break; // Exit do-while
194    }
195    elseif ($device['os'] == 'arista_eos' && $sensor_port['entPhysicalClass'] == 'container' && strlen($sensor_port['entPhysicalAlias']))
196    {
197      // Arista not have entAliasMappingIdentifier, but used entPhysicalAlias as ifDescr
198      $port_id = get_port_id_by_ifDescr($device['device_id'], $sensor_port['entPhysicalAlias']);
199      if (is_numeric($port_id))
200      {
201        // Hola, port really found
202        $port    = get_port_by_id($port_id);
203        $ifIndex = $port['ifIndex'];
204        //$options['entPhysicalIndex_measured'] = $ifIndex;
205        //$options['measured_class']  = 'port';
206        //$options['measured_entity'] = $port_id;
207        print_debug("Port is found: ifIndex = $ifIndex, port_id = " . $port_id);
208        return $port;
209        //break; // Exit do-while
210      }
211      $sensor_index = $sensor_port['entPhysicalContainedIn']; // Next ifIndex
212    }
213    elseif ($sensor_index == $sensor_port['entPhysicalContainedIn'])
214    {
215      break; // Break if current index same as next to avoid loop
216    } else {
217      $sensor_index = $sensor_port['entPhysicalContainedIn']; // Next ifIndex
218
219      // See: http://jira.observium.org/browse/OBS-2295
220      // IOS-XE and IOS-XR can store in module index both: sensors and port
221      $sensor_transceiver = $sensor_port['entPhysicalClass'] == 'sensor' &&
222        str_icontains($sensor_port['entPhysicalName'] . $sensor_port['entPhysicalDescr'] . $sensor_port['entPhysicalVendorType'], array('transceiver', '-PORT-'));
223      // This is multi-lane optical transceiver, ie 100G, 40G, multiple sensors for each port
224      $sensor_multilane   = $sensor_port['entPhysicalClass'] == 'container' &&
225        (in_array($sensor_port['entPhysicalVendorType'], ['cevContainer40GigBasePort', 'cevContainerCXP', 'cevContainerCPAK', ]) || // Known Cisco specific containers
226          str_contains($sensor_port['entPhysicalName'] . $sensor_port['entPhysicalDescr'], array('Optical')));
227      if ($sensor_transceiver)
228      {
229        $tmp_index = dbFetchCell('SELECT `entPhysicalIndex` FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalContainedIn` = ? AND `entPhysicalClass` = ?', array($device['device_id'], $sensor_index, 'port'));
230        if (is_numeric($tmp_index) && $tmp_index > 0)
231        {
232          // If port index found, try this entPhysicalIndex in next round
233          $sensor_index = $tmp_index;
234        }
235      }
236      elseif ($sensor_multilane)
237      {
238        $entries = dbFetchRow('SELECT `entPhysicalIndex`, `entPhysicalName` FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalContainedIn` = ? AND `entPhysicalClass` = ?', array($device['device_id'], $sensor_index, 'port'));
239        if (count($entries) > 1 &&
240          preg_match('/(?<start>\D{2})(?<num>\d+(?:\/\d+)+).*Lane\s*(?<lane>\d+)/', $sensor_name, $matches)) // detect port numeric part and lane
241        {
242          // There is each Line associated with breakout port, mostly is QSFP+ 40G
243          // FortyGigE0/0/0/0-Tx Lane 1 Power -> 0/RP0-TenGigE0/0/0/0/1
244          // FortyGigE0/0/0/0-Tx Lane 2 Power -> 0/RP0-TenGigE0/0/0/0/2
245          $lane_num = $matches['start'] . $matches['num'] . '/' . $matches['lane']; // FortyGigE0/0/0/0-Tx Lane 1 -> gE0/0/0/0/1
246          foreach ($entries as $entry)
247          {
248            if (str_ends($entry['entPhysicalName'], $lane_num))
249            {
250              $sensor_index = $entry['entPhysicalIndex'];
251              break;
252            }
253          }
254
255        }
256        elseif (is_numeric($entries[0]['entPhysicalIndex']) && $entries[0]['entPhysicalIndex'] > 0)
257        {
258          // Single multi-lane port association, ie 100G
259          $sensor_index = $entries[0]['entPhysicalIndex'];
260        }
261      }
262    }
263    // NOTE for self: entPhysicalParentRelPos >= 0 because on iosxr trouble
264  } while ($sensor_port['entPhysicalClass'] !== 'port' && $sensor_port['entPhysicalContainedIn'] > 0 && ($sensor_port['entPhysicalParentRelPos'] >= 0 || $device['os'] == 'arista_eos'));
265
266}
267
268// Get port array by ifIndex (using cache)
269// DOCME needs phpdoc block
270// TESTME needs unit testing
271function get_port_by_index_cache($device, $ifIndex, $deleted = 0)
272{
273  global $cache;
274
275  if (is_array($device) && isset($device['device_id']))
276  {
277    $device_id = $device['device_id'];
278  }
279  elseif (is_numeric($device))
280  {
281    $device_id = $device;
282  }
283  if (!isset($device_id) || !is_numeric($ifIndex))
284  {
285    print_error("Invalid arguments passed into function get_port_by_index_cache(). Please report to developers.");
286  }
287
288  if (isset($cache['port_index'][$device_id][$ifIndex]) && is_numeric($cache['port_index'][$device_id][$ifIndex]))
289  {
290    $id = $cache['port_index'][$device_id][$ifIndex];
291  } else {
292    $deleted = $deleted ? 1 : 0; // Just convert boolean to 0 or 1
293
294    $id = dbFetchCell("SELECT `port_id` FROM `ports` WHERE `device_id` = ? AND `ifIndex` = ? AND `deleted` = ? LIMIT 1", array($device_id, $ifIndex, $deleted));
295    if (!$deleted && is_numeric($id))
296    {
297      // Cache port IDs (except deleted)
298      $cache['port_index'][$device_id][$ifIndex] = $id;
299    }
300  }
301
302  $port = get_port_by_id_cache($id);
303  if (is_array($port)) { return $port; }
304
305  return FALSE;
306}
307
308// Get port array by ifIndex
309// DOCME needs phpdoc block
310// TESTME needs unit testing
311function get_port_by_ifIndex($device_id, $ifIndex)
312{
313  $port = dbFetchRow("SELECT * FROM `ports` WHERE `device_id` = ? AND `ifIndex` = ? LIMIT 1", array($device_id, $ifIndex));
314
315  if (is_array($port))
316  {
317    humanize_port($port);
318    return $port;
319  }
320
321  return FALSE;
322}
323
324// Get port ID by ifDescr (i.e. 'TenGigabitEthernet1/1') or ifName (i.e. 'Te1/1')
325// DOCME needs phpdoc block
326// TESTME needs unit testing
327function get_port_id_by_ifDescr($device_id, $ifDescr, $deleted = 0)
328{
329  $port_id = dbFetchCell("SELECT `port_id` FROM `ports` WHERE `device_id` = ? AND (`ifDescr` = ? OR `ifName` = ?) AND `deleted` = ? LIMIT 1", array($device_id, $ifDescr, $ifDescr, $deleted));
330
331  if (is_numeric($port_id))
332  {
333    return $port_id;
334  } else {
335    return FALSE;
336  }
337}
338
339// Get port ID by ifAlias (interface description)
340// DOCME needs phpdoc block
341// TESTME needs unit testing
342function get_port_id_by_ifAlias($device_id, $ifAlias, $deleted = 0)
343{
344  $port_id = dbFetchCell("SELECT `port_id` FROM `ports` WHERE `device_id` = ? AND `ifAlias` = ? AND `deleted` = ? LIMIT 1", array($device_id, $ifAlias, $deleted));
345
346  if (is_numeric($port_id))
347  {
348    return $port_id;
349  } else {
350    return FALSE;
351  }
352}
353
354// Get port ID by customer params (see http://www.observium.org/wiki/Interface_Description_Parsing)
355// DOCME needs phpdoc block
356// TESTME needs unit testing
357function get_port_id_by_customer($customer)
358{
359  $where = ' WHERE 1';
360  if (is_array($customer))
361  {
362    foreach ($customer as $var => $value)
363    {
364      if ($value != '')
365      {
366        switch ($var)
367        {
368          case 'device':
369          case 'device_id':
370            $where .= generate_query_values($value, 'device_id');
371            break;
372          case 'type':
373          case 'descr':
374          case 'circuit':
375          case 'speed':
376          case 'notes':
377            $where .= generate_query_values($value, 'port_descr_'.$var);
378            break;
379        }
380      }
381    }
382  } else {
383    return FALSE;
384  }
385
386  $query = 'SELECT `port_id` FROM `ports` ' . $where . ' ORDER BY `ifOperStatus` DESC';
387  $ids = dbFetchColumn($query);
388
389  //print_vars($ids);
390  switch (count($ids))
391  {
392    case 0:
393      return FALSE;
394    case 1:
395      return $ids[0];
396      break;
397    default:
398      foreach ($ids as $port_id)
399      {
400        $port = get_port_by_id_cache($port_id);
401        $device = device_by_id_cache($port['device_id']);
402        if ($device['disabled'] || !$device['status'])
403        {
404          continue; // switch to next ID
405        }
406        break;
407      }
408      return $port_id;
409  }
410  return FALSE;
411}
412
413// Delete port from database and associated rrd files
414// DOCME needs phpdoc block
415// TESTME needs unit testing
416function delete_port($int_id, $delete_rrd = TRUE)
417{
418  global $config;
419
420  $port = dbFetchRow("SELECT * FROM `ports`
421                      LEFT JOIN `devices` USING (`device_id`)
422                      WHERE `port_id` = ?", array($int_id));
423  $ret = "> Deleted interface from ".$port['hostname'].": id=$int_id (".$port['ifDescr'].")\n";
424
425  // Remove entities from common tables
426  $deleted_entities = array();
427  foreach ($config['entity_tables'] as $table)
428  {
429    $where = '`entity_type` = ?' . generate_query_values($int_id, 'entity_id');
430    $table_status = dbDelete($table, $where, array('port'));
431    if ($table_status) { $deleted_entities['port'] = 1; }
432  }
433  if (count($deleted_entities))
434  {
435    $ret .= ' * Deleted common entity entries linked to port.' . PHP_EOL;
436  }
437
438  // FIXME, move to definitions
439  $port_tables = array('eigrp_ports', 'ipv4_addresses', 'ipv6_addresses',
440                       'ip_mac', 'juniAtmVp', 'mac_accounting', 'ospf_nbrs', 'ospf_ports',
441                       'ports_adsl', 'ports_cbqos', 'ports_vlans', 'pseudowires', 'vlans_fdb',
442                       'neighbours', 'ports');
443  $deleted_tables = [];
444  foreach ($port_tables as $table)
445  {
446    $table_status = dbDelete($table, "`port_id` = ?", array($int_id));
447    if ($table_status) { $deleted_tables[] = $table; }
448  }
449
450  $table_status = dbDelete('ports_stack', "`port_id_high` = ?  OR `port_id_low` = ?",    array($int_id, $int_id));
451  if ($table_status) { $deleted_tables[] = 'ports_stack'; }
452  $table_status = dbDelete('entity_permissions', "`entity_type` = 'port' AND `entity_id` = ?", array($int_id));
453  if ($table_status) { $deleted_tables[] = 'entity_permissions'; }
454  $table_status = dbDelete('alert_table', "`entity_type` = 'port' AND `entity_id` = ?", array($int_id));
455  if ($table_status) { $deleted_tables[] = 'alert_table'; }
456  $table_status = dbDelete('group_table', "`entity_type` = 'port' AND `entity_id` = ?", array($int_id));
457  if ($table_status) { $deleted_tables[] = 'group_table'; }
458
459  $ret .= ' * Deleted interface entries from tables: '.implode(', ', $deleted_tables).PHP_EOL;
460
461  if ($delete_rrd)
462  {
463    $rrd_types = array('adsl', 'dot3', 'fdbcount', 'poe', NULL);
464    $deleted_rrds = [];
465    foreach ($rrd_types as $type)
466    {
467      $rrdfile = get_port_rrdfilename($port, $type, TRUE);
468      if (is_file($rrdfile))
469      {
470        unlink($rrdfile);
471        $deleted_rrds[] = $rrdfile;
472      }
473    }
474    $ret .= ' * Deleted interface RRD files: ' . implode(', ', $deleted_rrds) . PHP_EOL;
475  }
476
477  return $ret;
478}
479
480// DOCME needs phpdoc block
481// TESTME needs unit testing
482function get_port_rrdindex($port)
483{
484  global $config;
485
486  $device = device_by_id_cache($port['device_id']);
487
488  $device_identifier = strtolower($config['os'][$device['os']]['port_rrd_identifier']);
489
490  // default to ifIndex
491  $this_port_identifier = $port['ifIndex'];
492
493  if ($device_identifier == "ifname" && $port['ifName'] != "")
494  {
495    $this_port_identifier = strtolower(str_replace("/", "-", $port['ifName']));
496  }
497
498  return $this_port_identifier;
499}
500
501// CLEANME DEPRECATED
502function get_port_rrdfilename($port, $suffix = NULL, $fullpath = FALSE)
503{
504  $this_port_identifier = get_port_rrdindex($port);
505
506  if ($suffix == "")
507  {
508    $filename = "port-" . $this_port_identifier . ".rrd";
509  } else {
510    $filename = "port-" . $this_port_identifier . "-" . $suffix . ".rrd";
511  }
512
513  if ($fullpath)
514  {
515    $device   = device_by_id_cache($port['device_id']);
516    $filename = get_rrd_path($device, $filename);
517  }
518
519  return $filename;
520}
521
522// EOF