diff options
Diffstat (limited to 'src/network/kernel/qdnslookup.cpp')
-rw-r--r-- | src/network/kernel/qdnslookup.cpp | 840 |
1 files changed, 725 insertions, 115 deletions
diff --git a/src/network/kernel/qdnslookup.cpp b/src/network/kernel/qdnslookup.cpp index f679f27b36..b7784917c1 100644 --- a/src/network/kernel/qdnslookup.cpp +++ b/src/network/kernel/qdnslookup.cpp @@ -1,57 +1,43 @@ -/**************************************************************************** -** -** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> +// Copyright (C) 2023 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qdnslookup.h" #include "qdnslookup_p.h" +#include <qapplicationstatic.h> #include <qcoreapplication.h> #include <qdatetime.h> +#include <qendian.h> +#include <qloggingcategory.h> #include <qrandom.h> +#include <qspan.h> #include <qurl.h> +#if QT_CONFIG(ssl) +# include <qsslsocket.h> +#endif + #include <algorithm> QT_BEGIN_NAMESPACE -#if QT_CONFIG(thread) -Q_GLOBAL_STATIC(QDnsLookupThreadPool, theDnsLookupThreadPool); -#endif +using namespace Qt::StringLiterals; + +Q_STATIC_LOGGING_CATEGORY(lcDnsLookup, "qt.network.dnslookup", QtCriticalMsg) + +namespace { +struct QDnsLookupThreadPool : QThreadPool +{ + QDnsLookupThreadPool() + { + // Run up to 5 lookups in parallel. + setMaxThreadCount(5); + } +}; +} + +Q_APPLICATION_STATIC(QDnsLookupThreadPool, theDnsLookupThreadPool); static bool qt_qdnsmailexchangerecord_less_than(const QDnsMailExchangeRecord &r1, const QDnsMailExchangeRecord &r2) { @@ -86,7 +72,7 @@ static void qt_qdnsmailexchangerecord_sort(QList<QDnsMailExchangeRecord> &record // Randomize the slice of records. while (!slice.isEmpty()) { - const unsigned int pos = QRandomGenerator::global()->bounded(int(slice.size())); + const unsigned int pos = QRandomGenerator::global()->bounded(slice.size()); records[i++] = slice.takeAt(pos); } } @@ -155,9 +141,6 @@ static void qt_qdnsservicerecord_sort(QList<QDnsServiceRecord> &records) } } -const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses = - QT_TRANSLATE_NOOP("QDnsLookupRunnable", "IPv6 addresses for nameservers are currently not supported"); - /*! \class QDnsLookup \brief The QDnsLookup class represents a DNS lookup. @@ -184,6 +167,42 @@ const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses = \note If you simply want to find the IP address(es) associated with a host name, or the host name associated with an IP address you should use QHostInfo instead. + + \section1 DNS-over-TLS and Authentic Data + + QDnsLookup supports DNS-over-TLS (DoT, as specified by \l{RFC 7858}) on + some platforms. That currently includes all Unix platforms where regular + queries are supported, if \l QSslSocket support is present in Qt. To query + if support is present at runtime, use isProtocolSupported(). + + When using DNS-over-TLS, QDnsLookup only implements the "Opportunistic + Privacy Profile" method of authentication, as described in \l{RFC 7858} + section 4.1. In this mode, QDnsLookup (through \l QSslSocket) only + validates that the server presents a certificate that is valid for the + server being connected to. Clients may use setSslConfiguration() to impose + additional restrictions and sslConfiguration() to obtain information after + the query is complete. + + QDnsLookup will request DNS servers queried over TLS to perform + authentication on the data they return. If they confirm the data is valid, + the \l authenticData property will be set to true. QDnsLookup does not + verify the integrity of the data by itself, so applications should only + trust this property on servers they have confirmed through other means to + be trustworthy. + + \section2 Authentic Data without TLS + + QDnsLookup request Authentic Data for any server set with setNameserver(), + even if TLS encryption is not required. This is useful when querying a + caching nameserver on the same host as the application or on a trusted + network. Though similar to the TLS case, the application is responsible for + determining if the server it chose to use is trustworthy, and if the + unencrypted connection cannot be tampered with. + + QDnsLookup obeys the system configuration to request Authentic Data on the + default nameserver (that is, if setNameserver() is not called). This is + currently only supported on Linux systems using glibc 2.31 or later. On any + other systems, QDnsLookup will ignore the AD bit in the query header. */ /*! @@ -212,6 +231,9 @@ const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses = \value NotFoundError the requested domain name does not exist (NXDOMAIN). + + \value TimeoutError the server was not reached or did not reply + in time (since 6.6). */ /*! @@ -235,10 +257,72 @@ const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses = \value SRV service records. + \value[since 6.8] TLSA TLS association records. + \value TXT text records. */ /*! + \enum QDnsLookup::Protocol + + Indicates the type of DNS server that is being queried. + + \value Standard + Regular, unencrypted DNS, using UDP and falling back to TCP as necessary + (default port: 53) + + \value DnsOverTls + Encrypted DNS over TLS (DoT, as specified by \l{RFC 7858}), over TCP + (default port: 853) + + \sa isProtocolSupported(), nameserverProtocol, setNameserver() +*/ + +/*! + \since 6.8 + + Returns true if DNS queries using \a protocol are supported with QDnsLookup. + + \sa nameserverProtocol +*/ +bool QDnsLookup::isProtocolSupported(Protocol protocol) +{ +#if QT_CONFIG(libresolv) || defined(Q_OS_WIN) + switch (protocol) { + case QDnsLookup::Standard: + return true; + case QDnsLookup::DnsOverTls: +# if QT_CONFIG(ssl) + if (QSslSocket::supportsSsl()) + return true; +# endif + return false; + } +#else + Q_UNUSED(protocol) +#endif + return false; +} + +/*! + \since 6.8 + + Returns the standard (default) port number for the protocol \a protocol. + + \sa isProtocolSupported() +*/ +quint16 QDnsLookup::defaultPortForProtocol(Protocol protocol) noexcept +{ + switch (protocol) { + case QDnsLookup::Standard: + return DnsPort; + case QDnsLookup::DnsOverTls: + return DnsOverTlsPort; + } + return 0; // will probably fail somewhere +} + +/*! \fn void QDnsLookup::finished() This signal is emitted when the reply has finished processing. @@ -267,8 +351,8 @@ const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses = QDnsLookup::QDnsLookup(QObject *parent) : QObject(*new QDnsLookupPrivate, parent) { - qRegisterMetaType<QDnsLookupReply>(); } + /*! Constructs a QDnsLookup object for the given \a type and \a name and sets \a parent as the parent object. @@ -278,7 +362,6 @@ QDnsLookup::QDnsLookup(Type type, const QString &name, QObject *parent) : QObject(*new QDnsLookupPrivate, parent) { Q_D(QDnsLookup); - qRegisterMetaType<QDnsLookupReply>(); d->name = name; d->type = type; } @@ -286,21 +369,68 @@ QDnsLookup::QDnsLookup(Type type, const QString &name, QObject *parent) /*! \fn QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, QObject *parent) \since 5.4 - Constructs a QDnsLookup object for the given \a type, \a name and - \a nameserver and sets \a parent as the parent object. + + Constructs a QDnsLookup object to issue a query for \a name of record type + \a type, using the DNS server \a nameserver running on the default DNS port, + and sets \a parent as the parent object. */ QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, QObject *parent) + : QDnsLookup(type, name, nameserver, 0, parent) +{ +} + +/*! + \fn QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, quint16 port, QObject *parent) + \since 6.6 + + Constructs a QDnsLookup object to issue a query for \a name of record type + \a type, using the DNS server \a nameserver running on port \a port, and + sets \a parent as the parent object. + +//! [nameserver-port] + \note Setting the port number to any value other than the default (53) can + cause the name resolution to fail, depending on the operating system + limitations and firewalls, if the nameserverProtocol() to be used + QDnsLookup::Standard. Notably, the Windows API used by QDnsLookup is unable + to handle alternate port numbers. +//! [nameserver-port] +*/ +QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, quint16 port, QObject *parent) : QObject(*new QDnsLookupPrivate, parent) { Q_D(QDnsLookup); - qRegisterMetaType<QDnsLookupReply>(); d->name = name; d->type = type; + d->port = port; d->nameserver = nameserver; } /*! + \since 6.8 + + Constructs a QDnsLookup object to issue a query for \a name of record type + \a type, using the DNS server \a nameserver running on port \a port, and + sets \a parent as the parent object. + + The query will be sent using \a protocol, if supported. Use + isProtocolSupported() to check if it is supported. + + \include qdnslookup.cpp nameserver-port +*/ +QDnsLookup::QDnsLookup(Type type, const QString &name, Protocol protocol, + const QHostAddress &nameserver, quint16 port, QObject *parent) + : QObject(*new QDnsLookupPrivate, parent) +{ + Q_D(QDnsLookup); + d->name = name; + d->type = type; + d->nameserver = nameserver; + d->port = port; + d->protocol = protocol; +} + +/*! Destroys the QDnsLookup object. It is safe to delete a QDnsLookup object even if it is not finished, you @@ -312,6 +442,28 @@ QDnsLookup::~QDnsLookup() } /*! + \since 6.8 + \property QDnsLookup::authenticData + \brief whether the reply was authenticated by the resolver. + + QDnsLookup does not perform the authentication itself. Instead, it trusts + the name server that was queried to perform the authentication and report + it. The application is responsible for determining if any servers it + configured with setNameserver() are trustworthy; if no server was set, + QDnsLookup obeys system configuration on whether responses should be + trusted. + + This property may be set even if error() indicates a resolver error + occurred. + + \sa setNameserver(), nameserverProtocol() +*/ +bool QDnsLookup::isAuthenticData() const +{ + return d_func()->reply.authenticData; +} + +/*! \property QDnsLookup::error \brief the type of error that occurred if the DNS lookup failed, or NoError. */ @@ -344,6 +496,10 @@ bool QDnsLookup::isFinished() const \property QDnsLookup::name \brief the name to lookup. + If the name to look up is empty, QDnsLookup will attempt to resolve the + root domain of DNS. That query is usually performed with QDnsLookup::type + set to \l{QDnsLookup::Type}{NS}. + \note The name will be encoded using IDNA, which means it's unsuitable for querying SRV records compatible with the DNS-SD specification. */ @@ -356,10 +512,13 @@ QString QDnsLookup::name() const void QDnsLookup::setName(const QString &name) { Q_D(QDnsLookup); - if (name != d->name) { - d->name = name; - emit nameChanged(name); - } + d->name = name; +} + +QBindable<QString> QDnsLookup::bindableName() +{ + Q_D(QDnsLookup); + return &d->name; } /*! @@ -375,10 +534,13 @@ QDnsLookup::Type QDnsLookup::type() const void QDnsLookup::setType(Type type) { Q_D(QDnsLookup); - if (type != d->type) { - d->type = type; - emit typeChanged(type); - } + d->type = type; +} + +QBindable<QDnsLookup::Type> QDnsLookup::bindableType() +{ + Q_D(QDnsLookup); + return &d->type; } /*! @@ -394,10 +556,83 @@ QHostAddress QDnsLookup::nameserver() const void QDnsLookup::setNameserver(const QHostAddress &nameserver) { Q_D(QDnsLookup); - if (nameserver != d->nameserver) { - d->nameserver = nameserver; - emit nameserverChanged(nameserver); - } + d->nameserver = nameserver; +} + +QBindable<QHostAddress> QDnsLookup::bindableNameserver() +{ + Q_D(QDnsLookup); + return &d->nameserver; +} + +/*! + \property QDnsLookup::nameserverPort + \since 6.6 + \brief the port number of nameserver to use for DNS lookup. + + The value of 0 indicates that QDnsLookup should use the default port for + the nameserverProtocol(). + + \include qdnslookup.cpp nameserver-port +*/ + +quint16 QDnsLookup::nameserverPort() const +{ + return d_func()->port; +} + +void QDnsLookup::setNameserverPort(quint16 nameserverPort) +{ + Q_D(QDnsLookup); + d->port = nameserverPort; +} + +QBindable<quint16> QDnsLookup::bindableNameserverPort() +{ + Q_D(QDnsLookup); + return &d->port; +} + +/*! + \property QDnsLookup::nameserverProtocol + \since 6.8 + \brief the protocol to use when sending the DNS query + + \sa isProtocolSupported() +*/ +QDnsLookup::Protocol QDnsLookup::nameserverProtocol() const +{ + return d_func()->protocol; +} + +void QDnsLookup::setNameserverProtocol(Protocol protocol) +{ + d_func()->protocol = protocol; +} + +QBindable<QDnsLookup::Protocol> QDnsLookup::bindableNameserverProtocol() +{ + return &d_func()->protocol; +} + +/*! + \fn void QDnsLookup::setNameserver(const QHostAddress &nameserver, quint16 port) + \since 6.6 + + Sets the nameserver to \a nameserver and the port to \a port. + + \include qdnslookup.cpp nameserver-port + + \sa QDnsLookup::nameserver, QDnsLookup::nameserverPort +*/ + +void QDnsLookup::setNameserver(Protocol protocol, const QHostAddress &nameserver, quint16 port) +{ + Qt::beginPropertyUpdateGroup(); + setNameserver(nameserver); + setNameserverPort(port); + setNameserverProtocol(protocol); + Qt::endPropertyUpdateGroup(); } /*! @@ -472,6 +707,46 @@ QList<QDnsTextRecord> QDnsLookup::textRecords() const } /*! + \since 6.8 + Returns the list of TLS association records associated with this lookup. + + According to the standards relating to DNS-based Authentication of Named + Entities (DANE), this field should be ignored and must not be used for + verifying the authentity of a given server if the authenticity of the DNS + reply cannot itself be confirmed. See isAuthenticData() for more + information. + */ +QList<QDnsTlsAssociationRecord> QDnsLookup::tlsAssociationRecords() const +{ + return d_func()->reply.tlsAssociationRecords; +} + +#if QT_CONFIG(ssl) +/*! + \since 6.8 + Sets the \a sslConfiguration to use for outgoing DNS-over-TLS connections. + + \sa sslConfiguration(), QSslSocket::setSslConfiguration() +*/ +void QDnsLookup::setSslConfiguration(const QSslConfiguration &sslConfiguration) +{ + Q_D(QDnsLookup); + d->sslConfiguration.emplace(sslConfiguration); +} + +/*! + Returns the current SSL configuration. + + \sa setSslConfiguration() +*/ +QSslConfiguration QDnsLookup::sslConfiguration() const +{ + const Q_D(QDnsLookup); + return d->sslConfiguration.value_or(QSslConfiguration::defaultConfiguration()); +} +#endif + +/*! Aborts the DNS lookup operation. If the lookup is already finished, does nothing. @@ -501,13 +776,32 @@ void QDnsLookup::lookup() Q_D(QDnsLookup); d->isFinished = false; d->reply = QDnsLookupReply(); - d->runnable = new QDnsLookupRunnable(d->type, QUrl::toAce(d->name), d->nameserver); - connect(d->runnable, SIGNAL(finished(QDnsLookupReply)), - this, SLOT(_q_lookupFinished(QDnsLookupReply)), - Qt::BlockingQueuedConnection); -#if QT_CONFIG(thread) - theDnsLookupThreadPool()->start(d->runnable); + if (!QCoreApplication::instance()) { + // NOT qCWarning because this isn't a result of the lookup + qWarning("QDnsLookup requires a QCoreApplication"); + return; + } + + auto l = [this](const QDnsLookupReply &reply) { + Q_D(QDnsLookup); + if (d->runnable == sender()) { +#ifdef QDNSLOOKUP_DEBUG + qDebug("DNS reply for %s: %i (%s)", qPrintable(d->name), reply.error, qPrintable(reply.errorString)); #endif +#if QT_CONFIG(ssl) + d->sslConfiguration = std::move(reply.sslConfiguration); +#endif + d->reply = reply; + d->runnable = nullptr; + d->isFinished = true; + emit finished(); + } + }; + + d->runnable = new QDnsLookupRunnable(d); + connect(d->runnable, &QDnsLookupRunnable::finished, this, l, + Qt::BlockingQueuedConnection); + theDnsLookupThreadPool->start(d->runnable); } /*! @@ -984,18 +1278,249 @@ QDnsTextRecord &QDnsTextRecord::operator=(const QDnsTextRecord &other) very fast and never fails. */ -void QDnsLookupPrivate::_q_lookupFinished(const QDnsLookupReply &_reply) +/*! + \class QDnsTlsAssociationRecord + \since 6.8 + \brief The QDnsTlsAssociationRecord class stores information about a DNS TLSA record. + + \inmodule QtNetwork + \ingroup network + \ingroup shared + + When performing a text lookup, zero or more records will be returned. Each + record is represented by a QDnsTlsAssociationRecord instance. + + The meaning of the fields is defined in \l{RFC 6698}. + + \sa QDnsLookup +*/ + +QT_DEFINE_QSDP_SPECIALIZATION_DTOR(QDnsTlsAssociationRecordPrivate) + +/*! + \enum QDnsTlsAssociationRecord::CertificateUsage + + This enumeration contains valid values for the certificate usage field of + TLS Association queries. The following list is up-to-date with \l{RFC 6698} + section 2.1.1 and RFC 7218 section 2.1. Please refer to those documents for + authoritative instructions on interpreting this enumeration. + + \value CertificateAuthorityConstrait + Indicates the record includes an association to a specific Certificate + Authority that must be found in the TLS server's certificate chain and + must pass PKIX validation. + + \value ServiceCertificateConstraint + Indicates the record includes an association to a certificate that must + match the end entity certificate provided by the TLS server and must + pass PKIX validation. + + \value TrustAnchorAssertion + Indicates the record includes an association to a certificate that MUST + be used as the ultimate trust anchor to validate the TLS server's + certificate and must pass PKIX validation. + + \value DomainIssuedCertificate + Indicates the record includes an association to a certificate that must + match the end entity certificate provided by the TLS server. PKIX + validation is not tested. + + \value PrivateUse + No standard meaning applied. + + \value PKIX_TA + Alias; mnemonic for Public Key Infrastructure Trust Anchor + + \value PKIX_EE + Alias; mnemonic for Public Key Infrastructure End Entity + + \value DANE_TA + Alias; mnemonic for DNS-based Authentication of Named Entities Trust Anchor + + \value DANE_EE + Alias; mnemonic for DNS-based Authentication of Named Entities End Entity + + \value PrivCert + Alias + + Other values are currently reserved, but may be unreserved by future + standards. This enumeration can be used for those values even if no + enumerator is provided. + + \sa usage() +*/ + +/*! + \enum QDnsTlsAssociationRecord::Selector + + This enumeration contains valid values for the selector field of TLS + Association queries. The following list is up-to-date with \l{RFC 6698} + section 2.1.2 and RFC 7218 section 2.2. Please refer to those documents for + authoritative instructions on interpreting this enumeration. + + \value FullCertificate + Indicates this record refers to the full certificate in its binary + structure form. + + \value SubjectPublicKeyInfo + Indicates the record refers to the certificate's subject and public + key information, in DER-encoded binary structure form. + + \value PrivateUse + No standard meaning applied. + + \value Cert + Alias + + \value SPKI + Alias + + \value PrivSel + Alias + + Other values are currently reserved, but may be unreserved by future + standards. This enumeration can be used for those values even if no + enumerator is provided. + + \sa selector() +*/ + +/*! + \enum QDnsTlsAssociationRecord::MatchingType + + This enumeration contains valid values for the matching type field of TLS + Association queries. The following list is up-to-date with \l{RFC 6698} + section 2.1.3 and RFC 7218 section 2.3. Please refer to those documents for + authoritative instructions on interpreting this enumeration. + + \value Exact + Indicates this the certificate or SPKI data is stored verbatim in this + record. + + \value Sha256 + Indicates this a SHA-256 checksum of the the certificate or SPKI data + present in this record. + + \value Sha512 + Indicates this a SHA-512 checksum of the the certificate or SPKI data + present in this record. + + \value PrivateUse + No standard meaning applied. + + \value PrivMatch + Alias + + Other values are currently reserved, but may be unreserved by future + standards. This enumeration can be used for those values even if no + enumerator is provided. + + \sa matchType() +*/ + +/*! + Constructs an empty TLS Association record. + */ +QDnsTlsAssociationRecord::QDnsTlsAssociationRecord() + : d(new QDnsTlsAssociationRecordPrivate) { - Q_Q(QDnsLookup); - if (runnable == q->sender()) { -#ifdef QDNSLOOKUP_DEBUG - qDebug("DNS reply for %s: %i (%s)", qPrintable(name), _reply.error, qPrintable(_reply.errorString)); +} + +/*! + Constructs a copy of \a other. + */ +QDnsTlsAssociationRecord::QDnsTlsAssociationRecord(const QDnsTlsAssociationRecord &other) = default; + +/*! + Moves the content of \a other into this object. + */ +QDnsTlsAssociationRecord & +QDnsTlsAssociationRecord::operator=(const QDnsTlsAssociationRecord &other) = default; + +/*! + Destroys this TLS Association record object. + */ +QDnsTlsAssociationRecord::~QDnsTlsAssociationRecord() = default; + +/*! + Returns the name of this record. +*/ +QString QDnsTlsAssociationRecord::name() const +{ + return d->name; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ +quint32 QDnsTlsAssociationRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Returns the certificate usage field for this record. + */ +QDnsTlsAssociationRecord::CertificateUsage QDnsTlsAssociationRecord::usage() const +{ + return d->usage; +} + +/*! + Returns the selector field for this record. + */ +QDnsTlsAssociationRecord::Selector QDnsTlsAssociationRecord::selector() const +{ + return d->selector; +} + +/*! + Returns the match type field for this record. + */ +QDnsTlsAssociationRecord::MatchingType QDnsTlsAssociationRecord::matchType() const +{ + return d->matchType; +} + +/*! + Returns the binary data field for this record. The interpretation of this + binary data depends on the three numeric fields provided by + certificateUsage(), selector(), and matchType(). + + Do note this is a binary field, even for the checksums, similar to what + QCyrptographicHash::result() returns. + */ +QByteArray QDnsTlsAssociationRecord::value() const +{ + return d->value; +} + +static QDnsLookupRunnable::EncodedLabel encodeLabel(const QString &label) +{ + QDnsLookupRunnable::EncodedLabel::value_type rootDomain = u'.'; + if (label.isEmpty()) + return QDnsLookupRunnable::EncodedLabel(1, rootDomain); + + QString encodedLabel = qt_ACE_do(label, ToAceOnly, ForbidLeadingDot); +#ifdef Q_OS_WIN + return encodedLabel; +#else + return std::move(encodedLabel).toLatin1(); +#endif +} + +inline QDnsLookupRunnable::QDnsLookupRunnable(const QDnsLookupPrivate *d) + : requestName(encodeLabel(d->name)), + nameserver(d->nameserver), + requestType(d->type), + port(d->port), + protocol(d->protocol) +{ + if (port == 0) + port = QDnsLookup::defaultPortForProtocol(protocol); +#if QT_CONFIG(ssl) + sslConfiguration = d->sslConfiguration; #endif - reply = _reply; - runnable = nullptr; - isFinished = true; - emit q->finished(); - } } void QDnsLookupRunnable::run() @@ -1003,60 +1528,145 @@ void QDnsLookupRunnable::run() QDnsLookupReply reply; // Validate input. - if (requestName.isEmpty()) { + if (qsizetype n = requestName.size(); n > MaxDomainNameLength || n == 0) { reply.error = QDnsLookup::InvalidRequestError; - reply.errorString = tr("Invalid domain name"); - emit finished(reply); - return; + reply.errorString = QDnsLookup::tr("Invalid domain name"); + } else { + // Perform request. + query(&reply); + + // Sort results. + qt_qdnsmailexchangerecord_sort(reply.mailExchangeRecords); + qt_qdnsservicerecord_sort(reply.serviceRecords); } - // Perform request. - query(requestType, requestName, nameserver, &reply); + emit finished(reply); - // Sort results. - qt_qdnsmailexchangerecord_sort(reply.mailExchangeRecords); - qt_qdnsservicerecord_sort(reply.serviceRecords); + // maybe print the lookup error as warning + switch (reply.error) { + case QDnsLookup::NoError: + case QDnsLookup::OperationCancelledError: + case QDnsLookup::NotFoundError: + case QDnsLookup::ServerFailureError: + case QDnsLookup::ServerRefusedError: + case QDnsLookup::TimeoutError: + break; // no warning for these + + case QDnsLookup::ResolverError: + case QDnsLookup::InvalidRequestError: + case QDnsLookup::InvalidReplyError: + qCWarning(lcDnsLookup()).nospace() + << "DNS lookup failed (" << reply.error << "): " + << qUtf16Printable(reply.errorString) + << "; request was " << this; // continues below + } +} - emit finished(reply); +inline QDebug operator<<(QDebug &d, QDnsLookupRunnable *r) +{ + // continued: print the information about the request + d << r->requestName.left(MaxDomainNameLength); + if (r->requestName.size() > MaxDomainNameLength) + d << "... (truncated)"; + d << " type " << r->requestType; + if (!r->nameserver.isNull()) { + d << " to nameserver " << qUtf16Printable(r->nameserver.toString()) + << " port " << (r->port ? r->port : QDnsLookup::defaultPortForProtocol(r->protocol)); + switch (r->protocol) { + case QDnsLookup::Standard: + break; + case QDnsLookup::DnsOverTls: + d << " (TLS)"; + } + } + return d; } -#if QT_CONFIG(thread) -QDnsLookupThreadPool::QDnsLookupThreadPool() - : signalsConnected(false) +#if QT_CONFIG(ssl) +static constexpr std::chrono::milliseconds DnsOverTlsConnectTimeout(15'000); +static constexpr std::chrono::milliseconds DnsOverTlsTimeout(120'000); +static constexpr quint8 DnsAuthenticDataBit = 0x20; + +static int makeReplyErrorFromSocket(QDnsLookupReply *reply, const QAbstractSocket *socket) { - // Run up to 5 lookups in parallel. - setMaxThreadCount(5); + QDnsLookup::Error error = [&] { + switch (socket->error()) { + case QAbstractSocket::SocketTimeoutError: + case QAbstractSocket::ProxyConnectionTimeoutError: + return QDnsLookup::TimeoutError; + default: + return QDnsLookup::ResolverError; + } + }(); + reply->setError(error, socket->errorString()); + return false; } -void QDnsLookupThreadPool::start(QRunnable *runnable) +bool QDnsLookupRunnable::sendDnsOverTls(QDnsLookupReply *reply, QSpan<unsigned char> query, + ReplyBuffer &response) { - // Ensure threads complete at application destruction. - if (!signalsConnected) { - QMutexLocker signalsLocker(&signalsMutex); - if (!signalsConnected) { - QCoreApplication *app = QCoreApplication::instance(); - if (!app) { - qWarning("QDnsLookup requires a QCoreApplication"); - delete runnable; - return; - } + QSslSocket socket; + socket.setSslConfiguration(sslConfiguration.value_or(QSslConfiguration::defaultConfiguration())); + +# if QT_CONFIG(networkproxy) + socket.setProtocolTag("domain-s"_L1); +# endif + + // Request the name server attempt to authenticate the reply. + query[3] |= DnsAuthenticDataBit; - moveToThread(app->thread()); - connect(app, SIGNAL(destroyed()), - SLOT(_q_applicationDestroyed()), Qt::DirectConnection); - signalsConnected = true; + do { + quint16 size = qToBigEndian<quint16>(query.size()); + QDeadlineTimer timeout(DnsOverTlsTimeout); + + socket.connectToHostEncrypted(nameserver.toString(), port); + socket.write(reinterpret_cast<const char *>(&size), sizeof(size)); + socket.write(reinterpret_cast<const char *>(query.data()), query.size()); + if (!socket.waitForEncrypted(DnsOverTlsConnectTimeout.count())) + break; + + reply->sslConfiguration = socket.sslConfiguration(); + + // accumulate reply + auto waitForBytes = [&](void *buffer, int count) { + int remaining = timeout.remainingTime(); + while (remaining >= 0 && socket.bytesAvailable() < count) { + if (!socket.waitForReadyRead(remaining)) + return false; + } + return socket.read(static_cast<char *>(buffer), count) == count; + }; + if (!waitForBytes(&size, sizeof(size))) + break; + + // note: strictly speaking, we're allocating memory based on untrusted data + // but in practice, due to limited range of the data type (16 bits), + // the maximum allocation is small. + size = qFromBigEndian(size); + response.resize(size); + if (waitForBytes(response.data(), size)) { + // check if the AD bit is set; we'll trust it over TLS requests + if (size >= 4) + reply->authenticData = response[3] & DnsAuthenticDataBit; + return true; } - } + } while (false); - QThreadPool::start(runnable); + // handle errors + return makeReplyErrorFromSocket(reply, &socket); } - -void QDnsLookupThreadPool::_q_applicationDestroyed() +#else +bool QDnsLookupRunnable::sendDnsOverTls(QDnsLookupReply *reply, QSpan<unsigned char> query, + ReplyBuffer &response) { - waitForDone(); - signalsConnected = false; + Q_UNUSED(query) + Q_UNUSED(response) + reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr("SSL/TLS support not present")); + return false; } -#endif // QT_CONFIG(thread) +#endif + QT_END_NAMESPACE #include "moc_qdnslookup.cpp" +#include "moc_qdnslookup_p.cpp" |