diff --git a/libnymea/network/macaddressdatabase.cpp b/libnymea-core/hardware/network/macaddressdatabase.cpp similarity index 93% rename from libnymea/network/macaddressdatabase.cpp rename to libnymea-core/hardware/network/macaddressdatabase.cpp index b175573b..3e57aecd 100644 --- a/libnymea/network/macaddressdatabase.cpp +++ b/libnymea-core/hardware/network/macaddressdatabase.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -41,6 +41,8 @@ NYMEA_LOGGING_CATEGORY(dcMacAddressDatabase, "MacAddressDatabase") +namespace nymeaserver { + MacAddressDatabase::MacAddressDatabase(QObject *parent) : QObject(parent) { // Find database in system data locations @@ -96,7 +98,7 @@ bool MacAddressDatabase::available() const MacAddressDatabaseReply *MacAddressDatabase::lookupMacAddress(const QString &macAddress) { - MacAddressDatabaseReply *reply = new MacAddressDatabaseReply(this); + MacAddressDatabaseReplyImpl *reply = new MacAddressDatabaseReplyImpl(this); connect(reply, &MacAddressDatabaseReply::finished, reply, &MacAddressDatabaseReply::deleteLater); reply->m_macAddress = macAddress; @@ -140,6 +142,7 @@ bool MacAddressDatabase::initDatabase() return false; } + qCInfo(dcMacAddressDatabase()) << "Database initialized successfully" << m_databaseName; return true; } @@ -161,7 +164,7 @@ 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"; + qCInfo(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; @@ -172,7 +175,7 @@ void MacAddressDatabase::onLookupFinished() QString MacAddressDatabase::lookupMacAddressVendorInternal(const QString &macAddress) { - qCDebug(dcMacAddressDatabase()) << "Start looking up vendor for" << macAddress; + qCInfo(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(":"); @@ -221,3 +224,4 @@ QString MacAddressDatabase::lookupMacAddressVendorInternal(const QString &macAdd return manufacturer; } +} diff --git a/libnymea/network/macaddressdatabase.h b/libnymea-core/hardware/network/macaddressdatabase.h similarity index 77% rename from libnymea/network/macaddressdatabase.h rename to libnymea-core/hardware/network/macaddressdatabase.h index d661ad1d..75522abc 100644 --- a/libnymea/network/macaddressdatabase.h +++ b/libnymea-core/hardware/network/macaddressdatabase.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -36,30 +36,11 @@ #include #include -#include "libnymea.h" +#include "macaddressdatabasereplyimpl.h" -class LIBNYMEA_EXPORT MacAddressDatabaseReply : public QObject -{ - Q_OBJECT - friend class MacAddressDatabase; +namespace nymeaserver { -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 +class MacAddressDatabase : public QObject { Q_OBJECT public: @@ -77,9 +58,9 @@ private: QString m_connectionName; QString m_databaseName = "/usr/share/nymea/mac-addresses.db"; - MacAddressDatabaseReply *m_currentReply = nullptr; + MacAddressDatabaseReplyImpl *m_currentReply = nullptr; QFutureWatcher *m_futureWatcher = nullptr; - QQueue m_pendingReplies; + QQueue m_pendingReplies; bool initDatabase(); void runNextLookup(); @@ -90,4 +71,6 @@ private slots: }; +} + #endif // MACADDRESSDATABASE_H diff --git a/libnymea-core/hardware/network/macaddressdatabasereplyimpl.cpp b/libnymea-core/hardware/network/macaddressdatabasereplyimpl.cpp new file mode 100644 index 00000000..c6910cba --- /dev/null +++ b/libnymea-core/hardware/network/macaddressdatabasereplyimpl.cpp @@ -0,0 +1,51 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 "macaddressdatabasereplyimpl.h" + +namespace nymeaserver { + +MacAddressDatabaseReplyImpl::MacAddressDatabaseReplyImpl(QObject *parent) : + MacAddressDatabaseReply(parent) +{ + +} + +QString MacAddressDatabaseReplyImpl::macAddress() const +{ + return m_macAddress; +} + +QString MacAddressDatabaseReplyImpl::manufacturer() const +{ + return m_manufacturer; +} + +} diff --git a/libnymea-core/hardware/network/macaddressdatabasereplyimpl.h b/libnymea-core/hardware/network/macaddressdatabasereplyimpl.h new file mode 100644 index 00000000..3278f980 --- /dev/null +++ b/libnymea-core/hardware/network/macaddressdatabasereplyimpl.h @@ -0,0 +1,62 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 MACADDRESSDATABASEREPLYIMPL_H +#define MACADDRESSDATABASEREPLYIMPL_H + +#include + +#include + +namespace nymeaserver { + +class MacAddressDatabaseReplyImpl : public MacAddressDatabaseReply +{ + Q_OBJECT + + friend class MacAddressDatabase; + +public: + explicit MacAddressDatabaseReplyImpl(QObject *parent = nullptr); + ~MacAddressDatabaseReplyImpl() override = default; + + QString macAddress() const override; + QString manufacturer() const override; + +private: + QString m_macAddress; + QString m_manufacturer; + qint64 m_startTimestamp; + +}; + +} + +#endif // MACADDRESSDATABASEREPLYIMPL_H diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp new file mode 100644 index 00000000..5a7299dc --- /dev/null +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp @@ -0,0 +1,610 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 "networkdevicediscoveryimpl.h" + +#include "nymeasettings.h" +#include "loggingcategories.h" + +#include +#include +#include + +NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery") + +namespace nymeaserver { + +NetworkDeviceDiscoveryImpl::NetworkDeviceDiscoveryImpl(QObject *parent) : + NetworkDeviceDiscovery(parent) +{ + // Create ARP socket + m_arpSocket = new ArpSocket(this); + connect(m_arpSocket, &ArpSocket::arpResponse, this, &NetworkDeviceDiscoveryImpl::onArpResponseReceived); + 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(); + } + }); + + // Timer for updating the monitors + m_monitorTimer = new QTimer(this); + m_monitorTimer->setInterval(5000); + m_monitorTimer->setSingleShot(false); + connect(m_monitorTimer, &QTimer::timeout, this, &NetworkDeviceDiscoveryImpl::evaluateMonitors); + + if (!arpAvailable && !m_ping->available()) { + qCWarning(dcNetworkDeviceDiscovery()) << "Network device discovery is not available on this system."; + } else { + qCInfo(dcNetworkDeviceDiscovery()) << "Created successfully"; + } + + m_cacheSettings = new QSettings(NymeaSettings::cachePath() + "/network-device-discovery.cache", QSettings::IniFormat); + loadNetworkDeviceCache(); + + // Start the timer only if the resource is available + if (available()) { + // Start the monitor timer in any case, we do also the cache cleanup there... + m_monitorTimer->start(); + } +} + +NetworkDeviceDiscoveryImpl::~NetworkDeviceDiscoveryImpl() +{ + +} + +NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover() +{ + if (m_currentReply) { + qCDebug(dcNetworkDeviceDiscovery()) << "Discovery already running. Returning current pending discovery reply..."; + return m_currentReply; + } + + m_currentReply = new NetworkDeviceDiscoveryReplyImpl(this); + + if (!available()) { + qCWarning(dcNetworkDeviceDiscovery()) << "The network discovery is not available. Please make sure the binary has the required capability (CAP_NET_RAW) or start the application as root."; + // Finish the discovery in the next event loop so anny connections after the creation will work as expected + QTimer::singleShot(0, this, &NetworkDeviceDiscoveryImpl::finishDiscovery); + return m_currentReply; + } + + connect(m_currentReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, this, &NetworkDeviceDiscoveryImpl::updateCache); + qCInfo(dcNetworkDeviceDiscovery()) << "Starting network device discovery ..."; + + if (m_ping->available()) + pingAllNetworkDevices(); + + if (m_arpSocket->isOpen()) + m_arpSocket->sendRequest(); + + m_discoveryTimer->start(); + + m_running = true; + emit runningChanged(m_running); + + return m_currentReply; +} + +bool NetworkDeviceDiscoveryImpl::available() const +{ + return m_arpSocket->isOpen() || m_ping->available(); +} + +bool NetworkDeviceDiscoveryImpl::enabled() const +{ + return m_enabled; +} + +bool NetworkDeviceDiscoveryImpl::running() const +{ + return m_running; +} + +NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddress &macAddress) +{ + qCInfo(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress; + // Make sure we create only one monitor per MAC + if (m_monitors.contains(macAddress)) + return m_monitors.value(macAddress); + + if (macAddress.isNull()) { + qCWarning(dcNetworkDeviceDiscovery()) << "Could not register monitor for invalid" << macAddress; + return nullptr; + } + + + // Fill in cached information + NetworkDeviceInfo info; + if (m_networkInfoCache.contains(macAddress)) { + info = m_networkInfoCache.value(macAddress); + } else { + info.setMacAddress(macAddress.toString()); + } + + NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this); + monitor->setNetworkDeviceInfo(info); + m_monitors.insert(macAddress, monitor); + + if (!available()) { + qCWarning(dcNetworkDeviceDiscovery()) << "Registered monitor but the hardware resource is not available. The monitor will not work as expected" << monitor; + return monitor; + } + + // Restart the monitor timer since we evaluate this one now + m_monitorTimer->start(); + + if (!monitor->networkDeviceInfo().isValid()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Adding network device monitor for unresolved mac address. Starting a discovery..."; + discover(); + } + + evaluateMonitor(monitor); + + return monitor; +} + +void NetworkDeviceDiscoveryImpl::unregisterMonitor(const MacAddress &macAddress) +{ + if (m_monitors.contains(macAddress)) { + NetworkDeviceMonitor *monitor = m_monitors.take(macAddress); + qCInfo(dcNetworkDeviceDiscovery()) << "Unregister" << monitor; + monitor->deleteLater(); + } +} + +void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) +{ + unregisterMonitor(MacAddress(networkDeviceMonitor->networkDeviceInfo().macAddress())); +} + +PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address) +{ + // Note: we use any ping used trough this method also for the monitor evaluation + PingReply *reply = m_ping->ping(address); + connect(reply, &PingReply::finished, this, [=](){ + + // Search cache for mac address and update last seen + if (reply->error() == PingReply::ErrorNoError) { + foreach (const NetworkDeviceInfo &info, m_networkInfoCache) { + if (info.address() == address) { + // Found info for this ip, update the cache + MacAddress macAddress(info.macAddress()); + if (!macAddress.isNull() && m_networkInfoCache.contains(macAddress)) { + m_lastSeen[macAddress] = QDateTime::currentDateTime(); + saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); + } + } + } + } + + // Update any monitor + foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) { + if (monitor->networkDeviceInfo().address() == address) { + processMonitorPingResult(reply, monitor); + } + } + }); + + return reply; +} + +MacAddressDatabaseReply *NetworkDeviceDiscoveryImpl::lookupMacAddress(const QString &macAddress) +{ + return m_macAddressDatabase->lookupMacAddress(macAddress); +} + +MacAddressDatabaseReply *NetworkDeviceDiscoveryImpl::lookupMacAddress(const MacAddress &macAddress) +{ + return lookupMacAddress(macAddress.toString()); +} + +bool NetworkDeviceDiscoveryImpl::sendArpRequest(const QHostAddress &address) +{ + if (m_arpSocket && m_arpSocket->isOpen()) + return m_arpSocket->sendRequest(address); + + return false; +} + +void NetworkDeviceDiscoveryImpl::setEnabled(bool enabled) +{ + m_enabled = enabled; + emit enabledChanged(m_enabled); + // TODO: disable network discovery if false, not used for now +} + +void NetworkDeviceDiscoveryImpl::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()) { + qCDebug(dcNetworkDeviceDiscovery()) << " Checking entry" << entry.ip().toString(); + + // 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 = 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"; + if (m_currentReply) { + m_currentReply->processPingResponse(targetAddress, reply->hostName()); + } + } + + if (m_runningPingRepies.isEmpty() && m_currentReply && !m_discoveryTimer->isActive()) { + qCWarning(dcNetworkDeviceDiscovery()) << "All ping replies finished for discovery." << m_currentReply->networkDeviceInfos().count(); + finishDiscovery(); + } + }); + } + } + } +} + +void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, NetworkDeviceMonitorImpl *monitor) +{ + // Save the last time we tried to communicate + if (reply->error() == PingReply::ErrorNoError) { + qCDebug(dcNetworkDeviceDiscovery()) << "Ping response from" << monitor << reply->duration() << "ms"; + monitor->setLastSeen(QDateTime::currentDateTime()); + monitor->setReachable(true); + } else { + qCDebug(dcNetworkDeviceDiscovery()) << "Failed to ping device from" << monitor << reply->error(); + monitor->setReachable(false); + } +} + +void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() +{ + qCInfo(dcNetworkDeviceDiscovery()) << "Loading cached network device information from" << m_cacheSettings->fileName(); + + m_networkInfoCache.clear(); + QDateTime now = QDateTime::currentDateTime(); + + m_cacheSettings->beginGroup("NetworkDeviceInfos"); + foreach (const QString &macAddress, m_cacheSettings->childGroups()) { + m_cacheSettings->beginGroup(macAddress); + + MacAddress mac(macAddress); + QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong()); + + // Remove the info from the cache if not seen fo the last 30 days... + if (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); + m_cacheSettings->remove(""); + continue; + } + + NetworkDeviceInfo info(mac.toString()); + info.setAddress(QHostAddress(m_cacheSettings->value("address").toString())); + info.setHostName(m_cacheSettings->value("hostName").toString()); + info.setMacAddressManufacturer(m_cacheSettings->value("manufacturer").toString()); + info.setNetworkInterface(QNetworkInterface::interfaceFromName(m_cacheSettings->value("interface").toString())); + + if (info.isValid() && info.isComplete()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Loaded cached" << info << "last seen" << lastSeen.toString(); + m_networkInfoCache[mac] = info; + m_lastSeen[mac] = lastSeen; + } else { + qCWarning(dcNetworkDeviceDiscovery()) << "Clean up invalid cached network device info from cache" << info; + m_cacheSettings->remove(""); + } + + m_cacheSettings->endGroup(); // mac address + } + m_cacheSettings->endGroup(); // NetworkDeviceInfos + + // We just did some housekeeping while loading from the cache + m_lastCacheHousekeeping = QDateTime::currentDateTime(); +} + +void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const MacAddress &macAddress) +{ + if (macAddress.isNull()) + return; + + m_networkInfoCache.remove(macAddress); + m_lastSeen.remove(macAddress); + + m_cacheSettings->beginGroup("NetworkDeviceInfos"); + m_cacheSettings->beginGroup(macAddress.toString()); + m_cacheSettings->remove(""); + m_cacheSettings->endGroup(); // mac address + m_cacheSettings->endGroup(); // NetworkDeviceInfos +} + +void NetworkDeviceDiscoveryImpl::saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo) +{ + m_cacheSettings->beginGroup("NetworkDeviceInfos"); + m_cacheSettings->beginGroup(deviceInfo.macAddress()); + m_cacheSettings->setValue("address", deviceInfo.address().toString()); + m_cacheSettings->setValue("hostName", deviceInfo.hostName()); + m_cacheSettings->setValue("manufacturer", deviceInfo.macAddressManufacturer()); + m_cacheSettings->setValue("interface", deviceInfo.networkInterface().name()); + m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(MacAddress(deviceInfo.macAddress()))).toMSecsSinceEpoch()); + m_cacheSettings->endGroup(); // mac address + m_cacheSettings->endGroup(); // NetworkDeviceInfos +} + +void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo) +{ + MacAddress macAddress(deviceInfo.macAddress()); + if (macAddress.isNull()) + return; + + if (m_monitors.contains(macAddress)) { + NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); + monitor->setNetworkDeviceInfo(deviceInfo); + } + + if (m_networkInfoCache.value(macAddress) == deviceInfo) + return; + + m_networkInfoCache[macAddress] = deviceInfo; + saveNetworkDeviceCache(deviceInfo); +} + +void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monitor) +{ + // Start action if we have not seen the device for gracePeriod seconds + QDateTime currentDateTime = QDateTime::currentDateTime(); + + bool requiresRefresh = false; + if (monitor->lastSeen().isNull()) { + qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not seen since application start."; + requiresRefresh = true; + } + + if (!requiresRefresh && currentDateTime > monitor->lastSeen().addSecs(m_monitorInterval)) { + qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not see since" << (currentDateTime.toMSecsSinceEpoch() - monitor->lastSeen().toMSecsSinceEpoch()) / 1000.0 << "s"; + requiresRefresh = true; + } + + if (!requiresRefresh) + return; + + if (monitor->networkDeviceInfo().address().isNull()) + return; + + // Try to ping first + qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString(); + monitor->setLastConnectionAttempt(currentDateTime); + + PingReply *reply = m_ping->ping(monitor->networkDeviceInfo().address()); + connect(reply, &PingReply::finished, monitor, [=](){ + processMonitorPingResult(reply, monitor); + }); +} + +void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) +{ + QDateTime now = QDateTime::currentDateTime(); + m_lastSeen[macAddress] = now; + + if (m_networkInfoCache.contains(macAddress)) { + if (m_networkInfoCache.value(macAddress).address() != address) { + m_networkInfoCache[macAddress].setAddress(address); + saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); + } + } + + // Update the monitors + NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); + if (monitor) { + monitor->setLastSeen(now); + monitor->setReachable(true); + if (monitor->networkDeviceInfo().address() != address) { + NetworkDeviceInfo info = monitor->networkDeviceInfo(); + info.setAddress(address); + monitor->setNetworkDeviceInfo(info); + qCDebug(dcNetworkDeviceDiscovery()) << "NetworkDeviceMonitor" << monitor << "ip address changed"; + emit monitor->networkDeviceInfoChanged(monitor->networkDeviceInfo()); + } + } + + // Check if we have a reply running + if (m_currentReply) { + + // First process the response + m_currentReply->processArpResponse(interface, address, macAddress); + + // Check if we know the mac address manufacturer from the cache + if (m_networkInfoCache.contains(macAddress)) { + QString cachedManufacturer = m_networkInfoCache[macAddress].macAddressManufacturer(); + if (!cachedManufacturer.isEmpty()) { + // Found the mac address manufacturer in the cache, let's use that + m_currentReply->processMacManufacturer(macAddress, cachedManufacturer); + } + } else { + // Lookup the mac address vendor if possible + if (m_macAddressDatabase->available()) { + MacAddressDatabaseReply *reply = m_macAddressDatabase->lookupMacAddress(macAddress.toString()); + connect(reply, &MacAddressDatabaseReply::finished, m_currentReply, [=](){ + qCDebug(dcNetworkDeviceDiscovery()) << "MAC manufacturer lookup finished for" << macAddress << ":" << reply->manufacturer(); + m_currentReply->processMacManufacturer(macAddress, reply->manufacturer()); + }); + } else { + // Note: set the mac manufacturer explicitly to make the info complete + m_currentReply->processMacManufacturer(macAddress, QString()); + } + } + } +} + +bool NetworkDeviceDiscoveryImpl::longerAgoThan(const QDateTime &dateTime, uint seconds) +{ + uint duration = (QDateTime::currentDateTime().toMSecsSinceEpoch() - dateTime.toMSecsSinceEpoch()) / 1000.0; + return duration >= seconds; +} + +QDateTime NetworkDeviceDiscoveryImpl::convertMinuteBased(const QDateTime &dateTime) +{ + // We store the date time on minute accuracy to have + // less write cycles and a higher resolution is not necessary + + // If the given datetime is null, use the current datetime + QDateTime dateTimeToConvert; + if (dateTime.isNull()) { + dateTimeToConvert = QDateTime::currentDateTime(); + } else { + dateTimeToConvert = dateTime; + } + + dateTimeToConvert.setTime(QTime(dateTimeToConvert.time().hour(), dateTimeToConvert.time().minute(), 0, 0)); + return dateTimeToConvert; +} + +void NetworkDeviceDiscoveryImpl::onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) +{ + // Ignore ARP from zero mac + if (macAddress.isNull()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Ignoring ARP reply from" << address.toString() << macAddress.toString() << interface.name(); + return; + } + + qCDebug(dcNetworkDeviceDiscovery()) << "ARP reply received" << address.toString() << macAddress.toString() << interface.name(); + processArpTraffic(interface, address, macAddress); +} + +void NetworkDeviceDiscoveryImpl::onArpRequstReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) +{ + // Ignore ARP from zero mac + if (macAddress.isNull()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Ignoring ARP reply from" << address.toString() << macAddress.toString() << interface.name(); + return; + } + + qCDebug(dcNetworkDeviceDiscovery()) << "ARP request received" << address.toString() << macAddress.toString() << interface.name(); + processArpTraffic(interface, address, macAddress); +} + +void NetworkDeviceDiscoveryImpl::evaluateMonitors() +{ + bool monitorRequiresRediscovery = false; + foreach (NetworkDeviceMonitorImpl *monitor, m_monitors) { + evaluateMonitor(monitor); + + // Check if there is any monitor which has not be seen since + if (!monitor->reachable() && monitor->lastConnectionAttempt().isValid() && longerAgoThan(monitor->lastSeen(), m_monitorInterval)) { + monitorRequiresRediscovery = true; + } + } + + if (monitorRequiresRediscovery && longerAgoThan(m_lastDiscovery, m_rediscoveryInterval)) { + qCDebug(dcNetworkDeviceDiscovery()) << "There are unreachable monitors and the last discovery is more than" << m_rediscoveryInterval << "s ago. Starting network discovery to search the monitored network devices..."; + discover(); + } + + // Do some cache housekeeping if required + if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) { + qCInfo(dcNetworkDeviceDiscovery()) << "Starting cache housekeeping since it is more than one day since the last clanup..."; + QDateTime now = QDateTime::currentDateTime(); + foreach (const MacAddress &mac, m_lastSeen.keys()) { + // Remove the info from the cache if not seen fo the last 30 days... + if (m_lastSeen.value(mac).date().addDays(m_cacheCleanupPeriod) < QDateTime::currentDateTime().date()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); + removeFromNetworkDeviceCache(mac); + } + } + + m_lastCacheHousekeeping = now; + } +} + +void NetworkDeviceDiscoveryImpl::finishDiscovery() +{ + qCDebug(dcNetworkDeviceDiscovery()) << "Finishing discovery..."; + m_discoveryTimer->stop(); + + m_running = false; + emit runningChanged(m_running); + + emit networkDeviceInfoCacheUpdated(); + + m_lastDiscovery = QDateTime::currentDateTime(); + + m_currentReply->processDiscoveryFinished(); + m_currentReply->deleteLater(); + m_currentReply = nullptr; +} + +} diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h new file mode 100644 index 00000000..b0243ab4 --- /dev/null +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h @@ -0,0 +1,137 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 NETWORKDEVICEDISCOVERYIMPL_H +#define NETWORKDEVICEDISCOVERYIMPL_H + +#include +#include +#include +#include + +#include +#include + +#include "macaddressdatabase.h" + +#include "networkdevicemonitorimpl.h" +#include "macaddressdatabasereplyimpl.h" +#include "networkdevicediscoveryreplyimpl.h" + +class Ping; +class ArpSocket; + +Q_DECLARE_LOGGING_CATEGORY(dcNetworkDeviceDiscovery) + +namespace nymeaserver { + +class NetworkDeviceDiscoveryImpl : public NetworkDeviceDiscovery +{ + Q_OBJECT +public: + explicit NetworkDeviceDiscoveryImpl(QObject *parent = nullptr); + ~NetworkDeviceDiscoveryImpl() override; + + bool available() const override; + bool enabled() const override; + + bool running() const override; + + NetworkDeviceDiscoveryReply *discover() override; + + NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) override; + + void unregisterMonitor(const MacAddress &macAddress) override; + void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) override; + + PingReply *ping(const QHostAddress &address) override; + + MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) override; + MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) override; + + bool sendArpRequest(const QHostAddress &address) override; + +protected: + void setEnabled(bool enabled) override; + +private: + MacAddressDatabase *m_macAddressDatabase = nullptr; + ArpSocket *m_arpSocket = nullptr; + Ping *m_ping = nullptr; + bool m_enabled = true; + bool m_running = false; + + QTimer *m_discoveryTimer = nullptr; + QTimer *m_monitorTimer = nullptr; + + QDateTime m_lastDiscovery; + QDateTime m_lastCacheHousekeeping; + + uint m_rediscoveryInterval = 300; // 5 min + uint m_monitorInterval = 60; // 1 min + uint m_cacheCleanupPeriod = 30; // days + + NetworkDeviceDiscoveryReplyImpl *m_currentReply = nullptr; + QList m_runningPingRepies; + + QHash m_monitors; + QHash m_lastSeen; + + QSettings *m_cacheSettings; + QHash m_networkInfoCache; + + void pingAllNetworkDevices(); + + void processMonitorPingResult(PingReply *reply, NetworkDeviceMonitorImpl *monitor); + + void loadNetworkDeviceCache(); + void removeFromNetworkDeviceCache(const MacAddress &macAddress); + void saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo); + + void updateCache(const NetworkDeviceInfo &deviceInfo); + void evaluateMonitor(NetworkDeviceMonitorImpl *monitor); + + void processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); + + // Time helpers + bool longerAgoThan(const QDateTime &dateTime, uint minutes); + QDateTime convertMinuteBased(const QDateTime &dateTime = QDateTime()); + +private slots: + void onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); + void onArpRequstReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); + void evaluateMonitors(); + void finishDiscovery(); + +}; + +} + +#endif // NETWORKDEVICEDISCOVERYIMPL_H diff --git a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp new file mode 100644 index 00000000..71277fea --- /dev/null +++ b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp @@ -0,0 +1,221 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 "networkdevicediscoveryreplyimpl.h" +#include "loggingcategories.h" + +#include + +Q_DECLARE_LOGGING_CATEGORY(dcNetworkDeviceDiscovery) + +namespace nymeaserver { + +NetworkDeviceDiscoveryReplyImpl::NetworkDeviceDiscoveryReplyImpl(QObject *parent) : + NetworkDeviceDiscoveryReply(parent) +{ + m_startTimestamp = QDateTime::currentMSecsSinceEpoch(); +} + +NetworkDeviceInfos NetworkDeviceDiscoveryReplyImpl::networkDeviceInfos() const +{ + return m_networkDeviceInfos; +} + +NetworkDeviceInfos NetworkDeviceDiscoveryReplyImpl::virtualNetworkDeviceInfos() const +{ + return m_virtualNetworkDeviceInfos; +} + +QString NetworkDeviceDiscoveryReplyImpl::macAddressFromHostAddress(const QHostAddress &address) +{ + foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) { + if (info.address() == address) { + return info.macAddress(); + } + } + + return QString(); +} + +bool NetworkDeviceDiscoveryReplyImpl::hasHostAddress(const QHostAddress &address) +{ + return ! macAddressFromHostAddress(address).isEmpty(); +} + +void NetworkDeviceDiscoveryReplyImpl::verifyComplete(const MacAddress &macAddress) +{ + if (!m_networkDeviceCache.contains(macAddress)) + return; + + if (m_networkDeviceCache[macAddress].isComplete() && m_networkDeviceCache[macAddress].isValid()) { + if (m_networkDeviceInfos.hasMacAddress(macAddress)) { + if (m_networkDeviceInfos.get(macAddress) != m_networkDeviceCache.value(macAddress)) { + qCWarning(dcNetworkDeviceDiscovery()) << "Already complete network device info changed during discovery process! Please report a bug if you see this message."; + qCWarning(dcNetworkDeviceDiscovery()) << m_networkDeviceInfos.get(macAddress); + qCWarning(dcNetworkDeviceDiscovery()) << m_networkDeviceCache.value(macAddress); + } + } else { + m_networkDeviceInfos.append(m_networkDeviceCache.value(macAddress)); + emit networkDeviceInfoAdded(m_networkDeviceCache[macAddress]); + } + } +} + +void NetworkDeviceDiscoveryReplyImpl::processPingResponse(const QHostAddress &address, const QString &hostName) +{ + foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) { + if (info.address() == address) { + // Already found info, set host name and check if complete + MacAddress macAddress(info.macAddress()); + m_networkDeviceCache[macAddress].setHostName(hostName); + verifyComplete(macAddress); + return; + } + } + + // Unknown and we have no mac address yet, add it to the ping cache + NetworkDeviceInfo info; + info.setAddress(address); + info.setHostName(hostName); + m_pingCache.insert(address, info); + emit hostAddressDiscovered(address); +} + +void NetworkDeviceDiscoveryReplyImpl::processArpResponse(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) +{ + if (m_pingCache.contains(address)) { + // We know this device from a ping response + NetworkDeviceInfo info = m_pingCache.take(address); + info.setAddress(address); + info.setNetworkInterface(interface); + info.setMacAddress(macAddress.toString()); + m_networkDeviceCache[macAddress] = info; + emit hostAddressDiscovered(address); + } else { + if (m_networkDeviceCache.contains(macAddress)) { + m_networkDeviceCache[macAddress].setAddress(address); + m_networkDeviceCache[macAddress].setNetworkInterface(interface); + } else { + NetworkDeviceInfo info(macAddress.toString()); + info.setAddress(address); + info.setNetworkInterface(interface); + m_networkDeviceCache[macAddress] = info; + } + } + + verifyComplete(macAddress); +} + +void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &macAddress, const QString &manufacturer) +{ + if (macAddress.isNull()) + return; + + if (m_networkDeviceCache.contains(macAddress)) { + m_networkDeviceCache[macAddress].setMacAddressManufacturer(manufacturer); + } else { + NetworkDeviceInfo info(macAddress.toString()); + info.setMacAddressManufacturer(manufacturer); + m_networkDeviceCache[macAddress] = info; + } + + verifyComplete(macAddress); +} + +void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished() +{ + // Lets see if we have any incomplete infos but enougth data to be shown + foreach (const MacAddress &macAddress, m_networkDeviceCache.keys()) { + // If already in the result, ignore it + if (m_networkDeviceInfos.hasMacAddress(macAddress)) + continue; + + NetworkDeviceInfo info = m_networkDeviceCache.value(macAddress); + MacAddress infoMacAddress(info.macAddress()); + qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info + << "Valid:" << info.isValid() + << "Complete:" << info.isComplete() + << info.incompleteProperties(); + + // We need at least a valid mac address and a valid ip address, the rest ist pure informative + if (infoMacAddress == macAddress && !infoMacAddress.isNull() && !info.address().isNull()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Adding incomplete" << info << "to the final result:" << info.incompleteProperties(); + // Note: makeing it complete + m_networkDeviceCache[macAddress].setAddress(info.address()); + m_networkDeviceCache[macAddress].setHostName(info.hostName()); + m_networkDeviceCache[macAddress].setMacAddress(info.macAddress()); + m_networkDeviceCache[macAddress].setMacAddressManufacturer(info.macAddressManufacturer()); + m_networkDeviceCache[macAddress].setNetworkInterface(info.networkInterface()); + verifyComplete(macAddress); + } + } + + // Done, lets sort the result and inform + m_networkDeviceInfos.sortNetworkDevices(); + + qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startTimestamp; + qCInfo(dcNetworkDeviceDiscovery()) << "Discovery finished. Found" << networkDeviceInfos().count() << "network devices in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); + + // Process what's left and add it to result list + foreach (const NetworkDeviceInfo &info, m_networkDeviceInfos) { + qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info; + } + + // Create valid infos from the ping cache and offer them in the virtual infos + foreach (const NetworkDeviceInfo &info, m_pingCache) { + NetworkDeviceInfo finalInfo = info; + finalInfo.setAddress(finalInfo.address()); + finalInfo.setHostName(finalInfo.hostName()); + finalInfo.setMacAddress(finalInfo.macAddress()); + finalInfo.setNetworkInterface(finalInfo.networkInterface()); + finalInfo.setMacAddressManufacturer(finalInfo.macAddressManufacturer()); + m_virtualNetworkDeviceInfos.append(info); + } + + m_virtualNetworkDeviceInfos.sortNetworkDevices(); + + qCDebug(dcNetworkDeviceDiscovery()) << "Virtual hosts (" << m_virtualNetworkDeviceInfos.count() << ")"; + foreach (const NetworkDeviceInfo &info, m_virtualNetworkDeviceInfos) { + qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info; + } + + qCDebug(dcNetworkDeviceDiscovery()) << "Rest:"; + foreach (const MacAddress &macAddress, m_networkDeviceCache.keys()) { + if (m_networkDeviceInfos.hasMacAddress(macAddress)) + continue; + + NetworkDeviceInfo info = m_networkDeviceCache.value(macAddress); + qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info << "Valid:" << info.isValid() << "Complete:" << info.isComplete() << info.incompleteProperties(); + } + + emit finished(); +} + +} diff --git a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h new file mode 100644 index 00000000..5c06ef3f --- /dev/null +++ b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h @@ -0,0 +1,80 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 NETWORKDEVICEDISCOVERYREPLYIMPL_H +#define NETWORKDEVICEDISCOVERYREPLYIMPL_H + +#include +#include + +#include "network/networkdeviceinfo.h" +#include "network/networkdevicediscoveryreply.h" + +namespace nymeaserver { + +class NetworkDeviceDiscoveryReplyImpl : public NetworkDeviceDiscoveryReply +{ + Q_OBJECT + + friend class NetworkDeviceDiscoveryImpl; + +public: + explicit NetworkDeviceDiscoveryReplyImpl(QObject *parent = nullptr); + ~NetworkDeviceDiscoveryReplyImpl() override = default; + + NetworkDeviceInfos networkDeviceInfos() const override; + NetworkDeviceInfos virtualNetworkDeviceInfos() const override; + +private: + NetworkDeviceInfos m_networkDeviceInfos; // Contains only complete and valid infos + NetworkDeviceInfos m_virtualNetworkDeviceInfos; // Contains ping responses without ARP, like VPN devices + + QHash m_networkDeviceCache; + qint64 m_startTimestamp; + + // Temporary cache for ping responses where the mac is not known yet (like VPN devices) + QHash m_pingCache; + + QString macAddressFromHostAddress(const QHostAddress &address); + bool hasHostAddress(const QHostAddress &address); + + void verifyComplete(const MacAddress &macAddress); + + // Add or update the network device info and verify if completed + void processPingResponse(const QHostAddress &address, const QString &hostName); + void processArpResponse(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); + void processMacManufacturer(const MacAddress &macAddress, const QString &manufacturer); + + void processDiscoveryFinished(); +}; + +} + +#endif // NETWORKDEVICEDISCOVERYREPLYIMPL_H diff --git a/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp b/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp new file mode 100644 index 00000000..a00f797f --- /dev/null +++ b/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp @@ -0,0 +1,105 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 "networkdevicemonitorimpl.h" + +namespace nymeaserver { + +NetworkDeviceMonitorImpl::NetworkDeviceMonitorImpl(const MacAddress &macAddress, QObject *parent) : + NetworkDeviceMonitor(parent), + m_macAddress(macAddress) +{ + +} + +NetworkDeviceMonitorImpl::~NetworkDeviceMonitorImpl() +{ + +} + +MacAddress NetworkDeviceMonitorImpl::macAddress() const +{ + return m_macAddress; +} + +NetworkDeviceInfo NetworkDeviceMonitorImpl::networkDeviceInfo() const +{ + return m_networkDeviceInfo; +} + +void NetworkDeviceMonitorImpl::setNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo) +{ + if (m_networkDeviceInfo == networkDeviceInfo) + return; + + m_networkDeviceInfo = networkDeviceInfo; + emit networkDeviceInfoChanged(m_networkDeviceInfo); +} + +bool NetworkDeviceMonitorImpl::reachable() const +{ + return m_reachable; +} + +void NetworkDeviceMonitorImpl::setReachable(bool reachable) +{ + if (m_reachable == reachable) + return; + + m_reachable = reachable; + emit reachableChanged(m_reachable); +} + +QDateTime NetworkDeviceMonitorImpl::lastSeen() const +{ + return m_lastSeen; +} + +void NetworkDeviceMonitorImpl::setLastSeen(const QDateTime &lastSeen) +{ + if (m_lastSeen == lastSeen) + return; + + m_lastSeen = lastSeen; + + emit lastSeenChanged(m_lastSeen); +} + +QDateTime NetworkDeviceMonitorImpl::lastConnectionAttempt() const +{ + return m_lastConnectionAttempt; +} + +void NetworkDeviceMonitorImpl::setLastConnectionAttempt(const QDateTime &lastConnectionAttempt) +{ + m_lastConnectionAttempt = lastConnectionAttempt; +} + +} diff --git a/libnymea-core/hardware/network/networkdevicemonitorimpl.h b/libnymea-core/hardware/network/networkdevicemonitorimpl.h new file mode 100644 index 00000000..57720ebe --- /dev/null +++ b/libnymea-core/hardware/network/networkdevicemonitorimpl.h @@ -0,0 +1,74 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 NETWORKDEVICEMONITORIMPL_H +#define NETWORKDEVICEMONITORIMPL_H + +#include +#include + +#include "network/networkdevicemonitor.h" + +namespace nymeaserver { + +class NetworkDeviceMonitorImpl : public NetworkDeviceMonitor +{ + Q_OBJECT + +public: + explicit NetworkDeviceMonitorImpl(const MacAddress &macAddress, QObject *parent = nullptr); + ~NetworkDeviceMonitorImpl(); + + MacAddress macAddress() const override; + + NetworkDeviceInfo networkDeviceInfo() const override; + void setNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo); + + bool reachable() const override; + void setReachable(bool reachable); + + QDateTime lastSeen() const override; + void setLastSeen(const QDateTime &lastSeen); + + QDateTime lastConnectionAttempt() const; + void setLastConnectionAttempt(const QDateTime &lastConnectionAttempt); + +private: + NetworkDeviceInfo m_networkDeviceInfo; + MacAddress m_macAddress; + bool m_reachable = false; + QDateTime m_lastSeen; + QDateTime m_lastConnectionAttempt; + +}; + +} + +#endif // NETWORKDEVICEMONITORIMPL_H diff --git a/libnymea-core/hardwaremanagerimplementation.cpp b/libnymea-core/hardwaremanagerimplementation.cpp index 87d52b68..2a403cea 100644 --- a/libnymea-core/hardwaremanagerimplementation.cpp +++ b/libnymea-core/hardwaremanagerimplementation.cpp @@ -46,7 +46,7 @@ #include "hardware/modbus/modbusrtumanager.h" #include "hardware/modbus/modbusrtuhardwareresourceimplementation.h" -#include "network/networkdevicediscovery.h" +#include "hardware/network/networkdevicediscoveryimpl.h" namespace nymeaserver { @@ -79,7 +79,7 @@ HardwareManagerImplementation::HardwareManagerImplementation(Platform *platform, m_modbusRtuResource = new ModbusRtuHardwareResourceImplementation(modbusRtuManager, this); - m_networkDeviceDiscovery = new NetworkDeviceDiscovery(this); + m_networkDeviceDiscovery = new NetworkDeviceDiscoveryImpl(this); // Enable all the resources setResourceEnabled(m_pluginTimerManager, true); diff --git a/libnymea-core/hardwaremanagerimplementation.h b/libnymea-core/hardwaremanagerimplementation.h index c94153ef..89143a31 100644 --- a/libnymea-core/hardwaremanagerimplementation.h +++ b/libnymea-core/hardwaremanagerimplementation.h @@ -45,6 +45,7 @@ class ZigbeeManager; class ZigbeeHardwareResourceImplementation; class ModbusRtuManager; class ModbusRtuHardwareResourceImplementation; +class NetworkDeviceDiscoveryImpl; class HardwareManagerImplementation : public HardwareManager { @@ -84,7 +85,7 @@ private: I2CManager *m_i2cManager = nullptr; ZigbeeHardwareResourceImplementation *m_zigbeeResource = nullptr; ModbusRtuHardwareResourceImplementation *m_modbusRtuResource = nullptr; - NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; + NetworkDeviceDiscoveryImpl *m_networkDeviceDiscovery = nullptr; }; diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index 7fa7607c..2a0e5c4e 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -61,6 +61,7 @@ RESOURCES += $$top_srcdir/icons.qrc \ HEADERS += nymeacore.h \ + hardware/network/macaddressdatabasereplyimpl.h \ hardware/serialport/serialportmonitor.h \ integrations/apikeysprovidersloader.h \ integrations/plugininfocache.h \ @@ -141,7 +142,11 @@ HEADERS += nymeacore.h \ hardware/modbus/modbusrtumanager.h \ hardware/modbus/modbusrtumasterimpl.h \ hardware/modbus/modbusrtureplyimpl.h \ + hardware/network/macaddressdatabase.h \ hardware/network/networkaccessmanagerimpl.h \ + hardware/network/networkdevicediscoveryimpl.h \ + hardware/network/networkdevicediscoveryreplyimpl.h \ + hardware/network/networkdevicemonitorimpl.h \ hardware/network/upnp/upnpdiscoveryimplementation.h \ hardware/network/upnp/upnpdiscoveryrequest.h \ hardware/network/upnp/upnpdiscoveryreplyimplementation.h \ @@ -161,6 +166,7 @@ HEADERS += nymeacore.h \ SOURCES += nymeacore.cpp \ + hardware/network/macaddressdatabasereplyimpl.cpp \ hardware/serialport/serialportmonitor.cpp \ integrations/apikeysprovidersloader.cpp \ integrations/plugininfocache.cpp \ @@ -233,7 +239,11 @@ SOURCES += nymeacore.cpp \ hardware/modbus/modbusrtumanager.cpp \ hardware/modbus/modbusrtumasterimpl.cpp \ hardware/modbus/modbusrtureplyimpl.cpp \ + hardware/network/macaddressdatabase.cpp \ hardware/network/networkaccessmanagerimpl.cpp \ + hardware/network/networkdevicediscoveryimpl.cpp \ + hardware/network/networkdevicediscoveryreplyimpl.cpp \ + hardware/network/networkdevicemonitorimpl.cpp \ hardware/network/upnp/upnpdiscoveryimplementation.cpp \ hardware/network/upnp/upnpdiscoveryrequest.cpp \ hardware/network/upnp/upnpdiscoveryreplyimplementation.cpp \ diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index b1ddf358..5be73135 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -44,11 +44,13 @@ HEADERS += \ network/apikeys/apikeysprovider.h \ network/apikeys/apikeystorage.h \ network/arpsocket.h \ - network/macaddressdatabase.h \ + network/macaddress.h \ + network/macaddressdatabasereply.h \ network/networkdevicediscovery.h \ network/networkdevicediscoveryreply.h \ network/networkdeviceinfo.h \ network/networkdeviceinfos.h \ + network/networkdevicemonitor.h \ network/networkutils.h \ network/ping.h \ network/pingreply.h \ @@ -149,11 +151,12 @@ SOURCES += \ network/apikeys/apikeysprovider.cpp \ network/apikeys/apikeystorage.cpp \ network/arpsocket.cpp \ - network/macaddressdatabase.cpp \ + network/macaddress.cpp \ + network/macaddressdatabasereply.cpp \ network/networkdevicediscovery.cpp \ - network/networkdevicediscoveryreply.cpp \ network/networkdeviceinfo.cpp \ network/networkdeviceinfos.cpp \ + network/networkdevicemonitor.cpp \ network/networkutils.cpp \ network/ping.cpp \ network/pingreply.cpp \ diff --git a/libnymea/network/arpsocket.cpp b/libnymea/network/arpsocket.cpp index a05e84af..3c7401a5 100644 --- a/libnymea/network/arpsocket.cpp +++ b/libnymea/network/arpsocket.cpp @@ -85,16 +85,14 @@ bool ArpSocket::sendRequest(const QString &interfaceName) if (!m_isOpen) return false; - // Get the interface - qCDebug(dcArpSocket()) << "Sending ARP request to all network interfaces" << interfaceName << "..."; + qCDebug(dcArpSocket()) << "Sending ARP request to network interface" << 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); } @@ -107,7 +105,7 @@ bool ArpSocket::sendRequest(const QNetworkInterface &networkInterface) if (networkInterface.flags().testFlag(QNetworkInterface::IsLoopBack)) return false; - // If have no interface indes, we cannot use this network + // If the interface index is unknown, 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; @@ -130,8 +128,6 @@ bool ArpSocket::sendRequest(const QNetworkInterface &networkInterface) return false; } - loadArpCache(networkInterface); - qCDebug(dcArpSocket()) << "Verifying network interface" << networkInterface.name() << networkInterface.hardwareAddress() << "..."; foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { // Only IPv4 @@ -159,7 +155,7 @@ bool ArpSocket::sendRequest(const QNetworkInterface &networkInterface) if (targetAddress == entry.ip()) continue; - sendRequestInternally(networkInterface.index(), networkInterface.hardwareAddress(), entry.ip(), "ff:ff:ff:ff:ff:ff", targetAddress); + sendRequestInternally(networkInterface.index(), MacAddress(networkInterface.hardwareAddress()), entry.ip(), MacAddress::broadcast(), targetAddress); } } @@ -183,8 +179,11 @@ bool ArpSocket::sendRequest(const QHostAddress &targetAddress) 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); + qCDebug(dcArpSocket()) << "Check subnet for" << networkInterface.name() << entry.ip() << entry.netmask(); + if (targetAddress.isInSubnet(entry.ip(), entry.prefixLength())) { + return sendRequestInternally(networkInterface.index(), MacAddress(networkInterface.hardwareAddress()), entry.ip(), MacAddress::broadcast(), targetAddress); + } else { + qCDebug(dcArpSocket()) << targetAddress << "is not part of subnet" << entry.ip() << "netmask" << entry.netmask() << "netmask int" << entry.netmask().toIPv4Address(); } } } @@ -228,66 +227,17 @@ bool ArpSocket::openSocket() return; // Make sure to read all data from the socket... - while (true) { - char receiveBuffer[ETHER_ARP_PACKET_LEN]; + int bytesReceived = 0; + while (bytesReceived >= 0) { + unsigned 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; - } + bytesReceived = recv(m_socketDescriptor, receiveBuffer, ETHER_ARP_PACKET_LEN, 0); + if (bytesReceived < 0) + continue; - // 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; - } + processDataBuffer(receiveBuffer, bytesReceived); } }); @@ -318,7 +268,7 @@ void ArpSocket::closeSocket() qCDebug(dcArpSocket()) << "ARP disabled successfully"; } -bool ArpSocket::sendRequestInternally(int networkInterfaceIndex, const QString &senderMacAddress, const QHostAddress &senderHostAddress, const QString &targetMacAddress, const QHostAddress &targetHostAddress) +bool ArpSocket::sendRequestInternally(int networkInterfaceIndex, const MacAddress &senderMacAddress, const QHostAddress &senderHostAddress, const MacAddress &targetMacAddress, const QHostAddress &targetHostAddress) { // Set up data structures unsigned char sendingBuffer[ETHER_ARP_PACKET_LEN]; @@ -364,14 +314,80 @@ bool ArpSocket::sendRequestInternally(int networkInterfaceIndex, const QString & return true; } -QString ArpSocket::getMacAddressString(uint8_t *senderHardwareAddress) +void ArpSocket::processDataBuffer(unsigned char *receiveBuffer, int size) { - QStringList hexValues; - for (int i = 0; i < ETHER_ADDR_LEN; i++) { - hexValues.append(QString("%1").arg(senderHardwareAddress[i], 2, 16, QLatin1Char('0'))); + // Parse data using structs header + arp + QByteArray receivedBufferBytes; + for (int i = 0; i < size; i++) { + receivedBufferBytes.append(receiveBuffer[i]); } - return hexValues.join(":"); + struct ether_header *etherHeader = (struct ether_header *)(receiveBuffer); + struct ether_arp *arpPacket = (struct ether_arp *)(receiveBuffer + ETHER_HEADER_LEN); + MacAddress senderMacAddress = MacAddress(arpPacket->arp_sha); + QHostAddress senderHostAddress = getHostAddressString(arpPacket->arp_spa); + MacAddress targetMacAddress = MacAddress(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: { + // The sender of the arp request provides ip and mac. + // Lets find the corresponding interface and use it for the discovery and monitor + if (senderHostAddress.isNull()) + return; + + QNetworkInterface networkInterface = NetworkUtils::getInterfaceForHostaddress(senderHostAddress); + if (!networkInterface.isValid()) { + qCDebug(dcArpSocket()) << "Could not find local interface from ARP request" << senderHostAddress.toString() << senderMacAddress.toString(); + return; + } + + // Note: we are not interested in our own requests + if (senderMacAddress != MacAddress(networkInterface.hardwareAddress())) { + qCDebug(dcArpSocket()) << "ARP request" << receivedBufferBytes.toHex() << "from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString() << "on" << networkInterface.name(); + emit arpRequestReceived(networkInterface, senderHostAddress, senderMacAddress); + } + + break; + } + case ARPOP_REPLY: { + QNetworkInterface networkInterface = NetworkUtils::getInterfaceForMacAddress(targetMacAddress); + if (!networkInterface.isValid()) { + qCDebug(dcArpSocket()) << "Could not find local interface from ARP response" << targetHostAddress.toString() << targetMacAddress.toString(); + return; + } + + qCDebug(dcArpSocket()) << "ARP reply" << receivedBufferBytes.toHex() << "from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString() << "on" << networkInterface.name(); + emit arpResponse(networkInterface, senderHostAddress, senderMacAddress); + break; + } + case ARPOP_RREQUEST: + qCDebug(dcArpSocketTraffic()) << "RARP request from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString(); + break; + case ARPOP_RREPLY: + qCDebug(dcArpSocketTraffic()) << "PARP response from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString(); + break; + case ARPOP_InREQUEST: + qCDebug(dcArpSocketTraffic()) << "InARP request from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString(); + break; + case ARPOP_InREPLY: + qCDebug(dcArpSocketTraffic()) << "InARP response from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString(); + break; + case ARPOP_NAK: + qCDebug(dcArpSocketTraffic()) << "(ATM)ARP NAK from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString(); + break; + default: + qCWarning(dcArpSocketTraffic()) << "Received unhandled ARP operation code" << arpOperationCode << "from" << senderMacAddress.toString() << senderHostAddress.toString(); + break; + } } QHostAddress ArpSocket::getHostAddressString(uint8_t *senderIpAddress) @@ -403,52 +419,54 @@ bool ArpSocket::loadArpCache(const QNetworkInterface &interface) 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; - + 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; + qCWarning(dcArpSocket()) << "ARP cache line has invalid column count" << line; continue; } QHostAddress address(columns.at(0).trimmed()); if (address.isNull()) { - qCWarning(dcArpSocket()) << "Line has invalid address"; + qCWarning(dcArpSocket()) << "ARP cache line has invalid IP address"; continue; } - QString macAddress = columns.at(3).trimmed(); - if (macAddress.count() != 17) { - qCWarning(dcArpSocket()) << "Line has invalid mac address" << columns << macAddress; + QString macAddressString = columns.at(3).trimmed(); + MacAddress macAddress(macAddressString); + if (macAddress.isNull()) { + qCDebug(dcArpSocket()) << "ARP cache line has invalid MAC address" << macAddressString; continue; } QNetworkInterface addressInterface = QNetworkInterface::interfaceFromName(columns.at(5)); - if (!addressInterface.isValid()) + if (!addressInterface.isValid()) { + qCDebug(dcArpSocket()) << "ARP cache line has invalid network interface identifier" << columns << columns.at(5); 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(); + qCDebug(dcArpSocket()) << "Loaded from cache" << address.toString() << macAddress.toString() << addressInterface.name(); emit arpResponse(addressInterface, address, macAddress); } return true; } -void ArpSocket::fillMacAddress(uint8_t *targetArray, const QString &macAddress) +void ArpSocket::fillMacAddress(uint8_t *targetArray, const MacAddress &macAddress) { - QStringList macValues = macAddress.split(":"); + QStringList macValues = macAddress.toString().split(":"); for (int i = 0; i < ETHER_ADDR_LEN; i++) { targetArray[i] = macValues.at(i).toUInt(nullptr, 16); } diff --git a/libnymea/network/arpsocket.h b/libnymea/network/arpsocket.h index 2bc58175..3cfeb1b1 100644 --- a/libnymea/network/arpsocket.h +++ b/libnymea/network/arpsocket.h @@ -39,6 +39,7 @@ #include #include "libnymea.h" +#include "macaddress.h" class LIBNYMEA_EXPORT ArpSocket : public QObject { @@ -64,21 +65,23 @@ public: void closeSocket(); signals: - void arpResponse(const QNetworkInterface &networkInterface, const QHostAddress &address, const QString &macAddress); + void arpResponse(const QNetworkInterface &networkInterface, const QHostAddress &address, const MacAddress &macAddress); + void arpRequestReceived(const QNetworkInterface &networkInterface, const QHostAddress &address, const MacAddress &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); + bool sendRequestInternally(int networkInterfaceIndex, const MacAddress &senderMacAddress, const QHostAddress &senderHostAddress, const MacAddress &targetMacAddress, const QHostAddress &targetHostAddress); + + void processDataBuffer(unsigned char *receiveBuffer, int size); - 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 fillMacAddress(uint8_t *targetArray, const MacAddress &macAddress); void fillHostAddress(uint8_t *targetArray, const QHostAddress &hostAddress); }; diff --git a/libnymea/network/macaddress.cpp b/libnymea/network/macaddress.cpp new file mode 100644 index 00000000..c0d5dcea --- /dev/null +++ b/libnymea/network/macaddress.cpp @@ -0,0 +1,162 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 "macaddress.h" + +#include + +#include +#include + +MacAddress::MacAddress() +{ + clear(); +} + +MacAddress::MacAddress(const QString &macAddress) +{ + QString addressString; + + // Filter out any non hex characters from the string (like any separators) + QRegularExpression hexMatcher("^[0-9A-F]", QRegularExpression::CaseInsensitiveOption); + for (int i = 0; i < macAddress.count(); i++) { + // Remove all possible separators + QRegularExpressionMatch match = hexMatcher.match(macAddress.at(i)); + if (match.hasMatch()) { + addressString.append(macAddress.at(i)); + } + } + + // The remaining hex value has to be 12 characters (6 hex values) + if (addressString.size() == 12) { + m_rawData = QByteArray::fromHex(addressString.toUtf8()); + } else { + // Make invalid and null + m_rawData.clear(); + } +} + +MacAddress::MacAddress(const QByteArray &macAddress) +{ + m_rawData = macAddress; +} + +MacAddress::MacAddress(unsigned char *rawData) +{ + clear(); + for (int i = 0; i < ETH_ALEN; i++) { + m_rawData[i] = rawData[i]; + } +} + +MacAddress::MacAddress(const MacAddress &other) +{ + m_rawData = other.toByteArray(); +} + +MacAddress MacAddress::broadcast() +{ + return MacAddress(QByteArray::fromHex("ffffffffffff")); +} + +MacAddress MacAddress::fromString(const QString &macAddress) +{ + return MacAddress(macAddress); +} + +QString MacAddress::toString() const +{ + QString macString(QStringLiteral("%1:%2:%3:%4:%5:%6")); + QByteArray data = m_rawData; + if (!isValid()) { + data = QByteArray(ETH_ALEN, '\0'); + } + + for (int i = 0; i < data.size(); i++) { + macString = macString.arg(static_cast(data.at(i)), 2, 16, QLatin1Char('0')); + } + + return macString.toLower(); +} + +QByteArray MacAddress::toByteArray() const +{ + return m_rawData; +} + +bool MacAddress::isNull() const +{ + if (!isValid()) + return true; + + return m_rawData == QByteArray(ETH_ALEN, '\0'); +} + +bool MacAddress::isValid() const +{ + return m_rawData.size() == ETH_ALEN; +} + +void MacAddress::clear() +{ + m_rawData.fill('\0', ETH_ALEN); +} + +MacAddress &MacAddress::operator=(const MacAddress &other) +{ + m_rawData = other.toByteArray(); + return *this; +} + +bool MacAddress::operator<(const MacAddress &other) const +{ + return m_rawData < other.toByteArray(); +} + +bool MacAddress::operator>(const MacAddress &other) const +{ + return m_rawData > other.toByteArray(); +} + +bool MacAddress::operator==(const MacAddress &other) const +{ + return m_rawData == other.toByteArray(); +} + +bool MacAddress::operator!=(const MacAddress &other) const +{ + return m_rawData != other.toByteArray(); +} + +QDebug operator<<(QDebug debug, const MacAddress &macAddress) +{ + debug.nospace() << "MacAddress(" << macAddress.toString() << ")"; + return debug.space(); +} diff --git a/libnymea/network/macaddress.h b/libnymea/network/macaddress.h new file mode 100644 index 00000000..f77f97c0 --- /dev/null +++ b/libnymea/network/macaddress.h @@ -0,0 +1,86 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 MACADDRESS_H +#define MACADDRESS_H + +#include +#include +#include + +#include "libnymea.h" + +class LIBNYMEA_EXPORT MacAddress +{ +public: + explicit MacAddress(); + explicit MacAddress(const QString &macAddress); + explicit MacAddress(const QByteArray &macAddress); + explicit MacAddress(unsigned char *rawData); + MacAddress(const MacAddress &other); + + static MacAddress broadcast(); + static MacAddress fromString(const QString &macAddress); + + QString toString() const; + + QByteArray toByteArray() const; + + bool isNull() const; + bool isValid() const; + + void clear(); + + MacAddress &operator=(const MacAddress &other); + + bool operator<(const MacAddress &other) const; + bool operator>(const MacAddress &other) const; + bool operator==(const MacAddress &other) const; + bool operator!=(const MacAddress &other) const; + +private: + QByteArray m_rawData = 0; + +}; + + +#if QT_VERSION < 0x0600000 +using qhash_result_t = uint; +#else +using qhash_result_t = size_t; +#endif +inline qhash_result_t qHash(const MacAddress &macAddress, qhash_result_t seed) +{ + return qHash(macAddress.toByteArray(), seed); +} + +QDebug operator<<(QDebug debug, const MacAddress &address); + +#endif // MACADDRESS_H diff --git a/libnymea/network/networkdevicediscoveryreply.cpp b/libnymea/network/macaddressdatabasereply.cpp similarity index 83% rename from libnymea/network/networkdevicediscoveryreply.cpp rename to libnymea/network/macaddressdatabasereply.cpp index 958f1788..2efbd1c2 100644 --- a/libnymea/network/networkdevicediscoveryreply.cpp +++ b/libnymea/network/macaddressdatabasereply.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -28,15 +28,9 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include "networkdevicediscoveryreply.h" +#include "macaddressdatabasereply.h" -NetworkDeviceDiscoveryReply::NetworkDeviceDiscoveryReply(QObject *parent) : - QObject(parent) +MacAddressDatabaseReply::MacAddressDatabaseReply(QObject *parent) : QObject(parent) { } - -NetworkDeviceInfos &NetworkDeviceDiscoveryReply::networkDeviceInfos() -{ - return m_networkDeviceInfos; -} diff --git a/libnymea/network/macaddressdatabasereply.h b/libnymea/network/macaddressdatabasereply.h new file mode 100644 index 00000000..be08b3b7 --- /dev/null +++ b/libnymea/network/macaddressdatabasereply.h @@ -0,0 +1,54 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 MACADDRESSDATABASEREPLY_H +#define MACADDRESSDATABASEREPLY_H + +#include + +#include "libnymea.h" + +class LIBNYMEA_EXPORT MacAddressDatabaseReply : public QObject +{ + Q_OBJECT + +public: + explicit MacAddressDatabaseReply(QObject *parent = nullptr); + virtual ~MacAddressDatabaseReply() = default; + + virtual QString macAddress() const = 0; + virtual QString manufacturer() const = 0; + +signals: + void finished(); + +}; + +#endif // MACADDRESSDATABASEREPLY_H diff --git a/libnymea/network/networkaccessmanager.h b/libnymea/network/networkaccessmanager.h index e1d6477b..cfaeb1bd 100644 --- a/libnymea/network/networkaccessmanager.h +++ b/libnymea/network/networkaccessmanager.h @@ -42,7 +42,7 @@ #include #include -class LIBNYMEA_EXPORT NetworkAccessManager : public HardwareResource +class LIBNYMEA_EXPORT NetworkAccessManager : public HardwareResource { Q_OBJECT diff --git a/libnymea/network/networkdevicediscovery.cpp b/libnymea/network/networkdevicediscovery.cpp index 7d246270..526f10e5 100644 --- a/libnymea/network/networkdevicediscovery.cpp +++ b/libnymea/network/networkdevicediscovery.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -29,242 +29,10 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "networkdevicediscovery.h" -#include "loggingcategories.h" -#include "networkutils.h" -#include "macaddressdatabase.h" -#include "arpsocket.h" - -#include - -NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery") NetworkDeviceDiscovery::NetworkDeviceDiscovery(QObject *parent) : - QObject(parent) + HardwareResource("Network device discovery", 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) -{ - if (!m_currentReply) - return; - - 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, m_currentReply, [=](){ - qCDebug(dcNetworkDeviceDiscovery()) << "MAC manufacturer lookup finished for" << macAddress << ":" << reply->manufacturer(); - updateOrAddNetworkDeviceArp(interface, address, macAddress, reply->manufacturer()); - }); - } else { - updateOrAddNetworkDeviceArp(interface, address, macAddress); - } -} diff --git a/libnymea/network/networkdevicediscovery.h b/libnymea/network/networkdevicediscovery.h index 712ead8c..c09cae55 100644 --- a/libnymea/network/networkdevicediscovery.h +++ b/libnymea/network/networkdevicediscovery.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -35,50 +35,41 @@ #include #include -#include "ping.h" #include "libnymea.h" +#include "hardwareresource.h" + +#include "networkdevicemonitor.h" + +#include "pingreply.h" +#include "macaddressdatabasereply.h" #include "networkdevicediscoveryreply.h" -class ArpSocket; -class MacAddressDatabase; -class MacAddressDatabaseReply; - -Q_DECLARE_LOGGING_CATEGORY(dcNetworkDeviceDiscovery) - -class LIBNYMEA_EXPORT NetworkDeviceDiscovery : public QObject +class LIBNYMEA_EXPORT NetworkDeviceDiscovery : public HardwareResource { Q_OBJECT public: explicit NetworkDeviceDiscovery(QObject *parent = nullptr); + virtual ~NetworkDeviceDiscovery() = default; - NetworkDeviceDiscoveryReply *discover(); + virtual NetworkDeviceDiscoveryReply *discover() = 0; - bool available() const; - bool running() const; + virtual bool running() const = 0; - PingReply *ping(const QHostAddress &address); - MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress); + virtual NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) = 0; + + virtual void unregisterMonitor(const MacAddress &macAddress) = 0; + virtual void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) = 0; + + virtual PingReply *ping(const QHostAddress &address) = 0; + + virtual MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) = 0; + virtual MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) = 0; + + virtual bool sendArpRequest(const QHostAddress &address) = 0; 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 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); + void networkDeviceInfoCacheUpdated(); }; diff --git a/libnymea/network/networkdevicediscoveryreply.h b/libnymea/network/networkdevicediscoveryreply.h index 439e949f..9417fdf5 100644 --- a/libnymea/network/networkdevicediscoveryreply.h +++ b/libnymea/network/networkdevicediscoveryreply.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -39,19 +39,21 @@ class LIBNYMEA_EXPORT NetworkDeviceDiscoveryReply : public QObject { Q_OBJECT - - friend class NetworkDeviceDiscovery; - public: - NetworkDeviceInfos &networkDeviceInfos(); + explicit NetworkDeviceDiscoveryReply(QObject *parent = nullptr) : QObject(parent) { }; + virtual ~NetworkDeviceDiscoveryReply() = default; + + virtual NetworkDeviceInfos networkDeviceInfos() const = 0; + virtual NetworkDeviceInfos virtualNetworkDeviceInfos() const = 0; signals: - void finished(); + // Emitted whenever a certain host address has been pinged successfully + void hostAddressDiscovered(const QHostAddress &address); -private: - explicit NetworkDeviceDiscoveryReply(QObject *parent = nullptr); - NetworkDeviceInfos m_networkDeviceInfos; - qint64 m_startTimestamp; + // Emited whenerver a valid NetworkDeviceInfo has been added + void networkDeviceInfoAdded(const NetworkDeviceInfo &networkDeviceInfo); + + void finished(); }; diff --git a/libnymea/network/networkdeviceinfo.cpp b/libnymea/network/networkdeviceinfo.cpp index 64ba1e59..0e57e54c 100644 --- a/libnymea/network/networkdeviceinfo.cpp +++ b/libnymea/network/networkdeviceinfo.cpp @@ -1,6 +1,6 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -29,6 +29,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "networkdeviceinfo.h" +#include "macaddress.h" NetworkDeviceInfo::NetworkDeviceInfo() { @@ -38,7 +39,13 @@ NetworkDeviceInfo::NetworkDeviceInfo() NetworkDeviceInfo::NetworkDeviceInfo(const QString &macAddress): m_macAddress(macAddress) { + m_macAddressSet = true; +} +NetworkDeviceInfo::NetworkDeviceInfo(const QHostAddress &address): + m_address(address) +{ + m_addressSet = true; } QString NetworkDeviceInfo::macAddress() const @@ -49,6 +56,7 @@ QString NetworkDeviceInfo::macAddress() const void NetworkDeviceInfo::setMacAddress(const QString &macAddress) { m_macAddress = macAddress; + m_macAddressSet = true; } QString NetworkDeviceInfo::macAddressManufacturer() const @@ -59,6 +67,7 @@ QString NetworkDeviceInfo::macAddressManufacturer() const void NetworkDeviceInfo::setMacAddressManufacturer(const QString &macAddressManufacturer) { m_macAddressManufacturer = macAddressManufacturer; + m_macAddressManufacturerSet = true; } QHostAddress NetworkDeviceInfo::address() const @@ -69,6 +78,7 @@ QHostAddress NetworkDeviceInfo::address() const void NetworkDeviceInfo::setAddress(const QHostAddress &address) { m_address = address; + m_addressSet = true; } QString NetworkDeviceInfo::hostName() const @@ -79,6 +89,7 @@ QString NetworkDeviceInfo::hostName() const void NetworkDeviceInfo::setHostName(const QString &hostName) { m_hostName = hostName; + m_hostNameSet = true; } QNetworkInterface NetworkDeviceInfo::networkInterface() const @@ -89,27 +100,60 @@ QNetworkInterface NetworkDeviceInfo::networkInterface() const void NetworkDeviceInfo::setNetworkInterface(const QNetworkInterface &networkInterface) { m_networkInterface = networkInterface; + m_networkInterfaceSet = true; } bool NetworkDeviceInfo::isValid() const { - return (!m_address.isNull() || !m_macAddress.isEmpty()) && m_networkInterface.isValid(); + return (!m_address.isNull() || !MacAddress(m_macAddress).isNull()) && m_networkInterface.isValid(); +} + +bool NetworkDeviceInfo::isComplete() const +{ + return m_macAddressSet && m_macAddressManufacturerSet && m_addressSet && m_hostNameSet && m_networkInterfaceSet; +} + +QString NetworkDeviceInfo::incompleteProperties() const +{ + QStringList list; + if (!m_macAddressSet) list.append("MAC not set"); + if (!m_macAddressManufacturerSet) list.append("MAC vendor not set"); + if (!m_hostNameSet) list.append("hostname not set"); + if (!m_networkInterfaceSet) list.append("nework interface not set"); + return list.join(", "); +} + +bool NetworkDeviceInfo::operator==(const NetworkDeviceInfo &other) const +{ + return MacAddress(m_macAddress) == MacAddress(other.macAddress()) && + m_address == other.address() && + m_hostName == other.hostName() && + m_macAddressManufacturer == other.macAddressManufacturer() && + m_networkInterface.name() == other.networkInterface().name() && + isComplete() == other.isComplete(); +} + +bool NetworkDeviceInfo::operator!=(const NetworkDeviceInfo &other) const +{ + return !operator==(other); } 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.macAddress().isEmpty()) + dbg.nospace() << ", " << MacAddress(networkDeviceInfo.macAddress()).toString(); + if (!networkDeviceInfo.macAddressManufacturer().isEmpty()) dbg.nospace() << " (" << networkDeviceInfo.macAddressManufacturer() << ") "; + if (!networkDeviceInfo.hostName().isEmpty()) + dbg.nospace() << ", hostname: " << networkDeviceInfo.hostName(); + if (networkDeviceInfo.networkInterface().isValid()) dbg.nospace() << ", " << networkDeviceInfo.networkInterface().name(); dbg.nospace() << ")"; return dbg.space(); } - diff --git a/libnymea/network/networkdeviceinfo.h b/libnymea/network/networkdeviceinfo.h index e6407e2b..29f0418e 100644 --- a/libnymea/network/networkdeviceinfo.h +++ b/libnymea/network/networkdeviceinfo.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -43,7 +44,7 @@ class LIBNYMEA_EXPORT NetworkDeviceInfo public: explicit NetworkDeviceInfo(); explicit NetworkDeviceInfo(const QString &macAddress); - ~NetworkDeviceInfo() = default; + explicit NetworkDeviceInfo(const QHostAddress &address); QString macAddress() const; void setMacAddress(const QString &macAddress); @@ -61,6 +62,12 @@ public: void setNetworkInterface(const QNetworkInterface &networkInterface); bool isValid() const; + bool isComplete() const; + + QString incompleteProperties() const; + + bool operator==(const NetworkDeviceInfo &other) const; + bool operator!=(const NetworkDeviceInfo &other) const; private: QHostAddress m_address; @@ -69,8 +76,14 @@ private: QString m_hostName; QNetworkInterface m_networkInterface; + bool m_macAddressSet = false; + bool m_macAddressManufacturerSet = false; + bool m_addressSet = false; + bool m_hostNameSet = false; + bool m_networkInterfaceSet = false; }; + QDebug operator<<(QDebug debug, const NetworkDeviceInfo &networkDeviceInfo); diff --git a/libnymea/network/networkdeviceinfos.cpp b/libnymea/network/networkdeviceinfos.cpp index 5d9def02..a1421205 100644 --- a/libnymea/network/networkdeviceinfos.cpp +++ b/libnymea/network/networkdeviceinfos.cpp @@ -57,9 +57,14 @@ int NetworkDeviceInfos::indexFromHostAddress(const QHostAddress &address) } int NetworkDeviceInfos::indexFromMacAddress(const QString &macAddress) +{ + return indexFromMacAddress(MacAddress(macAddress)); +} + +int NetworkDeviceInfos::indexFromMacAddress(const MacAddress &macAddress) { for (int i = 0; i < size(); i++) { - if (at(i).macAddress().toLower() == macAddress.toLower()) { + if (MacAddress(at(i).macAddress()) == macAddress) { return i; } } @@ -77,7 +82,12 @@ bool NetworkDeviceInfos::hasMacAddress(const QString &macAddress) return indexFromMacAddress(macAddress) >= 0; } -NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address) +bool NetworkDeviceInfos::hasMacAddress(const MacAddress &macAddress) +{ + return indexFromMacAddress(macAddress) >= 0; +} + +NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address) const { foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) { if (networkDeviceInfo.address() == address) { @@ -88,7 +98,7 @@ NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address) return NetworkDeviceInfo(); } -NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress) +NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress) const { foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) { if (networkDeviceInfo.macAddress() == macAddress) { @@ -99,6 +109,25 @@ NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress) return NetworkDeviceInfo(); } +NetworkDeviceInfo NetworkDeviceInfos::get(const MacAddress &macAddress) const +{ + return get(macAddress.toString()); +} + +void NetworkDeviceInfos::removeMacAddress(const QString &macAddress) +{ + removeMacAddress(MacAddress(macAddress)); +} + +void NetworkDeviceInfos::removeMacAddress(const MacAddress &macAddress) +{ + for (int i = 0; i < size(); i++) { + if (MacAddress(at(i).macAddress()) == macAddress) { + remove(i); + } + } +} + void NetworkDeviceInfos::sortNetworkDevices() { std::sort(this->begin(), this->end(), [](const NetworkDeviceInfo& a, const NetworkDeviceInfo& b) { diff --git a/libnymea/network/networkdeviceinfos.h b/libnymea/network/networkdeviceinfos.h index ab23f77f..95fedd83 100644 --- a/libnymea/network/networkdeviceinfos.h +++ b/libnymea/network/networkdeviceinfos.h @@ -34,6 +34,7 @@ #include #include "libnymea.h" +#include "macaddress.h" #include "networkdeviceinfo.h" class LIBNYMEA_EXPORT NetworkDeviceInfos : public QVector @@ -45,12 +46,18 @@ public: int indexFromHostAddress(const QHostAddress &address); int indexFromMacAddress(const QString &macAddress); + int indexFromMacAddress(const MacAddress &macAddress); bool hasHostAddress(const QHostAddress &address); bool hasMacAddress(const QString &macAddress); + bool hasMacAddress(const MacAddress &macAddress); - NetworkDeviceInfo get(const QHostAddress &address); - NetworkDeviceInfo get(const QString &macAddress); + NetworkDeviceInfo get(const QHostAddress &address) const; + NetworkDeviceInfo get(const QString &macAddress) const; + NetworkDeviceInfo get(const MacAddress &macAddress) const; + + void removeMacAddress(const QString &macAddress); + void removeMacAddress(const MacAddress &macAddress); void sortNetworkDevices(); diff --git a/libnymea/network/networkdevicemonitor.cpp b/libnymea/network/networkdevicemonitor.cpp new file mode 100644 index 00000000..37edc216 --- /dev/null +++ b/libnymea/network/networkdevicemonitor.cpp @@ -0,0 +1,52 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 "networkdevicemonitor.h" +#include "networkdeviceinfo.h" + +NetworkDeviceMonitor::NetworkDeviceMonitor(QObject *parent) : + QObject(parent) +{ + +} + +QDebug operator<<(QDebug dbg, NetworkDeviceMonitor *networkDeviceMonitor) +{ + dbg.nospace() << "NetworkDeviceMonitor(" << networkDeviceMonitor->macAddress().toString(); + + if (!networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer().isEmpty()) + dbg.nospace() << " - " << networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer(); + + dbg.nospace() << ", " << networkDeviceMonitor->networkDeviceInfo().address().toString(); + + dbg.nospace() << ")"; + return dbg.space(); +} + diff --git a/libnymea/network/networkdevicemonitor.h b/libnymea/network/networkdevicemonitor.h new file mode 100644 index 00000000..e42d8f22 --- /dev/null +++ b/libnymea/network/networkdevicemonitor.h @@ -0,0 +1,65 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 NETWORKDEVICEMONITOR_H +#define NETWORKDEVICEMONITOR_H + +#include +#include + +#include "libnymea.h" +#include "macaddress.h" +#include "networkdeviceinfo.h" + +class LIBNYMEA_EXPORT NetworkDeviceMonitor : public QObject +{ + Q_OBJECT + +public: + explicit NetworkDeviceMonitor(QObject *parent = nullptr); + virtual ~NetworkDeviceMonitor() = default; + + virtual MacAddress macAddress() const = 0; + + virtual NetworkDeviceInfo networkDeviceInfo() const = 0; + + virtual bool reachable() const = 0; + virtual QDateTime lastSeen() const = 0; + +signals: + void reachableChanged(bool reachable); + void lastSeenChanged(const QDateTime &lastSeen); + void networkDeviceInfoChanged(const NetworkDeviceInfo &networkDeviceInfo); + +}; + +QDebug operator<<(QDebug debug, NetworkDeviceMonitor *networkDeviceMonitor); + +#endif // NETWORKDEVICEMONITOR_H diff --git a/libnymea/network/networkutils.cpp b/libnymea/network/networkutils.cpp index ddcf1f38..6d0895d8 100644 --- a/libnymea/network/networkutils.cpp +++ b/libnymea/network/networkutils.cpp @@ -13,7 +13,7 @@ QNetworkInterface NetworkUtils::getInterfaceForHostaddress(const QHostAddress &a if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol) continue; - if (address.isInSubnet(entry.ip(), entry.netmask().toIPv4Address())) { + if (address.isInSubnet(entry.ip(), entry.prefixLength())) { return networkInterface; } } @@ -32,3 +32,14 @@ QNetworkInterface NetworkUtils::getInterfaceForMacAddress(const QString &macAddr return QNetworkInterface(); } + +QNetworkInterface NetworkUtils::getInterfaceForMacAddress(const MacAddress &macAddress) +{ + foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + if (MacAddress(networkInterface.hardwareAddress()) == macAddress) { + return networkInterface; + } + } + + return QNetworkInterface(); +} diff --git a/libnymea/network/networkutils.h b/libnymea/network/networkutils.h index b75aaedb..d4684348 100644 --- a/libnymea/network/networkutils.h +++ b/libnymea/network/networkutils.h @@ -34,6 +34,8 @@ #include #include +#include "macaddress.h" + class NetworkUtils { public: @@ -41,6 +43,7 @@ public: static QNetworkInterface getInterfaceForHostaddress(const QHostAddress &address); static QNetworkInterface getInterfaceForMacAddress(const QString &macAddress); + static QNetworkInterface getInterfaceForMacAddress(const MacAddress &macAddress); }; #endif // NETWORKUTILS_H diff --git a/libnymea/network/ping.cpp b/libnymea/network/ping.cpp index 36038294..a16202c7 100644 --- a/libnymea/network/ping.cpp +++ b/libnymea/network/ping.cpp @@ -49,6 +49,13 @@ NYMEA_LOGGING_CATEGORY(dcPingTraffic, "PingTraffic") Ping::Ping(QObject *parent) : QObject(parent) { + m_queueTimer = new QTimer(this); + m_queueTimer->setInterval(20); + m_queueTimer->setSingleShot(true); + connect(m_queueTimer, &QTimer::timeout, this, [=](){ + sendNextReply(); + }); + // Build socket descriptor m_socketDescriptor = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (m_socketDescriptor < 0) { @@ -78,13 +85,6 @@ Ping::Ping(QObject *parent) : QObject(parent) 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 << ")"; @@ -135,7 +135,7 @@ void Ping::sendNextReply() 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); }); + QTimer::singleShot(0, reply, [=]() { performPing(reply); }); } void Ping::performPing(PingReply *reply) @@ -146,6 +146,13 @@ void Ping::performPing(PingReply *reply) return; } + if (reply->targetHostAddress().isNull()) { + m_error = PingReply::ErrorInvalidHostAddress; + qCWarning(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; @@ -411,7 +418,7 @@ void Ping::onHostLookupFinished(const QHostInfo &info) 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(); + qCDebug(dcPing()) << "Looked up hostname after successfull ping" << reply->targetHostAddress().toString() << info.hostName(); if (info.hostName() != reply->targetHostAddress().toString()) { reply->m_hostName = info.hostName(); } @@ -419,4 +426,3 @@ void Ping::onHostLookupFinished(const QHostInfo &info) finishReply(reply, PingReply::ErrorNoError); } - diff --git a/libnymea/network/pingreply.h b/libnymea/network/pingreply.h index 60837181..8bf8ca19 100644 --- a/libnymea/network/pingreply.h +++ b/libnymea/network/pingreply.h @@ -57,7 +57,8 @@ public: ErrorPermissionDenied, ErrorSocketError, ErrorTimeout, - ErrorHostUnreachable + ErrorHostUnreachable, + ErrorInvalidHostAddress }; Q_ENUM(Error) diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 697b95dc..e2ebff30 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -8,6 +8,7 @@ SUBDIRS = \ logging \ loggingdirect \ loggingloading \ + macaddress \ mqttbroker \ plugins \ pythonplugins \ @@ -21,4 +22,3 @@ SUBDIRS = \ webserver \ websocketserver \ #coap \ # temporary removed until fixed - diff --git a/tests/auto/integrations/testintegrations.cpp b/tests/auto/integrations/testintegrations.cpp index 3133dff4..1d4098e7 100644 --- a/tests/auto/integrations/testintegrations.cpp +++ b/tests/auto/integrations/testintegrations.cpp @@ -1040,7 +1040,6 @@ void TestIntegrations::getEventTypes() QVariantList eventTypes = response.toMap().value("params").toMap().value("eventTypes").toList(); QCOMPARE(eventTypes.count(), resultCount); - } void TestIntegrations::getStateTypes_data() diff --git a/tests/auto/macaddress/macaddress.pro b/tests/auto/macaddress/macaddress.pro new file mode 100644 index 00000000..a45951b8 --- /dev/null +++ b/tests/auto/macaddress/macaddress.pro @@ -0,0 +1,7 @@ +TARGET = testmacaddress + +include(../../../nymea.pri) +include(../autotests.pri) + +SOURCES += testmacaddress.cpp + diff --git a/tests/auto/macaddress/testmacaddress.cpp b/tests/auto/macaddress/testmacaddress.cpp new file mode 100644 index 00000000..55251f6c --- /dev/null +++ b/tests/auto/macaddress/testmacaddress.cpp @@ -0,0 +1,134 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 "nymeatestbase.h" + +#include + +using namespace nymeaserver; + +class TestMacAddress: public NymeaTestBase +{ + Q_OBJECT + +protected slots: + void initTestCase(); + +private: + QString m_zeroMacString = QString("00:00:00:00:00:00"); + QByteArray m_zeroMacByteArray = QByteArray(6, '\0'); + + QString m_alphaNumericMacString = QString("01:23:45:ab:cd:ef"); + QByteArray m_alphaNumericByteArray = QByteArray::fromHex("012345abcdef"); + +private slots: + void defaultConstructor(); + + void macAddressValidation_data(); + void macAddressValidation(); + +}; + +void TestMacAddress::initTestCase() +{ + NymeaTestBase::initTestCase("*.debug=false\nTests.debug=true\n"); +} + +void TestMacAddress::defaultConstructor() +{ + MacAddress mac; + QVERIFY(mac.isNull()); + QVERIFY(mac.isValid()); + QCOMPARE(mac.toByteArray().count(), 6); + QCOMPARE(mac.toByteArray(), QByteArray(6, '\0')); + QVERIFY(mac.toString() == m_zeroMacString); + + QVERIFY(!MacAddress(QString("acme")).isValid()); + QVERIFY(MacAddress(QString("acme")).isNull()); + QVERIFY(!MacAddress(QByteArray()).isValid()); + QVERIFY(MacAddress(QByteArray()).isNull()); + + QVERIFY(MacAddress(QByteArray()).toByteArray().isEmpty()); + + QCOMPARE(MacAddress(m_zeroMacByteArray).toString(), m_zeroMacString); + QCOMPARE(MacAddress(m_zeroMacString).toByteArray(), m_zeroMacByteArray); + + QCOMPARE(MacAddress(m_alphaNumericByteArray).toString(), m_alphaNumericMacString); + QCOMPARE(MacAddress(m_alphaNumericMacString).toByteArray(), m_alphaNumericByteArray); + + QByteArray validRawData = QByteArray::fromHex("aabbccddeeff"); + QCOMPARE(MacAddress(QString("aa:bb:cc:dd:ee:ff")).toByteArray(), validRawData); + QCOMPARE(MacAddress(QString("aa:bb:cc:dd:ee:ff")), MacAddress(validRawData)); + QCOMPARE(MacAddress(QString("aabbccddeeff")).toByteArray(), validRawData); + QCOMPARE(MacAddress(QString("aabbccddeeff")), MacAddress(validRawData)); +} + +void TestMacAddress::macAddressValidation_data() +{ + QTest::addColumn("macString"); + QTest::addColumn("isValid"); + QTest::addColumn("isNull"); + QTest::addColumn("toString"); + + QString mixedString = "11:22:33:dd:ee:ff"; + QString aplhaString = "aa:bb:cc:dd:ee:ff"; + + QTest::newRow("Valid zero") << "00:00:00:00:00:00" << true << true << m_zeroMacString; + QTest::newRow("Valid zero no colon") << "000000000000" << true << true << m_zeroMacString; + QTest::newRow("Valid non zero lower") << "11:22:33:dd:ee:ff" << true << false << mixedString; + QTest::newRow("Valid non zero upper") << "11:22:33:DD:EE:FF" << true << false << mixedString; + QTest::newRow("Valid non zero mixed case") << "11:22:33:Dd:Ee:Ff" << true << false << mixedString; + QTest::newRow("Valid non zero space separator") << "aa bb cc dd ee ff" << true << false << aplhaString; + QTest::newRow("Valid non zero dash separator") << "aa-bb-cc-dd-ee-ff" << true << false << aplhaString; + QTest::newRow("Valid non zero dot separator") << "aa.bb.cc.dd.ee.ff" << true << false << aplhaString; + QTest::newRow("Valid non zero crazy separator") << "aa#bb?cclddxeeäff" << true << false << aplhaString; + QTest::newRow("Valid non zero mixed separators") << "aa-bb cc.dd ee-ff" << true << false << aplhaString; + QTest::newRow("Valid non zero without colon") << "aabbccddeeff" << true << false << aplhaString; + QTest::newRow("Invalid characters") << "xx:yy:zz:dd:ee:ff" << false << true << m_zeroMacString; + QTest::newRow("Too short") << "xx:yy:zz:dd:ee" << false << true << m_zeroMacString; + QTest::newRow("Too long") << "xx:yy:zz:dd:ee:ee:ee" << false << true << m_zeroMacString; +} + +void TestMacAddress::macAddressValidation() +{ + QFETCH(QString, macString); + QFETCH(bool, isValid); + QFETCH(bool, isNull); + QFETCH(QString, toString); + + MacAddress mac(macString); + qCDebug(dcTests()) << "Verify" << macString << "resulting in" << mac; + QCOMPARE(mac.isValid(), isValid); + QCOMPARE(mac.isNull(), isNull); + QCOMPARE(mac.toString(), toString); +} + +#include "testmacaddress.moc" +QTEST_MAIN(TestMacAddress)