Merge PR #432: Network device discovery

This commit is contained in:
Jenkins nymea 2021-07-06 17:18:25 +02:00
commit 02390650ba
30 changed files with 2663 additions and 3 deletions

3
.gitignore vendored
View File

@ -9,6 +9,9 @@ snap/prime/
snap/stage/
doc/interfacelist.qdoc
# Never add downloaded json data
data/mac-database/macaddress.io-db.json
.crossbuilder/
debs*.tar

View File

@ -72,6 +72,15 @@ Chat with us on [Telegram](http://t.me/nymeacommunity) or [Discord](https://disc
A detailed documentation on how to develop with *nymea* is available on the [nymea | developer documentation](https://nymea.io/documentation/developers/).
## Network discovery
When starting nymead as user without root privileges, the network device discovery will not available due to missing raw socket permission.
If you still want to make use of this feature, the binary capabilities need to be adjusted.
sudo setcap cap_net_admin,cap_net_raw=eip /usr/bin/nymead
This will allow nymead to create raw sockets for ARP and ICMP network discovery tools even when nymead gets started as user without root privileges.
## License
--------------------------------------------
> nymea is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.

View File

@ -0,0 +1,21 @@
# Building the MAC address database
The MAC address database can be created using the `build-database.py` script.
The script will download the latest registered MAC address block information
from [https://macaddress.io](https://macaddress.io) and creates a size and access optimized
SQLITE database file.
The generated database is read performance optimized and tried to keep as small as possible for
searching MAC address OUIs (Organizationally Unique Identifiers) blocks and returning the registered company name.
$ python3 build-database.py
The final database will be named `mac-addresses.db`.
In nymea the `MacAddressDatabase` class will provide access to this generated database and provides an asynch threaded mechanism to
get the company name for a given MAC address.
The database will be searched in the system default data location `${XDG_DATA_DIRS}/nymead/`.
On debian package based system the database file will be installed in `/usr/share/nymea/nymead/mac-addresses.db`.

View File

@ -0,0 +1,97 @@
#!/usr/bin/env python
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# 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
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
import os
import sys
import json
import sqlite3
import requests
downloadUrl='https://macaddress.io/database/macaddress.io-db.json'
jsonDataFileName = 'macaddress.io-db.json'
databaseFileName = 'mac-addresses.db'
print('Downloading', downloadUrl, '...')
downloadRequest = requests.get(downloadUrl)
open(jsonDataFileName, 'wb').write(downloadRequest.content)
print('Reading JSON data..')
vendorInfoHash = {}
jsonDataFile = open(jsonDataFileName, 'r')
lines = jsonDataFile.readlines()
for line in lines:
vendorMap = json.loads(line)
vendorInfoHash[vendorMap['oui']] = vendorMap['companyName']
jsonDataFile.close()
if os.path.exists(databaseFileName):
print('Delete old db file', databaseFileName)
os.remove(databaseFileName)
print('Build up database', databaseFileName)
connection = sqlite3.connect(databaseFileName)
cursor = connection.cursor()
cursor.execute('CREATE TABLE companyNames (companyName TEXT PRIMARY KEY, UNIQUE(companyName));')
cursor.execute('CREATE TABLE oui (oui TEXT PRIMARY KEY, companyNameIndex INTEGER, UNIQUE(oui)) WITHOUT ROWID;')
#cursor.execute('CREATE UNIQUE INDEX ouiIndex ON `oui` (`oui`);')
# Insert all vendor names alphabetically
print('Writing company names into database...')
sortedVendorHash = sorted(vendorInfoHash.items(), key=lambda x: x[1], reverse=False)
vendorCount = 0
for vendorInfo in sortedVendorHash:
insertQuery = 'INSERT OR IGNORE INTO companyNames (companyName) VALUES(?);'
cursor.execute(insertQuery, [vendorInfo[1]])
cursor.execute('SELECT COUNT(companyName) FROM companyNames');
countResult = cursor.fetchall()
vendorCount = countResult[0][0]
connection.commit()
# Insert all oui with reference to company name
print('Writing OUI into database with company name refference...')
# Sort by oui for good binary search in the db
sortedOuiHash = sorted(vendorInfoHash.items(), key=lambda x: x[0], reverse=False)
ouiCount = 0
for vendorInfo in sortedOuiHash:
insertQuery = 'INSERT OR IGNORE INTO oui (oui, companyNameIndex) VALUES(?, (SELECT rowid FROM companyNames WHERE companyName = ?));'
cursor.execute(insertQuery, [vendorInfo[0].replace(':', ''), vendorInfo[1]])
cursor.execute('SELECT COUNT(oui) FROM oui');
countResult = cursor.fetchall()
ouiCount = countResult[0][0]
connection.commit()
connection.close()
print('Finished successfully. Loaded', ouiCount, 'OUI values from', vendorCount, 'manufacturers into', databaseFileName)

Binary file not shown.

20
debian/control vendored
View File

@ -59,6 +59,7 @@ Depends: libqt5network5,
libqt5websockets5,
libqt5bluetooth5,
libqt5sql5-sqlite,
libcap2-bin,
logrotate,
bluez,
tar,
@ -70,7 +71,8 @@ Depends: libqt5network5,
libnymea1 (= ${binary:Version}),
${shlibs:Depends},
${misc:Depends}
Recommends: nymea-cli,
Recommends: nymea-cli,
nymea-data,
network-manager,
nymea-update-plugin-impl,
nymea-system-plugin-impl,
@ -86,6 +88,22 @@ Description: An open source IoT server - daemon
This package will install the daemon.
Package: nymea-data
Section: misc
Architecture: all
Depends: ${misc:Depends}
Recommends: nymea
Description: Optional data for extending functionality in nymea daemon
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
allows them to interact.
With the powerful rule engine you are able to connect any device available
in the system and create individual scenes and behaviors for your environment.
.
This package provides the MAC address database for nymead and for the plugins in
order to get the manufacturer name for a given MAC address.
Package: nymea-doc
Section: doc
Architecture: all

2
debian/nymea-data.install vendored Normal file
View File

@ -0,0 +1,2 @@
data/mac-database/mac-addresses.db usr/share/nymea/nymead/

View File

@ -2,7 +2,7 @@
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> #
# Copyright (C) 2015 - 2021 nymea GmbH <developer@nymea.io> #
# #
# This file is part of nymea. #
# #
@ -20,6 +20,15 @@
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Make sure user will be able to perform a networkdiscovery
# using ARP and ICMP sockets (raw_sock).
setcap cap_net_admin,cap_net_raw=eip /usr/bin/nymead
if [ $? -eq 0 ]; then
echo "Set raw socket network capabilities successfully for nymead."
else
echo "Failed to set raw socket network capabilities for nymead. Network device discovery will not be available for non root users."
fi
# Restart the nymea daemon after update if it's running
systemctl daemon-reload
systemctl status nymead > /dev/null 2>&1

View File

@ -46,6 +46,7 @@
#include "hardware/modbus/modbusrtumanager.h"
#include "hardware/modbus/modbusrtuhardwareresourceimplementation.h"
#include "network/networkdevicediscovery.h"
namespace nymeaserver {
@ -78,6 +79,8 @@ HardwareManagerImplementation::HardwareManagerImplementation(Platform *platform,
m_modbusRtuResource = new ModbusRtuHardwareResourceImplementation(modbusRtuManager, this);
m_networkDeviceDiscovery = new NetworkDeviceDiscovery(this);
// Enable all the resources
setResourceEnabled(m_pluginTimerManager, true);
setResourceEnabled(m_radio433, true);
@ -100,6 +103,7 @@ HardwareManagerImplementation::HardwareManagerImplementation(Platform *platform,
HardwareManagerImplementation::~HardwareManagerImplementation()
{
}
Radio433 *HardwareManagerImplementation::radio433()
@ -152,6 +156,11 @@ ModbusRtuHardwareResource *HardwareManagerImplementation::modbusRtuResource()
return m_modbusRtuResource;
}
NetworkDeviceDiscovery *HardwareManagerImplementation::networkDeviceDiscovery()
{
return m_networkDeviceDiscovery;
}
void HardwareManagerImplementation::thingsLoaded()
{
m_zigbeeResource->thingsLoaded();

View File

@ -64,6 +64,7 @@ public:
I2CManager *i2cManager() override;
ZigbeeHardwareResource *zigbeeResource() override;
ModbusRtuHardwareResource *modbusRtuResource() override;
NetworkDeviceDiscovery *networkDeviceDiscovery() override;
public slots:
void thingsLoaded();
@ -83,6 +84,8 @@ private:
I2CManager *m_i2cManager = nullptr;
ZigbeeHardwareResourceImplementation *m_zigbeeResource = nullptr;
ModbusRtuHardwareResourceImplementation *m_modbusRtuResource = nullptr;
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
};
}

View File

@ -45,6 +45,7 @@ class I2CManager;
class ZigbeeHardwareResource;
class HardwareResource;
class ModbusRtuHardwareResource;
class NetworkDeviceDiscovery;
class HardwareManager : public QObject
{
@ -65,6 +66,7 @@ public:
virtual I2CManager *i2cManager() = 0;
virtual ZigbeeHardwareResource *zigbeeResource() = 0;
virtual ModbusRtuHardwareResource *modbusRtuResource() = 0;
virtual NetworkDeviceDiscovery *networkDeviceDiscovery() = 0;
protected:
void setResourceEnabled(HardwareResource* resource, bool enabled);

View File

@ -3,8 +3,9 @@ include(../nymea.pri)
TARGET = nymea
TEMPLATE = lib
QT += network bluetooth dbus serialport
QT += network bluetooth dbus serialport sql
QT -= gui
DEFINES += LIBNYMEA_LIBRARY
CONFIG += link_pkgconfig
@ -42,6 +43,15 @@ HEADERS += \
network/apikeys/apikey.h \
network/apikeys/apikeysprovider.h \
network/apikeys/apikeystorage.h \
network/arpsocket.h \
network/macaddressdatabase.h \
network/networkdevicediscovery.h \
network/networkdevicediscoveryreply.h \
network/networkdeviceinfo.h \
network/networkdeviceinfos.h \
network/networkutils.h \
network/ping.h \
network/pingreply.h \
platform/package.h \
platform/repository.h \
types/browseritem.h \
@ -140,6 +150,15 @@ SOURCES += \
network/apikeys/apikey.cpp \
network/apikeys/apikeysprovider.cpp \
network/apikeys/apikeystorage.cpp \
network/arpsocket.cpp \
network/macaddressdatabase.cpp \
network/networkdevicediscovery.cpp \
network/networkdevicediscoveryreply.cpp \
network/networkdeviceinfo.cpp \
network/networkdeviceinfos.cpp \
network/networkutils.cpp \
network/ping.cpp \
network/pingreply.cpp \
nymeasettings.cpp \
platform/package.cpp \
platform/repository.cpp \

View File

@ -0,0 +1,467 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "arpsocket.h"
#include "loggingcategories.h"
#include "networkutils.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <netinet/if_ether.h>
#include <QHostInfo>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QDataStream>
NYMEA_LOGGING_CATEGORY(dcArpSocket, "ArpSocket")
NYMEA_LOGGING_CATEGORY(dcArpSocketTraffic, "ArpSocketTraffic")
#define ETHER_PROTOCOL_LEN 4 // Length of the IPv4 address
#define ETHER_HEADER_LEN sizeof(struct ether_header)
#define ETHER_ARP_LEN sizeof(struct ether_arp)
#define ETHER_ARP_PACKET_LEN ETHER_HEADER_LEN + ETHER_ARP_LEN
ArpSocket::ArpSocket(QObject *parent) : QObject(parent)
{
}
bool ArpSocket::sendRequest()
{
if (!m_isOpen)
return false;
// Send the ARP request trough each network interface
qCDebug(dcArpSocket()) << "Sending ARP request to all local network interfaces...";
foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
sendRequest(networkInterface);
}
return true;
}
bool ArpSocket::sendRequest(const QString &interfaceName)
{
if (!m_isOpen)
return false;
// Get the interface
qCDebug(dcArpSocket()) << "Sending ARP request to all network interfaces" << interfaceName << "...";
QNetworkInterface networkInterface = QNetworkInterface::interfaceFromName(interfaceName);
if (!networkInterface.isValid()) {
qCWarning(dcArpSocket()) << "Failed to send the ARP request to network interface" << interfaceName << "because the interface is not valid.";
return false;
}
loadArpCache(networkInterface);
return sendRequest(networkInterface);
}
bool ArpSocket::sendRequest(const QNetworkInterface &networkInterface)
{
if (!m_isOpen)
return false;
// Skip local host
if (networkInterface.flags().testFlag(QNetworkInterface::IsLoopBack))
return false;
// If have no interface indes, we cannot use this network
if (networkInterface.index() == 0) {
qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because the system interface index is unknown.";
return false;
}
// Check if the interface is up and running
if (!networkInterface.flags().testFlag(QNetworkInterface::IsUp)) {
qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because it is not up.";
return false;
}
if (!networkInterface.flags().testFlag(QNetworkInterface::IsRunning)) {
qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because it is not running.";
return false;
}
// Verify we have a hardware address (virtual network interfaces like tunnels)
if (networkInterface.hardwareAddress().isEmpty()) {
qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because there is no hardware address which is required for ARP.";
return false;
}
loadArpCache(networkInterface);
qCDebug(dcArpSocket()) << "Verifying network interface" << networkInterface.name() << networkInterface.hardwareAddress() << "...";
foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {
// Only IPv4
if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
continue;
qCDebug(dcArpSocket()) << " Host address:" << entry.ip().toString();
qCDebug(dcArpSocket()) << " Broadcast address:" << entry.broadcast().toString();
qCDebug(dcArpSocket()) << " Netmask:" << entry.netmask().toString();
quint32 addressRangeStart = entry.ip().toIPv4Address() & entry.netmask().toIPv4Address();
quint32 addressRangeStop = entry.broadcast().toIPv4Address() | addressRangeStart;
quint32 range = addressRangeStop - addressRangeStart;
qCDebug(dcArpSocket()) << " Address range" << range << " | from" << QHostAddress(addressRangeStart).toString() << "-->" << QHostAddress(addressRangeStop).toString();
if (range > 255) {
qCWarning(dcArpSocket()) << "Not sending ARP requests to the network" << networkInterface.name() << "because it has a to wide range for ARP broadcast pinging.";
return false;
}
qCDebug(dcArpSocket()) << "Start sending ARP requests to each host within the range...";
// Send ARP request to each address within the range
for (quint32 i = 0; i < range; i++) {
quint32 address = addressRangeStart + i;
QHostAddress targetAddress(address);
if (targetAddress == entry.ip())
continue;
sendRequestInternally(networkInterface.index(), networkInterface.hardwareAddress(), entry.ip(), "ff:ff:ff:ff:ff:ff", targetAddress);
}
}
return true;
}
bool ArpSocket::sendRequest(const QHostAddress &targetAddress)
{
if (!m_isOpen)
return false;
if (targetAddress.protocol() != QAbstractSocket::IPv4Protocol) {
qCWarning(dcArpSocket()) << "Not sending ARP request to host" << targetAddress << "because only IPv4 is supported.";
return false;
}
qCDebug(dcArpSocket()) << "Sending ARP request to host" << targetAddress.toString() << "...";
foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {
// Only IPv4
if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
continue;
if (targetAddress.isInSubnet(entry.ip(), entry.netmask().toIPv4Address())) {
return sendRequestInternally(networkInterface.index(), networkInterface.hardwareAddress(), entry.ip(), "ff:ff:ff:ff:ff:ff", targetAddress);
}
}
}
qCWarning(dcArpSocket()) << "Failed to send ARP request to" << targetAddress.toString() << "because no valid network interface could be found.";
return false;
}
bool ArpSocket::isOpen() const
{
return m_isOpen;
}
bool ArpSocket::openSocket()
{
qCDebug(dcArpSocket()) << "Open ARP socket...";
if (m_isOpen) {
qCWarning(dcArpSocket()) << "Failed to enable ARP scanner because the scanner is already running.";
return false;
}
// Build socket descriptor
m_socketDescriptor = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if (m_socketDescriptor < 0) {
qCWarning(dcArpSocket()) << "Failed to create the ARP capture socket for" << "." << strerror(errno);
return false;
}
// Configure non blocking
if (fcntl(m_socketDescriptor, F_SETFL, fcntl(m_socketDescriptor, F_GETFL, 0) | O_NONBLOCK) != 0) {
qCWarning(dcArpSocket()) << "Failed to set the ARP socket function control to non-blocking" << strerror(errno);
close(m_socketDescriptor);
return false;
}
m_socketNotifier = new QSocketNotifier(m_socketDescriptor, QSocketNotifier::Read, this);
m_socketNotifier->setEnabled(false);
connect(m_socketNotifier, &QSocketNotifier::activated, this, [=](int socket){
if (socket != m_socketDescriptor)
return;
// Make sure to read all data from the socket...
while (true) {
char receiveBuffer[ETHER_ARP_PACKET_LEN];
memset(&receiveBuffer, 0, sizeof(receiveBuffer));
// Read the buffer
int bytesReceived = recv(m_socketDescriptor, receiveBuffer, ETHER_ARP_PACKET_LEN, 0);
if (bytesReceived < 0) {
// Finished reading
return;
}
// Parse data using structs header + arp
struct ether_header *etherHeader = (struct ether_header *)(receiveBuffer);
struct ether_arp *arpPacket = (struct ether_arp *)(receiveBuffer + ETHER_HEADER_LEN);
QString senderMacAddress = getMacAddressString(arpPacket->arp_sha);
QHostAddress senderHostAddress = getHostAddressString(arpPacket->arp_spa);
QString targetMacAddress = getMacAddressString(arpPacket->arp_tha);
QHostAddress targetHostAddress = getHostAddressString(arpPacket->arp_tpa);
uint16_t etherType = htons(etherHeader->ether_type);
if (etherType != ETHERTYPE_ARP) {
qCWarning(dcArpSocketTraffic()) << "Received ARP socket data header with invalid type" << etherType;
return;
}
// Filter for ARP replies
uint16_t arpOperationCode = htons(arpPacket->arp_op);
switch (arpOperationCode) {
case ARPOP_REQUEST:
//qCDebug(dcArpSocket()) << "ARP request from " << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_REPLY: {
QNetworkInterface networkInterface = NetworkUtils::getInterfaceForMacAddress(targetMacAddress);
if (!networkInterface.isValid()) {
qCWarning(dcArpSocket()) << "Could not find interface from ARP response" << targetHostAddress.toString() << targetMacAddress;
return;
}
qCDebug(dcArpSocketTraffic()) << "ARP response from" << senderMacAddress << senderHostAddress.toString() << "on" << networkInterface.name();
emit arpResponse(networkInterface, senderHostAddress, senderMacAddress.toLower());
break;
}
case ARPOP_RREQUEST:
qCDebug(dcArpSocketTraffic()) << "RARP request from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_RREPLY:
qCDebug(dcArpSocketTraffic()) << "PARP response from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_InREQUEST:
qCDebug(dcArpSocketTraffic()) << "InARP request from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_InREPLY:
qCDebug(dcArpSocketTraffic()) << "InARP response from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_NAK:
qCDebug(dcArpSocketTraffic()) << "(ATM)ARP NAK from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
default:
qCWarning(dcArpSocketTraffic()) << "Received unhandled ARP operation code" << arpOperationCode << "from" << senderMacAddress << senderHostAddress.toString();
break;
}
}
});
m_socketNotifier->setEnabled(true);
m_isOpen = true;
qCDebug(dcArpSocket()) << "ARP enabled successfully";
// Send broadcast request
//sendRequest();
return true;
}
void ArpSocket::closeSocket()
{
m_isOpen = false;
if (m_socketNotifier) {
m_socketNotifier->setEnabled(false);
delete m_socketNotifier;
m_socketNotifier = nullptr;
}
if (m_socketDescriptor >= 0) {
close(m_socketDescriptor);
m_socketDescriptor = -1;
}
qCDebug(dcArpSocket()) << "ARP disabled successfully";
}
bool ArpSocket::sendRequestInternally(int networkInterfaceIndex, const QString &senderMacAddress, const QHostAddress &senderHostAddress, const QString &targetMacAddress, const QHostAddress &targetHostAddress)
{
// Set up data structures
unsigned char sendingBuffer[ETHER_ARP_PACKET_LEN];
memset(sendingBuffer, 0, ETHER_ARP_PACKET_LEN);
struct ether_header *etherHeader = (struct ether_header *)sendingBuffer;
struct ether_arp *arpPacket = (struct ether_arp *)(sendingBuffer + sizeof(struct ether_header));
// Build the ethernet header
fillMacAddress(etherHeader->ether_dhost, targetMacAddress);
fillMacAddress(etherHeader->ether_shost, senderMacAddress);
etherHeader->ether_type = htons(ETHERTYPE_ARP);
// Build the ARP header
arpPacket->ea_hdr.ar_hrd = htons(ARPHRD_ETHER);
arpPacket->ea_hdr.ar_pro = htons(ETH_P_IP);
arpPacket->ea_hdr.ar_hln = ETHER_ADDR_LEN;
arpPacket->ea_hdr.ar_pln = ETHER_PROTOCOL_LEN;
arpPacket->ea_hdr.ar_op = htons(ARPOP_REQUEST);
// Write the ARP packet
fillMacAddress(arpPacket->arp_sha, senderMacAddress);
fillHostAddress(arpPacket->arp_spa, senderHostAddress);
fillMacAddress(arpPacket->arp_tha, targetMacAddress);
fillHostAddress(arpPacket->arp_tpa, targetHostAddress);
struct sockaddr_ll socketAddress;
memset(&socketAddress, 0, sizeof(socketAddress));
socketAddress.sll_family = AF_PACKET;
socketAddress.sll_protocol = htons(ETH_P_ARP);
socketAddress.sll_ifindex = networkInterfaceIndex;
socketAddress.sll_hatype = htons(ARPHRD_ETHER);
socketAddress.sll_pkttype = PACKET_BROADCAST;
socketAddress.sll_halen = ETH_ALEN;
memset(socketAddress.sll_addr, 0x00, 6);
//qCDebug(dcArpSocket()) << "Send ARP request to" << targetHostAddress.toString();
int bytesSent = sendto(m_socketDescriptor, sendingBuffer, ETHER_ARP_PACKET_LEN, 0, (struct sockaddr *)&socketAddress, sizeof(socketAddress));
if (bytesSent < 0) {
qCWarning(dcArpSocket()) << "Failed to send ARP packet data to" << targetHostAddress.toString() << strerror(errno);
return false;
}
return true;
}
QString ArpSocket::getMacAddressString(uint8_t *senderHardwareAddress)
{
QStringList hexValues;
for (int i = 0; i < ETHER_ADDR_LEN; i++) {
hexValues.append(QString("%1").arg(senderHardwareAddress[i], 2, 16, QLatin1Char('0')));
}
return hexValues.join(":");
}
QHostAddress ArpSocket::getHostAddressString(uint8_t *senderIpAddress)
{
QStringList values;
for (int i = 0; i < ETHER_PROTOCOL_LEN; i++) {
values.append(QString("%1").arg(senderIpAddress[i]));
}
return QHostAddress(values.join("."));
}
bool ArpSocket::loadArpCache(const QNetworkInterface &interface)
{
QFile arpFile("/proc/net/arp");
qCDebug(dcArpSocket()) << "Loading ARP cache from system" << arpFile.fileName() << "...";
if (!arpFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCWarning(dcArpSocket()) << "Failed to load ARP cache from" << arpFile.fileName() << arpFile.errorString();
return false;
}
// Read all data
QByteArray data = arpFile.readAll();
arpFile.close();
// Parse data line by line
int lineCount = -1;
QTextStream stream(&data);
while (!stream.atEnd()) {
QString line = stream.readLine();
lineCount += 1;
// Skip the first line since it's just the header
if (lineCount == 0)
continue;
//qCDebug(dcArpSocket()) << "Checking line" << line;
QStringList columns = line.split(QLatin1Char(' '));
columns.removeAll("");
// Make sure we have enought token
if (columns.count() < 6) {
qCWarning(dcArpSocket()) << "Line has invalid column count" << line;
continue;
}
QHostAddress address(columns.at(0).trimmed());
if (address.isNull()) {
qCWarning(dcArpSocket()) << "Line has invalid address";
continue;
}
QString macAddress = columns.at(3).trimmed();
if (macAddress.count() != 17) {
qCWarning(dcArpSocket()) << "Line has invalid mac address" << columns << macAddress;
continue;
}
QNetworkInterface addressInterface = QNetworkInterface::interfaceFromName(columns.at(5));
if (!addressInterface.isValid())
continue;
// Check if we filter for specific interfaces
if (interface.isValid() && addressInterface.name() != interface.name())
continue;
qCDebug(dcArpSocket()) << "Loaded from cache" << address.toString() << macAddress << addressInterface.name();
emit arpResponse(addressInterface, address, macAddress);
}
return true;
}
void ArpSocket::fillMacAddress(uint8_t *targetArray, const QString &macAddress)
{
QStringList macValues = macAddress.split(":");
for (int i = 0; i < ETHER_ADDR_LEN; i++) {
targetArray[i] = macValues.at(i).toUInt(nullptr, 16);
}
}
void ArpSocket::fillHostAddress(uint8_t *targetArray, const QHostAddress &hostAddress)
{
QByteArray hostData;
QDataStream stream(&hostData, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << hostAddress.toIPv4Address();
for (int i = 0; i < ETHER_PROTOCOL_LEN; i++) {
targetArray[i] = hostData.at(i);
}
}

View File

@ -0,0 +1,86 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 ARPSOCKET_H
#define ARPSOCKET_H
#include <QDebug>
#include <QObject>
#include <QSocketNotifier>
#include <QLoggingCategory>
#include <QHostAddress>
#include <QNetworkInterface>
#include "libnymea.h"
class LIBNYMEA_EXPORT ArpSocket : public QObject
{
Q_OBJECT
public:
explicit ArpSocket(QObject *parent = nullptr);
// Send ARP request to all local networks
bool sendRequest();
// Send ARP request to a specific network interface with the given name
bool sendRequest(const QString &interfaceName);
// Send ARP request to a specific network interface
bool sendRequest(const QNetworkInterface &networkInterface);
// Send ARP request to a specific address within the network
bool sendRequest(const QHostAddress &targetAddress);
bool isOpen() const;
bool openSocket();
void closeSocket();
signals:
void arpResponse(const QNetworkInterface &networkInterface, const QHostAddress &address, const QString &macAddress);
private:
QSocketNotifier *m_socketNotifier = nullptr;
int m_socketDescriptor = -1;
bool m_isOpen = false;
bool sendRequestInternally(int networkInterfaceIndex, const QString &senderMacAddress, const QHostAddress &senderHostAddress, const QString &targetMacAddress, const QHostAddress &targetHostAddress);
QString getMacAddressString(uint8_t *senderHardwareAddress);
QHostAddress getHostAddressString(uint8_t *senderIpAddress);
bool loadArpCache(const QNetworkInterface &interface = QNetworkInterface());
void fillMacAddress(uint8_t *targetArray, const QString &macAddress);
void fillHostAddress(uint8_t *targetArray, const QHostAddress &hostAddress);
};
#endif // ARPSOCKET_H

View File

@ -0,0 +1,223 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "macaddressdatabase.h"
#include "loggingcategories.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QFileInfo>
#include <QTimer>
#include <QSqlDatabase>
#include <QStandardPaths>
#include <QtConcurrent/QtConcurrent>
NYMEA_LOGGING_CATEGORY(dcMacAddressDatabase, "MacAddressDatabase")
MacAddressDatabase::MacAddressDatabase(QObject *parent) : QObject(parent)
{
// Find database in system data locations
QString databaseFileName;
foreach (const QString &dataLocation, QStandardPaths::standardLocations(QStandardPaths::DataLocation)) {
QFileInfo databaseFileInfo(dataLocation + QDir::separator() + "mac-addresses.db");
if (!databaseFileInfo.exists()) {
continue;
}
databaseFileName = databaseFileInfo.absoluteFilePath();
break;
}
if (databaseFileName.isEmpty()) {
qCWarning(dcMacAddressDatabase()) << "Could not find the mac address database in any system data location paths" << QStandardPaths::standardLocations(QStandardPaths::DataLocation);
qCWarning(dcMacAddressDatabase()) << "The mac address database lookup feature will not be available.";
return;
}
m_databaseName = databaseFileName;
m_available = initDatabase();
if (m_available) {
m_futureWatcher = new QFutureWatcher<QString>(this);
connect(m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MacAddressDatabase::onLookupFinished);
}
}
MacAddressDatabase::MacAddressDatabase(const QString &databaseName, QObject *parent) :
QObject(parent),
m_databaseName(databaseName)
{
m_available = initDatabase();
if (m_available) {
m_futureWatcher = new QFutureWatcher<QString>(this);
connect(m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MacAddressDatabase::onLookupFinished);
}
}
MacAddressDatabase::~MacAddressDatabase()
{
m_db.close();
m_db = QSqlDatabase();
QSqlDatabase::removeDatabase(m_connectionName);
}
bool MacAddressDatabase::available() const
{
return m_available;
}
MacAddressDatabaseReply *MacAddressDatabase::lookupMacAddress(const QString &macAddress)
{
MacAddressDatabaseReply *reply = new MacAddressDatabaseReply(this);
connect(reply, &MacAddressDatabaseReply::finished, reply, &MacAddressDatabaseReply::deleteLater);
reply->m_macAddress = macAddress;
if (!m_available) {
QTimer::singleShot(0, this, [=](){ emit reply->finished(); });
return reply;
}
m_pendingReplies.enqueue(reply);
runNextLookup();
return reply;
}
bool MacAddressDatabase::initDatabase()
{
qCDebug(dcMacAddressDatabase()) << "Starting to initialize the mac address database:" << m_databaseName;
m_connectionName = QFileInfo(m_databaseName).baseName();
m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
m_db.setDatabaseName(m_databaseName);
if (!m_db.isValid()) {
qCWarning(dcMacAddressDatabase()) << "The network database is not valid" << m_db.databaseName();
return false;
}
m_db.close();
if (!m_db.open()) {
qCWarning(dcMacAddressDatabase()) << "Could not open database" << m_db.databaseName() << "Initialization failed.";
return false;
}
// Verify the tables we need exist
qCDebug(dcMacAddressDatabase()) << "Tables" << m_db.tables();
if (!m_db.tables().contains("oui")) {
qCWarning(dcMacAddressDatabase()) << "Invalid database. Could not find \"oui\" table in" << m_db.databaseName();
return false;
}
if (!m_db.tables().contains("companyNames")) {
qCWarning(dcMacAddressDatabase()) << "Invalid database. Could not find \"companyNames\" table in" << m_db.databaseName();
return false;
}
return true;
}
void MacAddressDatabase::runNextLookup()
{
if (m_pendingReplies.isEmpty())
return;
if (m_futureWatcher->isRunning() || m_currentReply)
return;
m_currentReply = m_pendingReplies.dequeue();
m_currentReply->m_startTimestamp = QDateTime::currentMSecsSinceEpoch();
QFuture<QString> future = QtConcurrent::run(this, &MacAddressDatabase::lookupMacAddressVendorInternal, m_currentReply->macAddress());
m_futureWatcher->setFuture(future);
}
void MacAddressDatabase::onLookupFinished()
{
if (m_currentReply) {
QString manufacturer = m_futureWatcher->future().result();
qCDebug(dcMacAddressDatabase()) << "Manufacturer lookup for" << m_currentReply->macAddress() << "finished:" << manufacturer << QDateTime::currentMSecsSinceEpoch() - m_currentReply->m_startTimestamp << "ms";
m_currentReply->m_manufacturer = manufacturer;
emit m_currentReply->finished();
m_currentReply = nullptr;
}
runNextLookup();
}
QString MacAddressDatabase::lookupMacAddressVendorInternal(const QString &macAddress)
{
qCDebug(dcMacAddressDatabase()) << "Start looking up vendor for" << macAddress;
// Convert the mac address string to upper like in the database and remove : since they have been removed for size reasons
QString fullMacAddressString = QString(macAddress).toUpper().remove(":");
QString manufacturer;
int length = 6;
while (true) {
QString searchString = fullMacAddressString.left(length);
QString queryString = QString("SELECT COUNT(oui) FROM oui WHERE oui LIKE \'%1%\';").arg(searchString);
qCDebug(dcMacAddressDatabase()) << "Query:" << queryString;
QSqlQuery countQuery = m_db.exec(queryString);
if (countQuery.lastError().isValid()) {
qCWarning(dcMacAddressDatabase()) << "Query finished with error" << countQuery.lastError().text();
break;
}
if (!countQuery.next())
break;
int rowCount = countQuery.value(0).toInt();
qCDebug(dcMacAddressDatabase()) << "Found" << rowCount << "with" << searchString;
// If we have found the one...
if (rowCount == 1) {
// Query the name
queryString = QString("SELECT companyName from companyNames WHERE rowid IS (SELECT companyNameIndex FROM oui WHERE oui=\'%1\');").arg(searchString);
qCDebug(dcMacAddressDatabase()) << "Query:" << queryString;
countQuery = m_db.exec(queryString);
if (!countQuery.next())
break;
manufacturer = countQuery.value(0).toString();
break;
}
// If nothing found
if (rowCount == 0)
break;
// Found to many results, lets add a value until we find the matching vendor
length += 1;
if (length > fullMacAddressString.length())
break;
// Search with one addition digit
}
return manufacturer;
}

View File

@ -0,0 +1,93 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 MACADDRESSDATABASE_H
#define MACADDRESSDATABASE_H
#include <QQueue>
#include <QObject>
#include <QSqlDatabase>
#include <QFutureWatcher>
#include "libnymea.h"
class LIBNYMEA_EXPORT MacAddressDatabaseReply : public QObject
{
Q_OBJECT
friend class MacAddressDatabase;
public:
QString macAddress() const { return m_macAddress; };
QString manufacturer() const { return m_manufacturer; };
private:
explicit MacAddressDatabaseReply(QObject *parent = nullptr) : QObject(parent) { };
QString m_macAddress;
QString m_manufacturer;
qint64 m_startTimestamp;
signals:
void finished();
};
class LIBNYMEA_EXPORT MacAddressDatabase : public QObject
{
Q_OBJECT
public:
explicit MacAddressDatabase(QObject *parent = nullptr);
MacAddressDatabase(const QString &databaseName, QObject *parent = nullptr);
~MacAddressDatabase();
bool available() const;
MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress);
private:
QSqlDatabase m_db;
bool m_available = false;
QString m_connectionName;
QString m_databaseName = "/usr/share/nymea/mac-addresses.db";
MacAddressDatabaseReply *m_currentReply = nullptr;
QFutureWatcher<QString> *m_futureWatcher = nullptr;
QQueue<MacAddressDatabaseReply *> m_pendingReplies;
bool initDatabase();
void runNextLookup();
private slots:
void onLookupFinished();
QString lookupMacAddressVendorInternal(const QString &macAddress);
};
#endif // MACADDRESSDATABASE_H

View File

@ -0,0 +1,267 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "networkdevicediscovery.h"
#include "loggingcategories.h"
#include "networkutils.h"
#include "macaddressdatabase.h"
#include "arpsocket.h"
#include <QDateTime>
NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery")
NetworkDeviceDiscovery::NetworkDeviceDiscovery(QObject *parent) :
QObject(parent)
{
// Create ARP socket
m_arpSocket = new ArpSocket(this);
connect(m_arpSocket, &ArpSocket::arpResponse, this, &NetworkDeviceDiscovery::onArpResponseRceived);
bool arpAvailable = m_arpSocket->openSocket();
if (!arpAvailable) {
m_arpSocket->closeSocket();
}
// Create ping socket
m_ping = new Ping(this);
if (!m_ping->available())
qCWarning(dcNetworkDeviceDiscovery()) << "Failed to create ping tool" << m_ping->error();
// Init MAC database if available
m_macAddressDatabase = new MacAddressDatabase(this);
// Timer for max duration af a discovery
m_discoveryTimer = new QTimer(this);
m_discoveryTimer->setInterval(20000);
m_discoveryTimer->setSingleShot(true);
connect(m_discoveryTimer, &QTimer::timeout, this, [=](){
if (m_runningPingRepies.isEmpty() && m_currentReply) {
finishDiscovery();
}
});
if (!arpAvailable && !m_ping->available()) {
qCWarning(dcNetworkDeviceDiscovery()) << "Network device discovery is not available on this system.";
} else {
qCDebug(dcNetworkDeviceDiscovery()) << "Created successfully";
}
}
NetworkDeviceDiscoveryReply *NetworkDeviceDiscovery::discover()
{
if (m_currentReply) {
qCDebug(dcNetworkDeviceDiscovery()) << "Discovery already running. Returning current pending discovery reply...";
return m_currentReply;
}
qCDebug(dcNetworkDeviceDiscovery()) << "Starting network device discovery ...";
NetworkDeviceDiscoveryReply *reply = new NetworkDeviceDiscoveryReply(this);
m_currentReply = reply;
m_currentReply->m_startTimestamp = QDateTime::currentMSecsSinceEpoch();
if (m_ping->available()) {
pingAllNetworkDevices();
}
if (m_arpSocket->isOpen()) {
m_arpSocket->sendRequest();
}
m_discoveryTimer->start();
m_running = true;
emit runningChanged(m_running);
return reply;
}
bool NetworkDeviceDiscovery::available() const
{
return m_arpSocket->isOpen() || m_ping->available();
}
bool NetworkDeviceDiscovery::running() const
{
return m_running;
}
PingReply *NetworkDeviceDiscovery::ping(const QHostAddress &address)
{
return m_ping->ping(address);
}
MacAddressDatabaseReply *NetworkDeviceDiscovery::lookupMacAddress(const QString &macAddress)
{
return m_macAddressDatabase->lookupMacAddress(macAddress);
}
void NetworkDeviceDiscovery::pingAllNetworkDevices()
{
qCDebug(dcNetworkDeviceDiscovery()) << "Starting ping for all network devices...";
foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
if (networkInterface.flags().testFlag(QNetworkInterface::IsLoopBack))
continue;
if (!networkInterface.flags().testFlag(QNetworkInterface::IsUp))
continue;
if (!networkInterface.flags().testFlag(QNetworkInterface::IsRunning))
continue;
qCDebug(dcNetworkDeviceDiscovery()) << "Verifying network interface" << networkInterface.name() << networkInterface.hardwareAddress() << "...";
foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {
// Only IPv4
if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
continue;
qCDebug(dcNetworkDeviceDiscovery()) << " Host address:" << entry.ip().toString();
qCDebug(dcNetworkDeviceDiscovery()) << " Broadcast address:" << entry.broadcast().toString();
qCDebug(dcNetworkDeviceDiscovery()) << " Netmask:" << entry.netmask().toString();
quint32 addressRangeStart = entry.ip().toIPv4Address() & entry.netmask().toIPv4Address();
quint32 addressRangeStop = entry.broadcast().toIPv4Address() | addressRangeStart;
quint32 range = addressRangeStop - addressRangeStart;
// Let's scan only 255.255.255.0 networks for now
if (range > 255)
continue;
qCDebug(dcNetworkDeviceDiscovery()) << " Address range" << range << " | from" << QHostAddress(addressRangeStart).toString() << "-->" << QHostAddress(addressRangeStop).toString();
// Send ping request to each address within the range
for (quint32 i = 1; i < range; i++) {
quint32 address = addressRangeStart + i;
QHostAddress targetAddress(address);
// Skip our self
if (targetAddress == entry.ip())
continue;
PingReply *reply = m_ping->ping(targetAddress);
m_runningPingRepies.append(reply);
connect(reply, &PingReply::finished, this, [=](){
m_runningPingRepies.removeAll(reply);
if (reply->error() == PingReply::ErrorNoError) {
qCDebug(dcNetworkDeviceDiscovery()) << "Ping response from" << targetAddress.toString() << reply->hostName() << reply->duration() << "ms";
int index = m_currentReply->networkDeviceInfos().indexFromHostAddress(targetAddress);
if (index < 0) {
// Add the network device
NetworkDeviceInfo networkDeviceInfo;
networkDeviceInfo.setAddress(targetAddress);
networkDeviceInfo.setHostName(reply->hostName());
m_currentReply->networkDeviceInfos().append(networkDeviceInfo);
} else {
m_currentReply->networkDeviceInfos()[index].setAddress(targetAddress);
m_currentReply->networkDeviceInfos()[index].setHostName(reply->hostName());
if (!m_currentReply->networkDeviceInfos()[index].networkInterface().isValid()) {
m_currentReply->networkDeviceInfos()[index].setNetworkInterface(NetworkUtils::getInterfaceForHostaddress(targetAddress));
}
}
}
if (m_runningPingRepies.isEmpty() && m_currentReply && !m_discoveryTimer->isActive()) {
finishDiscovery();
}
});
}
}
}
}
void NetworkDeviceDiscovery::finishDiscovery()
{
m_discoveryTimer->stop();
m_running = false;
emit runningChanged(m_running);
// Sort by host address
m_currentReply->networkDeviceInfos().sortNetworkDevices();
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_currentReply->m_startTimestamp;
qCDebug(dcNetworkDeviceDiscovery()) << "Discovery finished. Found" << m_currentReply->networkDeviceInfos().count() << "network devices in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
emit m_currentReply->finished();
m_currentReply->deleteLater();
m_currentReply = nullptr;
}
void NetworkDeviceDiscovery::updateOrAddNetworkDeviceArp(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress, const QString &manufacturer)
{
int index = m_currentReply->networkDeviceInfos().indexFromHostAddress(address);
if (index >= 0) {
// Update the network device
m_currentReply->networkDeviceInfos()[index].setMacAddress(macAddress);
if (!manufacturer.isEmpty())
m_currentReply->networkDeviceInfos()[index].setMacAddressManufacturer(manufacturer);
if (interface.isValid()) {
m_currentReply->networkDeviceInfos()[index].setNetworkInterface(interface);
}
} else {
index = m_currentReply->networkDeviceInfos().indexFromMacAddress(macAddress);
if (index >= 0) {
// Update the network device
m_currentReply->networkDeviceInfos()[index].setAddress(address);
if (!manufacturer.isEmpty())
m_currentReply->networkDeviceInfos()[index].setMacAddressManufacturer(manufacturer);
if (interface.isValid()) {
m_currentReply->networkDeviceInfos()[index].setNetworkInterface(interface);
}
} else {
// Add the network device
NetworkDeviceInfo networkDeviceInfo;
networkDeviceInfo.setAddress(address);
networkDeviceInfo.setMacAddress(macAddress);
if (!manufacturer.isEmpty())
networkDeviceInfo.setMacAddressManufacturer(manufacturer);
if (interface.isValid())
networkDeviceInfo.setNetworkInterface(interface);
m_currentReply->networkDeviceInfos().append(networkDeviceInfo);
}
}
}
void NetworkDeviceDiscovery::onArpResponseRceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress)
{
if (!m_currentReply) {
qCDebug(dcNetworkDeviceDiscovery()) << "Received ARP reply from" << address.toString() << macAddress << "but there is no discovery running.";
return;
}
qCDebug(dcNetworkDeviceDiscovery()) << "ARP reply received" << address.toString() << macAddress << interface.name();
// Lookup the mac address vendor if possible
if (m_macAddressDatabase->available()) {
MacAddressDatabaseReply *reply = m_macAddressDatabase->lookupMacAddress(macAddress);
connect(reply, &MacAddressDatabaseReply::finished, this, [=](){
qCDebug(dcNetworkDeviceDiscovery()) << "MAC manufacturer lookup finished for" << macAddress << ":" << reply->manufacturer();
updateOrAddNetworkDeviceArp(interface, address, macAddress, reply->manufacturer());
});
} else {
updateOrAddNetworkDeviceArp(interface, address, macAddress);
}
}

