diff --git a/debian/control b/debian/control index 68b49f11..5415aa05 100644 --- a/debian/control +++ b/debian/control @@ -390,6 +390,7 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, nmap, + fping, nymea-plugins-translations, Replaces: guh-plugin-networkdetector Description: nymea.io plugin for networkdetector diff --git a/networkdetector/broadcastping.cpp b/networkdetector/broadcastping.cpp new file mode 100644 index 00000000..9fec3c1e --- /dev/null +++ b/networkdetector/broadcastping.cpp @@ -0,0 +1,51 @@ +#include "broadcastping.h" +#include "extern-plugininfo.h" + +#include +#include + +BroadcastPing::BroadcastPing(QObject *parent) : QObject(parent) +{ + +} + +void BroadcastPing::run() +{ + qDeleteAll(m_runningPings.keys()); + m_runningPings.clear(); + + foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { + if (!interface.flags().testFlag(QNetworkInterface::IsUp) || !interface.flags().testFlag(QNetworkInterface::CanBroadcast) || interface.flags().testFlag(QNetworkInterface::IsLoopBack)) { + continue; + } + foreach (const QNetworkAddressEntry &addressEntry, interface.addressEntries()) { + if (addressEntry.broadcast().isNull()) { + continue; + } + qCDebug(dcNetworkDetector()) << "Sending Broadcast Ping on" << addressEntry.broadcast().toString() + '/' + QString::number(addressEntry.prefixLength()) + "..."; + QProcess *p = new QProcess(this); + m_runningPings.insert(p, addressEntry); + p->start("fping", {"-a", "-c", "1", "-g", addressEntry.broadcast().toString() + "/" + QString::number(addressEntry.prefixLength())}); + connect(p, SIGNAL(finished(int)), this, SLOT(broadcastPingFinished(int))); + } + } + if (m_runningPings.isEmpty()) { + qCWarning(dcNetworkDetector()) << "Cound not find any suitable interface for broadcast pinging"; + emit finished(); + } +} + +void BroadcastPing::broadcastPingFinished(int exitCode) +{ + Q_UNUSED(exitCode); + QProcess *p = static_cast(sender()); + QNetworkAddressEntry addressEntry = m_runningPings.take(p); + qCDebug(dcNetworkDetector()) << "Broadcast ping finished for network" << addressEntry.broadcast().toString() + '/' + QString::number(addressEntry.prefixLength()); +// qCDebug(dcNetworkDetector()) << p->readAllStandardError(); + p->deleteLater(); + + if (m_runningPings.isEmpty()) { + qCDebug(dcNetworkDetector()) << "All broadcast pings finished"; + emit finished(); + } +} diff --git a/networkdetector/broadcastping.h b/networkdetector/broadcastping.h new file mode 100644 index 00000000..b41f376f --- /dev/null +++ b/networkdetector/broadcastping.h @@ -0,0 +1,28 @@ +#ifndef BROADCASTPING_H +#define BROADCASTPING_H + +#include +#include +#include +#include + +class BroadcastPing : public QObject +{ + Q_OBJECT +public: + explicit BroadcastPing(QObject *parent = nullptr); + +signals: + void finished(); + +public slots: + void run(); + +private slots: + void broadcastPingFinished(int exitCode); + +private: + QHash m_runningPings; +}; + +#endif // BROADCASTPING_H diff --git a/networkdetector/devicemonitor.cpp b/networkdetector/devicemonitor.cpp index 3c37976b..5caa17bd 100644 --- a/networkdetector/devicemonitor.cpp +++ b/networkdetector/devicemonitor.cpp @@ -4,17 +4,20 @@ #include -DeviceMonitor::DeviceMonitor(const QString &macAddress, const QString &ipAddress, QObject *parent): - QObject(parent) +DeviceMonitor::DeviceMonitor(const QString &name, const QString &macAddress, const QString &ipAddress, bool initialState, QObject *parent): + QObject(parent), + m_name(name), + m_macAddress(macAddress), + m_ipAddress(ipAddress), + m_reachable(initialState) { - m_host = new Host(); - m_host->setMacAddress(macAddress); - m_host->setAddress(ipAddress); - m_host->setReachable(false); - m_arpLookupProcess = new QProcess(this); connect(m_arpLookupProcess, SIGNAL(finished(int)), this, SLOT(arpLookupFinished(int))); + m_arpingProcess = new QProcess(this); + m_arpingProcess->setReadChannelMode(QProcess::MergedChannels); + connect(m_arpingProcess, SIGNAL(finished(int)), this, SLOT(arpingFinished(int))); + m_pingProcess = new QProcess(this); m_pingProcess->setReadChannelMode(QProcess::MergedChannels); connect(m_pingProcess, SIGNAL(finished(int)), this, SLOT(pingFinished(int))); @@ -22,13 +25,17 @@ DeviceMonitor::DeviceMonitor(const QString &macAddress, const QString &ipAddress DeviceMonitor::~DeviceMonitor() { - delete m_host; +} + +void DeviceMonitor::setGracePeriod(int minutes) +{ + m_gracePeriod = minutes; } void DeviceMonitor::update() { - if (m_pingProcess->state() != QProcess::NotRunning) { - qCDebug(dcNetworkDetector()) << "Previous ping still running for device" << m_host->address() << ". Not updating."; + if (m_arpingProcess->state() != QProcess::NotRunning || m_pingProcess->state() != QProcess::NotRunning) { +// log("Previous ping still running. Not updating."); return; } lookupArpCache(); @@ -39,13 +46,94 @@ void DeviceMonitor::lookupArpCache() m_arpLookupProcess->start("ip", {"-4", "-s", "neighbor", "list"}); } -void DeviceMonitor::ping() +void DeviceMonitor::arpLookupFinished(int exitCode) +{ + if (exitCode != 0) { + warn("Error looking up ARP cache."); + return; + } + + QString data = QString::fromLatin1(m_arpLookupProcess->readAll()); + bool found = false; + bool needsPing = true; + QString mostRecentIP = m_ipAddress; + qlonglong secsSinceLastSeen = -1; + foreach (QString line, data.split('\n')) { + line.replace(QRegExp("[ ]{1,}"), " "); + QStringList parts = line.split(" "); + int lladdrIndex = parts.indexOf("lladdr"); + if (lladdrIndex == -1 || parts.count() <= lladdrIndex) { + continue; + } + QString entryIP = parts.first(); + QString entryMAC = parts.at(lladdrIndex + 1); + + if (entryMAC.toLower() == m_macAddress.toLower()) { + found = true; + QString entryIP = parts.first(); + if (parts.last() == "REACHABLE") { + log("Device found in ARP cache and claims to be REACHABLE (Cache IP: " + entryIP + ")"); + if (!m_reachable) { + m_reachable = true; + emit reachableChanged(true); + } + emit seen(); + m_lastSeenTime = QDateTime::currentDateTime(); + // Verify if IP address is still the same + if (entryIP != mostRecentIP) { + mostRecentIP = entryIP; + } + // If we have a reachable entry, stop processing here + needsPing = false; + break; + } else { + // ARP claims the device to be stale... Flagging device to require a ping. + log("Device found in ARP cache but is marked as " + parts.last() + " (Cache IP: " + entryIP + ")"); + + int usedIndex = parts.indexOf("used"); + if (usedIndex >= 0 && parts.count() > usedIndex + 1) { + QString usedFields = parts.at(usedIndex + 1); + qlonglong newSecsSinceLastSeen = usedFields.split("/").first().toInt(); + if (secsSinceLastSeen == -1 || newSecsSinceLastSeen < secsSinceLastSeen) { + secsSinceLastSeen = newSecsSinceLastSeen; + mostRecentIP = entryIP; + } + } + } + } else if (entryIP == m_ipAddress) { + warn("There seems to be a device with our IP but different MAC. Resetting IP config."); + if (mostRecentIP == m_ipAddress) { + mostRecentIP.clear(); + } + } + } + if (mostRecentIP != m_ipAddress) { + log("Device has changed IP: " + m_ipAddress + " -> " + mostRecentIP + ")"); + m_ipAddress = mostRecentIP; + emit addressChanged(mostRecentIP); + } + if (m_ipAddress.isEmpty()) { + warn("Device not found in ARP cache and no IP config available. Marking as gone."); + if (m_reachable) { + m_reachable = false; + emit reachableChanged(false); + } + return; + } + if (!found) { + log("Device not found in ARP cache."); + arping(); + } else if (needsPing) { + arping(); + } +} + +void DeviceMonitor::arping() { - qCDebug(dcNetworkDetector()) << "Sending ARP Ping to" << m_host->hostName() << m_host->macAddress() << m_host->address(); QNetworkInterface targetInterface; foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { foreach (const QNetworkAddressEntry &addressEntry, interface.addressEntries()) { - QHostAddress clientAddress(m_host->address()); + QHostAddress clientAddress(m_ipAddress); if (clientAddress.isInSubnet(addressEntry.ip(), addressEntry.prefixLength())) { targetInterface = interface; break; @@ -53,78 +141,62 @@ void DeviceMonitor::ping() } } if (!targetInterface.isValid()) { - qCWarning(dcNetworkDetector()) << "Could not find a suitable interface to ping for" << m_host->address(); - if (m_host->reachable()) { - m_host->setReachable(false); + warn("Could not find a suitable interface to ARP Ping."); + if (m_reachable) { + m_reachable = false; emit reachableChanged(false); } return; } - m_pingProcess->start("arping", {"-I", targetInterface.name(), "-f", "-w", "180", m_host->address()}); + log("Sending ARP Ping to " + m_ipAddress + "..."); + m_arpingProcess->start("arping", {"-I", targetInterface.name(), "-f", "-w", "30", m_ipAddress}); } -void DeviceMonitor::arpLookupFinished(int exitCode) -{ - if (exitCode != 0) { - qCWarning(dcNetworkDetector()) << "Error looking up ARP cache."; - return; - } - - QString data = QString::fromLatin1(m_arpLookupProcess->readAll()); - bool found = false; - bool needsPing = true; - foreach (QString line, data.split('\n')) { - line.replace(QRegExp("[ ]{1,}"), " "); - QStringList parts = line.split(" "); - int lladdrIndex = parts.indexOf("lladdr"); - if (lladdrIndex >= 0 && parts.count() > lladdrIndex && parts.at(lladdrIndex+1).toLower() == m_host->macAddress().toLower()) { - found = true; - if (parts.last() == "REACHABLE") { - qCDebug(dcNetworkDetector()) << "Device" << m_host->macAddress() << "found in ARP cache and claims to be REACHABLE"; - if (!m_host->reachable()) { - m_host->setReachable(true); - emit reachableChanged(true); - } - m_host->seen(); - emit seen(); - // Verify if IP address is still the same - if (parts.first() != m_host->address()) { - m_host->setAddress(parts.first()); - emit addressChanged(parts.first()); - } - // If we have a reachable entry, stop processing here - needsPing = false; - break; - } else { - // ARP claims the device to be stale... Flagging device to require a ping. - qCDebug(dcNetworkDetector()) << "Device" << m_host->macAddress() << "found in ARP cache but is marked as" << parts.last(); - } - } - } - if (!found) { - qCDebug(dcNetworkDetector()) << "Device" << m_host->macAddress() << "not found in ARP cache."; - ping(); - } else if (needsPing) { - ping(); - } -} - -void DeviceMonitor::pingFinished(int exitCode) +void DeviceMonitor::arpingFinished(int exitCode) { if (exitCode == 0) { // we were able to ping the device - qCDebug(dcNetworkDetector()) << "Ping successful for" << m_host->macAddress() << m_host->address(); - m_host->seen(); - if (!m_host->reachable()) { - m_host->setReachable(true); + log("ARP Ping successful."); + if (!m_reachable) { + m_reachable = true; emit reachableChanged(true); } emit seen(); + m_lastSeenTime = QDateTime::currentDateTime(); } else { - qCDebug(dcNetworkDetector()) << "Could not ping device" << m_host->macAddress() << m_host->address(); - if (m_host->reachable()) { - m_host->setReachable(false); + log("ARP Ping failed."); + ping(); + } + // read data to discard it from socket + QString data = QString::fromLatin1(m_arpingProcess->readAll()); + Q_UNUSED(data) +// qCDebug(dcNetworkDetector()) << "have ping data" << data; +} + +void DeviceMonitor::ping() +{ + log("Sending ICMP Ping to " + m_ipAddress + "..."); + m_pingProcess->start("ping", {"-c", "30", m_ipAddress}); +} + +void DeviceMonitor::pingFinished(int exitCode) + +{ + if (exitCode == 0) { + // we were able to ping the device + log("ICMP Ping successful."); + if (!m_reachable) { + m_reachable = true; + emit reachableChanged(true); + } + emit seen(); + m_lastSeenTime = QDateTime::currentDateTime(); + } else { + log("ICMP Ping failed."); + if (m_reachable && m_lastSeenTime.addSecs(m_gracePeriod * 60) < QDateTime::currentDateTime()) { + log("Exceeded grace period of " + QString::number(m_gracePeriod) + " minutes. Marking device as offline."); + m_reachable = false; emit reachableChanged(false); } } @@ -133,3 +205,13 @@ void DeviceMonitor::pingFinished(int exitCode) Q_UNUSED(data) // qCDebug(dcNetworkDetector()) << "have ping data" << data; } + +void DeviceMonitor::log(const QString &message) +{ + qCDebug(dcNetworkDetector()).noquote().nospace() << m_name << " (" << m_macAddress << ", " << m_ipAddress << "): " << message; +} + +void DeviceMonitor::warn(const QString &message) +{ + qCWarning(dcNetworkDetector()).noquote().nospace() << m_name << " (" << m_macAddress << ", " << m_ipAddress << "): " << message; +} diff --git a/networkdetector/devicemonitor.h b/networkdetector/devicemonitor.h index 28fce3bd..a9bd9f33 100644 --- a/networkdetector/devicemonitor.h +++ b/networkdetector/devicemonitor.h @@ -3,17 +3,18 @@ #include #include - -#include "host.h" +#include class DeviceMonitor : public QObject { Q_OBJECT public: - explicit DeviceMonitor(const QString &macAddress, const QString &ipAddress, QObject *parent = nullptr); + explicit DeviceMonitor(const QString &name, const QString &macAddress, const QString &ipAddress, bool initialState, QObject *parent = nullptr); ~DeviceMonitor(); + void setGracePeriod(int minutes); + void update(); signals: @@ -23,17 +24,29 @@ signals: private: void lookupArpCache(); + void arping(); void ping(); + void log(const QString &message); + void warn(const QString &message); + private slots: void arpLookupFinished(int exitCode); + void arpingFinished(int exitCode); void pingFinished(int exitCode); private: - Host *m_host; - QProcess *m_arpLookupProcess; - QProcess *m_pingProcess; + QString m_name; + QString m_macAddress; + QString m_ipAddress; + QDateTime m_lastSeenTime; + bool m_reachable = false; + int m_gracePeriod = 5; + + QProcess *m_arpLookupProcess = nullptr; + QProcess *m_arpingProcess = nullptr; + QProcess *m_pingProcess = nullptr; }; #endif // DEVICEMONITOR_H diff --git a/networkdetector/devicepluginnetworkdetector.cpp b/networkdetector/devicepluginnetworkdetector.cpp index b4733afa..ae6bd65c 100644 --- a/networkdetector/devicepluginnetworkdetector.cpp +++ b/networkdetector/devicepluginnetworkdetector.cpp @@ -57,12 +57,13 @@ DevicePluginNetworkDetector::DevicePluginNetworkDetector() { m_discovery = new Discovery(this); connect(m_discovery, &Discovery::finished, this, &DevicePluginNetworkDetector::discoveryFinished); + + m_broadcastPing = new BroadcastPing(this); + connect(m_broadcastPing, &BroadcastPing::finished, this, &DevicePluginNetworkDetector::broadcastPingFinished); } DevicePluginNetworkDetector::~DevicePluginNetworkDetector() { - hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); - if (m_discovery->isRunning()) { m_discovery->abort(); } @@ -70,19 +71,27 @@ DevicePluginNetworkDetector::~DevicePluginNetworkDetector() void DevicePluginNetworkDetector::init() { - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); - connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginNetworkDetector::onPluginTimer); } DeviceManager::DeviceSetupStatus DevicePluginNetworkDetector::setupDevice(Device *device) { qCDebug(dcNetworkDetector()) << "Setup" << device->name() << device->params(); - DeviceMonitor *monitor = new DeviceMonitor(device->paramValue(networkDeviceDeviceMacAddressParamTypeId).toString(), device->paramValue(networkDeviceDeviceAddressParamTypeId).toString(), this); + DeviceMonitor *monitor = new DeviceMonitor(device->name(), + device->paramValue(networkDeviceDeviceMacAddressParamTypeId).toString(), + device->paramValue(networkDeviceDeviceAddressParamTypeId).toString(), + device->stateValue(networkDeviceIsPresentStateTypeId).toBool(), + this); connect(monitor, &DeviceMonitor::reachableChanged, this, &DevicePluginNetworkDetector::deviceReachableChanged); connect(monitor, &DeviceMonitor::addressChanged, this, &DevicePluginNetworkDetector::deviceAddressChanged); connect(monitor, &DeviceMonitor::seen, this, &DevicePluginNetworkDetector::deviceSeen); m_monitors.insert(monitor, device); - monitor->update(); + + if (!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(30); + connect(m_pluginTimer, &PluginTimer::timeout, m_broadcastPing, &BroadcastPing::run); + + m_broadcastPing->run(); + } return DeviceManager::DeviceSetupStatusSuccess; } @@ -110,9 +119,15 @@ void DevicePluginNetworkDetector::deviceRemoved(Device *device) DeviceMonitor *monitor = m_monitors.key(device); m_monitors.remove(monitor); delete monitor; + + if (m_monitors.isEmpty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + + } } -void DevicePluginNetworkDetector::onPluginTimer() +void DevicePluginNetworkDetector::broadcastPingFinished() { foreach (DeviceMonitor *monitor, m_monitors.keys()) { monitor->update(); @@ -124,7 +139,15 @@ void DevicePluginNetworkDetector::discoveryFinished(const QList &hosts) qCDebug(dcNetworkDetector()) << "Discovery finished. Found" << hosts.count() << "devices"; QList discoveredDevices; foreach (const Host &host, hosts) { - DeviceDescriptor descriptor(networkDeviceDeviceClassId, (host.hostName().isEmpty() ? host.address() : host.hostName() + "(" + host.address() + ")"), host.macAddress()); + + DeviceDescriptor descriptor(networkDeviceDeviceClassId, host.hostName().isEmpty() ? host.address() : host.hostName(), host.address() + " (" + host.macAddress() + ")"); + + foreach (Device *existingDevice, myDevices()) { + if (existingDevice->paramValue(networkDeviceDeviceMacAddressParamTypeId).toString() == host.macAddress()) { + descriptor.setDeviceId(existingDevice->id()); + break; + } + } ParamList paramList; Param macAddress(networkDeviceDeviceMacAddressParamTypeId, host.macAddress()); diff --git a/networkdetector/devicepluginnetworkdetector.h b/networkdetector/devicepluginnetworkdetector.h index 197d0a7c..e890aeb2 100644 --- a/networkdetector/devicepluginnetworkdetector.h +++ b/networkdetector/devicepluginnetworkdetector.h @@ -29,6 +29,7 @@ #include "discovery.h" #include "plugintimer.h" #include "devicemonitor.h" +#include "broadcastping.h" #include #include @@ -59,11 +60,12 @@ private slots: void deviceAddressChanged(const QString &address); void deviceSeen(); - void onPluginTimer(); + void broadcastPingFinished(); private: PluginTimer *m_pluginTimer = nullptr; Discovery *m_discovery = nullptr; + BroadcastPing *m_broadcastPing = nullptr; QHash m_monitors; }; diff --git a/networkdetector/devicepluginnetworkdetector.json b/networkdetector/devicepluginnetworkdetector.json index 074b3caa..1f5f4e4c 100644 --- a/networkdetector/devicepluginnetworkdetector.json +++ b/networkdetector/devicepluginnetworkdetector.json @@ -34,6 +34,13 @@ "displayName": "hardware address", "type": "QString", "inputType": "TextLine" + }, + { + "id": "40116f86-e6b3-4a20-b1e9-e1bd4b6d5b70", + "name": "gracePeriod", + "displayName": "Grace period (Minutes)", + "type": "uint", + "defaultValue": 5 } ], "stateTypes": [ diff --git a/networkdetector/networkdetector.pro b/networkdetector/networkdetector.pro index 7e1b989d..ccf6f674 100644 --- a/networkdetector/networkdetector.pro +++ b/networkdetector/networkdetector.pro @@ -8,12 +8,14 @@ SOURCES += \ devicepluginnetworkdetector.cpp \ host.cpp \ discovery.cpp \ - devicemonitor.cpp + devicemonitor.cpp \ + broadcastping.cpp HEADERS += \ devicepluginnetworkdetector.h \ host.h \ discovery.h \ - devicemonitor.h + devicemonitor.h \ + broadcastping.h