Current File : /home/escuelai/public_html/it/src/Inventory/Asset/NetworkPort.php |
<?php
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2022 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @copyright 2010-2022 by the FusionInventory Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/
namespace Glpi\Inventory\Asset;
use Glpi\Inventory\Conf;
use Glpi\Inventory\FilesToJSON;
use NetworkPortType;
use QueryParam;
use RuleImportAssetCollection;
use Toolbox;
use Unmanaged;
class NetworkPort extends InventoryAsset
{
use InventoryNetworkPort {
handlePorts as protected handlePortsTrait;
}
private $connections = [];
private $aggregates = [];
private $vlans = [];
private $connection_ports = [];
private $current_port;
private $current_connection;
private $vlan_stmt;
private $pvlan_stmt;
public function prepare(): array
{
$this->connections = [];
$this->aggregates = [];
$this->vlans = [];
$this->extra_data['\Glpi\Inventory\Asset\\' . $this->item->getType()] = null;
$mapping = [
'ifname' => 'name',
'ifnumber' => 'logical_number',
'ifportduplex' => 'portduplex',
'ifinoctets' => 'ifinbytes',
'ips' => 'ipaddress',
'ifoutoctets' => 'ifoutbytes'
];
foreach ($this->data as $k => &$val) {
$keep = true;
if (!property_exists($val, 'instantiation_type')) {
$val->instantiation_type = 'NetworkPortEthernet';
}
if (!property_exists($val, 'logical_number') && !property_exists($val, 'ifnumber')) {
unset($this->data[$k]);
continue;
}
if (property_exists($val, 'iftype')) {
$inst_type = NetworkPortType::getInstantiationType($val->iftype);
$keep = $inst_type !== false;
if ($inst_type !== false && $inst_type !== true) {
$val->instantiation_type = $inst_type;
}
}
if (property_exists($val, 'aggregate') && property_exists($val, 'ifnumber')) {
if (isset($val->aggregate[$val->ifnumber])) {
$keep = true;
$val->instantiation_type = 'NetworkPortAggregate';
}
}
if (!$keep) {
//port cannot be imported, remove from data source
unset($this->data[$k]);
continue;
}
if (property_exists($val, 'connections')) {
$this->connections += $this->prepareConnections($val);
unset($val->connections);
}
if (property_exists($val, 'vlans')) {
$this->vlans += $this->prepareVlans($val->vlans, $val->ifnumber);
unset($val->vlans);
}
if (property_exists($val, 'aggregate')) {
$this->aggregates[$val->ifnumber] = [
'aggregates' => array_fill_keys($val->aggregate, 0)
];
unset($val->aggregate);
}
foreach ($mapping as $origin => $dest) {
if (property_exists($val, $origin)) {
$val->$dest = $val->$origin;
}
}
if ((!property_exists($val, 'ifdescr') || empty($val->ifdescr)) && property_exists($val, 'name')) {
$val->ifdescr = $val->name;
}
if (!property_exists($val, 'trunk')) {
$val->trunk = 0;
}
}
$this->ports += $this->data;
return $this->data;
}
/**
* Prepare network ports connections
*
* @param \stdClass $port Port instance
*
* @return array
*/
private function prepareConnections(\stdClass $port)
{
$results = [];
$connections = $port->connections;
$ifnumber = $port->ifnumber;
$lldp_mapping = [
'ifnumber' => 'logical_number',
'model' => 'networkportmodels_id',
'sysmac' => 'mac',
'sysname' => 'name'
];
foreach ($connections as $connection) {
$connection = (object)$connection;
if ($this->isLLDP($port)) {
foreach ($lldp_mapping as $origin => $dest) {
if (property_exists($connection, $origin)) {
$connection->$dest = $connection->$origin;
}
}
if (!property_exists($connection, 'name') && property_exists($connection, 'ifdescr')) {
$connection->name = $connection->ifdescr;
}
$results[$ifnumber][] = $connection;
} else if (property_exists($connection, 'mac') && !empty($connection->mac)) {
$results[$ifnumber] = array_merge(($results[$ifnumber] ?? []), (array)$connection->mac);
} else {
continue;
}
}
return $results;
}
/**
* Prepare network ports vlans
*
* @param array $vlans Port vlans
*
* @return array
*/
private function prepareVlans($vlans, $ifnumber)
{
$results = [];
$mapping = [
'number' => 'tag'
];
foreach ($vlans as $vlan) {
$vlan = (object)$vlan;
foreach ($mapping as $origin => $dest) {
if (property_exists($vlan, $origin)) {
$vlan->$dest = $vlan->$origin;
}
}
unset($vlan->number);
$results[$ifnumber][] = $vlan;
}
return $results;
}
private function handleLLDPConnection(\stdClass $port, int $netports_id)
{
if (!property_exists($port, 'logical_number') || !isset($this->connections[$port->logical_number])) {
return;
}
//reset, will be populated from rulepassed
$this->connection_ports = [];
$this->current_port = $port;
$connections = $this->connections[$port->logical_number];
foreach ($connections as $connections_id => $connection) {
$this->current_connection = $connection;
$input = ['entities_id' => $this->entities_id];
$props = [
'ifdescr',
/*'sysdescr',*/
'ifnumber',
'mac',
'model',
'ip'
];
foreach ($props as $prop) {
if (property_exists($connection, $prop)) {
$input[$prop] = $connection->$prop;
}
}
$rule = new RuleImportAssetCollection();
$rule->getCollectionPart();
$rule->processAllRules($input, [], ['class' => $this]);
if (count($this->connection_ports)) {
$connections_id = current(current($this->connection_ports));
$this->addPortsWiring($netports_id, $connections_id);
}
}
unset($this->current_connection);
}
private function handleMacConnection(\stdClass $port, int $netports_id)
{
if (!property_exists($port, 'logical_number') || !isset($this->connections[$port->logical_number])) {
return;
}
//reset, will be populated from rulepassed
$this->connection_ports = [];
$this->current_port = $port;
$netport = new \NetworkPort();
$netport->getFromDB($netports_id);
$macs = $this->connections[$port->logical_number] ?? [];
foreach ($macs as $mac) {
$rule = new RuleImportAssetCollection();
$rule->getCollectionPart();
$this->current_port->mac = $mac;
$rule->processAllRules(['mac' => $mac], [], ['class' => $this]);
}
$found_macs = [];
//FIXME Static analysis says this property is always an empty array. I don't see it get set even within the rule(s)
foreach ($this->connection_ports as $ids) {
$found_macs += $ids;
}
if (count($found_macs) > 1 && $netport->fields['trunk'] == 1) {
return;
}
// Try detect phone + computer on this port
//FIXME Static analysis says this property is always an empty array. I don't see it get set even within the rule(s)
if (isset($this->connection_ports['Phone']) && count($found_macs) == 2) {
trigger_error('Phone/Computer MAC linked', E_USER_WARNING);
return;
}
if (count($found_macs) > 1) { // MultipleMac
//do not manage MAC addresses if we found one NetworkEquipment
if (isset($this->connections['NetworkEquipment'])) {
return;
}
$this->handleHub($found_macs, $netports_id);
} else { // One mac on port
//FIXME Static analysis says this property is always an empty array. I don't see it get set even within the rule(s)
if (count($this->connection_ports)) {
$connections_id = current(current($this->connection_ports));
$this->addPortsWiring($netports_id, $connections_id);
}
return;
}
}
private function handleVlans(\stdClass $port, int $netports_id)
{
global $DB;
if (!property_exists($port, 'logical_number')) {
return;
}
$vlan = new \Vlan();
$pvlan = new \NetworkPort_Vlan();
$vtable = $vlan->getTable();
$pvtable = $pvlan->getTable();
$data = $this->vlans[$port->logical_number] ?? [];
$db_vlans = [];
$iterator = $DB->request([
'SELECT' => [
"$pvtable.id",
"$vtable.name",
"$vtable.tag",
"$pvtable.tagged",
],
'FROM' => $pvtable,
'LEFT JOIN' => [
$vtable => [
'ON' => [
$vtable => 'id',
$pvtable => 'vlans_id'
]
]
],
'WHERE' => [
'networkports_id' => $netports_id
]
]);
foreach ($iterator as $row) {
$db_vlans[$row['id']] = $row;
}
if (count($db_vlans)) {
foreach ($data as $key => $values) {
foreach ($db_vlans as $keydb => $valuesdb) {
if (
$values->name ?? '' == $valuesdb['name']
&& ($values->tag ?? 0) == $valuesdb['tag']
&& ($values->tagged ?? 0) == $valuesdb['tagged']
) {
unset($data[$key]);
unset($db_vlans[$keydb]);
break;
}
}
}
if (!$this->main_asset || !$this->main_asset->isPartial()) {
foreach (array_keys($db_vlans) as $vlans_id) {
$pvlan->delete(['id' => $vlans_id]);
}
}
}
$db_vlans = [];
$vlans_iterator = $DB->request([
'FROM' => \Vlan::getTable()
]);
foreach ($vlans_iterator as $row) {
$db_vlans[$row['name'] . '|' . $row['tag']] = $row['id'];
}
//add new vlans
foreach ($data as $vlan_data) {
$vlan_key = ($vlan_data->name ?? '') . '|' . $vlan_data->tag;
$exists = isset($db_vlans[$vlan_key]);
if (!$exists) {
$stmt_columns = [
'name' => $vlan_data->name ?? '',
'tag' => $vlan_data->tag
];
$stmt_types = str_repeat('s', count($stmt_columns));
if ($this->vlan_stmt === null) {
$reference = array_fill_keys(
array_keys($stmt_columns),
new QueryParam()
);
$insert_query = $DB->buildInsert(
$vlan->getTable(),
$reference
);
if (!$this->vlan_stmt = $DB->prepare($insert_query)) {
trigger_error("Error preparing query $insert_query", E_USER_ERROR);
}
}
$stmt_values = array_values($stmt_columns);
$this->vlan_stmt->bind_param($stmt_types, ...$stmt_values);
$DB->executeStatement($this->vlan_stmt);
$vlans_id = $DB->insertId();
$db_vlans[$vlan_key] = $vlans_id;
}
$vlans_id = $db_vlans[$vlan_key];
$pvlan_stmt_columns = [
'networkports_id' => $netports_id,
'vlans_id' => $vlans_id,
'tagged' => $vlan_data->tagged ?? 0
];
$pvlan_stmt_types = str_repeat('s', count($pvlan_stmt_columns));
if ($this->pvlan_stmt === null) {
$reference = array_fill_keys(
array_keys($pvlan_stmt_columns),
new QueryParam()
);
$insert_query = $DB->buildInsert(
$pvlan->getTable(),
$reference
);
if (!$this->pvlan_stmt = $DB->prepare($insert_query)) {
trigger_error("Error preparing query $insert_query", E_USER_ERROR);
}
}
$pvlan_stmt_values = array_values($pvlan_stmt_columns);
$this->pvlan_stmt->bind_param($pvlan_stmt_types, ...$pvlan_stmt_values);
$DB->executeStatement($this->pvlan_stmt);
}
}
private function prepareAggregations(\stdClass $port, int $netports_id)
{
if (!property_exists($port, 'logical_number')) {
return;
}
if (!count($this->aggregates)) {
//no aggregation to manage, pass.
return;
}
foreach ($this->aggregates as $ifnumber => &$data) {
if ($ifnumber == $port->logical_number) {
//main part of the aggregate
$data['networkports_id'] = $netports_id;
return;
} else {
//last part of the aggregate. find ifnumber and keep ports_id
foreach ($data['aggregates'] as $lifnumber => &$ldata) {
if ($lifnumber == $port->logical_number) {
$ldata = $netports_id;
return;
}
}
}
}
}
private function handleAggregations()
{
$netport_aggregate = new \NetworkPortAggregate();
foreach ($this->aggregates as $data) {
$aggregates = $data['aggregates'];
$netports_id = $data['networkports_id'];
//create main aggregate port, if t does not exists
if ($netport_aggregate->getFromDB($netports_id)) {
$input = $netport_aggregate->fields;
} else {
$input = [
'networkports_id' => $netports_id,
'networkports_id_list' => []
];
$input['id'] = $netport_aggregate->add($input);
}
$input['networkports_id_list'] = array_values($aggregates);
$netport_aggregate->update($input, false);
}
}
private function handleConnections(\stdClass $port, int $netports_id)
{
if ($this->isLLDP($port)) {
$this->handleLLDPConnection($port, $netports_id);
} else {
$this->handleMacConnection($port, $netports_id);
}
}
public function handle()
{
$this->ports += $this->extra_data['\Glpi\Inventory\Asset\\' . $this->item->getType()]->getManagementPorts();
$this->handlePorts();
}
protected function portUpdated(\stdClass $port, int $netports_id)
{
$this->portChanged($port, $netports_id);
}
protected function portCreated(\stdClass $port, int $netports_id)
{
$this->portChanged($port, $netports_id);
}
protected function portChanged(\stdClass $port, int $netports_id)
{
$this->handleConnections($port, $netports_id);
$this->handleVlans($port, $netports_id);
$this->prepareAggregations($port, $netports_id);
}
/**
* After rule engine passed, update task (log) and create item if required
*
* @param integer $items_id id of the item (0 if new)
* @param string $itemtype Item type
* @param integer $rules_id Matched rule id, if any
* @param integer $ports_id Matched port id, if any
*/
public function rulepassed($items_id, $itemtype, $rules_id, $ports_id = 0)
{
$netport = new \NetworkPort();
if (empty($itemtype)) {
$itemtype = 'Unmanaged';
}
$port = $this->current_connection ?? $this->current_port;
$item = new $itemtype();
if ($items_id == "0") {
//not yet existing, create
$input = (array)$port;
if (property_exists($port, 'mac') && (!property_exists($port, 'name') || empty($port->name) || is_numeric($port->name) || preg_match('@([\w-]+)?(\d+)/\d+(/\d+)?@', $port->name))) {
if ($name = $this->getNameForMac($port->mac)) {
$input['name'] = $name;
}
}
$items_id = $item->add(Toolbox::addslashes_deep($input));
$rulesmatched = new \RuleMatchedLog();
$agents_id = $this->agent->fields['id'];
if (empty($agents_id)) {
$agents_id = 0;
}
$inputrulelog = [
'date' => date('Y-m-d H:i:s'),
'rules_id' => $rules_id,
'items_id' => $items_id,
'itemtype' => $itemtype,
'agents_id' => $agents_id,
'method' => 'inventory'
];
$rulesmatched->add($inputrulelog, [], false);
$rulesmatched->cleanOlddata($items_id, $itemtype);
}
if (!$ports_id) {
//create network port
$input = [
'items_id' => $items_id,
'itemtype' => $itemtype,
'instantiation_type' => 'NetworkPortEthernet'
];
if (property_exists($port, 'ip')) {
$input += [
'_create_children' => 1,
'NetworkName_name' => '',
'NetworkName_fqdns_id' => 0,
'NetworkName__ipaddresses' => [
'-1' => $port->ip
]
];
}
if (property_exists($port, 'mac')) {
if ($name = $this->getNameForMac($port->mac)) {
$input['name'] = $name;
}
}
if (property_exists($port, 'ifdescr') && !empty($port->ifdescr)) {
$input['name'] = $port->ifdescr;
}
if (property_exists($port, 'mac') && !empty($port->mac)) {
$input['mac'] = $port->mac;
}
$ports_id = $netport->add(Toolbox::addslashes_deep($input));
}
if (!isset($this->connection_ports[$itemtype])) {
$this->connection_ports[$itemtype] = [];
}
$this->connection_ports[$itemtype][$ports_id] = $ports_id;
}
/**
* Get port manufacturer name from OUIs database
*
* @param string $mac MAC address
*
* @return string|false
*/
public function getNameForMac($mac)
{
global $GLPI_CACHE;
$exploded = explode(':', $mac);
if (isset($exploded[2])) {
$ouis = $GLPI_CACHE->get('glpi_inventory_ouis');
if ($ouis === null) {
$jsonfile = new FilesToJSON();
$ouis = json_decode(file_get_contents($jsonfile->getJsonFilePath('ouis')), true);
$GLPI_CACHE->set('glpi_inventory_ouis', $ouis);
}
$mac = sprintf('%s:%s:%s', $exploded[0], $exploded[1], $exploded[2]);
return $ouis[strtoupper($mac)] ?? false;
}
return false;
}
/**
* Check if port connections are LLDP
*
* @param \stdClass $port Port
*
* @return boolean
*/
protected function isLLDP($port): bool
{
if (!property_exists($port, 'lldp') && !property_exists($port, 'cdp')) {
return false;
}
return (bool)($port->lldp ?? $port->cdp);
}
public function handlePorts($itemtype = null, $items_id = null)
{
$mainasset = $this->extra_data['\Glpi\Inventory\Asset\\' . $this->item->getType()];
//handle ports for stacked switches
if ($mainasset->isStackedSwitch()) {
$bkp_ports = $this->ports;
foreach ($this->ports as $k => $val) {
$matches = [];
if (preg_match('@[\w-]+(\d+)/\d+/\d+@', $val->name, $matches)) {
if ($matches[1] != $mainasset->getStackId()) {
//port attached to another stack entry, remove from here
unset($this->ports[$k]);
continue;
}
}
}
}
$this->handlePortsTrait($itemtype, $items_id);
if (isset($bkp_ports)) {
//all ports must be kept for next stack iteration
$this->ports = $bkp_ports;
}
}
/**
* Handle a hub (many MAC on a port means we face a hub)
*
* @param array $found_macs ID of ports foudn by mac
* @param integer $netports_id Network port id
*
* @return void
*/
public function handleHub($found_macs, $netports_id)
{
$hubs_id = 0;
$link = new \NetworkPort_NetworkPort();
$netport = new \NetworkPort();
$id = $link->getOppositeContact($netports_id);
$netport->getFromDB($id);
if ($id && $netport->fields['itemtype'] == Unmanaged::getType()) {
$unmanaged = new Unmanaged();
$unmanaged->getFromDB($netport->fields['items_id']);
if ($unmanaged->fields['hub'] == 1) {
//a hub is connected, updated connections
$hubs_id = $unmanaged->fields['id'];
} else {
//direct connections, drop to recreate
$link->disconnectFrom($id);
}
}
if (!$hubs_id) {
//create direct connection
$hubs_id = $link->createHub($netports_id, $this->entities_id);
}
$glpi_ports = [];
$dbports = $netport->find([
'items_id' => $hubs_id,
'itemtype' => Unmanaged::getType()
]);
foreach ($dbports as $dbport) {
$id = $link->getOppositeContact($dbport['id']);
if ($id) {
$glpi_ports[$id] = $dbport['id'];
}
}
foreach ($found_macs as $ports_id) {
if (!isset($glpi_ports[$ports_id])) {
// Connect port (port found in GLPI)
$link->connectToHub($ports_id, $hubs_id);
}
}
}
public function checkConf(Conf $conf): bool
{
return true;
}
public function getPart($part)
{
if (!in_array($part, ['connections', 'aggregates', 'vlans', 'connection_ports'])) {
return;
}
return $this->$part;
}
/**
* Add wiring between network ports.
*
* @param int $netports_id_1
* @param int $netports_id_2
*
* @return bool
*/
private function addPortsWiring(int $netports_id_1, int $netports_id_2): bool
{
if ($netports_id_1 == $netports_id_2) {
throw new \RuntimeException('Cannot wire a port to itself!');
}
$wire = new \NetworkPort_NetworkPort();
$current_port_1_opposite = $wire->getOppositeContact($netports_id_1);
if ($current_port_1_opposite !== false && $current_port_1_opposite == $netports_id_2) {
return true; // Connection already exists in DB
}
if ($current_port_1_opposite !== false) {
$wire->delete($wire->fields); // Drop previous connection on self
}
if ($wire->getFromDBForNetworkPort($netports_id_2)) {
$wire->delete($wire->fields); // Drop previous connection on opposite
}
return $wire->add([
'networkports_id_1' => $netports_id_1,
'networkports_id_2' => $netports_id_2,
]);
}
}