View File

@ -0,0 +1,85 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 NETWORKDEVICEDISCOVERY_H
#define NETWORKDEVICEDISCOVERY_H
#include <QTimer>
#include <QObject>
#include <QLoggingCategory>
#include "ping.h"
#include "libnymea.h"
#include "networkdevicediscoveryreply.h"
class ArpSocket;
class MacAddressDatabase;
class MacAddressDatabaseReply;
Q_DECLARE_LOGGING_CATEGORY(dcNetworkDeviceDiscovery)
class LIBNYMEA_EXPORT NetworkDeviceDiscovery : public QObject
{
Q_OBJECT
public:
explicit NetworkDeviceDiscovery(QObject *parent = nullptr);
NetworkDeviceDiscoveryReply *discover();
bool available() const;
bool running() const;
PingReply *ping(const QHostAddress &address);
MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress);
signals:
void runningChanged(bool running);
private:
MacAddressDatabase *m_macAddressDatabase = nullptr;
ArpSocket *m_arpSocket = nullptr;
Ping *m_ping = nullptr;
bool m_running = false;
QTimer *m_discoveryTimer = nullptr;
NetworkDeviceDiscoveryReply *m_currentReply = nullptr;
QList<PingReply *> m_runningPingRepies;
void pingAllNetworkDevices();
void finishDiscovery();
void updateOrAddNetworkDeviceArp(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress, const QString &manufacturer = QString());
private slots:
void onArpResponseRceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress);
};
#endif // NETWORKDEVICEDISCOVERY_H

