Merge PR #576: Network device discovery: create reply for each discovery request

This commit is contained in:
jenkins 2022-10-25 02:37:04 +02:00
commit 439b320cbe
5 changed files with 163 additions and 64 deletions

View File

@ -64,7 +64,7 @@ NetworkDeviceDiscoveryImpl::NetworkDeviceDiscoveryImpl(QObject *parent) :
m_discoveryTimer->setInterval(20000);
m_discoveryTimer->setSingleShot(true);
connect(m_discoveryTimer, &QTimer::timeout, this, [=](){
if (m_runningPingRepies.isEmpty() && m_currentReply) {
if (m_runningPingReplies.isEmpty() && m_currentReply) {
finishDiscovery();
}
});
@ -98,35 +98,89 @@ NetworkDeviceDiscoveryImpl::~NetworkDeviceDiscoveryImpl()
NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover()
{
if (m_currentReply) {
qCDebug(dcNetworkDeviceDiscovery()) << "Discovery already running. Returning current pending discovery reply...";
return m_currentReply;
// Each user calling this method receives it's own reply object.
// For internal tracking we use the current reply, but each caller gets it's own
// reply and owns the object, even if the discovery has been finished.
// Create the internal object if required
bool alreadyRunning = (m_currentReply != nullptr);
if (alreadyRunning) {
if (m_currentReply->isFinished()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Discovery internally already running and finished.";
} else {
qCDebug(dcNetworkDeviceDiscovery()) << "Discovery internally already running. Re-using the current running discovery reply.";
}
} else {
qCDebug(dcNetworkDeviceDiscovery()) << "Starting internally a new discovery.";
m_currentReply = new NetworkDeviceDiscoveryReplyImpl(this);
connect(m_currentReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, this, &NetworkDeviceDiscoveryImpl::updateCache);
connect(m_currentReply, &NetworkDeviceDiscoveryReplyImpl::finished, this, [this](){
// Finish all pending replies
foreach (NetworkDeviceDiscoveryReplyImpl *reply, m_pendingReplies) {
// Sync all network device infos with all pending replies
foreach (const NetworkDeviceInfo &info, m_currentReply->networkDeviceInfos()) {
reply->addCompleteNetworkDeviceInfo(info);
}
foreach (const NetworkDeviceInfo &info, m_currentReply->virtualNetworkDeviceInfos()) {
reply->addVirtualNetworkDeviceInfo(info);
}
}
// Delete the current reply before finishing the pending replies.
// Just in case some one restarts a discovery on finished, a new internal
// object should be created
m_currentReply->deleteLater();
m_currentReply = nullptr;
foreach (NetworkDeviceDiscoveryReplyImpl *reply, m_pendingReplies) {
m_pendingReplies.removeAll(reply);
reply->setFinished(true);
emit reply->finished();
}
});
}
m_currentReply = new NetworkDeviceDiscoveryReplyImpl(this);
// Create the reply for the user
NetworkDeviceDiscoveryReplyImpl *reply = new NetworkDeviceDiscoveryReplyImpl(this);
connect(m_currentReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, reply, &NetworkDeviceDiscoveryReplyImpl::addCompleteNetworkDeviceInfo);
connect(m_currentReply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered, reply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered);
m_pendingReplies.append(reply);
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
// Finish the discovery in the next event loop so any connections after the creation will work as expected
QTimer::singleShot(0, this, &NetworkDeviceDiscoveryImpl::finishDiscovery);
return m_currentReply;
return reply;
}
connect(m_currentReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, this, &NetworkDeviceDiscoveryImpl::updateCache);
qCInfo(dcNetworkDeviceDiscovery()) << "Starting network device discovery ...";
if (alreadyRunning) {
// Add already discovered network device infos in the next event loop
// so any connections after this method call will work as expected
QTimer::singleShot(0, reply, [this, reply](){
if (!m_currentReply)
return;
if (m_ping->available())
pingAllNetworkDevices();
foreach (const NetworkDeviceInfo &networkDeviceInfo, m_currentReply->networkDeviceInfos()) {
reply->addCompleteNetworkDeviceInfo(networkDeviceInfo);
}
});
} else {
qCInfo(dcNetworkDeviceDiscovery()) << "Starting network device discovery ...";
if (m_arpSocket->isOpen())
m_arpSocket->sendRequest();
if (m_ping->available())
pingAllNetworkDevices();
m_discoveryTimer->start();
if (m_arpSocket->isOpen())
m_arpSocket->sendRequest();
m_running = true;
emit runningChanged(m_running);
m_discoveryTimer->start();
return m_currentReply;
m_running = true;
emit runningChanged(m_running);
}
return reply;
}
bool NetworkDeviceDiscoveryImpl::available() const
@ -185,7 +239,8 @@ NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddre
if (!monitor->networkDeviceInfo().isValid()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Adding network device monitor for unresolved mac address. Starting a discovery...";
discover();
NetworkDeviceDiscoveryReply *reply = discover();
connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater);
}
evaluateMonitor(monitor);
@ -306,9 +361,9 @@ void NetworkDeviceDiscoveryImpl::pingAllNetworkDevices()
// Retry only once to ping a device and lookup the hostname on success
PingReply *reply = ping(targetAddress, true, 1);
m_runningPingRepies.append(reply);
m_runningPingReplies.append(reply);
connect(reply, &PingReply::finished, this, [=](){
m_runningPingRepies.removeAll(reply);
m_runningPingReplies.removeAll(reply);
if (reply->error() == PingReply::ErrorNoError) {
qCDebug(dcNetworkDeviceDiscovery()) << "Ping response from" << targetAddress.toString() << reply->hostName() << reply->duration() << "ms";
if (m_currentReply) {
@ -316,7 +371,7 @@ void NetworkDeviceDiscoveryImpl::pingAllNetworkDevices()
}
}
if (m_runningPingRepies.isEmpty() && m_currentReply && !m_discoveryTimer->isActive()) {
if (m_runningPingReplies.isEmpty() && m_currentReply && !m_discoveryTimer->isActive()) {
qCWarning(dcNetworkDeviceDiscovery()) << "All ping replies finished for discovery." << m_currentReply->networkDeviceInfos().count();
finishDiscovery();
}
@ -628,7 +683,8 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitors()
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();
NetworkDeviceDiscoveryReply *reply = discover();
connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater);
}
// Do some cache housekeeping if required
@ -659,9 +715,10 @@ void NetworkDeviceDiscoveryImpl::finishDiscovery()
m_lastDiscovery = QDateTime::currentDateTime();
m_currentReply->processDiscoveryFinished();
m_currentReply->deleteLater();
m_currentReply = nullptr;
// Clean up internal reply
if (m_currentReply) {
m_currentReply->processDiscoveryFinished();
}
}
}

