From d03ddf9fc8d002e526d3d049987227111a59656e Mon Sep 17 00:00:00 2001 From: Johannes Ziemke Date: Thu, 20 Nov 2025 12:54:44 +0100 Subject: [PATCH 1/2] Fix concurrent mDNS resolution by verifying requested hostname MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - libsrc/mdns/MdnsBrowser.cpp:119-170 now keeps each resolver self-contained: the resolver’s resolved signal is handled locally (no shared isAddressResolved signal), link‑local results are ignored on the spot, and the completion signal now carries the hostname so concurrent lookups can’t bleed into one another. - include/mdns/MdnsBrowser.h:62-112 reflects the slimmer API—removed the unused slot/signal and updated isFirstAddressResolved to emit both hostname and address. - include/utils/NetUtils.h:109-146 listens for the hostname-tagged completion and ignores unrelated emissions, so every resolveMDnsHostToAddress call exits only when its own hostname was resolved. Courtesy of OpenAI Codex ;) This fixes #1906 --- CHANGELOG.md | 3 ++- include/mdns/MdnsBrowser.h | 6 ++---- include/utils/NetUtils.h | 11 +++++++++-- libsrc/mdns/MdnsBrowser.cpp | 37 +++++++++++++------------------------ 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bb3dd211..39b3fa9f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - LED-device updates queue up and let Hyperion crash (#1887) - The color of the backlight threshold is green, not white/gray (#1899) - Install - Ubuntu 25.10 unable to install due to libcec package (#1934) + - Fix concurrent mDNS resolution (#1906) --- @@ -61,7 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Fixes:** - WebUI unreachable via IPv6 (#1871) - Align install_pr script working with default Qt6 builds & show authentication failures (#1871) - + - **Build:** - Added Debian Trixie to PR-builds for early testing diff --git a/include/mdns/MdnsBrowser.h b/include/mdns/MdnsBrowser.h index 17401a26a..18313c941 100644 --- a/include/mdns/MdnsBrowser.h +++ b/include/mdns/MdnsBrowser.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -88,8 +89,7 @@ public slots: */ void serviceRemoved(const QMdnsEngine::Service& service); - void isAddressResolved(QHostAddress address); - void isFirstAddressResolved(QHostAddress address); + void isFirstAddressResolved(QString hostname, QHostAddress address); void isServiceRecordResolved(QMdnsEngine::Record serviceRecord) const; @@ -101,8 +101,6 @@ private slots: void onServiceUpdated(const QMdnsEngine::Service& service); void onServiceRemoved(const QMdnsEngine::Service& service); - void onHostNameResolved(const QHostAddress& address); - private: /// The logger instance for mDNS-Service QSharedPointer _log; diff --git a/include/utils/NetUtils.h b/include/utils/NetUtils.h index a3a2f63e5..3c331e135 100644 --- a/include/utils/NetUtils.h +++ b/include/utils/NetUtils.h @@ -118,8 +118,15 @@ inline bool resolveMDnsHostToAddress(QSharedPointer log, const QString& QEventLoop loop; // Connect the signal to capture the resolved address - QObject::connect(browser, &MdnsBrowser::isFirstAddressResolved, &loop, [&](QHostAddress addr) { - hostAddress = std::move(addr); + QString const requestedHostname = hostname; + QObject::connect(browser, &MdnsBrowser::isFirstAddressResolved, &loop, + [&, requestedHostname](const QString& resolvedHostname, const QHostAddress& addr) { + if (resolvedHostname.compare(requestedHostname, Qt::CaseInsensitive) != 0) + { + return; + } + + hostAddress = addr; loop.quit(); }); diff --git a/libsrc/mdns/MdnsBrowser.cpp b/libsrc/mdns/MdnsBrowser.cpp index fcd071af5..5390c6830 100644 --- a/libsrc/mdns/MdnsBrowser.cpp +++ b/libsrc/mdns/MdnsBrowser.cpp @@ -116,21 +116,6 @@ void MdnsBrowser::onServiceRemoved(const QMdnsEngine::Service& service) emit serviceRemoved(service); } -void MdnsBrowser::onHostNameResolved(const QHostAddress& address) -{ - DebugIf(verboseBrowser, _log, "for address [%s], Thread: %s", QSTRING_CSTR(address.toString()), QSTRING_CSTR(QThread::currentThread()->objectName())); - - // Do not publish link local addresses -#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) - if (!address.isLinkLocal()) -#else - if (!address.toString().startsWith("fe80")) -#endif - { - emit isAddressResolved(address); - } -} - void MdnsBrowser::resolveFirstAddress(QSharedPointer log, const QString& hostname, std::chrono::milliseconds timeout) { qRegisterMetaType("Message"); @@ -140,7 +125,6 @@ void MdnsBrowser::resolveFirstAddress(QSharedPointer log, const QString& if (hostname.endsWith(".local") || hostname.endsWith(".local.")) { QMdnsEngine::Resolver const resolver (_server.get(), hostname.toUtf8(), _cache.get()); - connect(&resolver, &QMdnsEngine::Resolver::resolved, this, &MdnsBrowser::onHostNameResolved); DebugIf(verboseBrowser, log, "Wait for resolver on hostname [%s]", QSTRING_CSTR(hostname)); @@ -148,16 +132,21 @@ void MdnsBrowser::resolveFirstAddress(QSharedPointer log, const QString& QTimer timer; timer.setSingleShot(true); - connect(&timer, &QTimer::timeout, this, [&loop]() { - loop.quit(); // Stop waiting if timeout occurs - }); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + + connect(&resolver, &QMdnsEngine::Resolver::resolved, &loop, [ &loop, &resolvedAddress ](const QHostAddress& address) { + // Ignore link-local addresses +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + if (address.isLinkLocal()) +#else + if (address.toString().startsWith("fe80")) +#endif + { + return; + } - std::unique_ptr context{new QObject}; - QObject* pcontext = context.get(); - connect(this, &MdnsBrowser::isAddressResolved, pcontext, [ &loop, &resolvedAddress, context = std::move(context)](const QHostAddress &address) mutable { resolvedAddress = address; loop.quit(); - context.reset(); }); timer.start(timeout); @@ -177,7 +166,7 @@ void MdnsBrowser::resolveFirstAddress(QSharedPointer log, const QString& Error(log, "Hostname [%s] is not an mDNS hostname.", QSTRING_CSTR(hostname)); } - emit isFirstAddressResolved(resolvedAddress); + emit isFirstAddressResolved(hostname, resolvedAddress); } void MdnsBrowser::resolveServiceInstance(const QByteArray& serviceInstance, const std::chrono::milliseconds waitTime) const From 2dc27521170774772f980376d1fcbc181ea8708b Mon Sep 17 00:00:00 2001 From: Johannes Ziemke Date: Thu, 20 Nov 2025 14:11:48 +0100 Subject: [PATCH 2/2] Try this --- include/mdns/MdnsBrowser.h | 2 +- include/utils/NetUtils.h | 69 +++++++++++++++++++++---------------- libsrc/mdns/MdnsBrowser.cpp | 2 +- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/include/mdns/MdnsBrowser.h b/include/mdns/MdnsBrowser.h index 18313c941..bf1700f2f 100644 --- a/include/mdns/MdnsBrowser.h +++ b/include/mdns/MdnsBrowser.h @@ -91,7 +91,7 @@ public slots: void isFirstAddressResolved(QString hostname, QHostAddress address); - void isServiceRecordResolved(QMdnsEngine::Record serviceRecord) const; + void isServiceRecordResolved(QByteArray serviceInstance, QMdnsEngine::Record serviceRecord) const; private slots: diff --git a/include/utils/NetUtils.h b/include/utils/NetUtils.h index 3c331e135..95723deb0 100644 --- a/include/utils/NetUtils.h +++ b/include/utils/NetUtils.h @@ -196,7 +196,14 @@ inline QMdnsEngine::Record resolveMDnsServiceRecord(const QByteArray& serviceIns QEventLoop loop; // Connect the signal to capture the resolved service record - QObject::connect(browser, &MdnsBrowser::isServiceRecordResolved, &loop, [&](QMdnsEngine::Record resolvedServiceRecord) { + QByteArray const requestedService = serviceInstance; + QObject::connect(browser, &MdnsBrowser::isServiceRecordResolved, &loop, + [&, requestedService](const QByteArray& emittedService, const QMdnsEngine::Record& resolvedServiceRecord) { + if (emittedService != requestedService) + { + return; + } + serviceRecord = resolvedServiceRecord; loop.quit(); }); @@ -231,27 +238,29 @@ inline bool resolveHostToAddress(QSharedPointer log, const QString& host if (hostname.endsWith("._tcp.local")) { //Treat hostname as service instance name that requires to be resolved into an mDNS-Hostname first - QMdnsEngine::Record const service = resolveMDnsServiceRecord(hostname.toUtf8()); - if (!service.target().isEmpty()) + QByteArray requestedService = hostname.toUtf8(); + if (!requestedService.endsWith('.')) { - if (!service.target().isEmpty()) - { - Info(log, "Resolved service [%s] to mDNS hostname [%s], service port [%d]", QSTRING_CSTR(hostname), service.target().constData(), service.port()); - target = service.target(); - port = service.port(); - } - else - { - Error(log, "Failed to resolved service [%s] to an mDNS hostname", QSTRING_CSTR(hostname)); - return false; - } + requestedService.append('.'); } - else + + QMdnsEngine::Record const serviceRecord = resolveMDnsServiceRecord(hostname.toUtf8()); + if (serviceRecord.name().isEmpty() || serviceRecord.target().isEmpty()) { Error(log, "Cannot resolve mDNS hostname for given service [%s]!", QSTRING_CSTR(hostname)); return false; } + if (serviceRecord.name() != requestedService) + { + Error(log, "Resolved record [%s] does not match requested service [%s]", serviceRecord.name().constData(), requestedService.constData()); + return false; + } + + Info(log, "Resolved service [%s] to mDNS hostname [%s], service port [%d]", QSTRING_CSTR(hostname), serviceRecord.target().constData(), serviceRecord.port()); + target = serviceRecord.target(); + port = serviceRecord.port(); + QHostAddress resolvedAddress; if (NetUtils::resolveMDnsHostToAddress(log, target, resolvedAddress)) { @@ -293,27 +302,29 @@ inline bool resolveMdnsHost(QSharedPointer log, QString& hostname, int& if (hostname.endsWith("._tcp.local")) { //Treat hostname as service instance name that requires to be resolved into an mDNS-Hostname first - QMdnsEngine::Record const service = resolveMDnsServiceRecord(hostname.toUtf8()); - if (!service.target().isEmpty()) + QByteArray requestedService = hostname.toUtf8(); + if (!requestedService.endsWith('.')) { - if (!service.target().isEmpty()) - { - Info(log, "Resolved service [%s] to mDNS hostname [%s], service port [%d]", QSTRING_CSTR(hostname), service.target().constData(), service.port()); - target = service.target(); - port = service.port(); - } - else - { - Error(log, "Failed to resolved service [%s] to an mDNS hostname", QSTRING_CSTR(hostname)); - return false; - } + requestedService.append('.'); } - else + + QMdnsEngine::Record const serviceRecord = resolveMDnsServiceRecord(hostname.toUtf8()); + if (serviceRecord.name().isEmpty() || serviceRecord.target().isEmpty()) { Error(log, "Cannot resolve mDNS hostname for given service [%s]!", QSTRING_CSTR(hostname)); return false; } + if (serviceRecord.name() != requestedService) + { + Error(log, "Resolved record [%s] does not match requested service [%s]", serviceRecord.name().constData(), requestedService.constData()); + return false; + } + + Info(log, "Resolved service [%s] to mDNS hostname [%s], service port [%d]", QSTRING_CSTR(hostname), serviceRecord.target().constData(), serviceRecord.port()); + target = serviceRecord.target(); + port = serviceRecord.port(); + QHostAddress resolvedAddress; if (NetUtils::resolveMDnsHostToAddress(log, target, resolvedAddress)) { diff --git a/libsrc/mdns/MdnsBrowser.cpp b/libsrc/mdns/MdnsBrowser.cpp index 5390c6830..fede24583 100644 --- a/libsrc/mdns/MdnsBrowser.cpp +++ b/libsrc/mdns/MdnsBrowser.cpp @@ -214,7 +214,7 @@ void MdnsBrowser::resolveServiceInstance(const QByteArray& serviceInstance, cons Debug(_log, "No service record found for service instance [%s]", service.constData()); } } - emit isServiceRecordResolved(srvRecord); + emit isServiceRecordResolved(serviceInstance, srvRecord); } QMdnsEngine::Service MdnsBrowser::getFirstService(const QByteArray& serviceType, const QString& filter, const std::chrono::milliseconds waitTime) const