|
| 1 | +<?php |
| 2 | + |
| 3 | +/** |
| 4 | + * timos.inc.php |
| 5 | + * |
| 6 | + * Discover FDB data with TIMETRA-SERV-MIB for Nokia TiMOS devices |
| 7 | + * |
| 8 | + * This program is free software: you can redistribute it and/or modify |
| 9 | + * it under the terms of the GNU General Public License as published by |
| 10 | + * the Free Software Foundation, either version 3 of the License, or |
| 11 | + * (at your option) any later version. |
| 12 | + * |
| 13 | + * This program is distributed in the hope that it will be useful, |
| 14 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the |
| 16 | + * GNU General Public License for more details. |
| 17 | + * |
| 18 | + * You should have received a copy of the GNU General Public License |
| 19 | + * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 20 | + * |
| 21 | + * @link https://www.librenms.org |
| 22 | + * |
| 23 | + * @copyright 2025 LibreNMS Contributors |
| 24 | + * @author LibreNMS Contributors |
| 25 | + */ |
| 26 | + |
| 27 | +use App\Facades\PortCache; |
| 28 | +use App\Models\Vlan; |
| 29 | +use Illuminate\Support\Facades\Log; |
| 30 | +use LibreNMS\Util\Mac; |
| 31 | + |
| 32 | +/* |
| 33 | + * Nokia TiMOS devices use TIMETRA-SERV-MIB::tlsFdbInfoTable for FDB information. |
| 34 | + * |
| 35 | + * The table is indexed by: |
| 36 | + * - svcId (service ID) |
| 37 | + * - tlsFdbMacAddr (MAC address) |
| 38 | + * |
| 39 | + * Key OIDs: |
| 40 | + * - tlsFdbMacAddr: The MAC address |
| 41 | + * - tlsFdbLocale: Where the MAC is located (sap, sdp, cpm, endpoint, vxlan, evpnMpls, blackhole) |
| 42 | + * - tlsFdbPortId: The port ID when tlsFdbLocale is 'sap' (this is the TmnxPortID which equals ifIndex) |
| 43 | + * - tlsFdbEncapValue: The encapsulation value (encoded per TIMETRA-TC-MIB::TmnxEncapVal) |
| 44 | + * |
| 45 | + * TmnxEncapVal encoding (32-bit): |
| 46 | + * - nullEncap: 0 |
| 47 | + * - dot1qEncap: lower 12 bits = VLAN ID (0x0FFF mask) |
| 48 | + * - qinqEncap: lower 16 bits = outer VLAN, upper 16 bits = inner VLAN |
| 49 | + * |
| 50 | + * We only process entries where tlsFdbLocale is 'sap' (1) as these are the local learned MACs. |
| 51 | + * |
| 52 | + * Nokia SAP identifier format: ServiceID:PortId:EncapValue (e.g., 100:1/1/1:500) |
| 53 | + */ |
| 54 | + |
| 55 | +/** |
| 56 | + * Decode TmnxEncapVal to extract VLAN ID(s) |
| 57 | + * |
| 58 | + * @param int|string $encapVal The encoded encapsulation value |
| 59 | + * @return array Array with 'outer' and optionally 'inner' VLAN IDs |
| 60 | + * |
| 61 | + * @see TIMETRA-TC-MIB::TmnxEncapVal |
| 62 | + */ |
| 63 | +function decodeNokiaEncapValue($encapVal): array |
| 64 | +{ |
| 65 | + $encapVal = (int) $encapVal; |
| 66 | + |
| 67 | + // Null encapsulation |
| 68 | + if ($encapVal == 0) { |
| 69 | + return ['outer' => 0, 'inner' => null]; |
| 70 | + } |
| 71 | + |
| 72 | + // Check for QinQ: if upper 16 bits have a value (ignoring special bits) |
| 73 | + $innerVlan = ($encapVal >> 16) & 0x0FFF; // Upper 12 bits of upper 16 bits |
| 74 | + $outerVlan = $encapVal & 0x0FFF; // Lower 12 bits |
| 75 | + |
| 76 | + if ($innerVlan > 0) { |
| 77 | + // QinQ encapsulation |
| 78 | + return ['outer' => $outerVlan, 'inner' => $innerVlan]; |
| 79 | + } |
| 80 | + |
| 81 | + // Simple dot1q encapsulation - VLAN is in lower 12 bits |
| 82 | + return ['outer' => $outerVlan, 'inner' => null]; |
| 83 | +} |
| 84 | + |
| 85 | +/** |
| 86 | + * Format TmnxEncapVal for display (Nokia-friendly format) |
| 87 | + * |
| 88 | + * @param int|string $encapVal The encoded encapsulation value |
| 89 | + * @return string Formatted encap value (e.g., "500" or "100.200" for QinQ) |
| 90 | + */ |
| 91 | +function formatNokiaEncapValue($encapVal): string |
| 92 | +{ |
| 93 | + $decoded = decodeNokiaEncapValue($encapVal); |
| 94 | + |
| 95 | + if ($decoded['inner'] !== null) { |
| 96 | + // QinQ format: outer.inner |
| 97 | + return $decoded['outer'] . '.' . $decoded['inner']; |
| 98 | + } |
| 99 | + |
| 100 | + if ($decoded['outer'] == 4095) { |
| 101 | + return '*'; // Wildcard |
| 102 | + } |
| 103 | + |
| 104 | + return (string) $decoded['outer']; |
| 105 | +} |
| 106 | + |
| 107 | +// Walk only the required FDB columns for best performance |
| 108 | +// Testing showed: 5 columns = 388s, 3 columns = 144s, full table entry = 10+ min |
| 109 | +// The selective approach is fastest because tlsFdbInfoEntry has 25+ columns we don't need |
| 110 | +$fdbTable = SnmpQuery::hideMib()->walk([ |
| 111 | + 'TIMETRA-SERV-MIB::tlsFdbLocale', |
| 112 | + 'TIMETRA-SERV-MIB::tlsFdbPortId', |
| 113 | + 'TIMETRA-SERV-MIB::tlsFdbEncapValue', |
| 114 | +])->table(2); |
| 115 | + |
| 116 | +if (! empty($fdbTable)) { |
| 117 | + // Count SAP entries for progress indication |
| 118 | + $sapCount = 0; |
| 119 | + foreach ($fdbTable as $svcId => $macEntries) { |
| 120 | + foreach ($macEntries as $entry) { |
| 121 | + $locale = $entry['tlsFdbLocale'] ?? null; |
| 122 | + if ($locale === 'sap' || $locale === '1' || $locale === 1) { |
| 123 | + $sapCount++; |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + echo "TIMETRA-SERV-MIB: $sapCount SAP entries" . PHP_EOL; |
| 128 | + |
| 129 | + foreach ($fdbTable as $svcId => $macEntries) { |
| 130 | + foreach ($macEntries as $macIndex => $entry) { |
| 131 | + // Only process entries with tlsFdbLocale = 'sap' (1) |
| 132 | + // sap(1), sdp(2), cpm(3), endpoint(4), vxlan(5), evpnMpls(6), blackhole(7) |
| 133 | + $locale = $entry['tlsFdbLocale'] ?? null; |
| 134 | + if ($locale !== 'sap' && $locale !== '1' && $locale !== 1) { |
| 135 | + continue; |
| 136 | + } |
| 137 | + |
| 138 | + // Get the port ID (this is the TmnxPortID which is the same as ifIndex for physical ports) |
| 139 | + $portId = $entry['tlsFdbPortId'] ?? null; |
| 140 | + if (empty($portId) || $portId == 0) { |
| 141 | + Log::debug("Skipping MAC $macIndex in svc $svcId - no valid port ID\n"); |
| 142 | + continue; |
| 143 | + } |
| 144 | + |
| 145 | + // Get the encapsulation value (VLAN ID) |
| 146 | + $encapValue = $entry['tlsFdbEncapValue'] ?? 0; |
| 147 | + |
| 148 | + // Parse the MAC address - the index format contains the MAC address |
| 149 | + // Format: svcId.macAddr where macAddr is 6 octets separated by dots |
| 150 | + $mac_address = Mac::parse($macIndex)->hex(); |
| 151 | + if (strlen($mac_address) != 12) { |
| 152 | + Log::debug("MAC address parsing failed for $macIndex\n"); |
| 153 | + continue; |
| 154 | + } |
| 155 | + |
| 156 | + // Get the port_id from the ifIndex (TmnxPortID equals ifIndex for physical ports) |
| 157 | + $port_id = PortCache::getIdFromIfIndex($portId, $device['device_id']); |
| 158 | + |
| 159 | + if (! $port_id) { |
| 160 | + Log::debug("Could not find port for TmnxPortID $portId (MAC: $mac_address)\n"); |
| 161 | + continue; |
| 162 | + } |
| 163 | + |
| 164 | + // Decode the encapsulation value to get VLAN ID(s) |
| 165 | + // TmnxEncapVal is encoded: dot1q uses lower 12 bits, QinQ uses upper/lower 16 bits |
| 166 | + $decodedEncap = decodeNokiaEncapValue($encapValue); |
| 167 | + $vlanNumber = $decodedEncap['outer']; // Use outer VLAN for FDB lookup |
| 168 | + |
| 169 | + // Skip if no valid VLAN (null encap or wildcard) |
| 170 | + if ($vlanNumber == 0 || $vlanNumber == 4095) { |
| 171 | + Log::debug("Skipping MAC $mac_address - no valid VLAN (encap: $encapValue, decoded: $vlanNumber)\n"); |
| 172 | + continue; |
| 173 | + } |
| 174 | + |
| 175 | + // Use VLAN ID from vlans_dict if available, otherwise use VLAN number directly |
| 176 | + // This allows FDB entries to be added even if VLAN discovery module is disabled |
| 177 | + $vlan_id = $vlans_dict[$vlanNumber] ?? $vlanNumber; |
| 178 | + |
| 179 | + // Nokia SAP format: ServiceID:Port:EncapValue (formatted for display) |
| 180 | + $formattedEncap = formatNokiaEncapValue($encapValue); |
| 181 | + $sapIdentifier = "$svcId:$portId:$formattedEncap"; |
| 182 | + |
| 183 | + $insert[$vlan_id][$mac_address]['port_id'] = $port_id; |
| 184 | + Log::debug("SAP $sapIdentifier mac $mac_address vlan $vlanNumber port ($portId) $port_id\n"); |
| 185 | + } |
| 186 | + } |
| 187 | +} |
| 188 | + |
| 189 | +unset($fdbTable); |
0 commit comments