From 341a07cd8500706c8f10249d5d240cbb65acf9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 9 Dec 2024 17:08:22 +0100 Subject: [PATCH] Update ping and monitor handling --- .../network/networkdevicediscoveryimpl.cpp | 273 +++++++++--------- .../network/networkdevicediscoveryimpl.h | 3 + libnymea/network/networkdevicediscovery.h | 1 + libnymea/network/networkdeviceinfos.cpp | 11 + libnymea/network/networkdeviceinfos.h | 1 + libnymea/network/ping.cpp | 187 +++++++++--- libnymea/network/ping.h | 7 +- libnymea/network/pingreply.h | 4 +- 8 files changed, 306 insertions(+), 181 deletions(-) diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp index f6f07472..38545742 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp @@ -89,7 +89,7 @@ NetworkDeviceDiscoveryImpl::NetworkDeviceDiscoveryImpl(QObject *parent) : // Timer for updating the monitors m_monitorTimer = new QTimer(this); - m_monitorTimer->setInterval(5000); + m_monitorTimer->setInterval(10000); m_monitorTimer->setSingleShot(false); connect(m_monitorTimer, &QTimer::timeout, this, &NetworkDeviceDiscoveryImpl::evaluateMonitors); @@ -218,58 +218,6 @@ bool NetworkDeviceDiscoveryImpl::running() const return m_running; } -// NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddress &macAddress) -// { -// if (macAddress.isNull()) { -// qCWarning(dcNetworkDeviceDiscovery()) << "Could not register monitor for invalid" << macAddress; -// return nullptr; -// } - -// // Make sure we create only one monitor per MAC and keep track how many user -// // have access to this monitor otherwise an unregister could cause a crash in -// // an other plugin plugin which might still need it -// if (m_monitors.contains(macAddress)) { -// m_monitorsReferenceCount[macAddress] += 1; -// qCInfo(dcNetworkDeviceDiscovery()) << "Register network device monitor for" << macAddress << "which already exists. Returning existing monitor having now" << m_monitorsReferenceCount[macAddress] << "references."; -// return m_monitors.value(macAddress); -// } - -// qCInfo(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress; - -// // Fill in cached information -// NetworkDeviceInfo info; -// if (m_networkInfoCache.contains(macAddress)) { -// info = m_networkInfoCache.value(macAddress); -// } else { -// info.setMacAddress(macAddress.toString()); -// } - -// NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this); -// monitor->setNetworkDeviceInfo(info); -// monitor->setLastSeen(m_lastSeen.value(macAddress, QDateTime())); -// m_monitors.insert(macAddress, monitor); -// m_monitorsReferenceCount[macAddress] = 1; - -// if (!available()) { -// qCWarning(dcNetworkDeviceDiscovery()) << "Registered monitor but the hardware resource is not available. The monitor will not work as expected" << monitor; -// return monitor; -// } - -// // Restart the monitor timer since we evaluate this one now -// m_monitorTimer->start(); - -// if (!monitor->networkDeviceInfo().isValid()) { -// qCDebug(dcNetworkDeviceDiscovery()) << "Adding network device monitor for unresolved mac address. Starting a discovery..."; -// NetworkDeviceDiscoveryReply *reply = discover(); -// connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater); -// } else { -// evaluateMonitor(monitor); -// } - -// qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << monitor; -// return monitor; -// } - NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(Thing *thing) { if (!thing->thingClass().interfaces().contains("networkdevice")) { @@ -308,11 +256,14 @@ NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(Thing *thing) } } + bool newMonitor = true; if (internalMonitor) { qCDebug(dcNetworkDeviceDiscovery()) << "Already have an internal monitor for this network device" << internalMonitor; + newMonitor = false; } else { // Create a new monitor for the internal use internalMonitor = new NetworkDeviceMonitorImpl(macAddress, hostName, address, this); + m_monitors.insert(internalMonitor, QVector()); } internalMonitor->setMonitorMode(mode); @@ -354,7 +305,13 @@ NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(Thing *thing) // Create a new plugin monitor object we are going to return... NetworkDeviceMonitorImpl *pluginMonitor = createPluginMonitor(internalMonitor); + m_monitors[internalMonitor].append(pluginMonitor); qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << pluginMonitor; + + // In case this is a new monitor, let's evaluate it right the way so know asap if the device is reachable or not + if (newMonitor) + evaluateMonitor(internalMonitor); + return pluginMonitor; } @@ -374,6 +331,14 @@ PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint re return reply; } +PingReply *NetworkDeviceDiscoveryImpl::ping(const QString &hostName, uint retries) +{ + PingReply *reply = m_ping->ping(hostName, retries); + // Note: we use any ping used trough this method also for the monitor evaluation + watchPingReply(reply); + return reply; +} + PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, bool lookupHost, uint retries) { PingReply *reply = m_ping->ping(address, lookupHost, retries); @@ -521,7 +486,35 @@ void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, Netw // Save the last time we tried to communicate if (reply->error() == PingReply::ErrorNoError) { qCDebug(dcNetworkDeviceDiscovery()) << "Ping response from" << monitor << reply->duration() << "ms"; - monitor->setLastSeen(QDateTime::currentDateTime()); + + QDateTime currentDateTime = QDateTime::currentDateTime(); + m_lastSeen[reply->targetHostAddress()] = currentDateTime; + + for (int i = 0; i < m_networkInfoCache.count(); i++) { + + if (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeHostName) { + if (m_networkInfoCache.at(i).hostName() == monitor->hostName() && m_networkInfoCache.at(i).address() != reply->targetHostAddress()) { + QHostAddress oldAddress = m_networkInfoCache.at(i).address(); + qCDebug(dcNetworkDeviceDiscovery()) << "Hostname" << monitor->hostName() << "changed the IP address from" + << oldAddress.toString() + << "-->" + << reply->targetHostAddress().toString(); + + removeFromNetworkDeviceCache(oldAddress); + + NetworkDeviceInfo info = m_networkInfoCache.at(i); + info.setAddress(reply->targetHostAddress()); + + monitor->setNetworkDeviceInfo(info); + m_networkInfoCache[i] = info; + m_networkInfoCache.sortNetworkDevices(); + saveNetworkDeviceCache(info); + break; + } + } + } + + monitor->setLastSeen(currentDateTime); monitor->setReachable(true); } else { qCDebug(dcNetworkDeviceDiscovery()) << "Failed to ping device from" << monitor << "retrying" << reply->retries() << "times:" << reply->error(); @@ -531,29 +524,25 @@ void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, Netw void NetworkDeviceDiscoveryImpl::watchPingReply(PingReply *reply) { - connect(reply, &PingReply::finished, this, [=](){ - // Search cache for mac address and update last seen + connect(reply, &PingReply::finished, this, [this, reply](){ + if (reply->error() == PingReply::ErrorNoError) { - // FIXME - // if (reply->error() == PingReply::ErrorNoError) { - // foreach (const NetworkDeviceInfo &info, m_networkInfoCache) { - // if (info.address() == reply->targetHostAddress()) { - // // Found info for this ip, update the cache - // MacAddress macAddress(info.macAddress()); - // if (!macAddress.isNull() && m_networkInfoCache.contains(macAddress)) { - // m_lastSeen[macAddress] = QDateTime::currentDateTime(); - // saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); - // } - // } - // } - // } + int index = m_networkInfoCache.indexFromHostAddress(reply->targetHostAddress()); + if (index < 0) + return; - // Update any monitor - // foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) { - // if (monitor->networkDeviceInfo().address() == reply->targetHostAddress()) { - // processMonitorPingResult(reply, monitor); - // } - // } + m_lastSeen[reply->targetHostAddress()] = QDateTime::currentDateTime(); + saveNetworkDeviceCache(m_networkInfoCache.at(index)); + } + + // Update any relevant monitor + foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) { + if ((monitor->monitorMode() == NetworkDeviceInfo::MonitorModeIp && reply->targetHostAddress() == monitor->address()) || + (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeHostName && reply->hostName() == monitor->hostName()) || + (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeMac && reply->targetHostAddress() == monitor->networkDeviceInfo().address())) { + processMonitorPingResult(reply, monitor); + } + } }); } @@ -562,7 +551,7 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() qCInfo(dcNetworkDeviceDiscovery()) << "Loading cached network device information from" << m_cacheSettings->fileName(); m_networkInfoCache.clear(); - QDateTime now = QDateTime::currentDateTime(); + QDateTime currentDateTime = QDateTime::currentDateTime(); uint cacheVersion = m_cacheSettings->value("version", 0).toUInt(); @@ -579,7 +568,7 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong()); // Remove the info from the cache if not seen fo the last 30 days... - if (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) { + if (lastSeen.date().addDays(m_cacheCleanupPeriod) < currentDateTime.date()) { qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << address.toString(); m_cacheSettings->remove(""); m_cacheSettings->endGroup(); // mac address @@ -624,21 +613,6 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() m_lastCacheHousekeeping = QDateTime::currentDateTime(); } -void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const MacAddress &macAddress) -{ - if (macAddress.isNull()) - return; - - // m_networkInfoCache.remove(macAddress); - // m_lastSeen.remove(macAddress); - // m_cacheSettings->beginGroup("NetworkDeviceInfos"); - // m_cacheSettings->beginGroup(macAddress.toString()); - // m_cacheSettings->remove(""); - // m_cacheSettings->endGroup(); // mac address - // m_cacheSettings->endGroup(); // NetworkDeviceInfos - // m_cacheSettings->sync(); -} - void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const QHostAddress &address) { if (address.isNull()) @@ -707,17 +681,23 @@ void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monitor) { - // if (!monitor->networkDeviceInfo().isValid()) - // return; - - - if (monitor->currentPingReply()) + if (monitor->currentPingReply()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Monitor has still a ping reply pending:" << monitor; return; + } + + qCDebug(dcNetworkDeviceDiscovery()) << "Evaluate monitor" << monitor; // Start action if we have not seen the device for gracePeriod seconds QDateTime currentDateTime = QDateTime::currentDateTime(); bool requiresRefresh = false; + + if (!monitor->networkDeviceInfo().isValid()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Network device info not valid for" << monitor; + requiresRefresh = true; + } + if (monitor->lastSeen().isNull()) { qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not seen since application start."; requiresRefresh = true; @@ -744,57 +724,85 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monit if (!requiresRefresh) return; - // Try to ping first - qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString(); - PingReply *reply = m_ping->ping(monitor->networkDeviceInfo().address(), monitor->pingRetries()); + // Try to ping first. + + // IMPORTANT: use the ping methods from this object, so the result will automatically + // be evaluated for the monitors and cache + PingReply *reply = nullptr; + if (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeHostName) { + qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->hostName(); + reply = ping(monitor->hostName(), monitor->pingRetries()); + } else { + qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString(); + reply = ping(monitor->networkDeviceInfo().address(), monitor->pingRetries()); + } + monitor->setCurrentPingReply(reply); monitor->setLastConnectionAttempt(currentDateTime); - connect(reply, &PingReply::retry, monitor, [=](PingReply::Error error, uint retryCount){ + connect(reply, &PingReply::retry, monitor, [monitor](PingReply::Error error, uint retryCount){ Q_UNUSED(error) Q_UNUSED(retryCount) monitor->setLastConnectionAttempt(QDateTime::currentDateTime()); }); - connect(reply, &PingReply::destroyed, monitor, [=](){ - monitor->setCurrentPingReply(nullptr); + connect(reply, &PingReply::destroyed, monitor, [monitor, reply](){ + if (monitor->currentPingReply() == reply) { + monitor->setCurrentPingReply(nullptr); + } }); - connect(reply, &PingReply::finished, monitor, [=](){ - processMonitorPingResult(reply, monitor); + connect(reply, &PingReply::finished, monitor, [monitor, reply](){ + if (monitor->currentPingReply() == reply) { + monitor->setCurrentPingReply(nullptr); + } }); } void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) { - QDateTime now = QDateTime::currentDateTime(); - // m_lastSeen[macAddress] = now; + QDateTime currentDateTime = QDateTime::currentDateTime(); + m_lastSeen[address] = currentDateTime; - // FIXME + // Update monitors and cache + for (int i = 0; i < m_networkInfoCache.count(); i++) { + switch (m_networkInfoCache.at(i).monitorMode()) { + case NetworkDeviceInfo::MonitorModeMac: + if (m_networkInfoCache.at(i).macAddressInfos().hasMacAddress(macAddress) && + m_networkInfoCache.at(i).address() != address) { - // if (m_networkInfoCache.hasMacAddress(macAddress)) { - // if (m_networkInfoCache.value(macAddress).address() != address) { - // m_networkInfoCache[macAddress].setAddress(address); - // saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); - // } - // } + QHostAddress oldAddress = m_networkInfoCache.at(i).address(); + qCDebug(dcNetworkDeviceDiscovery()) << "Host" << macAddress.toString() << "changed the IP address from" + << oldAddress.toString() + << "-->" + << address.toString(); - // Update the monitors - // NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); - // if (monitor) { - // monitor->setLastSeen(now); - // if (monitor->networkDeviceInfo().address() != address) { - // NetworkDeviceInfo info = monitor->networkDeviceInfo(); - // info.setAddress(address); - // monitor->setNetworkDeviceInfo(info); - // qCDebug(dcNetworkDeviceDiscovery()) << "NetworkDeviceMonitor" << monitor << "ip address changed"; - // emit monitor->networkDeviceInfoChanged(monitor->networkDeviceInfo()); - // } + removeFromNetworkDeviceCache(oldAddress); - // if (monitor->networkDeviceInfo().isComplete()) { - // monitor->setReachable(true); - // } - // } + NetworkDeviceInfo info = m_networkInfoCache.at(i); + info.setAddress(address); + + m_networkInfoCache[i] = info; + m_networkInfoCache.sortNetworkDevices(); + saveNetworkDeviceCache(info); + + foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) { + if (monitor->macAddress() == macAddress) { + monitor->setNetworkDeviceInfo(info); + monitor->setLastSeen(currentDateTime); + monitor->setReachable(true); + break; + } + } + break; + } + break; + case NetworkDeviceInfo::MonitorModeHostName: + case NetworkDeviceInfo::MonitorModeIp: + saveNetworkDeviceCache(m_networkInfoCache.at(i)); + break; + } + } // Check if we have currently reply running if (!m_currentDiscoveryReply) @@ -910,6 +918,7 @@ void NetworkDeviceDiscoveryImpl::cleanupPluginMonitor(NetworkDeviceMonitorImpl * foreach (NetworkDeviceMonitorImpl *internalMonitor, m_monitors.keys()) { if (m_monitors.value(internalMonitor).contains(pluginMonitor)) { m_monitors[internalMonitor].removeAll(pluginMonitor); + pluginMonitor->deleteLater(); if (m_monitors.value(internalMonitor).isEmpty()) { qCDebug(dcNetworkDeviceDiscovery()) << "No monitor registered for this network device any more. Unregister internal monitor" << internalMonitor; @@ -952,7 +961,7 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitors() evaluateMonitor(monitor); // Check if there is any monitor which has not be seen since - if (!monitor->reachable() /*&& monitor->lastConnectionAttempt().isValid()*/ && longerAgoThan(monitor->lastSeen(), m_monitorInterval)) { + if (!monitor->reachable() && monitor->lastConnectionAttempt().isValid() && longerAgoThan(monitor->lastSeen(), m_monitorInterval)) { monitorRequiresRediscovery = true; } } @@ -967,7 +976,7 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitors() // // Do some cache housekeeping if required // if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) { // qCInfo(dcNetworkDeviceDiscovery()) << "Starting cache housekeeping since it is more than one day since the last clanup..."; - // QDateTime now = QDateTime::currentDateTime(); + // QDateTime currentDateTime = QDateTime::currentDateTime(); // foreach (const MacAddress &mac, m_lastSeen.keys()) { // // Remove the info from the cache if not seen fo the last 30 days... // if (m_lastSeen.value(mac).date().addDays(m_cacheCleanupPeriod) < QDateTime::currentDateTime().date()) { @@ -975,7 +984,7 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitors() // removeFromNetworkDeviceCache(mac); // } // } - // m_lastCacheHousekeeping = now; + // m_lastCacheHousekeeping = currentDateTime; // } } diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h index 30c41ea3..f17f04c6 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h @@ -71,6 +71,7 @@ public: void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) override; PingReply *ping(const QHostAddress &address, uint retries = 3) override; + PingReply *ping(const QString &hostName, uint retries = 3) override; PingReply *ping(const QHostAddress &address, bool lookupHost, uint retries = 3); MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) override; @@ -138,6 +139,8 @@ private: void processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); + void testPingMonitor(NetworkDeviceMonitorImpl *monitor); + // Time helpers bool longerAgoThan(const QDateTime &dateTime, uint minutes); QDateTime convertMinuteBased(const QDateTime &dateTime = QDateTime()); diff --git a/libnymea/network/networkdevicediscovery.h b/libnymea/network/networkdevicediscovery.h index fa216def..e260e60b 100644 --- a/libnymea/network/networkdevicediscovery.h +++ b/libnymea/network/networkdevicediscovery.h @@ -61,6 +61,7 @@ public: virtual void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) = 0; virtual PingReply *ping(const QHostAddress &address, uint retries = 3) = 0; + virtual PingReply *ping(const QString &hostName, uint retries = 3) = 0; virtual MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) = 0; virtual MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) = 0; diff --git a/libnymea/network/networkdeviceinfos.cpp b/libnymea/network/networkdeviceinfos.cpp index 91e54ee9..4c824894 100644 --- a/libnymea/network/networkdeviceinfos.cpp +++ b/libnymea/network/networkdeviceinfos.cpp @@ -56,6 +56,17 @@ int NetworkDeviceInfos::indexFromHostAddress(const QHostAddress &address) return -1; } +int NetworkDeviceInfos::indexFromHostName(const QString &hostName) +{ + for (int i = 0; i < this->size(); i++) { + if (at(i).hostName() == hostName) { + return i; + } + } + + return -1; +} + QList NetworkDeviceInfos::indexFromMacAddress(const QString &macAddress) { return indexFromMacAddress(MacAddress(macAddress)); diff --git a/libnymea/network/networkdeviceinfos.h b/libnymea/network/networkdeviceinfos.h index d1302873..86d1433a 100644 --- a/libnymea/network/networkdeviceinfos.h +++ b/libnymea/network/networkdeviceinfos.h @@ -44,6 +44,7 @@ public: NetworkDeviceInfos(const QVector &other); int indexFromHostAddress(const QHostAddress &address); + int indexFromHostName(const QString &hostName); QList indexFromMacAddress(const QString &macAddress); QList indexFromMacAddress(const MacAddress &macAddress); diff --git a/libnymea/network/ping.cpp b/libnymea/network/ping.cpp index 54e6dc2d..94071503 100644 --- a/libnymea/network/ping.cpp +++ b/libnymea/network/ping.cpp @@ -141,6 +141,18 @@ PingReply *Ping::ping(const QHostAddress &hostAddress, bool lookupHost, uint ret return reply; } +PingReply *Ping::ping(const QString &hostName, uint retries) +{ + // Make first the host lookup, then the ping the first address and save it in the reply + PingReply *reply = createReply(hostName); + reply->m_retries = retries; + + // Note: due to a Qt bug < 5.9 we need to use old SLOT style and cannot make use of lambda here + int lookupId = QHostInfo::lookupHost(hostName, this, SLOT(onHostLookupFinished(QHostInfo))); + m_pendingHostAddressLookups.insert(lookupId, reply); + return reply; +} + void Ping::sendNextReply() { if (m_queueTimer->isActive()) @@ -150,7 +162,7 @@ void Ping::sendNextReply() return; m_currentReply = m_replyQueue.dequeue(); - qCDebug(dcPing()) << "Send next reply " << m_currentReply->targetHostAddress().toString() << QString("0x%1").arg(m_currentReply->requestId(), 4, 16, QChar('0')) << ", " << m_replyQueue.count() << "left in queue"; + qCDebug(dcPing()).nospace().noquote() << "Send next reply " << m_currentReply->targetHostAddress().toString() << " ID: " << QString("0x%1").arg(m_currentReply->requestId(), 4, 16, QChar('0')) << ", " << m_replyQueue.count() << "left in queue"; m_queueTimer->start(); QTimer::singleShot(0, this, [=]() { if (!m_currentReply) @@ -209,8 +221,8 @@ void Ping::performPing(PingReply *reply) } qCDebug(dcPingTraffic()) << "Send ICMP echo request" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]" - << "ID:" << QString("0x%1").arg(reply->requestId(), 4, 16, QChar('0')) - << "Sequence:" << reply->sequenceNumber(); + << "ID:" << QString("0x%1").arg(reply->requestId(), 4, 16, QChar('0')) + << "Sequence:" << reply->sequenceNumber(); // Send packet to the target ip int bytesSent = sendto(m_socketDescriptor, &requestPacket, sizeof(requestPacket), 0, (struct sockaddr *)&pingAddress, sizeof(pingAddress)); @@ -329,14 +341,43 @@ PingReply *Ping::createReply(const QHostAddress &hostAddress) return reply; } +PingReply *Ping::createReply(const QString &hostName) +{ + PingReply *reply = new PingReply(this); + reply->m_hostName = hostName; + + connect(reply, &PingReply::timeout, this, [=](){ + // Note: this is not the ICMP timeout, here we actually got nothing from anybody... + finishReply(reply, PingReply::ErrorTimeout); + }); + + connect(reply, &PingReply::aborted, this, [=](){ + finishReply(reply, PingReply::ErrorAborted); + }); + + connect(reply, &PingReply::finished, this, [=](){ + cleanUpReply(reply); + reply->deleteLater(); + }); + + // Just in case the reply get's deleted before beeing able to finish ... + connect(reply, &PingReply::destroyed, this, [this, reply](){ + cleanUpReply(reply); + }); + + return reply; +} + void Ping::finishReply(PingReply *reply, PingReply::Error error) { // Check if we should retry if (reply->m_retryCount >= reply->m_retries || - error == PingReply::ErrorNoError || - error == PingReply::ErrorAborted || - error == PingReply::ErrorInvalidHostAddress || - error == PingReply::ErrorPermissionDenied) { + error == PingReply::ErrorNoError || + error == PingReply::ErrorAborted || + error == PingReply::ErrorInvalidHostAddress || + error == PingReply::ErrorPermissionDenied|| + error == PingReply::ErrorHostNameLookupFailed || + error == PingReply::ErrorHostNameNotFound) { // No retry, we are done reply->m_error = error; reply->m_timer->stop(); @@ -374,11 +415,11 @@ void Ping::cleanUpReply(PingReply *reply) m_pendingReplies.remove(reply->requestId()); m_replyQueue.removeAll(reply); - if (m_pendingHostLookups.values().contains(reply)) { + if (m_pendingHostNameLookups.values().contains(reply)) { // Abort any pending host lookups, the reply has been finished - int lookupId = m_pendingHostLookups.key(reply); + int lookupId = m_pendingHostNameLookups.key(reply); QHostInfo::abortHostLookup(lookupId); - m_pendingHostLookups.remove(lookupId); + m_pendingHostNameLookups.remove(lookupId); } } @@ -404,19 +445,19 @@ void Ping::onSocketReadyRead(int socketDescriptor) QHostAddress destinationAddress(qFromBigEndian(ipHeader->daddr)); qCDebug(dcPingTraffic()) << "IP header: Lenght" << ipHeaderLength - << "Sender:" << senderAddress.toString() - << "Destination:" << destinationAddress.toString() - << "Size:" << htons(ipHeader->tot_len) << "B" - << "TTL" << ipHeader->ttl; + << "Sender:" << senderAddress.toString() + << "Destination:" << destinationAddress.toString() + << "Size:" << htons(ipHeader->tot_len) << "B" + << "TTL" << ipHeader->ttl; struct icmp *responsePacket = reinterpret_cast(receiveBuffer + ipHeaderLength); quint16 icmpId = htons(responsePacket->icmp_id); quint16 icmpSequnceNumber = htons(responsePacket->icmp_seq); qCDebug(dcPingTraffic()) << "ICMP packt (Size:" << icmpPacketSize << "Bytes):" - << "Type" << responsePacket->icmp_type - << "Code:" << responsePacket->icmp_code - << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) - << "Sequence:" << icmpSequnceNumber; + << "Type" << responsePacket->icmp_type + << "Code:" << responsePacket->icmp_code + << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber; if (responsePacket->icmp_type == ICMP_ECHOREPLY) { @@ -447,14 +488,14 @@ void Ping::onSocketReadyRead(int socketDescriptor) reply->m_duration = qRound((receiveTimeValue.tv_sec * 1000 + (double)receiveTimeValue.tv_usec / 1000) * 100) / 100.0; qCDebug(dcPing()) << "Received ICMP response" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]" - << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) - << "Sequence:" << icmpSequnceNumber - << "Time:" << reply->duration() << "[ms]"; + << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber + << "Time:" << reply->duration() << "[ms]"; if (reply->doHostLookup()) { // Note: due to a Qt bug < 5.9 we need to use old SLOT style and cannot make use of lambda here int lookupId = QHostInfo::lookupHost(senderAddress.toString(), this, SLOT(onHostLookupFinished(QHostInfo))); - m_pendingHostLookups.insert(lookupId, reply); + m_pendingHostNameLookups.insert(lookupId, reply); // Finish the reply after the host lookup has finished } else { finishReply(reply, PingReply::ErrorNoError); @@ -472,20 +513,20 @@ void Ping::onSocketReadyRead(int socketDescriptor) QHostAddress nestedDestinationAddress(qFromBigEndian(nestedIpHeader->daddr)); qCDebug(dcPingTraffic()) << "++ IP header: Lenght" << nestedIpHeaderLength - << "Sender:" << nestedSenderAddress.toString() - << "Destination:" << nestedDestinationAddress.toString() - << "Size:" << htons(nestedIpHeader->tot_len) << "B" - << "TTL" << ipHeader->ttl; + << "Sender:" << nestedSenderAddress.toString() + << "Destination:" << nestedDestinationAddress.toString() + << "Size:" << htons(nestedIpHeader->tot_len) << "B" + << "TTL" << ipHeader->ttl; struct icmp *nestedResponsePacket = reinterpret_cast(receiveBuffer + messageOffset + nestedIpHeaderLength); icmpId = htons(nestedResponsePacket->icmp_id); icmpSequnceNumber = htons(nestedResponsePacket->icmp_seq); qCDebug(dcPingTraffic()) << "++ ICMP packt (Size:" << nestedIcmpPacketSize << "Bytes):" - << "Type" << nestedResponsePacket->icmp_type - << "Code:" << nestedResponsePacket->icmp_code - << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) - << "Sequence:" << icmpSequnceNumber; + << "Type" << nestedResponsePacket->icmp_type + << "Code:" << nestedResponsePacket->icmp_code + << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber; qCDebug(dcPing()) << "ICMP destination unreachable" << nestedDestinationAddress.toString() << "Code:" << nestedResponsePacket->icmp_code @@ -495,9 +536,9 @@ void Ping::onSocketReadyRead(int socketDescriptor) PingReply *reply = m_pendingReplies.value(icmpId); if (!reply) { qCDebug(dcPingTraffic()) << "No pending reply for ping echo response unreachable with ID" - << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) - << "Sequence:" << icmpSequnceNumber - << "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString(); + << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber + << "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString(); return; } @@ -508,20 +549,74 @@ void Ping::onSocketReadyRead(int socketDescriptor) void Ping::onHostLookupFinished(const QHostInfo &info) { - PingReply *reply = m_pendingHostLookups.value(info.lookupId()); - if (!reply) { - qCWarning(dcPing()) << "Could not find reply after host lookup."; - return; - } + if (m_pendingHostNameLookups.contains(info.lookupId())) { - if (info.error() != QHostInfo::NoError) { - qCWarning(dcPing()) << "Failed to look up hostname after successfull ping" << reply->targetHostAddress().toString() << info.error(); - } else { - qCDebug(dcPing()) << "Looked up hostname after successfull ping" << reply->targetHostAddress().toString() << info.hostName(); - if (info.hostName() != reply->targetHostAddress().toString()) { - reply->m_hostName = info.hostName(); + PingReply *reply = m_pendingHostNameLookups.take(info.lookupId()); + if (!reply) { + qCWarning(dcPing()) << "Could not find ping reply after finished host lookup" << info.hostName() << info.addresses() << info.errorString(); + return; } - } - finishReply(reply, PingReply::ErrorNoError); + PingReply::Error pingError = PingReply::ErrorNoError; + switch(info.error()) { + case QHostInfo::NoError: + qCDebug(dcPing()) << "Looked up hostname after successfull ping" << reply->targetHostAddress().toString() << info.hostName(); + if (info.hostName() != reply->targetHostAddress().toString()) + reply->m_hostName = info.hostName(); + + break; + case QHostInfo::HostNotFound: + qCWarning(dcPing()) << "Unable to find hostname:" << reply->hostName() << info.errorString(); + pingError = PingReply::ErrorHostNameNotFound; + break; + default: + qCWarning(dcPing()) << "Failed to lookup hostname" << (reply->targetHostAddress().isNull() ? reply->hostName() : reply->targetHostAddress().toString()) << info.errorString(); + pingError = PingReply::ErrorHostNameLookupFailed; + break; + } + + finishReply(reply, pingError); + + } else if (m_pendingHostAddressLookups.contains(info.lookupId())) { + + PingReply *reply = m_pendingHostAddressLookups.take(info.lookupId()); + if (!reply) { + qCWarning(dcPing()) << "Could not find ping reply after finished host lookup" << info.hostName() << info.addresses() << info.errorString(); + return; + } + + PingReply::Error pingError = PingReply::ErrorNoError; + switch(info.error()) { + case QHostInfo::NoError: + qCDebug(dcPing()) << "Looked up address for hostname finished successfully" << info.hostName() << info.addresses(); + if (info.addresses().isEmpty()) { + qCWarning(dcPing()) << "Looked up address finished succesfully but there are no addresses available for" << info.hostName(); + pingError = PingReply::ErrorHostNameNotFound; + } else { + reply->m_targetHostAddress = info.addresses().first(); + reply->m_networkInterface = NetworkUtils::getInterfaceForHostaddress(reply->targetHostAddress()); + pingError = PingReply::ErrorNoError; + } + break; + case QHostInfo::HostNotFound: + qCDebug(dcPing()) << "Unable to find hostname:" << reply->hostName() << info.errorString(); + pingError = PingReply::ErrorHostNameNotFound; + break; + default: + qCWarning(dcPing()) << "Failed to lookup hostname" << (reply->targetHostAddress().isNull() ? reply->hostName() : reply->targetHostAddress().toString()) << info.errorString(); + pingError = PingReply::ErrorHostNameLookupFailed; + break; + } + + if (pingError != PingReply::ErrorNoError) { + // Host lookup failed, we cannot continue, nothing to ping + finishReply(reply, pingError); + } else { + // Ping the resolved host address + m_replyQueue.enqueue(reply); + sendNextReply(); + } + } else { + qCWarning(dcPing()) << "Host name lookup finished but we have no ping reply for it. Ignoring looked up information" << info.hostName() << info.addresses() << info.error(); + } } diff --git a/libnymea/network/ping.h b/libnymea/network/ping.h index 3545b196..26ae1c85 100644 --- a/libnymea/network/ping.h +++ b/libnymea/network/ping.h @@ -67,6 +67,8 @@ public: PingReply *ping(const QHostAddress &hostAddress, uint retries = 3); PingReply *ping(const QHostAddress &hostAddress, bool lookupHost, uint retries = 3); + PingReply *ping(const QString &hostName, uint retries = 3); + signals: void availableChanged(bool available); @@ -91,7 +93,8 @@ private: QTimer *m_queueTimer = nullptr; PingReply *m_currentReply = nullptr; void sendNextReply(); - QHash m_pendingHostLookups; + QHash m_pendingHostNameLookups; + QHash m_pendingHostAddressLookups; //Error performPing(const QString &address); void performPing(PingReply *reply); @@ -104,13 +107,13 @@ private: quint16 calculateRequestId(); PingReply *createReply(const QHostAddress &hostAddress); + PingReply *createReply(const QString &hostName); void finishReply(PingReply *reply, PingReply::Error error); void cleanUpReply(PingReply *reply); private slots: void onSocketReadyRead(int socketDescriptor); void onHostLookupFinished(const QHostInfo &info); - }; #endif // PING_H diff --git a/libnymea/network/pingreply.h b/libnymea/network/pingreply.h index 0276d824..e45f57d8 100644 --- a/libnymea/network/pingreply.h +++ b/libnymea/network/pingreply.h @@ -59,7 +59,9 @@ public: ErrorSocketError, ErrorTimeout, ErrorHostUnreachable, - ErrorInvalidHostAddress + ErrorInvalidHostAddress, + ErrorHostNameLookupFailed, + ErrorHostNameNotFound }; Q_ENUM(Error)