diff options
author | Thiago Macieira <thiago.macieira@intel.com> | 2023-05-12 09:34:23 -0700 |
---|---|---|
committer | Thiago Macieira <thiago.macieira@intel.com> | 2023-05-26 06:46:31 -0700 |
commit | b673fa09c39e3dc86cbfb49efc5d2bbf8149af72 (patch) | |
tree | aab8a5309fac1728c8e882ce745e291d24462bb7 /src/network/kernel/qdnslookup_unix.cpp | |
parent | 4a46ba1209907796f4a14f6feb35ed4d70155d7d (diff) |
QDnsLookup/Unix large replies: use res_nmkquery & res_nsend [1/3]
The current code is inefficient when dealing with replies exceeding the
PACKETSZ default size and especially those that require the use of a
Virtual Circuit (TCP socket). When the TC (Truncated) bit is set in the
DNS reply header, res_nquery() automatically switches to VC so it is
able to return the full size of the reply to us. This means we make the
same request four times:
1) over UDP, getting ~512 bytes of data
2) over TCP, getting the full reply but returning ~512 to us
3) over UDP again, getting (maybe) 1400 bytes of data
4) over TCP again, getting all the data
This commit splits res_nquery() into its two component functions,
res_nmkquery() and res_nsend(). This is incomplete: the four queries
above still happen.
Change-Id: I3e3bfef633af4130a03afffd175e728d96d6a604
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src/network/kernel/qdnslookup_unix.cpp')
-rw-r--r-- | src/network/kernel/qdnslookup_unix.cpp | 61 |
1 files changed, 47 insertions, 14 deletions
diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp index 3dba60d58a..22b4e4798c 100644 --- a/src/network/kernel/qdnslookup_unix.cpp +++ b/src/network/kernel/qdnslookup_unix.cpp @@ -21,10 +21,18 @@ QT_REQUIRE_CONFIG(res_ninit); #endif #include <resolv.h> +#include <array> + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +static constexpr qsizetype ReplyBufferSize = PACKETSZ; + +// maximum length of a DNS query with a 255-character domain (rounded up to 16) +static constexpr qsizetype QueryBufferSize = HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1; +using QueryBuffer = std::array<unsigned char, (QueryBufferSize + 15) / 16 * 16>; + #if QT_CONFIG(res_setservers) // https://www.ibm.com/docs/en/i/7.3?topic=ssw_ibm_i_73/apis/ressetservers.html // https://docs.oracle.com/cd/E86824_01/html/E54774/res-setservers-3resolv.html @@ -97,6 +105,16 @@ static bool applyNameServer(res_state state, const QHostAddress &nameserver, qui } #endif // !QT_CONFIG(res_setservers) +static int +prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_rcode type) +{ + // Create header and our query + int queryLength = res_nmkquery(state, QUERY, label, C_IN, type, nullptr, 0, nullptr, + buffer.data(), buffer.size()); + Q_ASSERT(queryLength < int(buffer.size())); + return queryLength; +} + void QDnsLookupRunnable::query(QDnsLookupReply *reply) { // Initialize state. @@ -116,35 +134,50 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) state.options |= RES_DEBUG; #endif + // Prepare the DNS query. + QueryBuffer qbuffer; + int queryLength = prepareQueryBuffer(&state, qbuffer, requestName, ns_rcode(requestType)); + if (Q_UNLIKELY(queryLength < 0)) + return reply->makeResolverSystemError(); + // Perform DNS query. - QVarLengthArray<unsigned char, PACKETSZ> buffer(PACKETSZ); - std::memset(buffer.data(), 0, buffer.size()); - int responseLength = res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size()); - if (Q_UNLIKELY(responseLength > PACKETSZ)) { + 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 + reply->makeResolverSystemError(); + } + return responseLength; + }; + + int responseLength = attemptToSend(); + if (responseLength > buffer.size()) { + // increase our buffer size buffer.resize(responseLength); - std::memset(buffer.data(), 0, buffer.size()); - responseLength = res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size()); + responseLength = attemptToSend(); if (Q_UNLIKELY(responseLength > buffer.size())) { // Ok, we give up. return reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr("Reply was too large")); } } - - unsigned char *response = buffer.data(); - // Check the response header. Though res_nquery returns -1 as a - // responseLength in case of error, we still can extract the - // exact error code from the response. - HEADER *header = (HEADER*)response; - if (header->rcode) - return reply->makeDnsRcodeError(header->rcode); + if (responseLength < 0) + return; // Check the reply is valid. if (responseLength < int(sizeof(HEADER))) return reply->makeInvalidReplyError(); + // Parse the reply. + auto header = reinterpret_cast<HEADER *>(buffer.data()); + if (header->rcode) + return reply->makeDnsRcodeError(header->rcode); + char host[PACKETSZ], answer[PACKETSZ]; qptrdiff offset = sizeof(HEADER); + unsigned char *response = buffer.data(); int status; if (ntohs(header->qdcount) == 1) { |