View File

@ -0,0 +1,42 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "networkdevicediscoveryreply.h"
NetworkDeviceDiscoveryReply::NetworkDeviceDiscoveryReply(QObject *parent) :
QObject(parent)
{
}
NetworkDeviceInfos &NetworkDeviceDiscoveryReply::networkDeviceInfos()
{
return m_networkDeviceInfos;
}

View File

@ -0,0 +1,58 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 NETWORKDEVICEDISCOVERYREPLY_H
#define NETWORKDEVICEDISCOVERYREPLY_H
#include <QObject>
#include "libnymea.h"
#include "networkdeviceinfos.h"
class LIBNYMEA_EXPORT NetworkDeviceDiscoveryReply : public QObject
{
Q_OBJECT
friend class NetworkDeviceDiscovery;
public:
NetworkDeviceInfos &networkDeviceInfos();
signals:
void finished();
private:
explicit NetworkDeviceDiscoveryReply(QObject *parent = nullptr);
NetworkDeviceInfos m_networkDeviceInfos;
qint64 m_startTimestamp;
};
#endif // NETWORKDEVICEDISCOVERYREPLY_H

View File

@ -0,0 +1,115 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "networkdeviceinfo.h"
NetworkDeviceInfo::NetworkDeviceInfo()
{
}
NetworkDeviceInfo::NetworkDeviceInfo(const QString &macAddress):
m_macAddress(macAddress)
{
}
QString NetworkDeviceInfo::macAddress() const
{
return m_macAddress;
}
void NetworkDeviceInfo::setMacAddress(const QString &macAddress)
{
m_macAddress = macAddress;
}
QString NetworkDeviceInfo::macAddressManufacturer() const
{
return m_macAddressManufacturer;
}
void NetworkDeviceInfo::setMacAddressManufacturer(const QString &macAddressManufacturer)
{
m_macAddressManufacturer = macAddressManufacturer;
}
QHostAddress NetworkDeviceInfo::address() const
{
return m_address;
}
void NetworkDeviceInfo::setAddress(const QHostAddress &address)
{
m_address = address;
}
QString NetworkDeviceInfo::hostName() const
{
return m_hostName;
}
void NetworkDeviceInfo::setHostName(const QString &hostName)
{
m_hostName = hostName;
}
QNetworkInterface NetworkDeviceInfo::networkInterface() const
{
return m_networkInterface;
}
void NetworkDeviceInfo::setNetworkInterface(const QNetworkInterface &networkInterface)
{
m_networkInterface = networkInterface;
}
bool NetworkDeviceInfo::isValid() const
{
return (!m_address.isNull() || !m_macAddress.isEmpty()) && m_networkInterface.isValid();
}
QDebug operator<<(QDebug dbg, const NetworkDeviceInfo &networkDeviceInfo)
{
dbg.nospace() << "NetworkDeviceInfo(" << networkDeviceInfo.address().toString();
if (!networkDeviceInfo.hostName().isEmpty())
dbg.nospace() << " (" << networkDeviceInfo.hostName() << ")";
dbg.nospace() << ", " << networkDeviceInfo.macAddress();
if (!networkDeviceInfo.macAddressManufacturer().isEmpty())
dbg.nospace() << " (" << networkDeviceInfo.macAddressManufacturer() << ") ";
if (networkDeviceInfo.networkInterface().isValid())
dbg.nospace() << ", " << networkDeviceInfo.networkInterface().name();
dbg.nospace() << ")";
return dbg.space();
}

