diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp index ed86dc82..a350667b 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp @@ -93,7 +93,7 @@ NetworkDeviceDiscoveryImpl::NetworkDeviceDiscoveryImpl(QObject *parent) : NetworkDeviceDiscoveryImpl::~NetworkDeviceDiscoveryImpl() { - + delete m_cacheSettings; } NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover() @@ -146,16 +146,21 @@ bool NetworkDeviceDiscoveryImpl::running() const NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddress &macAddress) { - qCInfo(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress; - // Make sure we create only one monitor per MAC - if (m_monitors.contains(macAddress)) - return m_monitors.value(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; @@ -168,6 +173,7 @@ NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddre NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this); monitor->setNetworkDeviceInfo(info); 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; @@ -189,10 +195,19 @@ NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddre void NetworkDeviceDiscoveryImpl::unregisterMonitor(const MacAddress &macAddress) { + if (m_monitorsReferenceCount.contains(macAddress)) { + m_monitorsReferenceCount[macAddress] -= 1; + if (m_monitorsReferenceCount[macAddress] > 0) { + qCDebug(dcNetworkDeviceDiscovery()) << "Unregistered monitor for" << macAddress.toString() << "but keeping the monitor. There are still" << m_monitorsReferenceCount[macAddress] << "references to it."; + return; + } + } + if (m_monitors.contains(macAddress)) { NetworkDeviceMonitor *monitor = m_monitors.take(macAddress); qCInfo(dcNetworkDeviceDiscovery()) << "Unregister" << monitor; monitor->deleteLater(); + m_monitorsReferenceCount.remove(macAddress); } } @@ -201,34 +216,19 @@ void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *network unregisterMonitor(MacAddress(networkDeviceMonitor->networkDeviceInfo().macAddress())); } -PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address) +PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint retries) { + PingReply *reply = m_ping->ping(address, retries); // Note: we use any ping used trough this method also for the monitor evaluation - PingReply *reply = m_ping->ping(address); - connect(reply, &PingReply::finished, this, [=](){ - - // Search cache for mac address and update last seen - if (reply->error() == PingReply::ErrorNoError) { - foreach (const NetworkDeviceInfo &info, m_networkInfoCache) { - if (info.address() == address) { - // 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)); - } - } - } - } - - // Update any monitor - foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) { - if (monitor->networkDeviceInfo().address() == address) { - processMonitorPingResult(reply, monitor); - } - } - }); + watchPingReply(reply); + return reply; +} +PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, bool lookupHost, uint retries) +{ + PingReply *reply = m_ping->ping(address, lookupHost, retries); + // Note: we use any ping used trough this method also for the monitor evaluation + watchPingReply(reply); return reply; } @@ -304,7 +304,8 @@ void NetworkDeviceDiscoveryImpl::pingAllNetworkDevices() if (targetAddress == entry.ip()) continue; - PingReply *reply = ping(targetAddress); + // Retry only once to ping a device and lookup the hostname on success + PingReply *reply = ping(targetAddress, true, 1); m_runningPingRepies.append(reply); connect(reply, &PingReply::finished, this, [=](){ m_runningPingRepies.removeAll(reply); @@ -333,11 +334,37 @@ void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, Netw monitor->setLastSeen(QDateTime::currentDateTime()); monitor->setReachable(true); } else { - qCDebug(dcNetworkDeviceDiscovery()) << "Failed to ping device from" << monitor << reply->error(); + qCDebug(dcNetworkDeviceDiscovery()) << "Failed to ping device from" << monitor << "retrying" << reply->retries() << "times:" << reply->error(); monitor->setReachable(false); } } +void NetworkDeviceDiscoveryImpl::watchPingReply(PingReply *reply) +{ + connect(reply, &PingReply::finished, this, [=](){ + // Search cache for mac address and update last seen + 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)); + } + } + } + } + + // Update any monitor + foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) { + if (monitor->networkDeviceInfo().address() == reply->targetHostAddress()) { + processMonitorPingResult(reply, monitor); + } + } + }); +} + void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() { qCInfo(dcNetworkDeviceDiscovery()) << "Loading cached network device information from" << m_cacheSettings->fileName(); @@ -356,6 +383,7 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() if (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) { qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); m_cacheSettings->remove(""); + m_cacheSettings->endGroup(); // mac address continue; } @@ -391,16 +419,19 @@ void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const MacAddress & 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::saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo) { + if (!deviceInfo.isValid() || !deviceInfo.isComplete()) + return; + m_cacheSettings->beginGroup("NetworkDeviceInfos"); m_cacheSettings->beginGroup(deviceInfo.macAddress()); m_cacheSettings->setValue("address", deviceInfo.address().toString()); @@ -410,6 +441,7 @@ void NetworkDeviceDiscoveryImpl::saveNetworkDeviceCache(const NetworkDeviceInfo m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(MacAddress(deviceInfo.macAddress()))).toMSecsSinceEpoch()); m_cacheSettings->endGroup(); // mac address m_cacheSettings->endGroup(); // NetworkDeviceInfos + m_cacheSettings->sync(); } void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo) @@ -432,6 +464,12 @@ void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monitor) { + if (monitor->networkDeviceInfo().address().isNull()) + return; + + if (monitor->currentPingReply()) + return; + // Start action if we have not seen the device for gracePeriod seconds QDateTime currentDateTime = QDateTime::currentDateTime(); @@ -449,15 +487,20 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monit if (!requiresRefresh) return; - if (monitor->networkDeviceInfo().address().isNull()) - 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()); + monitor->setCurrentPingReply(reply); monitor->setLastConnectionAttempt(currentDateTime); - PingReply *reply = m_ping->ping(monitor->networkDeviceInfo().address()); + connect(reply, &PingReply::retry, monitor, [=](PingReply::Error error, uint retryCount){ + Q_UNUSED(error) + Q_UNUSED(retryCount) + monitor->setLastConnectionAttempt(QDateTime::currentDateTime()); + }); + connect(reply, &PingReply::finished, monitor, [=](){ + monitor->setCurrentPingReply(nullptr); processMonitorPingResult(reply, monitor); }); } diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h index d5ce9cb5..36b082a5 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h @@ -71,7 +71,8 @@ public: void unregisterMonitor(const MacAddress &macAddress) override; void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) override; - PingReply *ping(const QHostAddress &address) override; + PingReply *ping(const QHostAddress &address, uint retries = 3) override; + PingReply *ping(const QHostAddress &address, bool lookupHost, uint retries = 3); MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) override; MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) override; @@ -104,6 +105,7 @@ private: QList m_runningPingRepies; QHash m_monitors; + QHash m_monitorsReferenceCount; QHash m_lastSeen; QSettings *m_cacheSettings; @@ -113,6 +115,8 @@ private: void processMonitorPingResult(PingReply *reply, NetworkDeviceMonitorImpl *monitor); + void watchPingReply(PingReply *reply); + void loadNetworkDeviceCache(); void removeFromNetworkDeviceCache(const MacAddress &macAddress); void saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo); diff --git a/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp b/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp index a00f797f..c0191ff2 100644 --- a/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp +++ b/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp @@ -29,6 +29,9 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "networkdevicemonitorimpl.h" +#include "loggingcategories.h" + +Q_DECLARE_LOGGING_CATEGORY(dcNetworkDeviceDiscovery) namespace nymeaserver { @@ -41,7 +44,9 @@ NetworkDeviceMonitorImpl::NetworkDeviceMonitorImpl(const MacAddress &macAddress, NetworkDeviceMonitorImpl::~NetworkDeviceMonitorImpl() { - + if (m_currentPingReply) { + m_currentPingReply->abort(); + } } MacAddress NetworkDeviceMonitorImpl::macAddress() const @@ -73,6 +78,7 @@ void NetworkDeviceMonitorImpl::setReachable(bool reachable) if (m_reachable == reachable) return; + qCDebug(dcNetworkDeviceDiscovery()) << this << (reachable ? "is now reachable" : "is not reachable any more."); m_reachable = reachable; emit reachableChanged(m_reachable); } @@ -92,6 +98,26 @@ void NetworkDeviceMonitorImpl::setLastSeen(const QDateTime &lastSeen) emit lastSeenChanged(m_lastSeen); } +uint NetworkDeviceMonitorImpl::pingRetries() const +{ + return m_pingRetries; +} + +void NetworkDeviceMonitorImpl::setPingRetries(uint pingRetries) +{ + m_pingRetries = pingRetries; +} + +PingReply *NetworkDeviceMonitorImpl::currentPingReply() const +{ + return m_currentPingReply; +} + +void NetworkDeviceMonitorImpl::setCurrentPingReply(PingReply *reply) +{ + m_currentPingReply = reply; +} + QDateTime NetworkDeviceMonitorImpl::lastConnectionAttempt() const { return m_lastConnectionAttempt; diff --git a/libnymea-core/hardware/network/networkdevicemonitorimpl.h b/libnymea-core/hardware/network/networkdevicemonitorimpl.h index 57720ebe..e438d2d7 100644 --- a/libnymea-core/hardware/network/networkdevicemonitorimpl.h +++ b/libnymea-core/hardware/network/networkdevicemonitorimpl.h @@ -35,6 +35,7 @@ #include #include "network/networkdevicemonitor.h" +#include "network/pingreply.h" namespace nymeaserver { @@ -44,7 +45,7 @@ class NetworkDeviceMonitorImpl : public NetworkDeviceMonitor public: explicit NetworkDeviceMonitorImpl(const MacAddress &macAddress, QObject *parent = nullptr); - ~NetworkDeviceMonitorImpl(); + ~NetworkDeviceMonitorImpl() override; MacAddress macAddress() const override; @@ -57,16 +58,24 @@ public: QDateTime lastSeen() const override; void setLastSeen(const QDateTime &lastSeen); + uint pingRetries() const override; + void setPingRetries(uint pingRetries) override; + + PingReply *currentPingReply() const; + void setCurrentPingReply(PingReply *reply); + QDateTime lastConnectionAttempt() const; void setLastConnectionAttempt(const QDateTime &lastConnectionAttempt); + private: NetworkDeviceInfo m_networkDeviceInfo; MacAddress m_macAddress; bool m_reachable = false; QDateTime m_lastSeen; QDateTime m_lastConnectionAttempt; - + uint m_pingRetries = 5; + PingReply *m_currentPingReply = nullptr; }; } diff --git a/libnymea/network/networkdevicediscovery.h b/libnymea/network/networkdevicediscovery.h index 422ae79f..c5450add 100644 --- a/libnymea/network/networkdevicediscovery.h +++ b/libnymea/network/networkdevicediscovery.h @@ -60,7 +60,7 @@ public: virtual void unregisterMonitor(const MacAddress &macAddress) = 0; virtual void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) = 0; - virtual PingReply *ping(const QHostAddress &address) = 0; + virtual PingReply *ping(const QHostAddress &address, uint retries = 3) = 0; virtual MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) = 0; virtual MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) = 0; diff --git a/libnymea/network/networkdevicemonitor.h b/libnymea/network/networkdevicemonitor.h index e42d8f22..dcd8f459 100644 --- a/libnymea/network/networkdevicemonitor.h +++ b/libnymea/network/networkdevicemonitor.h @@ -53,6 +53,9 @@ public: virtual bool reachable() const = 0; virtual QDateTime lastSeen() const = 0; + virtual uint pingRetries() const = 0; + virtual void setPingRetries(uint pingRetries) = 0; + signals: void reachableChanged(bool reachable); void lastSeenChanged(const QDateTime &lastSeen); diff --git a/libnymea/network/ping.cpp b/libnymea/network/ping.cpp index a16202c7..ec8aa1cd 100644 --- a/libnymea/network/ping.cpp +++ b/libnymea/network/ping.cpp @@ -111,11 +111,23 @@ PingReply::Error Ping::error() const return m_error; } -PingReply *Ping::ping(const QHostAddress &hostAddress) +PingReply *Ping::ping(const QHostAddress &hostAddress, uint retries) { - PingReply *reply = new PingReply(this); - reply->m_targetHostAddress = hostAddress; - reply->m_networkInterface = NetworkUtils::getInterfaceForHostaddress(hostAddress); + PingReply *reply = createReply(hostAddress); + reply->m_retries = retries; + + // Perform the reply in the next event loop to give the user time to do the reply connects + m_replyQueue.enqueue(reply); + sendNextReply(); + + return reply; +} + +PingReply *Ping::ping(const QHostAddress &hostAddress, bool lookupHost, uint retries) +{ + PingReply *reply = createReply(hostAddress); + reply->m_retries = retries; + reply->m_doHostLookup = lookupHost; // Perform the reply in the next event loop to give the user time to do the reply connects m_replyQueue.enqueue(reply); @@ -133,7 +145,7 @@ void Ping::sendNextReply() return; PingReply *reply = m_replyQueue.dequeue(); - //qCDebug(dcPing()) << "Send next reply," << m_replyQueue.count() << "left in queue"; + qCDebug(dcPing()) << "Send next reply," << m_replyQueue.count() << "left in queue"; m_queueTimer->start(); QTimer::singleShot(0, reply, [=]() { performPing(reply); }); } @@ -168,11 +180,10 @@ void Ping::performPing(PingReply *reply) memset(&requestPacket, 0, sizeof(requestPacket)); requestPacket.icmpHeadr.type = ICMP_ECHO; if (reply->requestId() == 0) { - requestPacket.icmpHeadr.un.echo.id = calculateRequestId(); - } else { - requestPacket.icmpHeadr.un.echo.id = reply->requestId(); + reply->m_requestId = calculateRequestId(); } - requestPacket.icmpHeadr.un.echo.sequence = htons(reply->m_sequenceNumber++); + requestPacket.icmpHeadr.un.echo.id = htons(reply->requestId()); + requestPacket.icmpHeadr.un.echo.sequence = htons(reply->sequenceNumber()); // Write the ICMP payload memset(&requestPacket.icmpPayload, ' ', sizeof(requestPacket.icmpPayload)); @@ -187,13 +198,9 @@ void Ping::performPing(PingReply *reply) qCWarning(dcPing()) << "Failed to get start time for ping measurement" << strerror(errno); } - reply->m_requestId = requestPacket.icmpHeadr.un.echo.id; - reply->m_targetHostAddress = targetHostAddress; - reply->m_sequenceNumber = requestPacket.icmpHeadr.un.echo.sequence; - qCDebug(dcPingTraffic()) << "Send ICMP echo request" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]" - << "ID:" << QString("0x%1").arg(requestPacket.icmpHeadr.un.echo.id, 4, 16, QChar('0')) - << "Sequence:" << htons(requestPacket.icmpHeadr.un.echo.sequence); + << "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)); @@ -206,10 +213,7 @@ void Ping::performPing(PingReply *reply) // Start reply timer and handle timeout m_pendingReplies.insert(reply->requestId(), reply); - reply->m_timer->start(8000); - connect(reply, &PingReply::timeout, this, [=](){ - finishReply(reply, PingReply::ErrorTimeout); - }); + reply->m_timer->start(m_timeoutDuration); } void Ping::verifyErrno(int error) @@ -287,12 +291,66 @@ quint16 Ping::calculateRequestId() return requestId; } +PingReply *Ping::createReply(const QHostAddress &hostAddress) +{ + PingReply *reply = new PingReply(this); + reply->m_targetHostAddress = hostAddress; + reply->m_networkInterface = NetworkUtils::getInterfaceForHostaddress(hostAddress); + + connect(reply, &PingReply::timeout, this, [=](){ + // Note: this is not the ICMP timeout, here we actually got nothing from nobody... + finishReply(reply, PingReply::ErrorTimeout); + }); + + connect(reply, &PingReply::aborted, this, [=](){ + finishReply(reply, PingReply::ErrorAborted); + }); + + connect(reply, &PingReply::finished, this, [=](){ + reply->deleteLater(); + + // Cleanup any retry left over queue stuff + m_pendingReplies.remove(reply->requestId()); + m_replyQueue.removeAll(reply); + }); + + return reply; +} + void Ping::finishReply(PingReply *reply, PingReply::Error error) { - reply->m_error = error; - m_pendingReplies.remove(reply->requestId()); - emit reply->finished(); - reply->deleteLater(); + // Check if we should retry + if (reply->m_retryCount >= reply->m_retries || + error == PingReply::ErrorNoError || + error == PingReply::ErrorAborted || + error == PingReply::ErrorInvalidHostAddress || + error == PingReply::ErrorPermissionDenied) { + // No retry, we are done + reply->m_error = error; + reply->m_timer->stop(); + m_pendingReplies.remove(reply->requestId()); + emit reply->finished(); + } else { + // Note: don't remove from m_pendingReplies to prevent + // double assignmet of request id's between 2 retries + reply->m_error = error; + reply->m_retryCount++; + reply->m_sequenceNumber++; + + if (reply->m_retries > 1) { + qCDebug(dcPing()) << "Ping finished with error" << error << "Retry ping" << reply->targetHostAddress().toString() << reply->m_retryCount << "/" << reply->m_retries; + } else { + qCDebug(dcPing()) << "Ping finished with error" << error << "Retry ping" << reply->targetHostAddress().toString(); + } + emit reply->retry(error, reply->retryCount()); + + // Note: will be restarted once actually sent trough the network + reply->m_timer->stop(); + + // Re-Enqueu the reply + m_replyQueue.enqueue(reply); + sendNextReply(); + } } void Ping::onSocketReadyRead(int socketDescriptor) @@ -323,16 +381,19 @@ void Ping::onSocketReadyRead(int socketDescriptor) << "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(responsePacket->icmp_id, 4, 16, QChar('0')) - << "Sequence:" << responsePacket->icmp_seq; + << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber; if (responsePacket->icmp_type == ICMP_ECHOREPLY) { - PingReply *reply = m_pendingReplies.take(responsePacket->icmp_id); + + PingReply *reply = m_pendingReplies.take(icmpId); if (!reply) { - qCDebug(dcPing()) << "No pending reply for ping echo response with id" << QString("0x%1").arg(responsePacket->icmp_id, 4, 16, QChar('0')) << "Sequence:" << htons(responsePacket->icmp_seq) << "from" << senderAddress.toString(); + qCDebug(dcPing()) << "No pending reply for ping echo response with id" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) << "Sequence:" << icmpSequnceNumber << "from" << senderAddress.toString(); return; } @@ -344,8 +405,8 @@ void Ping::onSocketReadyRead(int socketDescriptor) } // Verify sequence number - if (responsePacket->icmp_seq != reply->sequenceNumber()) { - qCWarning(dcPing()) << "Received echo reply with different sequence number" << htons(responsePacket->icmp_seq); + if (icmpSequnceNumber != reply->sequenceNumber()) { + qCWarning(dcPing()) << "Received echo reply with different sequence number" << icmpSequnceNumber; finishReply(reply, PingReply::ErrorInvalidResponse); return; } @@ -356,15 +417,21 @@ void Ping::onSocketReadyRead(int socketDescriptor) timeValueSubtract(&receiveTimeValue, &reply->m_startTime); reply->m_duration = qRound((receiveTimeValue.tv_sec * 1000 + (double)receiveTimeValue.tv_usec / 1000) * 100) / 100.0; - // 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); - - qCDebug(dcPingTraffic()) << "Received ICMP response" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]" - << "ID:" << QString("0x%1").arg(responsePacket->icmp_id, 4, 16, QChar('0')) - << "Sequence:" << htons(responsePacket->icmp_seq) + 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]"; + 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); + } else { + finishReply(reply, PingReply::ErrorNoError); + } + + + } else if (responsePacket->icmp_type == ICMP_DEST_UNREACH) { // Get the sending package @@ -382,22 +449,25 @@ void Ping::onSocketReadyRead(int socketDescriptor) << "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(nestedResponsePacket->icmp_id, 4, 16, QChar('0')) - << "Sequence:" << nestedResponsePacket->icmp_seq; + << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber; qCDebug(dcPing()) << "ICMP destination unreachable" << nestedDestinationAddress.toString() << "Code:" << nestedResponsePacket->icmp_code - << "ID:" << QString("0x%1").arg(nestedResponsePacket->icmp_id, 4, 16, QChar('0')) - << "Sequence:" << htons(nestedResponsePacket->icmp_seq); + << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber; - PingReply *reply = m_pendingReplies.take(nestedResponsePacket->icmp_id); + PingReply *reply = m_pendingReplies.take(icmpId); if (!reply) { qCDebug(dcPingTraffic()) << "No pending reply for ping echo response unreachable with ID" - << QString("0x%1").arg(nestedResponsePacket->icmp_id, 4, 16, QChar('0')) - << "Sequence:" << htons(nestedResponsePacket->icmp_seq) + << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber << "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString(); return; } diff --git a/libnymea/network/ping.h b/libnymea/network/ping.h index 3ef536e2..42f26cea 100644 --- a/libnymea/network/ping.h +++ b/libnymea/network/ping.h @@ -62,7 +62,8 @@ public: PingReply::Error error() const; - PingReply *ping(const QHostAddress &hostAddress); + PingReply *ping(const QHostAddress &hostAddress, uint retries = 3); + PingReply *ping(const QHostAddress &hostAddress, bool lookupHost, uint retries = 3); signals: void availableChanged(bool available); @@ -76,6 +77,7 @@ private: // Config QByteArray m_payload = "ping from nymea"; PingReply::Error m_error = PingReply::ErrorNoError; + uint m_timeoutDuration = 5000; // Socket QSocketNotifier *m_socketNotifier = nullptr; @@ -98,6 +100,7 @@ private: void timeValueSubtract(struct timeval *start, struct timeval *stop); quint16 calculateRequestId(); + PingReply *createReply(const QHostAddress &hostAddress); void finishReply(PingReply *reply, PingReply::Error error); private slots: diff --git a/libnymea/network/pingreply.cpp b/libnymea/network/pingreply.cpp index c3fa499e..42ca099a 100644 --- a/libnymea/network/pingreply.cpp +++ b/libnymea/network/pingreply.cpp @@ -63,6 +63,16 @@ QNetworkInterface PingReply::networkInterface() const return m_networkInterface; } +uint PingReply::retries() const +{ + return m_retries; +} + +uint PingReply::retryCount() const +{ + return m_retryCount; +} + double PingReply::duration() const { return m_duration; @@ -72,3 +82,15 @@ PingReply::Error PingReply::error() const { return m_error; } + +bool PingReply::doHostLookup() const +{ + return m_doHostLookup; +} + +void PingReply::abort() +{ + m_timer->stop(); + m_error = ErrorAborted; + emit aborted(); +} diff --git a/libnymea/network/pingreply.h b/libnymea/network/pingreply.h index 8bf8ca19..0276d824 100644 --- a/libnymea/network/pingreply.h +++ b/libnymea/network/pingreply.h @@ -51,6 +51,7 @@ class LIBNYMEA_EXPORT PingReply : public QObject public: enum Error { ErrorNoError, + ErrorAborted, ErrorInvalidResponse, ErrorNetworkDown, ErrorNetworkUnreachable, @@ -70,22 +71,36 @@ public: QString hostName() const; QNetworkInterface networkInterface() const; + uint retries() const; + uint retryCount() const; + double duration() const; Error error() const; + bool doHostLookup() const; + +public slots: + void abort(); + signals: void finished(); void timeout(); + void retry(Error error, uint retryCount); + void aborted(); private: QTimer *m_timer = nullptr; QHostAddress m_targetHostAddress; - quint16 m_sequenceNumber = 0; + quint16 m_sequenceNumber = 1; quint16 m_requestId = 0; QString m_hostName; QNetworkInterface m_networkInterface; + bool m_doHostLookup = false; + + uint m_retries = 0; + uint m_retryCount = 0; uint m_timeout = 3; double m_duration = 0; Error m_error = ErrorNoError;