Add ping utils
This commit is contained in:
parent
355dcd50d4
commit
c3bb9a14b0
@ -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 \
|
||||
|
||||
434
libnymea/network/ping.cpp
Normal file
434
libnymea/network/ping.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <resolv.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip_icmp.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include <QtEndian>
|
||||
#include <QHostInfo>
|
||||
|
||||
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<uint>(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<unsigned short *>(&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<struct icmp *>(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<struct icmp *>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
112
libnymea/network/ping.h
Normal file
112
libnymea/network/ping.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <QUrl>
|
||||
#include <QQueue>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
#include <QSocketNotifier>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include <netinet/ip_icmp.h>
|
||||
|
||||
#include "libnymea.h"
|
||||
|
||||
#include "pingreply.h"
|
||||
|
||||
#include <netinet/ip_icmp.h>
|
||||
|
||||
#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<quint16, PingReply *> m_pendingReplies;
|
||||
bool m_available = false;
|
||||
|
||||
QQueue<PingReply *> 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
|
||||
71
libnymea/network/pingreply.cpp
Normal file
71
libnymea/network/pingreply.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
91
libnymea/network/pingreply.h
Normal file
91
libnymea/network/pingreply.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <QTimer>
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkInterface>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user