View File

@ -0,0 +1,77 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 NETWORKDEVICEINFO_H
#define NETWORKDEVICEINFO_H
#include <QDebug>
#include <QObject>
#include <QHostAddress>
#include <QNetworkInterface>
#include "libnymea.h"
class LIBNYMEA_EXPORT NetworkDeviceInfo
{
public:
explicit NetworkDeviceInfo();
explicit NetworkDeviceInfo(const QString &macAddress);
~NetworkDeviceInfo() = default;
QString macAddress() const;
void setMacAddress(const QString &macAddress);
QString macAddressManufacturer() const;
void setMacAddressManufacturer(const QString &macAddressManufacturer);
QHostAddress address() const;
void setAddress(const QHostAddress &address);
QString hostName() const;
void setHostName(const QString &hostName);
QNetworkInterface networkInterface() const;
void setNetworkInterface(const QNetworkInterface &networkInterface);
bool isValid() const;
private:
QHostAddress m_address;
QString m_macAddress;
QString m_macAddressManufacturer;
QString m_hostName;
QNetworkInterface m_networkInterface;
};
QDebug operator<<(QDebug debug, const NetworkDeviceInfo &networkDeviceInfo);
#endif // NETWORKDEVICEINFO_H

View File

@ -0,0 +1,113 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "networkdeviceinfos.h"
#include <algorithm>
NetworkDeviceInfos::NetworkDeviceInfos() :
QVector<NetworkDeviceInfo>()
{
}
NetworkDeviceInfos::NetworkDeviceInfos(const QVector<NetworkDeviceInfo> &other) :
QVector<NetworkDeviceInfo>(other)
{
}
int NetworkDeviceInfos::indexFromHostAddress(const QHostAddress &address)
{
for (int i = 0; i < this->size(); i++) {
if (at(i).address().toIPv4Address() == address.toIPv4Address()) {
return i;
}
}
return -1;
}
int NetworkDeviceInfos::indexFromMacAddress(const QString &macAddress)
{
for (int i = 0; i < size(); i++) {
if (at(i).macAddress().toLower() == macAddress.toLower()) {
return i;
}
}
return -1;
}
bool NetworkDeviceInfos::hasHostAddress(const QHostAddress &address)
{
return indexFromHostAddress(address) >= 0;
}
bool NetworkDeviceInfos::hasMacAddress(const QString &macAddress)
{
return indexFromMacAddress(macAddress) >= 0;
}
NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address)
{
foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) {
if (networkDeviceInfo.address() == address) {
return networkDeviceInfo;
}
}
return NetworkDeviceInfo();
}
NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress)
{
foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) {
if (networkDeviceInfo.macAddress() == macAddress) {
return networkDeviceInfo;
}
}
return NetworkDeviceInfo();
}
void NetworkDeviceInfos::sortNetworkDevices()
{
std::sort(this->begin(), this->end(), [](const NetworkDeviceInfo& a, const NetworkDeviceInfo& b) {
return a.address().toIPv4Address() < b.address().toIPv4Address();
});
}
NetworkDeviceInfos &NetworkDeviceInfos::operator <<(const NetworkDeviceInfo &networkDeviceInfo)
{
this->append(networkDeviceInfo);
return *this;
}

