fixed LLDP based topology discovery issues on some devices (mostly D-Link)
[public/netxms.git] / src / server / core / lldp.cpp
CommitLineData
eec253a8
VK
1/*
2** NetXMS - Network Management System
fe58ae54 3** Copyright (C) 2003-2016 Victor Kirhenshtein
eec253a8
VK
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation; either version 2 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18**
19** File: lldp.cpp
20**
21**/
22
23#include "nxcore.h"
24
f632f8ac
VK
25/**
26 * Handler for walking local port table
27 */
967893bb 28static UINT32 PortLocalInfoHandler(UINT32 snmpVersion, SNMP_Variable *var, SNMP_Transport *transport, void *arg)
f632f8ac 29{
3a82d5ae 30 LLDP_LOCAL_PORT_INFO *port = new LLDP_LOCAL_PORT_INFO;
e0471fad 31 port->portNumber = var->getName()->getValue()[11];
f632f8ac
VK
32 port->localIdLen = var->getRawValue(port->localId, 256);
33
e0471fad 34 SNMP_ObjectId *oid = var->getName();
967893bb
VK
35 UINT32 newOid[128];
36 memcpy(newOid, oid->getValue(), oid->getLength() * sizeof(UINT32));
f632f8ac
VK
37 SNMP_PDU *pRqPDU = new SNMP_PDU(SNMP_GET_REQUEST, SnmpNewRequestId(), snmpVersion);
38
39 newOid[oid->getLength() - 2] = 4; // lldpLocPortDescr
40 pRqPDU->bindVariable(new SNMP_Variable(newOid, oid->getLength()));
41
fe58ae54
VK
42 newOid[oid->getLength() - 2] = 2; // lldpLocPortIdSubtype
43 pRqPDU->bindVariable(new SNMP_Variable(newOid, oid->getLength()));
44
f632f8ac 45 SNMP_PDU *pRespPDU = NULL;
296ae03d 46 UINT32 rcc = transport->doRequest(pRqPDU, &pRespPDU, SnmpGetDefaultTimeout(), 3);
f632f8ac
VK
47 delete pRqPDU;
48 if (rcc == SNMP_ERR_SUCCESS)
49 {
fe58ae54
VK
50 if (pRespPDU->getNumVariables() >= 2)
51 {
52 pRespPDU->getVariable(0)->getValueAsString(port->ifDescr, 192);
53 port->localIdSubtype = pRespPDU->getVariable(1)->getValueAsUInt();
54 }
f632f8ac
VK
55 delete pRespPDU;
56 }
57 else
58 {
59 _tcscpy(port->ifDescr, _T("###error###"));
60 }
61
3a82d5ae 62 ((ObjectArray<LLDP_LOCAL_PORT_INFO> *)arg)->add(port);
f632f8ac
VK
63 return SNMP_ERR_SUCCESS;
64}
65
66/**
3a82d5ae 67 * Get information about LLDP local ports
f632f8ac 68 */
3a82d5ae 69ObjectArray<LLDP_LOCAL_PORT_INFO> *GetLLDPLocalPortInfo(SNMP_Transport *snmp)
f632f8ac 70{
3a82d5ae 71 ObjectArray<LLDP_LOCAL_PORT_INFO> *ports = new ObjectArray<LLDP_LOCAL_PORT_INFO>(64, 64, true);
46b7166d 72 if (SnmpWalk(snmp->getSnmpVersion(), snmp, _T(".1.0.8802.1.1.2.1.3.7.1.3"), PortLocalInfoHandler, ports, FALSE) != SNMP_ERR_SUCCESS)
f632f8ac 73 {
3a82d5ae 74 delete ports;
7aad6641 75 return NULL;
f632f8ac 76 }
3a82d5ae 77 return ports;
f632f8ac
VK
78}
79
80/**
81 * Find remote interface
3a82d5ae
VK
82 *
83 * @param node remote node
84 * @param idType port ID type (value of lldpRemPortIdSubtype)
85 * @param id port ID
86 * @param idLen port ID length in bytes
f632f8ac 87 */
cf1d689e 88static Interface *FindRemoteInterface(Node *node, UINT32 idType, BYTE *id, size_t idLen)
eec253a8 89{
71ea7674 90 LLDP_LOCAL_PORT_INFO port;
cf1d689e 91 TCHAR ifName[130], buffer[256];
040c45fa
VK
92 Interface *ifc;
93
cf1d689e
VK
94 DbgPrintf(5, _T("LLDPTopoHandler(%s [%d]): FindRemoteInterface: idType=%d id=%s (%d)"), node->getName(), node->getId(), idType, BinToStr(id, idLen, buffer), (int)idLen);
95
fe58ae54
VK
96 // Try local LLDP port info first
97 if (node->getLldpLocalPortInfo(idType, id, idLen, &port))
98 {
cf1d689e 99 DbgPrintf(5, _T("LLDPTopoHandler(%s [%d]): FindRemoteInterface: getLldpLocalPortInfo found port: %d \"%s\""), node->getName(), node->getId(), port.portNumber, port.ifDescr);
fe58ae54
VK
100 if (node->isBridge())
101 ifc = node->findBridgePort(port.portNumber);
102 else
103 ifc = node->findInterfaceByIndex(port.portNumber);
104 if (ifc == NULL) // unable to find interface by bridge port number or interface index, try description
105 ifc = node->findInterfaceByName(port.ifDescr); /* TODO: find by cached ifName value */
106 if (ifc != NULL)
107 return ifc;
108 }
109
cf1d689e 110 DbgPrintf(5, _T("LLDPTopoHandler(%s [%d]): FindRemoteInterface: interface not found by getLldpLocalPortInfo"), node->getName(), node->getId());
eec253a8
VK
111 switch(idType)
112 {
113 case 3: // MAC address
114 return node->findInterfaceByMAC(id);
115 case 4: // Network address
116 if (id[0] == 1) // IPv4
117 {
967893bb
VK
118 UINT32 ipAddr;
119 memcpy(&ipAddr, &id[1], sizeof(UINT32));
eec253a8
VK
120 return node->findInterfaceByIP(ntohl(ipAddr));
121 }
122 return NULL;
040c45fa
VK
123 case 5: // Interface name
124#ifdef UNICODE
8d7a71be 125 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (char *)id, (int)idLen, ifName, 128);
040c45fa
VK
126 ifName[min(idLen, 127)] = 0;
127#else
608d0550
VK
128 {
129 int len = min(idLen, 127);
130 memcpy(ifName, id, len);
131 ifName[len] = 0;
132 }
040c45fa 133#endif
c75e9ee4 134 ifc = node->findInterfaceByName(ifName); /* TODO: find by cached ifName value */
ee97f663 135 if ((ifc == NULL) && !_tcsncmp(node->getObjectId(), _T(".1.3.6.1.4.1.1916.2"), 19))
040c45fa
VK
136 {
137 // Hack for Extreme Networks switches
138 // Must be moved into driver
ee97f663 139 memmove(&ifName[2], ifName, (_tcslen(ifName) + 1) * sizeof(TCHAR));
040c45fa
VK
140 ifName[0] = _T('1');
141 ifName[1] = _T(':');
c75e9ee4 142 ifc = node->findInterfaceByName(ifName);
040c45fa
VK
143 }
144 return ifc;
f632f8ac 145 case 7: // local identifier
fe58ae54 146 return NULL; // already tried to find port using local info
eec253a8
VK
147 default:
148 return NULL;
149 }
150}
151
f632f8ac 152/**
cf1d689e 153 * Get variable from cache
f632f8ac 154 */
cf1d689e
VK
155static SNMP_Variable *GetVariableFromCache(UINT32 *oid, size_t oidLen, StringObjectMap<SNMP_Variable> *cache)
156{
157 TCHAR oidText[MAX_OID_LEN * 6];
158 SNMPConvertOIDToText(oidLen, oid, oidText, MAX_OID_LEN * 6);
159 return cache->get(oidText);
160}
161
162/**
163 * Process LLDP connection database entry
164 */
165static void ProcessLLDPConnectionEntry(Node *node, StringObjectMap<SNMP_Variable> *connections, SNMP_Variable *var, LinkLayerNeighbors *nbs)
eec253a8 166{
e0471fad 167 SNMP_ObjectId *oid = var->getName();
eec253a8
VK
168
169 // Get additional info for current record
967893bb
VK
170 UINT32 newOid[128];
171 memcpy(newOid, oid->getValue(), oid->getLength() * sizeof(UINT32));
eec253a8 172
df94e0ce 173 newOid[oid->getLength() - 4] = 4; // lldpRemChassisIdSubtype
cf1d689e 174 SNMP_Variable *lldpRemChassisIdSubtype = GetVariableFromCache(newOid, oid->getLength(), connections);
eec253a8 175
df94e0ce 176 newOid[oid->getLength() - 4] = 7; // lldpRemPortId
cf1d689e 177 SNMP_Variable *lldpRemPortId = GetVariableFromCache(newOid, oid->getLength(), connections);
eec253a8 178
df94e0ce 179 newOid[oid->getLength() - 4] = 6; // lldpRemPortIdSubtype
cf1d689e 180 SNMP_Variable *lldpRemPortIdSubtype = GetVariableFromCache(newOid, oid->getLength(), connections);
eec253a8 181
841c9abc 182 newOid[oid->getLength() - 4] = 8; // lldpRemPortDesc
cf1d689e 183 SNMP_Variable *lldpRemPortDesc = GetVariableFromCache(newOid, oid->getLength(), connections);
841c9abc 184
052e15b3 185 newOid[oid->getLength() - 4] = 9; // lldpRemSysName
cf1d689e 186 SNMP_Variable *lldpRemSysName = GetVariableFromCache(newOid, oid->getLength(), connections);
052e15b3 187
cf1d689e 188 if ((lldpRemChassisIdSubtype != NULL) && (lldpRemPortId != NULL) && (lldpRemPortIdSubtype != NULL) && (lldpRemPortDesc != NULL) && (lldpRemSysName != NULL))
eec253a8
VK
189 {
190 // Build LLDP ID for remote system
191 TCHAR remoteId[256];
cf1d689e 192 BuildLldpId(lldpRemChassisIdSubtype->getValueAsInt(), var->getValue(), (int)var->getValueLength(), remoteId, 256);
eec253a8 193 Node *remoteNode = FindNodeByLLDPId(remoteId);
052e15b3
VK
194
195 // Try to find node by sysName as fallback
196 if (remoteNode == NULL)
197 {
cf1d689e
VK
198 TCHAR sysName[256] = _T("");
199 lldpRemSysName->getValueAsString(sysName, 256);
200 Trim(sysName);
201 DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): remoteId=%s: FindNodeByLLDPId failed, fallback to sysName (\"%s\")"), node->getName(), node->getId(), remoteId, sysName);
202 remoteNode = FindNodeBySysName(sysName);
052e15b3
VK
203 }
204
eec253a8
VK
205 if (remoteNode != NULL)
206 {
cf1d689e 207 DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): remoteId=%s remoteNode=%s [%d]"), node->getName(), node->getId(), remoteId, remoteNode->getName(), remoteNode->getId());
7bb0ef53 208
eec253a8 209 BYTE remoteIfId[1024];
cf1d689e
VK
210 size_t remoteIfIdLen = lldpRemPortId->getRawValue(remoteIfId, 1024);
211 Interface *ifRemote = FindRemoteInterface(remoteNode, lldpRemPortIdSubtype->getValueAsUInt(), remoteIfId, remoteIfIdLen);
841c9abc
VK
212 if (ifRemote == NULL)
213 {
214 // Try to find remote interface by description
cf1d689e
VK
215 TCHAR *ifDescr = lldpRemPortDesc->getValueAsString((TCHAR *)remoteIfId, 1024 / sizeof(TCHAR));
216 Trim(ifDescr);
217 DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): FindRemoteInterface failed, lookup by description (\"%s\")"), node->getName(), node->getId(), CHECK_NULL(ifDescr));
841c9abc 218 if (ifDescr != NULL)
c75e9ee4 219 ifRemote = remoteNode->findInterfaceByName(ifDescr);
841c9abc 220 }
eec253a8
VK
221
222 LL_NEIGHBOR_INFO info;
223
c42b4551 224 info.objectId = remoteNode->getId();
eec253a8
VK
225 info.ifRemote = (ifRemote != NULL) ? ifRemote->getIfIndex() : 0;
226 info.isPtToPt = true;
227 info.protocol = LL_PROTO_LLDP;
0717a453 228 info.isCached = false;
eec253a8
VK
229
230 // Index to lldpRemTable is lldpRemTimeMark, lldpRemLocalPortNum, lldpRemIndex
967893bb 231 UINT32 localPort = oid->getValue()[oid->getLength() - 2];
eec253a8
VK
232
233 // Determine interface index from local port number. It can be
234 // either ifIndex or dot1dBasePort, as described in LLDP MIB:
235 // A port number has no mandatory relationship to an
236 // InterfaceIndex object (of the interfaces MIB, IETF RFC 2863).
237 // If the LLDP agent is a IEEE 802.1D, IEEE 802.1Q bridge, the
238 // LldpPortNumber will have the same value as the dot1dBasePort
239 // object (defined in IETF RFC 1493) associated corresponding
240 // bridge port. If the system hosting LLDP agent is not an
241 // IEEE 802.1D or an IEEE 802.1Q bridge, the LldpPortNumber
242 // will have the same value as the corresponding interface's
243 // InterfaceIndex object.
244 if (node->isBridge())
245 {
246 Interface *localIf = node->findBridgePort(localPort);
247 if (localIf != NULL)
248 info.ifLocal = localIf->getIfIndex();
cf1d689e 249 DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): lookup bridge port: localPort=%d iface=%s"), node->getName(), node->getId(), localPort, (localIf != NULL) ? localIf->getName() : _T("(null)"));
eec253a8
VK
250 }
251 else
252 {
253 info.ifLocal = localPort;
254 }
255
256 nbs->addConnection(&info);
cf1d689e
VK
257 DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): added connection: objectId=%d ifRemote=%d ifLocal=%d"), node->getName(), node->getId(), info.objectId, info.ifRemote, info.ifLocal);
258 }
259 else
260 {
261 DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): remoteId=%s: remote node not found"), node->getName(), node->getId(), remoteId);
eec253a8
VK
262 }
263 }
cf1d689e
VK
264 else
265 {
266 TCHAR remoteId[256];
267 BinToStr(var->getValue(), var->getValueLength(), remoteId);
268 DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): SNMP get failed for remote ID %s"), node->getName(), node->getId(), remoteId);
269 }
270}
271
272/**
273 * Topology table walker's callback for LLDP topology table
274 */
275static UINT32 LLDPTopoHandler(UINT32 snmpVersion, SNMP_Variable *var, SNMP_Transport *transport, void *arg)
276{
277 ((StringObjectMap<SNMP_Variable> *)arg)->set(var->getName()->getValueAsText(), new SNMP_Variable(var));
278 return SNMP_ERR_SUCCESS;
eec253a8
VK
279}
280
f632f8ac
VK
281/**
282 * Add LLDP-discovered neighbors
283 */
eec253a8
VK
284void AddLLDPNeighbors(Node *node, LinkLayerNeighbors *nbs)
285{
286 if (!(node->getFlags() & NF_IS_LLDP))
287 return;
288
c42b4551 289 DbgPrintf(5, _T("LLDP: collecting topology information for node %s [%d]"), node->getName(), node->getId());
cf1d689e
VK
290
291 // Entire table should be cached before processing because some devices (D-Link for example)
292 // do not allow GET requests for table elements
293 StringObjectMap<SNMP_Variable> connections(true);
294 node->callSnmpEnumerate(_T(".1.0.8802.1.1.2.1.4.1.1"), LLDPTopoHandler, &connections);
295 if (connections.size() > 0)
296 {
297 DbgPrintf(5, _T("LLDP: %d entries in connection database for node %s [%d]"), connections.size(), node->getName(), node->getId());
298 StringList *oids = connections.keys();
299 for(int i = 0; i < oids->size(); i++)
300 {
301 const TCHAR *oid = oids->get(i);
302 if (_tcsncmp(oid, _T(".1.0.8802.1.1.2.1.4.1.1.5."), 26))
303 continue;
304 SNMP_Variable *var = connections.get(oid);
305 ProcessLLDPConnectionEntry(node, &connections, var, nbs);
306 }
307 delete oids;
308 }
309 else
310 {
311 DbgPrintf(5, _T("LLDP: connection database empty for node %s [%d]"), node->getName(), node->getId());
312 }
c42b4551 313 DbgPrintf(5, _T("LLDP: finished collecting topology information for node %s [%d]"), node->getName(), node->getId());
eec253a8 314}
20c7bdf7
VK
315
316/**
317 * Parse MAC address. Could be without separators or with any separator char.
318 */
319static bool ParseMACAddress(const char *text, int length, BYTE *mac, int *macLength)
320{
321 bool withSeparator = false;
322 char separator = 0;
323 int p = 0;
324 bool hi = true;
325 for(int i = 0; (i < length) && (p < 64); i++)
326 {
327 char c = toupper(text[i]);
328 if ((i % 3 == 2) && withSeparator)
329 {
330 if (c != separator)
331 return false;
332 continue;
333 }
fedc5635 334 if (!isdigit(c) && ((c < 'A') || (c > 'F')))
20c7bdf7
VK
335 {
336 if (i == 2)
337 {
338 withSeparator = true;
339 separator = c;
340 continue;
341 }
342 return false;
343 }
344 if (hi)
345 {
346 mac[p] = (isdigit(c) ? (c - '0') : (c - 'A' + 10)) << 4;
fedc5635 347 hi = false;
20c7bdf7
VK
348 }
349 else
350 {
351 mac[p] |= (isdigit(c) ? (c - '0') : (c - 'A' + 10));
352 p++;
fedc5635 353 hi = true;
20c7bdf7
VK
354 }
355 }
356 *macLength = p;
357 return true;
358}
359
360/**
361 * Build LLDP ID for node
362 */
363void BuildLldpId(int type, const BYTE *data, int length, TCHAR *id, int idLen)
364{
365 _sntprintf(id, idLen, _T("%d@"), type);
366 if (type == 4)
367 {
368 // Some D-Link switches returns MAC address for ID type 4 as formatted text instead of raw bytes
369 BYTE macAddr[64];
370 int macLength;
371 if ((length >= MAC_ADDR_LENGTH * 2) && ParseMACAddress((const char *)data, length, macAddr, &macLength))
372 {
373 BinToStr(macAddr, macLength, &id[_tcslen(id)]);
374 }
375 else
376 {
377 BinToStr(data, length, &id[_tcslen(id)]);
378 }
379 }
380 else
381 {
382 BinToStr(data, length, &id[_tcslen(id)]);
383 }
384}