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..bf1700f2f 100644 --- a/include/mdns/MdnsBrowser.h +++ b/include/mdns/MdnsBrowser.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -88,10 +89,9 @@ 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; + void isServiceRecordResolved(QByteArray serviceInstance, QMdnsEngine::Record serviceRecord) const; private slots: @@ -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..95723deb0 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(); }); @@ -189,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(); }); @@ -224,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)) { @@ -286,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 fcd071af5..fede24583 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 @@ -225,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