View File

@ -0,0 +1,61 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 NETWORKDEVICEINFOS_H
#define NETWORKDEVICEINFOS_H
#include <QObject>
#include "libnymea.h"
#include "networkdeviceinfo.h"
class LIBNYMEA_EXPORT NetworkDeviceInfos : public QVector<NetworkDeviceInfo>
{
public:
explicit NetworkDeviceInfos();
NetworkDeviceInfos(const QVector<NetworkDeviceInfo> &other);
int indexFromHostAddress(const QHostAddress &address);
int indexFromMacAddress(const QString &macAddress);
bool hasHostAddress(const QHostAddress &address);
bool hasMacAddress(const QString &macAddress);
NetworkDeviceInfo get(const QHostAddress &address);
NetworkDeviceInfo get(const QString &macAddress);
void sortNetworkDevices();
NetworkDeviceInfos &operator<<(const NetworkDeviceInfo &networkDeviceInfo);
};
#endif // NETWORKDEVICEINFOS_H

View File

@ -0,0 +1,34 @@
#include "networkutils.h"
NetworkUtils::NetworkUtils()
{
}
QNetworkInterface NetworkUtils::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 NetworkUtils::getInterfaceForMacAddress(const QString &macAddress)
{
foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
if (networkInterface.hardwareAddress().toLower() == macAddress.toLower()) {
return networkInterface;
}
}
return QNetworkInterface();
}

