fixed LLDP based topology discovery issues on some devices (mostly D-Link)
authorVictor Kirhenshtein <victor@netxms.org>
Fri, 29 Apr 2016 17:15:45 +0000 (20:15 +0300)
committerVictor Kirhenshtein <victor@netxms.org>
Fri, 29 Apr 2016 17:15:45 +0000 (20:15 +0300)
ChangeLog
src/server/core/lldp.cpp
src/server/core/main.cpp
src/server/core/node.cpp
src/server/core/objects.cpp
src/server/include/nms_objects.h

index f47fa8e..c7a5dc6 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,7 @@
 *
 
 - New parameters in Linux platform subagent to get additional CPU information (frequency, model, etc.)
+- Fixed bugs in LLDP based network topology discovery
 - Management console
        - Improved dashboard gauge widget
        - Added translucence option for dashboard line charts
index a106260..ebd1d1f 100644 (file)
@@ -84,17 +84,19 @@ ObjectArray<LLDP_LOCAL_PORT_INFO> *GetLLDPLocalPortInfo(SNMP_Transport *snmp)
  * @param idType port ID type (value of lldpRemPortIdSubtype)
  * @param id port ID
  * @param idLen port ID length in bytes
- * @param nbs link layer neighbors list which is being built
  */
-static Interface *FindRemoteInterface(Node *node, UINT32 idType, BYTE *id, size_t idLen, LinkLayerNeighbors *nbs)
+static Interface *FindRemoteInterface(Node *node, UINT32 idType, BYTE *id, size_t idLen)
 {
    LLDP_LOCAL_PORT_INFO port;
-       TCHAR ifName[130];
+       TCHAR ifName[130], buffer[256];
        Interface *ifc;
 
+   DbgPrintf(5, _T("LLDPTopoHandler(%s [%d]): FindRemoteInterface: idType=%d id=%s (%d)"), node->getName(), node->getId(), idType, BinToStr(id, idLen, buffer), (int)idLen);
+
        // Try local LLDP port info first
    if (node->getLldpLocalPortInfo(idType, id, idLen, &port))
    {
+      DbgPrintf(5, _T("LLDPTopoHandler(%s [%d]): FindRemoteInterface: getLldpLocalPortInfo found port: %d \"%s\""), node->getName(), node->getId(), port.portNumber, port.ifDescr);
       if (node->isBridge())
          ifc = node->findBridgePort(port.portNumber);
       else
@@ -105,6 +107,7 @@ static Interface *FindRemoteInterface(Node *node, UINT32 idType, BYTE *id, size_
          return ifc;
    }
 
+   DbgPrintf(5, _T("LLDPTopoHandler(%s [%d]): FindRemoteInterface: interface not found by getLldpLocalPortInfo"), node->getName(), node->getId());
        switch(idType)
        {
                case 3: // MAC address
@@ -147,62 +150,71 @@ static Interface *FindRemoteInterface(Node *node, UINT32 idType, BYTE *id, size_
 }
 
 /**
- * Topology table walker's callback for LLDP topology table
+ * Get variable from cache
  */
-static UINT32 LLDPTopoHandler(UINT32 snmpVersion, SNMP_Variable *var, SNMP_Transport *transport, void *arg)
+static SNMP_Variable *GetVariableFromCache(UINT32 *oid, size_t oidLen, StringObjectMap<SNMP_Variable> *cache)
+{
+   TCHAR oidText[MAX_OID_LEN * 6];
+   SNMPConvertOIDToText(oidLen, oid, oidText, MAX_OID_LEN * 6);
+   return cache->get(oidText);
+}
+
+/**
+ * Process LLDP connection database entry
+ */
+static void ProcessLLDPConnectionEntry(Node *node, StringObjectMap<SNMP_Variable> *connections, SNMP_Variable *var, LinkLayerNeighbors *nbs)
 {
-       LinkLayerNeighbors *nbs = (LinkLayerNeighbors *)arg;
-       Node *node = (Node *)nbs->getData();
        SNMP_ObjectId *oid = var->getName();
 
        // Get additional info for current record
        UINT32 newOid[128];
        memcpy(newOid, oid->getValue(), oid->getLength() * sizeof(UINT32));
-   SNMP_PDU *pRqPDU = new SNMP_PDU(SNMP_GET_REQUEST, SnmpNewRequestId(), snmpVersion);
 
        newOid[oid->getLength() - 4] = 4;       // lldpRemChassisIdSubtype
-       pRqPDU->bindVariable(new SNMP_Variable(newOid, oid->getLength()));
+       SNMP_Variable *lldpRemChassisIdSubtype = GetVariableFromCache(newOid, oid->getLength(), connections);
 
        newOid[oid->getLength() - 4] = 7;       // lldpRemPortId
-       pRqPDU->bindVariable(new SNMP_Variable(newOid, oid->getLength()));
+   SNMP_Variable *lldpRemPortId = GetVariableFromCache(newOid, oid->getLength(), connections);
 
        newOid[oid->getLength() - 4] = 6;       // lldpRemPortIdSubtype
-       pRqPDU->bindVariable(new SNMP_Variable(newOid, oid->getLength()));
+   SNMP_Variable *lldpRemPortIdSubtype = GetVariableFromCache(newOid, oid->getLength(), connections);
 
        newOid[oid->getLength() - 4] = 8;       // lldpRemPortDesc
-       pRqPDU->bindVariable(new SNMP_Variable(newOid, oid->getLength()));
+   SNMP_Variable *lldpRemPortDesc = GetVariableFromCache(newOid, oid->getLength(), connections);
 
    newOid[oid->getLength() - 4] = 9;   // lldpRemSysName
-   pRqPDU->bindVariable(new SNMP_Variable(newOid, oid->getLength()));
+   SNMP_Variable *lldpRemSysName = GetVariableFromCache(newOid, oid->getLength(), connections);
 
-       SNMP_PDU *pRespPDU = NULL;
-   UINT32 rcc = transport->doRequest(pRqPDU, &pRespPDU, SnmpGetDefaultTimeout(), 3);
-       delete pRqPDU;
-       if ((rcc == SNMP_ERR_SUCCESS) && (pRespPDU->getNumVariables() >= 4))
+       if ((lldpRemChassisIdSubtype != NULL) && (lldpRemPortId != NULL) && (lldpRemPortIdSubtype != NULL) && (lldpRemPortDesc != NULL) && (lldpRemSysName != NULL))
    {
                // Build LLDP ID for remote system
                TCHAR remoteId[256];
-      BuildLldpId(pRespPDU->getVariable(0)->getValueAsInt(), var->getValue(), (int)var->getValueLength(), remoteId, 256);
+      BuildLldpId(lldpRemChassisIdSubtype->getValueAsInt(), var->getValue(), (int)var->getValueLength(), remoteId, 256);
                Node *remoteNode = FindNodeByLLDPId(remoteId);
 
                // Try to find node by sysName as fallback
                if (remoteNode == NULL)
                {
-                  TCHAR sysName[256];
-                  remoteNode = FindNodeBySysName(pRespPDU->getVariable(4)->getValueAsString(sysName, 256));
+                  TCHAR sysName[256] = _T("");
+                  lldpRemSysName->getValueAsString(sysName, 256);
+                  Trim(sysName);
+         DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): remoteId=%s: FindNodeByLLDPId failed, fallback to sysName (\"%s\")"), node->getName(), node->getId(), remoteId, sysName);
+         remoteNode = FindNodeBySysName(sysName);
                }
 
                if (remoteNode != NULL)
                {
-                       nbs->setData(2, transport);
+             DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): remoteId=%s remoteNode=%s [%d]"), node->getName(), node->getId(), remoteId, remoteNode->getName(), remoteNode->getId());
 
                        BYTE remoteIfId[1024];
-                       size_t remoteIfIdLen = pRespPDU->getVariable(1)->getRawValue(remoteIfId, 1024);
-                       Interface *ifRemote = FindRemoteInterface(remoteNode, pRespPDU->getVariable(2)->getValueAsUInt(), remoteIfId, remoteIfIdLen, nbs);
+                       size_t remoteIfIdLen = lldpRemPortId->getRawValue(remoteIfId, 1024);
+                       Interface *ifRemote = FindRemoteInterface(remoteNode, lldpRemPortIdSubtype->getValueAsUInt(), remoteIfId, remoteIfIdLen);
          if (ifRemote == NULL)
          {
             // Try to find remote interface by description
-            TCHAR *ifDescr = pRespPDU->getVariable(3)->getValueAsString((TCHAR *)remoteIfId, 1024 / sizeof(TCHAR));
+            TCHAR *ifDescr = lldpRemPortDesc->getValueAsString((TCHAR *)remoteIfId, 1024 / sizeof(TCHAR));
+            Trim(ifDescr);
+            DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): FindRemoteInterface failed, lookup by description (\"%s\")"), node->getName(), node->getId(), CHECK_NULL(ifDescr));
             if (ifDescr != NULL)
                ifRemote = remoteNode->findInterfaceByName(ifDescr);
          }
@@ -234,6 +246,7 @@ static UINT32 LLDPTopoHandler(UINT32 snmpVersion, SNMP_Variable *var, SNMP_Trans
                                Interface *localIf = node->findBridgePort(localPort);
                                if (localIf != NULL)
                                        info.ifLocal = localIf->getIfIndex();
+            DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): lookup bridge port: localPort=%d iface=%s"), node->getName(), node->getId(), localPort, (localIf != NULL) ? localIf->getName() : _T("(null)"));
                        }
                        else
                        {
@@ -241,10 +254,28 @@ static UINT32 LLDPTopoHandler(UINT32 snmpVersion, SNMP_Variable *var, SNMP_Trans
                        }
 
                        nbs->addConnection(&info);
+         DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): added connection: objectId=%d ifRemote=%d ifLocal=%d"), node->getName(), node->getId(), info.objectId, info.ifRemote, info.ifLocal);
+               }
+               else
+               {
+         DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): remoteId=%s: remote node not found"), node->getName(), node->getId(), remoteId);
                }
        }
