Merge PR #432: Network device discovery
This commit is contained in:
commit
02390650ba
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
21
data/mac-database/README.md
Normal file
21
data/mac-database/README.md
Normal 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`.
|
||||
|
||||
97
data/mac-database/build-database.py
Normal file
97
data/mac-database/build-database.py
Normal 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)
|
||||
|
||||
|
||||
BIN
data/mac-database/mac-addresses.db
Normal file
BIN
data/mac-database/mac-addresses.db
Normal file
Binary file not shown.
20
debian/control
vendored
20
debian/control
vendored
@ -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
2
debian/nymea-data.install
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
data/mac-database/mac-addresses.db usr/share/nymea/nymead/
|
||||
|
||||
11
debian/nymead.postinst
vendored
11
debian/nymead.postinst
vendored
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 \
|
||||
|
||||
467
libnymea/network/arpsocket.cpp
Normal file
467
libnymea/network/arpsocket.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
86
libnymea/network/arpsocket.h
Normal file
86
libnymea/network/arpsocket.h
Normal 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
|
||||
223
libnymea/network/macaddressdatabase.cpp
Normal file
223
libnymea/network/macaddressdatabase.cpp
Normal 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;
|
||||
}
|
||||
|
||||
93
libnymea/network/macaddressdatabase.h
Normal file
93
libnymea/network/macaddressdatabase.h
Normal 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
|
||||
267
libnymea/network/networkdevicediscovery.cpp
Normal file
267
libnymea/network/networkdevicediscovery.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
85
libnymea/network/networkdevicediscovery.h
Normal file
85
libnymea/network/networkdevicediscovery.h
Normal 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
|
||||
42
libnymea/network/networkdevicediscoveryreply.cpp
Normal file
42
libnymea/network/networkdevicediscoveryreply.cpp
Normal 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;
|
||||
}
|
||||
58
libnymea/network/networkdevicediscoveryreply.h
Normal file
58
libnymea/network/networkdevicediscoveryreply.h
Normal 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
|
||||
115
libnymea/network/networkdeviceinfo.cpp
Normal file
115
libnymea/network/networkdeviceinfo.cpp
Normal 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();
|
||||
}
|
||||
|
||||
77
libnymea/network/networkdeviceinfo.h
Normal file
77
libnymea/network/networkdeviceinfo.h
Normal 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
|
||||
113
libnymea/network/networkdeviceinfos.cpp
Normal file
113
libnymea/network/networkdeviceinfos.cpp
Normal 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;
|
||||
}
|
||||
61
libnymea/network/networkdeviceinfos.h
Normal file
61
libnymea/network/networkdeviceinfos.h
Normal 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
|
||||
34
libnymea/network/networkutils.cpp
Normal file
34
libnymea/network/networkutils.cpp
Normal 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();
|
||||
}
|
||||
46
libnymea/network/networkutils.h
Normal file
46
libnymea/network/networkutils.h
Normal 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
422
libnymea/network/ping.cpp
Normal 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
109
libnymea/network/ping.h
Normal 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
|
||||
74
libnymea/network/pingreply.cpp
Normal file
74
libnymea/network/pingreply.cpp
Normal 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;
|
||||
}
|
||||
96
libnymea/network/pingreply.h
Normal file
96
libnymea/network/pingreply.h
Normal 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
|
||||
Reference in New Issue
Block a user