diff --git a/LibreNMS/Modules/Links.php b/LibreNMS/Modules/Links.php index 7f3e0cae004..4f7ecf571a8 100644 --- a/LibreNMS/Modules/Links.php +++ b/LibreNMS/Modules/Links.php @@ -20,6 +20,10 @@ use LibreNMS\Util\StringHelpers; use SnmpQuery; +/// AVOID the dbFacline() legacy function helpers +use Illuminate\Support\Facades\DB; + + class Links implements Module { use SyncsModels; @@ -151,6 +155,13 @@ private function discoverLldp(Device $device): Collection } if (! empty($lldpRows)) { + + // Create or Update the table ports_lldplocportid with the lldpLocPortId entries for this device, + // along with the LibreNMS device ID and LibreNMS port ID. This makes it possible to look up the + // lldpLocPortId later to make links between device ports that use less standard identifiers (such + // as MAC address) + discoverLldpLocPortId($device); + $oidsremadd = SnmpQuery::hideMib()->numeric()->walk('LLDP-MIB::lldpRemManAddrIfSubtype')->values(); foreach ($oidsremadd as $key => $tmp1) { $res = preg_match("/1\.0\.8802\.1\.1\.2\.1\.4\.2\.1\.3\.([^\.]*)\.([^\.]*)\.([^\.]*)\.([^\.]*)\.([^\.]*).(([^\.]*)(\.([^\.]*))+)/", (string) $key, $matches); @@ -278,11 +289,13 @@ private function discoverLldp(Device $device): Collection $remoteSysName = StringHelpers::linksRemSysName($data['lldpRemSysName']); $remoteSysName = (empty($remoteSysName)) ? StringHelpers::linksRemSysName($data['lldpRemSysDesc']) : $remoteSysName; - $remoteDeviceIp = $data['lldpRemManAddr'] ?? ''; $remoteDeviceId = find_device_id($remoteSysName, $remoteDeviceIp, $remoteMac); - $remotePortId = find_port_id($data['lldpRemPortDesc'] ?? null, $data['lldpRemPortId'], $remoteDeviceId); + // Lookup the device ID and port ID by checking the ports_lldpLocPortId table + $remotePortIdByLldpPortId = find_port_id_by_lldp_port_id($data["lldpRemPortId"], $remoteDeviceId); + + $remotePortId = $remotePortIdByLldpPortId; $remotePortName = (empty($remotePortId)) ? $remotePortName . ' (' . $remoteMac . ')' : $remotePortName; if (! empty($data['localPortId']) && (! empty($remoteSysName))) { @@ -299,10 +312,9 @@ private function discoverLldp(Device $device): Collection 'remote_version' => $data['lldpRemSysDesc'] ?? '', ])); } - } } } - + return $links->filter(); } diff --git a/database/migrations/2025-10-15-060000_create_ports_lldplocportid_table.php b/database/migrations/2025-10-15-060000_create_ports_lldplocportid_table.php new file mode 100644 index 00000000000..b72ae68f265 --- /dev/null +++ b/database/migrations/2025-10-15-060000_create_ports_lldplocportid_table.php @@ -0,0 +1,33 @@ +unsignedInteger('device_id'); + $table->string('lldpLocPortId'); + $table->unsignedInteger('port_id')->nullable(); + $table->unique(['device_id', 'lldpLocPortId']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('ports_lldplocportid'); + } +}; diff --git a/includes/discovery/functions.inc.php b/includes/discovery/functions.inc.php index 6538ba869f0..9a7a6351358 100644 --- a/includes/discovery/functions.inc.php +++ b/includes/discovery/functions.inc.php @@ -30,6 +30,70 @@ use LibreNMS\Util\Number; use LibreNMS\Util\UserFuncHelper; +function discoverLldpLocPortId($device) +{ + d_echo('Store lldpLocPortId'); + $module_start = microtime(true); + + $live_ports = []; + $port_stats = []; + $live_ports = snmpwalk_cache_oid($device, 'lldpLocPortId', $port_stats, 'LLDP-MIB'); + + $db_lldp_ports = []; + + // Replaced dbFetchRows() with DB::table to avoid Ignition query-recorder type error when bindings are used + $db_lldp_ports = DB::table('ports_lldplocportid') + ->where('device_id', $device['device_id']) + ->get() + ->map(fn($r) => (array)$r) // convert stdClass to associative array + ->all(); + + $db_ports = []; + $db_ports = DB::table('ports') + ->where('device_id', $device['device_id']) + ->get() + ->map(fn($r) => (array)$r) // convert stdClass → associative array + ->all(); + + foreach ($live_ports as $ifIndex => $snmp_data) { + $db_lldp_index = array_search($snmp_data['lldpLocPortId'], array_column($db_lldp_ports, 'lldpLocPortId')); + $port_index = array_search($ifIndex, array_column($db_ports, 'ifIndex')); + + if (is_int($port_index)) { + $port_id = $db_ports[$port_index]['port_id']; + } else { + $port_id = NULL; + } + $data['device_id'] = $device['device_id']; + $data['lldpLocPortId'] = $snmp_data['lldpLocPortId']; + $data['port_id'] = $port_id; + + if (!is_int($db_lldp_index)) { + d_echo('Adding: ' . $snmp_data['lldpLocPortId']); + dbInsert($data, 'ports_lldplocportid'); + } else { + if ($port_id != $db_lldp_ports[$db_lldp_index]['port_id']) { + d_echo('Updating: ' . $snmp_data['lldpLocPortId'] . ' port_id ' . $db_lldp_ports[$db_lldp_index]['port_id'] . ' to ' . $port_id); + dbUpdate($data, 'ports_lldplocportid', '`device_id` = ? and `lldpLocPortId` = ?', [$data['device_id'], $data['lldpLocPortId']]); + } else { + d_echo('No Change: ' . $snmp_data['lldpLocPortId']); + } + } + } + foreach($db_lldp_ports as $db_data) { + $db_lldp_ports_index = array_search($db_data['lldpLocPortId'], array_column($live_ports, 'lldpLocPortId')); + if (!is_int($db_lldp_ports_index)) { + d_echo('Deleting: ' . $db_data['lldpLocPortId']); + dbDelete('ports_lldplocportid', '`device_id` = ? and `lldpLocPortId` = ?', [$device['device_id'], $db_data['lldpLocPortId']]); + } + } + $module_time = microtime(true) - $module_start; + $module_time = substr($module_time, 0, 5); + d_echo($module_time . ' seconds'); + + //dd('debug stop'); +} + /** * @param string $hostname * @param array $device @@ -927,6 +991,35 @@ function find_device_id($name = '', $ip = '', $mac_address = '') return 0; } + + +/** + * Try to find a port by lldp port id + * + * @param string $lldpremportid matched against ports.lldpLocPortId + * @param int $device_id restrict search to ports on a specific device + * @return int + */ +function find_port_id_by_lldp_port_id($lldpremportid, $device_id) +{ + if (!$device_id) { + return 0; + } + d_echo($lldpremportid . ':' . $device_id); + $sql = "SELECT `port_id` FROM `ports_lldplocportid` WHERE `device_id`=? AND `lldpLocPortId`=? LIMIT 1"; + $params[] = $device_id; + $params[] = $lldpremportid; + + $remote_port_id = (int)dbFetchCell($sql, $params); + if (!$remote_port_id) { + return 0; + } else { + return $remote_port_id; + } +} + + + /** * Try to find a port by ifDescr, ifName, ifAlias, or MAC *