-   delete pRespPDU;
-       return SNMP_ERR_SUCCESS;
+       else
+       {
+      TCHAR remoteId[256];
+      BinToStr(var->getValue(), var->getValueLength(), remoteId);
+          DbgPrintf(5, _T("ProcessLLDPConnectionEntry(%s [%d]): SNMP get failed for remote ID %s"), node->getName(), node->getId(), remoteId);
+       }
+}
+
+/**
+ * Topology table walker's callback for LLDP topology table
+ */
+static UINT32 LLDPTopoHandler(UINT32 snmpVersion, SNMP_Variable *var, SNMP_Transport *transport, void *arg)
+{
+   ((StringObjectMap<SNMP_Variable> *)arg)->set(var->getName()->getValueAsText(), new SNMP_Variable(var));
+   return SNMP_ERR_SUCCESS;
 }
 
 /**
@@ -256,9 +287,29 @@ void AddLLDPNeighbors(Node *node, LinkLayerNeighbors *nbs)
                return;
 
        DbgPrintf(5, _T("LLDP: collecting topology information for node %s [%d]"), node->getName(), node->getId());
-       nbs->setData(0, node);
-       nbs->setData(1, NULL);  // local port info cache
-       node->callSnmpEnumerate(_T(".1.0.8802.1.1.2.1.4.1.1.5"), LLDPTopoHandler, nbs);
+
+       // Entire table should be cached before processing because some devices (D-Link for example)
+       // do not allow GET requests for table elements
+       StringObjectMap<SNMP_Variable> connections(true);
+   node->callSnmpEnumerate(_T(".1.0.8802.1.1.2.1.4.1.1"), LLDPTopoHandler, &connections);
+   if (connections.size() > 0)
+   {
+      DbgPrintf(5, _T("LLDP: %d entries in connection database for node %s [%d]"), connections.size(), node->getName(), node->getId());
+      StringList *oids = connections.keys();
+      for(int i = 0; i < oids->size(); i++)
+      {
+         const TCHAR *oid = oids->get(i);
+         if (_tcsncmp(oid, _T(".1.0.8802.1.1.2.1.4.1.1.5."), 26))
+            continue;
+         SNMP_Variable *var = connections.get(oid);
+         ProcessLLDPConnectionEntry(node, &connections, var, nbs);
+      }
+      delete oids;
+   }
+   else
+   {
+      DbgPrintf(5, _T("LLDP: connection database empty for node %s [%d]"), node->getName(), node->getId());
+   }
        DbgPrintf(5, _T("LLDP: finished collecting topology information for node %s [%d]"), node->getName(), node->getId());
 }
 
index ac3270b..acad712 100644 (file)
@@ -1575,6 +1575,35 @@ int ProcessConsoleCommand(const TCHAR *pszCmdLine, CONSOLE_CTX pCtx)
                                        ConsoleWrite(pCtx, _T("ERROR: Invalid index name\n\n"));
                        }
                }
+               else if (IsCommand(_T("LLDP"), szBuffer, 4))
+      {
+         // Get argument
+         pArg = ExtractWord(pArg, szBuffer);
+         UINT32 dwNode = _tcstoul(szBuffer, NULL, 0);
+         if (dwNode != 0)
+         {
+            NetObj *pObject = FindObjectById(dwNode);
+            if (pObject != NULL)
+            {
+               if (pObject->getObjectClass() == OBJECT_NODE)
+               {
+                  ((Node *)pObject)->showLLDPInfo(pCtx);
+               }
+               else
+               {
+                  ConsoleWrite(pCtx, _T("ERROR: Object is not a node\n\n"));
+               }
+            }
+            else
+            {
+               ConsolePrintf(pCtx, _T("ERROR: Object with ID %d does not exist\n\n"), dwNode);
+            }
+         }
+         else
+         {
+            ConsoleWrite(pCtx, _T("ERROR: Invalid or missing node ID\n\n"));
+         }
+      }
                else if (IsCommand(_T("MODULES"), szBuffer, 3))
                {
          ConsoleWrite(pCtx, _T("Loaded server modules:\n"));
index 8439c47..9d4d489 100644 (file)
@@ -740,11 +740,13 @@ Interface *Node::findInterfaceByIndex(UINT32 ifIndex)
  */
 Interface *Node::findInterfaceByName(const TCHAR *name)
 {
-   UINT32 i;
+   if ((name == NULL) || (name[0] == 0))
+      return NULL;
+
    Interface *pInterface;
 
    LockChildList(FALSE);
-   for(i = 0; i < m_dwChildCount; i++)
+   for(UINT32 i = 0; i < m_dwChildCount; i++)
       if (m_pChildList[i]->getObjectClass() == OBJECT_INTERFACE)
       {
          pInterface = (Interface *)m_pChildList[i];
@@ -7131,6 +7133,35 @@ bool Node::getLldpLocalPortInfo(UINT32 idType, BYTE *id, size_t idLen, LLDP_LOCA
 }
 
 /**
+ * Show node LLDP information
+ */
+void Node::showLLDPInfo(CONSOLE_CTX console)
+{
+   TCHAR buffer[256];
+
+   lockProperties();
+   ConsolePrintf(console, _T("\x1b[1m*\x1b[0m Node LLDP ID: %s\n\n"), m_lldpNodeId);
+   ConsolePrintf(console, _T("\x1b[1m*\x1b[0m Local LLDP ports\n"));
+   if (m_lldpLocalPortInfo != NULL)
+   {
+      ConsolePrintf(console, _T("   Port | ST | Len | Local ID                 | Description\n")
+                             _T("   -----+----+-----+--------------------------+--------------------------------------\n"));
+      for(int i = 0; i < m_lldpLocalPortInfo->size(); i++)
+      {
+         LLDP_LOCAL_PORT_INFO *port = m_lldpLocalPortInfo->get(i);
+         ConsolePrintf(console, _T("   %4d | %2d | %3d | %-24s | %s\n"),
+                       port->portNumber, port->localIdSubtype, (int)port->localIdLen,
+                       BinToStr(port->localId, port->localIdLen, buffer), port->ifDescr);
+      }
+   }
+   else
+   {
+      ConsolePrintf(console, _T("   No local LLDP port info\n"));
+   }
+   unlockProperties();
+}
+
+/**
  * Fill NXCP message with software package list
  */
 void Node::writePackageListToMessage(NXCPMessage *msg)
index b78bfcf..c529ea5 100644 (file)
@@ -832,6 +832,8 @@ static bool SysNameComparator(NetObj *object, void *sysName)
  */
 Node NXCORE_EXPORTABLE *FindNodeBySysName(const TCHAR *sysName)
 {
+   if ((sysName == NULL) || (sysName[0] == 0))
+      return NULL;
    return (Node *)g_idxNodeById.find(SysNameComparator, (void *)sysName);
 }
 
index b80e228..a7772b2 100644 (file)
@@ -1450,6 +1450,7 @@ public:
    bool getOutwardInterface(const InetAddress& destAddr, InetAddress *srcAddr, UINT32 *srcIfIndex);
        ComponentTree *getComponents();
    bool getLldpLocalPortInfo(UINT32 idType, BYTE *id, size_t idLen, LLDP_LOCAL_PORT_INFO *port);
+   void showLLDPInfo(CONSOLE_CTX console);
 
        void setRecheckCapsFlag() { m_dwDynamicFlags |= NDF_RECHECK_CAPABILITIES; }
    void setDiscoveryPollTimeStamp();