diff options
Diffstat (limited to 'src/network/kernel/qdnslookup_unix.cpp')
-rw-r--r-- | src/network/kernel/qdnslookup_unix.cpp | 166 |
1 files changed, 115 insertions, 51 deletions
diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp index 0217293348..9de073b781 100644 --- a/src/network/kernel/qdnslookup_unix.cpp +++ b/src/network/kernel/qdnslookup_unix.cpp @@ -6,6 +6,7 @@ #include <qendian.h> #include <qscopedpointer.h> +#include <qspan.h> #include <qurl.h> #include <qvarlengtharray.h> #include <private/qnativesocketengine_p.h> // for setSockAddr @@ -32,15 +33,13 @@ QT_REQUIRE_CONFIG(libresolv); QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; - -// minimum IPv6 MTU (1280) minus the IPv6 (40) and UDP headers (8) -static constexpr qsizetype ReplyBufferSize = 1280 - 40 - 8; +using ReplyBuffer = QDnsLookupRunnable::ReplyBuffer; // https://www.rfc-editor.org/rfc/rfc6891 static constexpr unsigned char Edns0Record[] = { 0x00, // root label T_OPT >> 8, T_OPT & 0xff, // type OPT - ReplyBufferSize >> 8, ReplyBufferSize & 0xff, // payload size + ReplyBuffer::PreallocatedSize >> 8, ReplyBuffer::PreallocatedSize & 0xff, // payload size NOERROR, // extended rcode 0, // version 0x00, 0x00, // flags @@ -68,11 +67,9 @@ using Cache = QList<QDnsCachedName>; // QHash or QMap are overkill // https://docs.oracle.com/cd/E86824_01/html/E54774/res-setservers-3resolv.html static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port) { - if (!nameserver.isNull()) { - union res_sockaddr_union u; - setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port); - res_setservers(state, &u, 1); - } + union res_sockaddr_union u; + setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port); + res_setservers(state, &u, 1); return true; } #else @@ -123,9 +120,6 @@ template <typename State> bool setIpv6NameServer(State *, const void *, quint16) static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port) { - if (nameserver.isNull()) - return true; - state->nscount = 1; state->nsaddr_list[0].sin_family = AF_UNSPEC; if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) @@ -153,51 +147,53 @@ prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_r return queryLength + sizeof(Edns0Record); } -void QDnsLookupRunnable::query(QDnsLookupReply *reply) +static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<unsigned char> qbuffer, + ReplyBuffer &buffer, const QHostAddress &nameserver, quint16 port) { - // Initialize state. - std::remove_pointer_t<res_state> state = {}; - if (res_ninit(&state) < 0) { - int error = errno; - qErrnoWarning(error, "QDnsLookup: Resolver initialization failed"); - return reply->makeResolverSystemError(error); - } - auto guard = qScopeGuard([&] { res_nclose(&state); }); + // Check if a nameserver was set. If so, use it. + if (!nameserver.isNull()) { + if (!applyNameServer(state, nameserver, port)) { + reply->setError(QDnsLookup::ResolverError, + QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS")); + return -1; + } - //Check if a nameserver was set. If so, use it - if (!applyNameServer(&state, nameserver, port)) - return reply->setError(QDnsLookup::ResolverError, - QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS")); -#ifdef QDNSLOOKUP_DEBUG - state.options |= RES_DEBUG; -#endif + // Request the name server attempt to authenticate the reply. + reinterpret_cast<HEADER *>(buffer.data())->ad = true; - // Prepare the DNS query. - QueryBuffer qbuffer; - int queryLength = prepareQueryBuffer(&state, qbuffer, requestName.constData(), ns_rcode(requestType)); - if (Q_UNLIKELY(queryLength < 0)) - return reply->makeResolverSystemError(); +#ifdef RES_TRUSTAD + // Need to set this option even though we set the AD bit, otherwise + // glibc turns it off. + state->options |= RES_TRUSTAD; +#endif + } - // Perform DNS query. - QVarLengthArray<unsigned char, ReplyBufferSize> buffer(ReplyBufferSize); auto attemptToSend = [&]() { std::memset(buffer.data(), 0, HFIXEDSZ); // the header is enough - int responseLength = res_nsend(&state, qbuffer.data(), queryLength, buffer.data(), buffer.size()); - if (responseLength < 0) { - // network error of some sort - if (errno == ETIMEDOUT) - reply->makeTimeoutError(); - else - reply->makeResolverSystemError(); - } - return responseLength; + int responseLength = res_nsend(state, qbuffer.data(), qbuffer.size(), buffer.data(), buffer.size()); + if (responseLength >= 0) + return responseLength; // success + + // libresolv uses ETIMEDOUT for resolver errors ("no answer") + if (errno == ECONNREFUSED) + reply->setError(QDnsLookup::ServerRefusedError, qt_error_string()); + else if (errno != ETIMEDOUT) + reply->makeResolverSystemError(); // some other error + + auto query = reinterpret_cast<HEADER *>(qbuffer.data()); + auto header = reinterpret_cast<HEADER *>(buffer.data()); + if (query->id == header->id && header->qr) + reply->makeDnsRcodeError(header->rcode); + else + reply->makeTimeoutError(); // must really be a timeout + return -1; }; // strictly use UDP, we'll deal with truncated replies ourselves - state.options |= RES_IGNTC; + state->options |= RES_IGNTC; int responseLength = attemptToSend(); if (responseLength < 0) - return; + return responseLength; // check if we need to use the virtual circuit (TCP) auto header = reinterpret_cast<HEADER *>(buffer.data()); @@ -208,17 +204,65 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) // remove the EDNS record in the query reinterpret_cast<HEADER *>(qbuffer.data())->arcount = 0; - queryLength -= sizeof(Edns0Record); + qbuffer = qbuffer.first(qbuffer.size() - sizeof(Edns0Record)); // send using the virtual circuit - state.options |= RES_USEVC; + state->options |= RES_USEVC; responseLength = attemptToSend(); if (Q_UNLIKELY(responseLength > buffer.size())) { // Ok, we give up. - return reply->setError(QDnsLookup::ResolverError, - QDnsLookup::tr("Reply was too large")); + reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr("Reply was too large")); + return -1; } } + + // We only trust the AD bit in the reply if we're querying a custom name + // server or if we can tell the system administrator configured the resolver + // to trust replies. +#ifndef RES_TRUSTAD + if (nameserver.isNull()) + header->ad = false; +#endif + reply->authenticData = header->ad; + + return responseLength; +} + +void QDnsLookupRunnable::query(QDnsLookupReply *reply) +{ + // Initialize state. + std::remove_pointer_t<res_state> state = {}; + if (res_ninit(&state) < 0) { + int error = errno; + qErrnoWarning(error, "QDnsLookup: Resolver initialization failed"); + return reply->makeResolverSystemError(error); + } + auto guard = qScopeGuard([&] { res_nclose(&state); }); + +#ifdef QDNSLOOKUP_DEBUG + state.options |= RES_DEBUG; +#endif + + // Prepare the DNS query. + QueryBuffer qbuffer; + int queryLength = prepareQueryBuffer(&state, qbuffer, requestName.constData(), ns_rcode(requestType)); + if (Q_UNLIKELY(queryLength < 0)) + return reply->makeResolverSystemError(); + + // Perform DNS query. + ReplyBuffer buffer(ReplyBufferSize); + int responseLength = -1; + switch (protocol) { + case QDnsLookup::Standard: + responseLength = sendStandardDns(reply, &state, qbuffer, buffer, nameserver, port); + break; + case QDnsLookup::DnsOverTls: + if (!sendDnsOverTls(reply, qbuffer, buffer)) + return; + responseLength = buffer.size(); + break; + } + if (responseLength < 0) return; @@ -227,6 +271,7 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) return reply->makeInvalidReplyError(); // Parse the reply. + auto header = reinterpret_cast<HEADER *>(buffer.data()); if (header->rcode) return reply->makeDnsRcodeError(header->rcode); @@ -265,7 +310,7 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) expandHost(offset); if (status < 0) return; - if (offset + status + 4 >= responseLength) + if (offset + status + 4 > responseLength) header->qdcount = 0xffff; // invalid reply below else offset += status + 4; @@ -348,6 +393,8 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid mail exchange record")); reply->mailExchangeRecords.append(record); } else if (type == QDnsLookup::SRV) { + if (size < 7) + return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record")); const quint16 priority = qFromBigEndian<quint16>(response + offset); const quint16 weight = qFromBigEndian<quint16>(response + offset + 2); const quint16 port = qFromBigEndian<quint16>(response + offset + 4); @@ -361,6 +408,23 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) if (status < 0) return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record")); reply->serviceRecords.append(record); + } else if (type == QDnsLookup::TLSA) { + // https://datatracker.ietf.org/doc/html/rfc6698#section-2.1 + if (size < 3) + return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid TLS association record")); + + const quint8 usage = response[offset]; + const quint8 selector = response[offset + 1]; + const quint8 matchType = response[offset + 2]; + + QDnsTlsAssociationRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage); + record.d->selector = QDnsTlsAssociationRecord::Selector(selector); + record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType); + record.d->value.assign(response + offset + 3, response + offset + size); + reply->tlsAssociationRecords.append(std::move(record)); } else if (type == QDnsLookup::TXT) { QDnsTextRecord record; record.d->name = name; |