View File

@ -0,0 +1,46 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 NETWORKUTILS_H
#define NETWORKUTILS_H
#include <QHostAddress>
#include <QNetworkInterface>
class NetworkUtils
{
public:
NetworkUtils();
static QNetworkInterface getInterfaceForHostaddress(const QHostAddress &address);
static QNetworkInterface getInterfaceForMacAddress(const QString &macAddress);
};
#endif // NETWORKUTILS_H

422
libnymea/network/ping.cpp Normal file
View File

@ -0,0 +1,422 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "networkutils.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>
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 = NetworkUtils::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);
reply->m_timer->start(8000);
connect(reply, &PingReply::timeout, this, [=](){
finishReply(reply, PingReply::ErrorTimeout);
});
}
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();
}
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 and give some extra space for nested pakets...
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::ErrorInvalidResponse);
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;
// 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)
<< "Time:" << reply->duration() << "[ms]";
} 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);
}
}
}
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 (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();
}
}
finishReply(reply, PingReply::ErrorNoError);
}

109
libnymea/network/ping.h Normal file
View File

@ -0,0 +1,109 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 <QHostInfo>
#include <QHostAddress>
#include <QSocketNotifier>
#include <QLoggingCategory>
#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();
QHash<int, PingReply *> m_pendingHostLookups;
//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);
private slots:
void onSocketReadyRead(int socketDescriptor);
void onHostLookupFinished(const QHostInfo &info);
};
#endif // PING_H

View File

@ -0,0 +1,74 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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)
{
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, &PingReply::timeout);
connect(this, &PingReply::finished, m_timer, &QTimer::stop);
}
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;
}

View File

@ -0,0 +1,96 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "libnymea.h"
#include <QHostAddress>
#include <QNetworkInterface>
class LIBNYMEA_EXPORT PingReply : public QObject
{
Q_OBJECT
friend class Ping;
public:
enum Error {
ErrorNoError,
ErrorInvalidResponse,
ErrorNetworkDown,
ErrorNetworkUnreachable,
ErrorPermissionDenied,
ErrorSocketError,
ErrorTimeout,
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();
void timeout();
private:
QTimer *m_timer = nullptr;
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