Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdnslookup_unix.cpp
Go to the documentation of this file.
1// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
2// Copyright (C) 2023 Intel Corporation.
3// Copyright (C) 2026 Linus Jahn <lnj@kaidan.im>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5// Qt-Security score:critical reason:data-parser
6
7#include "qdnslookup_p.h"
8
9#include <qendian.h>
10#include <qrandom.h>
11#include <qspan.h>
12#include <qurl.h>
13#include <qvarlengtharray.h>
14#include <private/qnativesocketengine_p.h> // for setSockAddr
15#include <private/qtnetwork-config_p.h>
16
17// Two backends: libresolv on Unix and Android's DnsResolver (bionic has no
18// libresolv). They share everything but the transport.
19static_assert(QT_CONFIG(libresolv) || QT_CONFIG(android_dnsresolver),
20 "This file requires either libresolv or Android's DnsResolver");
21
22#include <sys/types.h>
23#include <netinet/in.h>
24#include <arpa/nameser.h>
25#if __has_include(<arpa/nameser_compat.h>)
26# include <arpa/nameser_compat.h>
27#endif
28#include <errno.h>
29#include <resolv.h>
30
31#if QT_CONFIG(android_dnsresolver)
32# include <android/multinetwork.h>
33#endif
34
35#include <array>
36
37#ifndef T_OPT
38// the older arpa/nameser_compat.h wasn't updated between 1999 and 2016 in glibc
39# define T_OPT ns_t_opt
40#endif
41
42QT_BEGIN_NAMESPACE
43
44using namespace Qt::StringLiterals;
45using ReplyBuffer = QDnsLookupRunnable::ReplyBuffer;
46
47// https://www.rfc-editor.org/rfc/rfc6891
48static constexpr unsigned char Edns0Record[] = {
49 0x00, // root label
50 T_OPT >> 8, T_OPT & 0xff, // type OPT
51 ReplyBuffer::PreallocatedSize >> 8, ReplyBuffer::PreallocatedSize & 0xff, // payload size
52 NOERROR, // extended rcode
53 0, // version
54 0x00, 0x00, // flags
55 0x00, 0x00, // option length
56};
57
58// maximum length of a EDNS0 query with a 255-character domain (rounded up to 16)
59static constexpr qsizetype QueryBufferSize =
60 HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1 + sizeof(Edns0Record);
61using QueryBuffer = std::array<unsigned char, (QueryBufferSize + 15) / 16 * 16>;
62
63namespace {
64struct QDnsCachedName
65{
66 QString name;
67 int code = 0;
68 QDnsCachedName(const QString &name, int code) : name(name), code(code) {}
69};
70}
72using Cache = QList<QDnsCachedName>; // QHash or QMap are overkill
73
74#if QT_CONFIG(libresolv)
75#if QT_CONFIG(res_setservers)
76// https://www.ibm.com/docs/en/i/7.3?topic=ssw_ibm_i_73/apis/ressetservers.html
77// https://docs.oracle.com/cd/E86824_01/html/E54774/res-setservers-3resolv.html
78static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
79{
80 union res_sockaddr_union u;
81 setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port);
82 res_setservers(state, &u, 1);
83 return true;
84}
85#else
86template <typename T> void setNsMap(T &ext, std::enable_if_t<sizeof(T::nsmap) != 0, uint16_t> v)
87{
88 // Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address
89 // See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html
90 // Unneeded since glibc 2.22 (2015), but doesn't hurt to set it
91 // See: https://sourceware.org/git/?p=glibc.git;a=commit;h=2212c1420c92a33b0e0bd9a34938c9814a56c0f7
92 ext.nsmap[0] = v;
93}
94template <typename T> void setNsMap(T &, ...)
95{
96 // fallback
97}
98
99template <bool Condition>
100using EnableIfIPv6 = std::enable_if_t<Condition, const QHostAddress *>;
101
102template <typename State>
103bool setIpv6NameServer(State *state,
104 EnableIfIPv6<sizeof(std::declval<State>()._u._ext.nsaddrs) != 0> addr,
105 quint16 port)
106{
107 // glibc-like API to set IPv6 name servers
108 struct sockaddr_in6 *ns = state->_u._ext.nsaddrs[0];
109
110 // nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf
111 if (!ns) {
112 // Memory allocated here will be free()'d in res_close() as we
113 // have done res_init() above.
114 ns = static_cast<struct sockaddr_in6*>(calloc(1, sizeof(struct sockaddr_in6)));
115 Q_CHECK_PTR(ns);
116 state->_u._ext.nsaddrs[0] = ns;
117 }
118
119 setNsMap(state->_u._ext, MAXNS + 1);
120 state->_u._ext.nscount6 = 1;
121 setSockaddr(ns, *addr, port);
122 return true;
123}
124
125template <typename State> bool setIpv6NameServer(State *, const void *, quint16)
126{
127 // fallback
128 return false;
129}
130
131static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
132{
133 state->nscount = 1;
134 state->nsaddr_list[0].sin_family = AF_UNSPEC;
135 if (nameserver.protocol() == QAbstractSocket::IPv6Protocol)
136 return setIpv6NameServer(state, &nameserver, port);
137 setSockaddr(&state->nsaddr_list[0], nameserver, port);
138 return true;
139}
140#endif // !QT_CONFIG(res_setservers)
141
142static int
143prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_rcode type)
144{
145 // Create header and our query
146 int queryLength = res_nmkquery(state, QUERY, label, C_IN, type, nullptr, 0, nullptr,
147 buffer.data(), buffer.size());
148 Q_ASSERT(queryLength < int(buffer.size()));
149 if (Q_UNLIKELY(queryLength < 0))
150 return queryLength;
151
152 // Append EDNS0 record and set the number of additional RRs to 1
153 Q_ASSERT(queryLength + sizeof(Edns0Record) < buffer.size());
154 std::copy_n(std::begin(Edns0Record), sizeof(Edns0Record), buffer.begin() + queryLength);
155 reinterpret_cast<HEADER *>(buffer.data())->arcount = qToBigEndian<quint16>(1);
156
157 return queryLength + sizeof(Edns0Record);
158}
159
160static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<unsigned char> qbuffer,
161 ReplyBuffer &buffer, const QHostAddress &nameserver, quint16 port)
162{
163 // Check if a nameserver was set. If so, use it.
164 if (!nameserver.isNull()) {
165 if (!applyNameServer(state, nameserver, port)) {
166 reply->setError(QDnsLookup::ResolverError,
167 QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS"));
168 return -1;
169 }
170
171 // Request the name server attempt to authenticate the reply.
172 reinterpret_cast<HEADER *>(buffer.data())->ad = true;
173
174#ifdef RES_TRUSTAD
175 // Need to set this option even though we set the AD bit, otherwise
176 // glibc turns it off.
177 state->options |= RES_TRUSTAD;
178#endif
179 }
180
181 auto attemptToSend = [&]() {
182 std::memset(buffer.data(), 0, HFIXEDSZ); // the header is enough
183 int responseLength = res_nsend(state, qbuffer.data(), qbuffer.size(), buffer.data(), buffer.size());
184 if (responseLength >= 0)
185 return responseLength; // success
186
187 // libresolv uses ETIMEDOUT for resolver errors ("no answer")
188 if (errno == ECONNREFUSED)
189 reply->setError(QDnsLookup::ServerRefusedError, qt_error_string());
190 else if (errno != ETIMEDOUT)
191 reply->makeResolverSystemError(); // some other error
192
193 auto query = reinterpret_cast<HEADER *>(qbuffer.data());
194 auto header = reinterpret_cast<HEADER *>(buffer.data());
195 if (query->id == header->id && header->qr)
196 reply->makeDnsRcodeError(header->rcode);
197 else
198 reply->makeTimeoutError(); // must really be a timeout
199 return -1;
200 };
201
202 // strictly use UDP, we'll deal with truncated replies ourselves
203 state->options |= RES_IGNTC;
204 int responseLength = attemptToSend();
205 if (responseLength < 0)
206 return responseLength;
207
208 // check if we need to use the virtual circuit (TCP)
209 auto header = reinterpret_cast<HEADER *>(buffer.data());
210 if (header->rcode == NOERROR && header->tc) {
211 // yes, increase our buffer size
212 buffer.resize(std::numeric_limits<quint16>::max());
213 header = reinterpret_cast<HEADER *>(buffer.data());
214
215 // remove the EDNS record in the query
216 reinterpret_cast<HEADER *>(qbuffer.data())->arcount = 0;
217 qbuffer = qbuffer.first(qbuffer.size() - sizeof(Edns0Record));
218
219 // send using the virtual circuit
220 state->options |= RES_USEVC;
221 responseLength = attemptToSend();
222 if (Q_UNLIKELY(responseLength > buffer.size())) {
223 // Ok, we give up.
224 reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr("Reply was too large"));
225 return -1;
226 }
227 }
228
229 // We only trust the AD bit in the reply if we're querying a custom name
230 // server or if we can tell the system administrator configured the resolver
231 // to trust replies.
232#ifndef RES_TRUSTAD
233 if (nameserver.isNull())
234 header->ad = false;
235#endif
236 reply->authenticData = header->ad;
237
238 return responseLength;
239}
240
241void QDnsLookupRunnable::query(QDnsLookupReply *reply)
242{
243 // Initialize state.
244 std::remove_pointer_t<res_state> state = {};
245 if (res_ninit(&state) < 0) {
246 int error = errno;
247 qErrnoWarning(error, "QDnsLookup: Resolver initialization failed");
248 return reply->makeResolverSystemError(error);
249 }
250 auto guard = qScopeGuard([&] { res_nclose(&state); });
251
252#ifdef QDNSLOOKUP_DEBUG
253 state.options |= RES_DEBUG;
254#endif
255
256 // Prepare the DNS query.
257 QueryBuffer qbuffer;
258 int queryLength = prepareQueryBuffer(&state, qbuffer, requestName.constData(), ns_rcode(requestType));
259 if (Q_UNLIKELY(queryLength < 0))
260 return reply->makeResolverSystemError();
261
262 // Perform DNS query.
263 QSpan query(qbuffer.data(), queryLength);
264 ReplyBuffer buffer(ReplyBufferSize);
265 int responseLength = -1;
266 switch (protocol) {
267 case QDnsLookup::Standard:
268 responseLength = sendStandardDns(reply, &state, query, buffer, nameserver, port);
269 break;
270 case QDnsLookup::DnsOverTls:
271 if (!sendDnsOverTls(reply, query, buffer))
272 return;
273 responseLength = buffer.size();
274 break;
275 }
276
277 if (responseLength < 0)
278 return;
279
280 parseResponse(reply, buffer, responseLength);
281}
282
283#else // QT_CONFIG(libresolv), that is: Q_OS_ANDROID
284
285// Bionic does not export res_nmkquery(), so we assemble the query ourselves.
286// See https://www.rfc-editor.org/rfc/rfc1035#section-4.1.
287static int prepareQueryBuffer(QueryBuffer &buffer, const char *label, ns_type type)
288{
289 std::memset(buffer.data(), 0, HFIXEDSZ);
290 auto header = new (buffer.data()) HEADER;
291 header->id = QRandomGenerator::system()->generate();
292 header->opcode = QUERY;
293 header->rd = true; // recursion desired
294 header->qdcount = qToBigEndian<quint16>(1);
295 header->arcount = qToBigEndian<quint16>(1); // EDNS0 record
296
297 // the question: QNAME, QTYPE, QCLASS; a single-question query has nothing
298 // to compress against, so dn_comp() gets no compression pointers
299 unsigned char *ptr = buffer.data() + HFIXEDSZ;
300 int labelLength = dn_comp(label, ptr,
301 int(buffer.size() - HFIXEDSZ - QFIXEDSZ - sizeof(Edns0Record)),
302 nullptr, nullptr);
303 if (Q_UNLIKELY(labelLength < 0))
304 return labelLength;
305 ptr += labelLength;
306 qToBigEndian<quint16>(type, ptr);
307 qToBigEndian<quint16>(C_IN, ptr + sizeof(quint16));
308 ptr += QFIXEDSZ;
309
310 Q_ASSERT(ptr + sizeof(Edns0Record) <= buffer.data() + buffer.size());
311 ptr = std::copy_n(std::begin(Edns0Record), sizeof(Edns0Record), ptr);
312
313 return ptr - buffer.data();
314}
315
316// These are API level 29, while Qt still supports 28, where the NDK headers
317// mark them unavailable; weakrefs are resolved to null if they are missing.
318static int local_res_nsend(net_handle_t network, const uint8_t *msg, size_t msglen, uint32_t flags)
319__attribute__((weakref("android_res_nsend")));
320
321static int local_res_nresult(int fd, int *rcode, uint8_t *answer, size_t anslen)
322__attribute__((weakref("android_res_nresult")));
323
325{
326 return local_res_nsend && local_res_nresult;
327}
328
329static int sendStandardDns(QDnsLookupReply *reply, QSpan<unsigned char> qbuffer,
330 ReplyBuffer &buffer, const QHostAddress &nameserver)
331{
333 reply->setError(QDnsLookup::ResolverError,
334 QDnsLookup::tr("DNS lookups require Android 10 or later"));
335 return -1;
336 }
337
338 // the DnsResolver only queries the name servers of the network we're bound to
339 if (!nameserver.isNull()) {
340 reply->setError(QDnsLookup::ResolverError,
341 QDnsLookup::tr("Setting a nameserver is currently not supported on this OS"));
342 return -1;
343 }
344
345 // unused: the rcode is also in the reply header, where parseResponse() finds it
346 int rcode = 0;
347 auto attemptToSend = [&]() {
348 int fd = local_res_nsend(NETWORK_UNSPECIFIED, qbuffer.data(), qbuffer.size(), 0);
349 if (fd < 0)
350 return fd;
351 // android_res_nresult() closes the file descriptor in all cases
352 return local_res_nresult(fd, &rcode, buffer.data(), buffer.size());
353 };
354
355 int responseLength = attemptToSend();
356 if (responseLength == -EMSGSIZE) {
357 // The reply did not fit (was truncated) and the resolver closed the
358 // socket, so ask again with a buffer that any DNS message fits into.
359 // The repeated query will normally be answered from the resolver's cache.
360 buffer.resize(std::numeric_limits<quint16>::max());
361 responseLength = attemptToSend();
362 }
363 if (responseLength < 0) {
364 if (responseLength == -ETIMEDOUT)
366 else
367 reply->makeResolverSystemError(-responseLength);
368 return -1;
369 }
370
371 // We can't tell whether the system resolver validated the reply, so don't
372 // pass on the AD bit (see the RES_TRUSTAD handling above).
373 if (responseLength >= int(sizeof(HEADER)))
374 reinterpret_cast<HEADER *>(buffer.data())->ad = false;
375
376 return responseLength;
377}
378
379void QDnsLookupRunnable::query(QDnsLookupReply *reply)
380{
381 // Prepare the DNS query.
382 QueryBuffer qbuffer;
383 int queryLength = prepareQueryBuffer(qbuffer, requestName.constData(), ns_type(requestType));
384 if (Q_UNLIKELY(queryLength < 0))
385 return reply->setError(QDnsLookup::InvalidRequestError,
386 QDnsLookup::tr("Invalid domain name"));
387
388 // Perform DNS query.
389 QSpan query(qbuffer.data(), queryLength);
390 ReplyBuffer buffer(ReplyBufferSize);
391 int responseLength = -1;
392 switch (protocol) {
393 case QDnsLookup::Standard:
394 responseLength = sendStandardDns(reply, query, buffer, nameserver);
395 break;
396 case QDnsLookup::DnsOverTls:
397 if (!sendDnsOverTls(reply, query, buffer))
398 return;
399 responseLength = buffer.size();
400 break;
401 }
402
403 if (responseLength < 0)
404 return;
405
406 parseResponse(reply, buffer, responseLength);
407}
408
409#endif // QT_CONFIG(libresolv)
410
411void QDnsLookupRunnable::parseResponse(QDnsLookupReply *reply, ReplyBuffer &buffer,
412 int responseLength)
413{
414 // Check the reply is valid.
415 if (responseLength < int(sizeof(HEADER)))
416 return reply->makeInvalidReplyError();
417
418 // Parse the reply.
419 auto header = reinterpret_cast<HEADER *>(buffer.data());
420 if (header->rcode)
421 return reply->makeDnsRcodeError(header->rcode);
422
423 qptrdiff offset = sizeof(HEADER);
424 unsigned char *response = buffer.data();
425 int status;
426
427 auto expandHost = [&, cache = Cache{}](qptrdiff offset) mutable {
428 if (uchar n = response[offset]; n & NS_CMPRSFLGS) {
429 // compressed name, see if we already have it cached
430 if (offset + 1 < responseLength) {
431 int id = ((n & ~NS_CMPRSFLGS) << 8) | response[offset + 1];
432 auto it = std::find_if(cache.constBegin(), cache.constEnd(),
433 [id](const QDnsCachedName &n) { return n.code == id; });
434 if (it != cache.constEnd()) {
435 status = 2;
436 return it->name;
437 }
438 }
439 }
440
441 // uncached, expand it
442 char host[MAXCDNAME + 1];
443 status = dn_expand(response, response + responseLength, response + offset,
444 host, sizeof(host));
445 if (status >= 0)
446 return cache.emplaceBack(decodeLabel(QLatin1StringView(host)), offset).name;
447
448 // failed
449 reply->makeInvalidReplyError(QDnsLookup::tr("Could not expand domain name"));
450 return QString();
451 };
452
453 if (ntohs(header->qdcount) == 1) {
454 // Skip the query host, type (2 bytes) and class (2 bytes).
455 expandHost(offset);
456 if (status < 0)
457 return;
458 if (offset + status + 4 > responseLength)
459 header->qdcount = 0xffff; // invalid reply below
460 else
461 offset += status + 4;
462 }
463 if (ntohs(header->qdcount) > 1)
464 return reply->makeInvalidReplyError();
465
466 // Extract results.
467 const int answerCount = ntohs(header->ancount);
468 int answerIndex = 0;
469 while ((offset < responseLength) && (answerIndex < answerCount)) {
470 const QString name = expandHost(offset);
471 if (status < 0)
472 return;
473
474 offset += status;
475 if (offset + RRFIXEDSZ > responseLength) {
476 // probably just a truncated reply, return what we have
477 return;
478 }
479 const quint16 type = qFromBigEndian<quint16>(response + offset);
480 const qint16 rrclass = qFromBigEndian<quint16>(response + offset + 2);
481 const quint32 ttl = qFromBigEndian<quint32>(response + offset + 4);
482 const quint16 size = qFromBigEndian<quint16>(response + offset + 8);
483 offset += RRFIXEDSZ;
484 if (offset + size > responseLength)
485 return; // truncated
486 if (rrclass != C_IN)
487 continue;
488
489 if (type == QDnsLookup::A) {
490 if (size != 4)
491 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid IPv4 address record"));
492 const quint32 addr = qFromBigEndian<quint32>(response + offset);
493 QDnsHostAddressRecord record;
494 record.d->name = name;
495 record.d->timeToLive = ttl;
496 record.d->value = QHostAddress(addr);
497 reply->hostAddressRecords.append(record);
498 } else if (type == QDnsLookup::AAAA) {
499 if (size != 16)
500 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid IPv6 address record"));
501 QDnsHostAddressRecord record;
502 record.d->name = name;
503 record.d->timeToLive = ttl;
504 record.d->value = QHostAddress(response + offset);
505 reply->hostAddressRecords.append(record);
506 } else if (type == QDnsLookup::CNAME) {
507 QDnsDomainNameRecord record;
508 record.d->name = name;
509 record.d->timeToLive = ttl;
510 record.d->value = expandHost(offset);
511 if (status < 0)
512 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid canonical name record"));
513 reply->canonicalNameRecords.append(record);
514 } else if (type == QDnsLookup::NS) {
515 QDnsDomainNameRecord record;
516 record.d->name = name;
517 record.d->timeToLive = ttl;
518 record.d->value = expandHost(offset);
519 if (status < 0)
520 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid name server record"));
521 reply->nameServerRecords.append(record);
522 } else if (type == QDnsLookup::PTR) {
523 QDnsDomainNameRecord record;
524 record.d->name = name;
525 record.d->timeToLive = ttl;
526 record.d->value = expandHost(offset);
527 if (status < 0)
528 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid pointer record"));
529 reply->pointerRecords.append(record);
530 } else if (type == QDnsLookup::MX) {
531 const quint16 preference = qFromBigEndian<quint16>(response + offset);
532 QDnsMailExchangeRecord record;
533 record.d->exchange = expandHost(offset + 2);
534 record.d->name = name;
535 record.d->preference = preference;
536 record.d->timeToLive = ttl;
537 if (status < 0)
538 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid mail exchange record"));
539 reply->mailExchangeRecords.append(record);
540 } else if (type == QDnsLookup::SRV) {
541 if (size < 7)
542 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
543 const quint16 priority = qFromBigEndian<quint16>(response + offset);
544 const quint16 weight = qFromBigEndian<quint16>(response + offset + 2);
545 const quint16 port = qFromBigEndian<quint16>(response + offset + 4);
546 QDnsServiceRecord record;
547 record.d->name = name;
548 record.d->target = expandHost(offset + 6);
549 record.d->port = port;
550 record.d->priority = priority;
551 record.d->timeToLive = ttl;
552 record.d->weight = weight;
553 if (status < 0)
554 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
555 reply->serviceRecords.append(record);
556 } else if (type == QDnsLookup::TLSA) {
557 // https://datatracker.ietf.org/doc/html/rfc6698#section-2.1
558 if (size < 3)
559 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid TLS association record"));
560
561 const quint8 usage = response[offset];
562 const quint8 selector = response[offset + 1];
563 const quint8 matchType = response[offset + 2];
564
565 QDnsTlsAssociationRecord record;
566 record.d->name = name;
567 record.d->timeToLive = ttl;
568 record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage);
569 record.d->selector = QDnsTlsAssociationRecord::Selector(selector);
570 record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType);
571 record.d->value.assign(response + offset + 3, response + offset + size);
572 reply->tlsAssociationRecords.append(std::move(record));
573 } else if (type == QDnsLookup::TXT) {
574 QDnsTextRecord record;
575 record.d->name = name;
576 record.d->timeToLive = ttl;
577 qptrdiff txt = offset;
578 while (txt < offset + size) {
579 const unsigned char length = response[txt];
580 txt++;
581 if (txt + length > offset + size)
582 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid text record"));
583 record.d->values << QByteArrayView(response + txt, length).toByteArray();
584 txt += length;
585 }
586 reply->textRecords.append(record);
587 }
588 offset += size;
589 answerIndex++;
590 }
591}
592
593QT_END_NAMESPACE
void makeResolverSystemError(int code=-1)
void makeTimeoutError()
Definition qspan.h:320
#define __has_include(x)
static int local_res_nsend(net_handle_t network, const uint8_t *msg, size_t msglen, uint32_t flags) __attribute__((weakref("android_res_nsend")))
static int prepareQueryBuffer(QueryBuffer &buffer, const char *label, ns_type type)
static int sendStandardDns(QDnsLookupReply *reply, QSpan< unsigned char > qbuffer, ReplyBuffer &buffer, const QHostAddress &nameserver)
static int local_res_nresult(int fd, int *rcode, uint8_t *answer, size_t anslen) __attribute__((weakref("android_res_nresult")))
bool qt_androidDnsResolverAvailable()
static constexpr unsigned char Edns0Record[]
#define T_OPT
Q_DECLARE_TYPEINFO(QDnsCachedName, Q_RELOCATABLE_TYPE)
static constexpr qsizetype QueryBufferSize