diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 41752d75..2c9ea6c0 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -42,6 +42,8 @@ HEADERS += \ network/apikeys/apikey.h \ network/apikeys/apikeysprovider.h \ network/apikeys/apikeystorage.h \ + network/ping.h \ + network/pingreply.h \ platform/package.h \ platform/repository.h \ types/browseritem.h \ @@ -140,6 +142,8 @@ SOURCES += \ network/apikeys/apikey.cpp \ network/apikeys/apikeysprovider.cpp \ network/apikeys/apikeystorage.cpp \ + network/ping.cpp \ + network/pingreply.cpp \ nymeasettings.cpp \ platform/package.cpp \ platform/repository.cpp \ diff --git a/libnymea/network/ping.cpp b/libnymea/network/ping.cpp new file mode 100644 index 00000000..35aa04f8 --- /dev/null +++ b/libnymea/network/ping.cpp @@ -0,0 +1,434 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "ping.h" +#include "loggingcategories.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +NYMEA_LOGGING_CATEGORY(dcPing, "Ping") +NYMEA_LOGGING_CATEGORY(dcPingTraffic, "PingTraffic") + +Ping::Ping(QObject *parent) : QObject(parent) +{ + // Build socket descriptor + m_socketDescriptor = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (m_socketDescriptor < 0) { + qCWarning(dcPing()) << "Failed to create the ICMP socket." << strerror(errno); + verifyErrno(errno); + return; + } + + // Set time to live value + const int val = ICMP_TTL_VALUE; + if (setsockopt(m_socketDescriptor, SOL_IP, IP_TTL, &val, sizeof(val)) != 0) { + verifyErrno(errno); + qCWarning(dcPing()) << "Failed to set the ICMP socket TTL option:" << strerror(errno); + cleanUpSocket(); + return; + } + + // Configure non blocking + if (fcntl(m_socketDescriptor, F_SETFL, fcntl(m_socketDescriptor, F_GETFL, 0) | O_NONBLOCK) != 0) { + verifyErrno(errno); + qCWarning(dcPing()) << "Failed to set the ICMP socket function control to non-blocking" << strerror(errno); + cleanUpSocket(); + return; + } + + // Create the socket notifier for read notification + m_socketNotifier = new QSocketNotifier(m_socketDescriptor, QSocketNotifier::Read, this); + connect(m_socketNotifier, &QSocketNotifier::activated, this, &Ping::onSocketReadyRead); + + m_queueTimer = new QTimer(this); + m_queueTimer->setInterval(20); + m_queueTimer->setSingleShot(true); + connect(m_queueTimer, &QTimer::timeout, this, [=](){ + sendNextReply(); + }); + + m_socketNotifier->setEnabled(true); + m_available = true; + qCDebug(dcPing()) << "ICMP socket set up successfully (Socket ID:" << m_socketDescriptor << ")"; +} + +QByteArray Ping::payload() const +{ + return m_payload; +} + +void Ping::setPayload(const QByteArray &payload) +{ + Q_ASSERT_X(static_cast(payload.count()) <= ICMP_PAYLOAD_SIZE, "ping", QString("maximal payload size is %1").arg(ICMP_PAYLOAD_SIZE).toLocal8Bit()); + m_payload = payload; +} + +bool Ping::available() const +{ + return m_available; +} + +PingReply::Error Ping::error() const +{ + return m_error; +} + +PingReply *Ping::ping(const QHostAddress &hostAddress) +{ + PingReply *reply = new PingReply(this); + reply->m_targetHostAddress = hostAddress; + reply->m_networkInterface = getInterfaceForHostaddress(hostAddress); + + // 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; +} + +void Ping::sendNextReply() +{ + if (m_queueTimer->isActive()) + return; + + if (m_replyQueue.isEmpty()) + return; + + PingReply *reply = m_replyQueue.dequeue(); + //qCDebug(dcPing()) << "Send next reply," << m_replyQueue.count() << "left in queue"; + m_queueTimer->start(); + QTimer::singleShot(0, this, [=]() { performPing(reply); }); +} + +void Ping::performPing(PingReply *reply) +{ + if (!m_available) { + qCDebug(dcPing()) << "Cannot send ping request" << m_error; + finishReply(reply, m_error); + return; + } + + // Get host ip address + struct hostent *hostname = gethostbyname(reply->targetHostAddress().toString().toLocal8Bit().constData()); + struct sockaddr_in pingAddress; + memset(&pingAddress, 0, sizeof(pingAddress)); + pingAddress.sin_family = hostname->h_addrtype; + pingAddress.sin_port = 0; + pingAddress.sin_addr.s_addr = *(long*)hostname->h_addr; + + QHostAddress targetHostAddress = QHostAddress(qFromBigEndian(pingAddress.sin_addr.s_addr)); + + // Build the ICMP echo request packet + struct icmpPacket requestPacket; + 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(); + } + requestPacket.icmpHeadr.un.echo.sequence = htons(reply->m_sequenceNumber++); + + // Write the ICMP payload + memset(&requestPacket.icmpPayload, ' ', sizeof(requestPacket.icmpPayload)); + for (int i = 0; i < m_payload.count(); i++) + requestPacket.icmpPayload[i] = m_payload.at(i); + + // Calculate the ICMP packet checksum + requestPacket.icmpHeadr.checksum = calculateChecksum(reinterpret_cast(&requestPacket), sizeof(requestPacket)); + + // Get time for ping measurement and fill reply information + if (gettimeofday(&reply->m_startTime, nullptr) < 0 ) { + 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); + + // Send packet to the target ip + int bytesSent = sendto(m_socketDescriptor, &requestPacket, sizeof(requestPacket), 0, (struct sockaddr *)&pingAddress, sizeof(pingAddress)); + if (bytesSent < 0) { + verifyErrno(errno); + qCWarning(dcPing()) << "Failed to send data to" << reply->targetHostAddress().toString() << strerror(errno); + finishReply(reply, m_error); + return; + } + + // Start reply timer and handle timeout + m_pendingReplies.insert(reply->requestId(), reply); +} + +void Ping::verifyErrno(int error) +{ + switch (error) { + case ENETDOWN: + m_error = PingReply::ErrorNetworkDown; + break; + case ENETUNREACH: + m_error = PingReply::ErrorNetworkUnreachable; + break; + case EACCES: + case EPERM: + m_error = PingReply::ErrorPermissionDenied; + break; + default: + m_error = PingReply::ErrorSocketError; + } +} + +unsigned short Ping::calculateChecksum(unsigned short *b, int len) +{ + unsigned short *buf = b; + unsigned int sum = 0; + unsigned short result; + + for (sum = 0; len > 1; len -= 2) + sum += *buf++; + + if (len == 1) + sum += *(unsigned char*)buf; + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + result = ~sum; + return result; +} + +void Ping::cleanUpSocket() +{ + m_available = false; + + if (m_socketNotifier) { + m_socketNotifier->setEnabled(false); + delete m_socketNotifier; + m_socketNotifier = nullptr; + } + + if (m_socketDescriptor >= 0) { + close(m_socketDescriptor); + m_socketDescriptor = -1; + } +} + +void Ping::timeValueSubtract(timeval *start, timeval *stop) +{ + int sec = start->tv_sec - stop->tv_sec; + int usec = start->tv_usec - stop->tv_usec; + if (usec < 0) { + start->tv_sec = sec - 1; + start->tv_usec = 1000000 + usec; + } else { + start->tv_sec = sec; + start->tv_usec = usec; + } +} + +quint16 Ping::calculateRequestId() +{ + quint16 requestId = 0; + while (requestId == 0 || m_pendingReplies.contains(requestId)) { + requestId = rand(); + } + + return requestId; +} + +void Ping::finishReply(PingReply *reply, PingReply::Error error) +{ + reply->m_error = error; + m_pendingReplies.remove(reply->requestId()); + emit reply->finished(); + reply->deleteLater(); +} + +QNetworkInterface Ping::getInterfaceForHostaddress(const QHostAddress &address) +{ + foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { + // Only IPv4 + if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol) + continue; + + if (address.isInSubnet(entry.ip(), entry.netmask().toIPv4Address())) { + return networkInterface; + } + } + } + + return QNetworkInterface(); +} + +QNetworkInterface Ping::getInterfaceForMacAddress(const QString &macAddress) +{ + foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + if (networkInterface.hardwareAddress().toLower() == macAddress.toLower()) { + return networkInterface; + } + } + + return QNetworkInterface(); +} + +void Ping::onSocketReadyRead(int socketDescriptor) +{ + // We must read all data otherwise the socket notifier does not work as expected + while (true) { + // Read the socket data... + int receiveBufferSize = 2 * ICMP_PACKET_SIZE + sizeof(struct iphdr); + char receiveBuffer[receiveBufferSize]; + memset(&receiveBuffer, 0, sizeof(receiveBufferSize)); + + int bytesReceived = recv(socketDescriptor, &receiveBuffer, receiveBufferSize, 0); + if (bytesReceived < 0) { + return; + } + + qCDebug(dcPingTraffic()) << "Received" << bytesReceived << "bytes" << "( Socket ID:" << m_socketDescriptor << ")"; + struct iphdr *ipHeader = (struct iphdr *)receiveBuffer; + int ipHeaderLength = ipHeader->ihl << 2; + int icmpPacketSize = htons(ipHeader->tot_len) - ipHeaderLength; + QHostAddress senderAddress(qFromBigEndian(ipHeader->saddr)); + 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; + + struct icmp *responsePacket = reinterpret_cast(receiveBuffer + ipHeaderLength); + 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; + + if (responsePacket->icmp_type == ICMP_ECHOREPLY) { + PingReply *reply = m_pendingReplies.take(responsePacket->icmp_id); + 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(); + return; + } + + // Make sure the sender matches the target + if (reply->targetHostAddress() != senderAddress) { + qCWarning(dcPing()) << "Received id for different target reply" << reply->targetHostAddress().toString() << "!=" << senderAddress.toString(); + finishReply(reply, PingReply::ErrorHostUnreachable); + return; + } + + // Verify sequence number + if (responsePacket->icmp_seq != reply->sequenceNumber()) { + qCWarning(dcPing()) << "Received echo reply with different sequence number" << htons(responsePacket->icmp_seq); + finishReply(reply, PingReply::ErrorInvalidSequenceNumberResponse); + return; + } + + // Calculate ping duration 2 digits accuracy + struct timeval receiveTimeValue; + gettimeofday(&receiveTimeValue, nullptr); + timeValueSubtract(&receiveTimeValue, &reply->m_startTime); + reply->m_duration = qRound((receiveTimeValue.tv_sec * 1000 + (double)receiveTimeValue.tv_usec / 1000) * 100) / 100.0; + + QHostInfo::lookupHost(senderAddress.toString(), this, [=](const QHostInfo &info){ + if (info.error() != QHostInfo::NoError) { + qCWarning(dcPing()) << "Failed to look up hostname after successfull ping" << senderAddress.toString() << info.error(); + } else { + qCDebug(dcPing()) << "********Looked up hostname after successfull ping" << senderAddress.toString() << info.hostName(); + if (info.hostName() != senderAddress.toString()) { + reply->m_hostName = info.hostName(); + } + } + + 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) + << "Time:" << reply->duration() << "[ms]" << info.hostName(); + + finishReply(reply, PingReply::ErrorNoError); + }); + } else if (responsePacket->icmp_type == ICMP_DEST_UNREACH) { + + // Get the sending package + int messageOffset = sizeof(struct iphdr) + 8; + struct iphdr *nestedIpHeader = (struct iphdr *)(receiveBuffer + messageOffset); + int nestedIpHeaderLength = nestedIpHeader->ihl << 2; + int nestedIcmpPacketSize = htons(nestedIpHeader->tot_len) - nestedIpHeaderLength; + QHostAddress nestedSenderAddress(qFromBigEndian(nestedIpHeader->saddr)); + 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; + + struct icmp *nestedResponsePacket = reinterpret_cast(receiveBuffer + messageOffset + nestedIpHeaderLength); + 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; + + 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); + + PingReply *reply = m_pendingReplies.take(nestedResponsePacket->icmp_id); + 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) + << "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString(); + return; + } + + finishReply(reply, PingReply::ErrorHostUnreachable); + } + } +} + diff --git a/libnymea/network/ping.h b/libnymea/network/ping.h new file mode 100644 index 00000000..df673e9a --- /dev/null +++ b/libnymea/network/ping.h @@ -0,0 +1,112 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef PING_H +#define PING_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libnymea.h" + +#include "pingreply.h" + +#include + +#define ICMP_PACKET_SIZE 64 +#define ICMP_TTL_VALUE 64 +#define ICMP_PAYLOAD_SIZE (ICMP_PACKET_SIZE - sizeof(struct icmphdr)) + +class LIBNYMEA_EXPORT Ping : public QObject +{ + Q_OBJECT +public: + explicit Ping(QObject *parent = nullptr); + + QByteArray payload() const; + void setPayload(const QByteArray &payload); + + bool available() const; + + PingReply::Error error() const; + + PingReply *ping(const QHostAddress &hostAddress); + +signals: + void availableChanged(bool available); + +private: + struct icmpPacket { + struct icmphdr icmpHeadr; + char icmpPayload[ICMP_PAYLOAD_SIZE]; + }; + + // Config + QByteArray m_payload = "ping from nymea"; + PingReply::Error m_error = PingReply::ErrorNoError; + + // Socket + QSocketNotifier *m_socketNotifier = nullptr; + int m_socketDescriptor = -1; + QHash m_pendingReplies; + bool m_available = false; + + QQueue m_replyQueue; + QTimer *m_queueTimer = nullptr; + void sendNextReply(); + + //Error performPing(const QString &address); + void performPing(PingReply *reply); + void verifyErrno(int error); + + // Helper + unsigned short calculateChecksum(unsigned short *b, int len); + void cleanUpSocket(); + void timeValueSubtract(struct timeval *start, struct timeval *stop); + quint16 calculateRequestId(); + + void finishReply(PingReply *reply, PingReply::Error error); + + QNetworkInterface getInterfaceForHostaddress(const QHostAddress &address); + QNetworkInterface getInterfaceForMacAddress(const QString &macAddress); + +private slots: + void onSocketReadyRead(int socketDescriptor); + +}; + +#endif // PING_H diff --git a/libnymea/network/pingreply.cpp b/libnymea/network/pingreply.cpp new file mode 100644 index 00000000..e97034a1 --- /dev/null +++ b/libnymea/network/pingreply.cpp @@ -0,0 +1,71 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "pingreply.h" + +PingReply::PingReply(QObject *parent) : QObject(parent) +{ + +} + +QHostAddress PingReply::targetHostAddress() const +{ + return m_targetHostAddress; +} + +quint16 PingReply::sequenceNumber() const +{ + return m_sequenceNumber; +} + +quint16 PingReply::requestId() const +{ + return m_requestId; +} + +QString PingReply::hostName() const +{ + return m_hostName; +} + +QNetworkInterface PingReply::networkInterface() const +{ + return m_networkInterface; +} + +double PingReply::duration() const +{ + return m_duration; +} + +PingReply::Error PingReply::error() const +{ + return m_error; +} diff --git a/libnymea/network/pingreply.h b/libnymea/network/pingreply.h new file mode 100644 index 00000000..da57b41b --- /dev/null +++ b/libnymea/network/pingreply.h @@ -0,0 +1,91 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef PINGREPLY_H +#define PINGREPLY_H + +#include +#include +#include + +#include + +#include +#include + +class PingReply : public QObject +{ + Q_OBJECT + + friend class Ping; + +public: + enum Error { + ErrorNoError, + ErrorInvalidSequenceNumberResponse, + ErrorNetworkDown, + ErrorNetworkUnreachable, + ErrorPermissionDenied, + ErrorSocketError, + ErrorHostUnreachable + }; + Q_ENUM(Error) + + explicit PingReply(QObject *parent = nullptr); + + QHostAddress targetHostAddress() const; + quint16 sequenceNumber() const; + quint16 requestId() const; + QString hostName() const; + QNetworkInterface networkInterface() const; + + double duration() const; + + Error error() const; + +signals: + void finished(); + +private: + QHostAddress m_targetHostAddress; + quint16 m_sequenceNumber = 0; + quint16 m_requestId = 0; + QString m_hostName; + QNetworkInterface m_networkInterface; + + uint m_timeout = 3; + double m_duration = 0; + Error m_error = ErrorNoError; + + struct timeval m_startTime; + +}; + +#endif // PINGREPLY_H