View File

@ -102,7 +102,8 @@ private:
uint m_cacheCleanupPeriod = 30; // days
NetworkDeviceDiscoveryReplyImpl *m_currentReply = nullptr;
QList<PingReply *> m_runningPingRepies;
QList<NetworkDeviceDiscoveryReplyImpl *> m_pendingReplies;
QList<PingReply *> m_runningPingReplies;
QHash<MacAddress, NetworkDeviceMonitorImpl *> m_monitors;
QHash<MacAddress, int> m_monitorsReferenceCount;

View File

@ -53,39 +53,14 @@ NetworkDeviceInfos NetworkDeviceDiscoveryReplyImpl::virtualNetworkDeviceInfos()
return m_virtualNetworkDeviceInfos;
}
QString NetworkDeviceDiscoveryReplyImpl::macAddressFromHostAddress(const QHostAddress &address)
bool NetworkDeviceDiscoveryReplyImpl::isFinished() const
{
foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) {
if (info.address() == address) {
return info.macAddress();
}
}
return QString();
return m_isFinished;
}
bool NetworkDeviceDiscoveryReplyImpl::hasHostAddress(const QHostAddress &address)
void NetworkDeviceDiscoveryReplyImpl::setFinished(bool finished)
{
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]);
}
}
m_isFinished = finished;
}
void NetworkDeviceDiscoveryReplyImpl::processPingResponse(const QHostAddress &address, const QString &hostName)
@ -208,16 +183,71 @@ void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished()
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();
qCDebug(dcNetworkDeviceDiscovery()) << "Unhandled information:" << info << "Valid:" << info.isValid() << "Complete:" << info.isComplete() << info.incompleteProperties();
}
m_isFinished = true;
emit finished();
}
void NetworkDeviceDiscoveryReplyImpl::addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo)
{
// Note: this method will be called only from the discovery
if (!m_networkDeviceInfos.hasMacAddress(networkDeviceInfo.macAddress())) {
m_networkDeviceInfos.append(networkDeviceInfo);
emit hostAddressDiscovered(networkDeviceInfo.address());
emit networkDeviceInfoAdded(networkDeviceInfo);
}
}
void NetworkDeviceDiscoveryReplyImpl::addVirtualNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo)
{
// Note: this method will be called only from the discovery
if (!m_networkDeviceInfos.hasHostAddress(networkDeviceInfo.address())) {
m_virtualNetworkDeviceInfos.append(networkDeviceInfo);
}
}
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 containing following 2 lines:";
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]);
}
}
}
}

View File

@ -43,8 +43,6 @@ class NetworkDeviceDiscoveryReplyImpl : public NetworkDeviceDiscoveryReply
{
Q_OBJECT
friend class NetworkDeviceDiscoveryImpl;
public:
explicit NetworkDeviceDiscoveryReplyImpl(QObject *parent = nullptr);
~NetworkDeviceDiscoveryReplyImpl() override = default;
@ -52,6 +50,20 @@ public:
NetworkDeviceInfos networkDeviceInfos() const override;
NetworkDeviceInfos virtualNetworkDeviceInfos() const override;
bool isFinished() const override;
void setFinished(bool finished);
// 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();
public slots:
void addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo);
void addVirtualNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo);
private:
NetworkDeviceInfos m_networkDeviceInfos; // Contains only complete and valid infos
NetworkDeviceInfos m_virtualNetworkDeviceInfos; // Contains ping responses without ARP, like VPN devices
@ -59,6 +71,8 @@ private:
QHash<MacAddress, NetworkDeviceInfo> m_networkDeviceCache;
qint64 m_startTimestamp;
bool m_isFinished = false;
// Temporary cache for ping responses where the mac is not known yet (like VPN devices)
QHash<QHostAddress, NetworkDeviceInfo> m_pingCache;
@ -67,12 +81,7 @@ private:
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();
};
}

View File

@ -46,6 +46,8 @@ public:
virtual NetworkDeviceInfos networkDeviceInfos() const = 0;
virtual NetworkDeviceInfos virtualNetworkDeviceInfos() const = 0;
virtual bool isFinished() const = 0;
signals:
// Emitted whenever a certain host address has been pinged successfully
void hostAddressDiscovered(const QHostAddress &address);