diff --git a/libnymea-core/hardwaremanagerimplementation.cpp b/libnymea-core/hardwaremanagerimplementation.cpp index e873b839..87d52b68 100644 --- a/libnymea-core/hardwaremanagerimplementation.cpp +++ b/libnymea-core/hardwaremanagerimplementation.cpp @@ -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(); diff --git a/libnymea-core/hardwaremanagerimplementation.h b/libnymea-core/hardwaremanagerimplementation.h index c808b0d2..c94153ef 100644 --- a/libnymea-core/hardwaremanagerimplementation.h +++ b/libnymea-core/hardwaremanagerimplementation.h @@ -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; + }; } diff --git a/libnymea/hardwaremanager.h b/libnymea/hardwaremanager.h index 4dab713e..893fa340 100644 --- a/libnymea/hardwaremanager.h +++ b/libnymea/hardwaremanager.h @@ -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); diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 411dcf87..bae23e8e 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -43,6 +43,11 @@ HEADERS += \ network/apikeys/apikeysprovider.h \ network/apikeys/apikeystorage.h \ network/arpsocket.h \ + network/networkdevice.h \ + network/networkdevicediscovery.h \ + network/networkdevicediscoveryreply.h \ + network/networkdevices.h \ + network/networkutils.h \ network/ping.h \ network/pingreply.h \ platform/package.h \ @@ -144,6 +149,11 @@ SOURCES += \ network/apikeys/apikeysprovider.cpp \ network/apikeys/apikeystorage.cpp \ network/arpsocket.cpp \ + network/networkdevice.cpp \ + network/networkdevicediscovery.cpp \ + network/networkdevicediscoveryreply.cpp \ + network/networkdevices.cpp \ + network/networkutils.cpp \ network/ping.cpp \ network/pingreply.cpp \ nymeasettings.cpp \ diff --git a/libnymea/network/arpsocket.cpp b/libnymea/network/arpsocket.cpp index 0543eeda..ab1ef1bc 100644 --- a/libnymea/network/arpsocket.cpp +++ b/libnymea/network/arpsocket.cpp @@ -1,5 +1,36 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include "arpsocket.h" #include "loggingcategories.h" +#include "networkutils.h" #include #include @@ -161,7 +192,7 @@ bool ArpSocket::isOpen() const bool ArpSocket::openSocket() { - qCDebug(dcArpSocket()) << "Enabeling ARP scanner..."; + qCDebug(dcArpSocket()) << "Open ARP socket..."; if (m_isOpen) { qCWarning(dcArpSocket()) << "Failed to enable ARP scanner because the scanner is already running."; @@ -220,7 +251,7 @@ bool ArpSocket::openSocket() //qCDebug(dcArpSocket()) << "ARP request from " << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString(); break; case ARPOP_REPLY: { - QNetworkInterface networkInterface = getInterfaceForMacAddress(targetMacAddress); + QNetworkInterface networkInterface = NetworkUtils::getInterfaceForMacAddress(targetMacAddress); if (!networkInterface.isValid()) { qCWarning(dcArpSocket()) << "Could not find interface from ARP response" << targetHostAddress.toString() << targetMacAddress; return; @@ -364,30 +395,3 @@ void ArpSocket::fillHostAddress(uint8_t *targetArray, const QHostAddress &hostAd } } -QNetworkInterface ArpSocket::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 ArpSocket::getInterfaceForMacAddress(const QString &macAddress) -{ - foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { - if (networkInterface.hardwareAddress().toLower() == macAddress.toLower()) { - return networkInterface; - } - } - - return QNetworkInterface(); -} diff --git a/libnymea/network/arpsocket.h b/libnymea/network/arpsocket.h index d910d27e..4cea59fd 100644 --- a/libnymea/network/arpsocket.h +++ b/libnymea/network/arpsocket.h @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #ifndef ARPSOCKET_H #define ARPSOCKET_H @@ -8,7 +38,9 @@ #include #include -class ArpSocket : public QObject +#include "libnymea.h" + +class LIBNYMEA_EXPORT ArpSocket : public QObject { Q_OBJECT public: @@ -47,8 +79,6 @@ private: void fillMacAddress(uint8_t *targetArray, const QString &macAddress); void fillHostAddress(uint8_t *targetArray, const QHostAddress &hostAddress); - QNetworkInterface getInterfaceForHostaddress(const QHostAddress &address); - QNetworkInterface getInterfaceForMacAddress(const QString &macAddress); }; #endif // ARPSOCKET_H diff --git a/libnymea/network/networkdevice.cpp b/libnymea/network/networkdevice.cpp new file mode 100644 index 00000000..4e516dc6 --- /dev/null +++ b/libnymea/network/networkdevice.cpp @@ -0,0 +1,116 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "networkdevice.h" + +NetworkDevice::NetworkDevice() +{ + +} + +NetworkDevice::NetworkDevice(const QString &macAddress): + m_macAddress(macAddress) +{ + +} + +QString NetworkDevice::macAddress() const +{ + return m_macAddress; +} + +void NetworkDevice::setMacAddress(const QString &macAddress) +{ + m_macAddress = macAddress; +} + +QString NetworkDevice::macAddressManufacturer() const +{ + return m_macAddressManufacturer; +} + +void NetworkDevice::setMacAddressManufacturer(const QString &macAddressManufacturer) +{ + m_macAddressManufacturer = macAddressManufacturer; +} + +QHostAddress NetworkDevice::address() const +{ + return m_address; +} + +void NetworkDevice::setAddress(const QHostAddress &address) +{ + m_address = address; +} + +QString NetworkDevice::hostName() const +{ + return m_hostName; +} + +void NetworkDevice::setHostName(const QString &hostName) +{ + m_hostName = hostName; +} + +QNetworkInterface NetworkDevice::networkInterface() const +{ + return m_networkInterface; +} + +void NetworkDevice::setNetworkInterface(const QNetworkInterface &networkInterface) +{ + m_networkInterface = networkInterface; +} + +bool NetworkDevice::isValid() const +{ + return (!m_address.isNull() || !m_macAddress.isEmpty()) && m_networkInterface.isValid(); +} + +QDebug operator<<(QDebug dbg, const NetworkDevice &networkDevice) +{ + dbg.nospace() << "NetworkDevice("; + dbg.nospace() << networkDevice.address().toString() << ", "; + dbg.nospace() << networkDevice.macAddress(); + if (!networkDevice.macAddressManufacturer().isEmpty()) + dbg.nospace() << "(" << networkDevice.macAddressManufacturer() << ")"; + + if (!networkDevice.hostName().isEmpty()) + dbg.nospace() << ", " << networkDevice.hostName(); + + if (networkDevice.networkInterface().isValid()) + dbg.nospace() << ", " << networkDevice.networkInterface().name(); + + dbg.nospace() << ")"; + return dbg.space(); +} + diff --git a/libnymea/network/networkdevice.h b/libnymea/network/networkdevice.h new file mode 100644 index 00000000..a2e57395 --- /dev/null +++ b/libnymea/network/networkdevice.h @@ -0,0 +1,76 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef NETWORKDEVICE_H +#define NETWORKDEVICE_H + +#include +#include +#include +#include + +#include "libnymea.h" + +class LIBNYMEA_EXPORT NetworkDevice +{ +public: + explicit NetworkDevice(); + explicit NetworkDevice(const QString &macAddress); + + 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 NetworkDevice &networkDevice); + + +#endif // NETWORKDEVICE_H diff --git a/libnymea/network/networkdevicediscovery.cpp b/libnymea/network/networkdevicediscovery.cpp new file mode 100644 index 00000000..52b14798 --- /dev/null +++ b/libnymea/network/networkdevicediscovery.cpp @@ -0,0 +1,224 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "networkdevicediscovery.h" +#include "loggingcategories.h" +#include "networkutils.h" + +NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery") + +NetworkDeviceDiscovery::NetworkDeviceDiscovery(QObject *parent) : + QObject(parent) +{ + m_arpSocket = new ArpSocket(this); + connect(m_arpSocket, &ArpSocket::arpResponse, this, &NetworkDeviceDiscovery::onArpResponseRceived); + + if (!m_arpSocket->openSocket()) { + qCWarning(dcNetworkDeviceDiscovery()) << "Network discovery will not make use of ARP packages."; + m_arpSocket->closeSocket(); + } + + m_ping = new Ping(this); + if (!m_ping->available()) + qCWarning(dcNetworkDeviceDiscovery()) << "Failed to create ping tool" << m_ping->error(); + + m_discoveryTimer = new QTimer(this); + m_discoveryTimer->setInterval(5000); + m_discoveryTimer->setSingleShot(true); + connect(m_discoveryTimer, &QTimer::timeout, this, [=](){ + if (m_runningPingRepies.isEmpty() && m_currentReply) { + finishDiscovery(); + } + }); + + 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; + + 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); +} + +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->networkDevices().indexFromHostAddress(targetAddress); + if (index < 0) { + // Add the network device + NetworkDevice networkDevice; + networkDevice.setAddress(targetAddress); + networkDevice.setHostName(reply->hostName()); + m_currentReply->networkDevices().append(networkDevice); + } else { + m_currentReply->networkDevices()[index].setAddress(targetAddress); + m_currentReply->networkDevices()[index].setHostName(reply->hostName()); + if (!m_currentReply->networkDevices()[index].networkInterface().isValid()) { + m_currentReply->networkDevices()[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); + + qCDebug(dcNetworkDeviceDiscovery()) << "Discovery finished. Found" << m_currentReply->networkDevices().count() << "network devices."; + m_arpSocket->closeSocket(); + emit m_currentReply->finished(); + m_currentReply->deleteLater(); + m_currentReply = nullptr; +} + +void NetworkDeviceDiscovery::onArpResponseRceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress) +{ + if (!m_currentReply) { + qCDebug(dcNetworkDeviceDiscovery()) << "Received ARP reply but there is no discovery running."; + return; + } + + qCDebug(dcNetworkDeviceDiscovery()) << "ARP reply received" << address.toString() << macAddress << interface.name(); + // Update or add network device + int index = m_currentReply->networkDevices().indexFromHostAddress(address); + if (index >= 0) { + // Update the network device + m_currentReply->networkDevices()[index].setMacAddress(macAddress); + if (interface.isValid()) { + m_currentReply->networkDevices()[index].setNetworkInterface(interface); + } + } else { + index = m_currentReply->networkDevices().indexFromMacAddress(macAddress); + if (index >= 0) { + // Update the network device + m_currentReply->networkDevices()[index].setAddress(address); + if (interface.isValid()) { + m_currentReply->networkDevices()[index].setNetworkInterface(interface); + } + } else { + // Add the network device + NetworkDevice networkDevice; + networkDevice.setAddress(address); + networkDevice.setMacAddress(macAddress); + if (interface.isValid()) + networkDevice.setNetworkInterface(interface); + + m_currentReply->networkDevices().append(networkDevice); + } + } +} diff --git a/libnymea/network/networkdevicediscovery.h b/libnymea/network/networkdevicediscovery.h new file mode 100644 index 00000000..0d426383 --- /dev/null +++ b/libnymea/network/networkdevicediscovery.h @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef NETWORKDEVICEDISCOVERY_H +#define NETWORKDEVICEDISCOVERY_H + +#include +#include +#include + +#include "ping.h" +#include "libnymea.h" +#include "arpsocket.h" +#include "networkdevicediscoveryreply.h" + +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); + +signals: + void runningChanged(bool running); + +private: + ArpSocket *m_arpSocket = nullptr; + Ping *m_ping = nullptr; + bool m_running = false; + + QTimer *m_discoveryTimer = nullptr; + NetworkDeviceDiscoveryReply *m_currentReply = nullptr; + QList m_runningPingRepies; + + void pingAllNetworkDevices(); + void finishDiscovery(); + +private slots: + void onArpResponseRceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress); + +}; + +#endif // NETWORKDEVICEDISCOVERY_H diff --git a/libnymea/network/networkdevicediscoveryreply.cpp b/libnymea/network/networkdevicediscoveryreply.cpp new file mode 100644 index 00000000..71f7a453 --- /dev/null +++ b/libnymea/network/networkdevicediscoveryreply.cpp @@ -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 . +* +* 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) +{ + +} + +NetworkDevices &NetworkDeviceDiscoveryReply::networkDevices() +{ + return m_networkDevices; +} diff --git a/libnymea/network/networkdevicediscoveryreply.h b/libnymea/network/networkdevicediscoveryreply.h new file mode 100644 index 00000000..1787c02e --- /dev/null +++ b/libnymea/network/networkdevicediscoveryreply.h @@ -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 . +* +* 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 + +#include "libnymea.h" +#include "networkdevices.h" + +class LIBNYMEA_EXPORT NetworkDeviceDiscoveryReply : public QObject +{ + Q_OBJECT + + friend class NetworkDeviceDiscovery; + +public: + explicit NetworkDeviceDiscoveryReply(QObject *parent = nullptr); + + NetworkDevices &networkDevices(); + +signals: + void finished(); + +private: + NetworkDevices m_networkDevices; + +}; + +#endif // NETWORKDEVICEDISCOVERYREPLY_H diff --git a/libnymea/network/networkdevices.cpp b/libnymea/network/networkdevices.cpp new file mode 100644 index 00000000..8f940f8e --- /dev/null +++ b/libnymea/network/networkdevices.cpp @@ -0,0 +1,103 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "networkdevices.h" + +NetworkDevices::NetworkDevices() : + QList() +{ + +} + +NetworkDevices::NetworkDevices(const QList &other) : + QList(other) +{ + +} + +NetworkDevices NetworkDevices::operator<<(const NetworkDevice &networkDevice) +{ + this->append(networkDevice); + return *this; +} + +int NetworkDevices::indexFromHostAddress(const QHostAddress &address) +{ + for (int i = 0; i < this->size(); i++) { + if (at(i).address().toIPv4Address() == address.toIPv4Address()) { + return i; + } + } + + return -1; +} + +int NetworkDevices::indexFromMacAddress(const QString &macAddress) +{ + for (int i = 0; i < size(); i++) { + if (at(i).macAddress().toLower() == macAddress.toLower()) { + return i; + } + } + + return -1; +} + +bool NetworkDevices::hasHostAddress(const QHostAddress &address) +{ + return indexFromHostAddress(address) >= 0; +} + +bool NetworkDevices::hasMacAddress(const QString &macAddress) +{ + return indexFromMacAddress(macAddress) >= 0; +} + +NetworkDevice NetworkDevices::get(const QHostAddress &address) +{ + foreach (const NetworkDevice &networkDevice, *this) { + if (networkDevice.address() == address) { + return networkDevice; + } + } + + return NetworkDevice(); +} + +NetworkDevice NetworkDevices::get(const QString &macAddress) +{ + foreach (const NetworkDevice &networkDevice, *this) { + if (networkDevice.macAddress() == macAddress) { + return networkDevice; + } + } + + return NetworkDevice(); +} diff --git a/libnymea/network/networkdevices.h b/libnymea/network/networkdevices.h new file mode 100644 index 00000000..8d3fc2cc --- /dev/null +++ b/libnymea/network/networkdevices.h @@ -0,0 +1,59 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef NETWORKDEVICES_H +#define NETWORKDEVICES_H + +#include + +#include "libnymea.h" +#include "networkdevice.h" + +class LIBNYMEA_EXPORT NetworkDevices : public QList +{ + +public: + explicit NetworkDevices(); + NetworkDevices(const QList &other); + + NetworkDevices operator<<(const NetworkDevice &networkDevice); + + int indexFromHostAddress(const QHostAddress &address); + int indexFromMacAddress(const QString &macAddress); + + bool hasHostAddress(const QHostAddress &address); + bool hasMacAddress(const QString &macAddress); + + NetworkDevice get(const QHostAddress &address); + NetworkDevice get(const QString &macAddress); + +}; + +#endif // NETWORKDEVICES_H diff --git a/libnymea/network/networkutils.cpp b/libnymea/network/networkutils.cpp new file mode 100644 index 00000000..ddcf1f38 --- /dev/null +++ b/libnymea/network/networkutils.cpp @@ -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(); +} diff --git a/libnymea/network/networkutils.h b/libnymea/network/networkutils.h new file mode 100644 index 00000000..b75aaedb --- /dev/null +++ b/libnymea/network/networkutils.h @@ -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 . +* +* 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 +#include + +class NetworkUtils +{ +public: + NetworkUtils(); + + static QNetworkInterface getInterfaceForHostaddress(const QHostAddress &address); + static QNetworkInterface getInterfaceForMacAddress(const QString &macAddress); +}; + +#endif // NETWORKUTILS_H diff --git a/libnymea/network/ping.cpp b/libnymea/network/ping.cpp index 35aa04f8..c26b2914 100644 --- a/libnymea/network/ping.cpp +++ b/libnymea/network/ping.cpp @@ -29,6 +29,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "ping.h" +#include "networkutils.h" #include "loggingcategories.h" #include @@ -115,7 +116,7 @@ PingReply *Ping::ping(const QHostAddress &hostAddress) { PingReply *reply = new PingReply(this); reply->m_targetHostAddress = hostAddress; - reply->m_networkInterface = getInterfaceForHostaddress(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); @@ -284,34 +285,6 @@ void Ping::finishReply(PingReply *reply, PingReply::Error error) reply->deleteLater(); } -QNetworkInterface Ping::getInterfaceForHostaddress(const QHostAddress &address) -{ - foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { - foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { - // Only IPv4 - if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol) - continue; - - if (address.isInSubnet(entry.ip(), entry.netmask().toIPv4Address())) { - return networkInterface; - } - } - } - - return QNetworkInterface(); -} - -QNetworkInterface Ping::getInterfaceForMacAddress(const QString &macAddress) -{ - foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { - if (networkInterface.hardwareAddress().toLower() == macAddress.toLower()) { - return networkInterface; - } - } - - return QNetworkInterface(); -} - void Ping::onSocketReadyRead(int socketDescriptor) { // We must read all data otherwise the socket notifier does not work as expected diff --git a/libnymea/network/ping.h b/libnymea/network/ping.h index df673e9a..3ce13f84 100644 --- a/libnymea/network/ping.h +++ b/libnymea/network/ping.h @@ -101,9 +101,6 @@ private: void finishReply(PingReply *reply, PingReply::Error error); - QNetworkInterface getInterfaceForHostaddress(const QHostAddress &address); - QNetworkInterface getInterfaceForMacAddress(const QString &macAddress); - private slots: void onSocketReadyRead(int socketDescriptor); diff --git a/libnymea/network/pingreply.h b/libnymea/network/pingreply.h index da57b41b..e1c8d9c2 100644 --- a/libnymea/network/pingreply.h +++ b/libnymea/network/pingreply.h @@ -37,10 +37,12 @@ #include +#include "libnymea.h" + #include #include -class PingReply : public QObject +class LIBNYMEA_EXPORT PingReply : public QObject { Q_OBJECT