13#include <qvarlengtharray.h>
14#include <private/qnativesocketengine_p.h>
15#include <private/qtnetwork-config_p.h>
19static_assert(QT_CONFIG(libresolv) || QT_CONFIG(android_dnsresolver),
20 "This file requires either libresolv or Android's DnsResolver");
23#include <netinet/in.h>
24#include <arpa/nameser.h>
26# include <arpa/nameser_compat.h>
31#if QT_CONFIG(android_dnsresolver)
32# include <android/multinetwork.h>
39# define T_OPT ns_t_opt
44using namespace Qt::StringLiterals;
45using ReplyBuffer = QDnsLookupRunnable::ReplyBuffer;
51 ReplyBuffer::PreallocatedSize >> 8, ReplyBuffer::PreallocatedSize & 0xff,
60 HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1 +
sizeof(Edns0Record);
61using QueryBuffer = std::array<
unsigned char, (QueryBufferSize + 15) / 16 * 16>;
68 QDnsCachedName(
const QString &name,
int code) : name(name), code(code) {}
72using Cache = QList<QDnsCachedName>;
74#if QT_CONFIG(libresolv)
75#if QT_CONFIG(res_setservers)
78static bool applyNameServer(res_state state,
const QHostAddress &nameserver, quint16 port)
80 union res_sockaddr_union u;
81 setSockaddr(
reinterpret_cast<sockaddr *>(&u.sin), nameserver, port);
82 res_setservers(state, &u, 1);
86template <
typename T>
void setNsMap(T &ext, std::enable_if_t<
sizeof(T::nsmap) != 0, uint16_t> v)
94template <
typename T>
void setNsMap(T &, ...)
99template <
bool Condition>
100using EnableIfIPv6 = std::enable_if_t<Condition,
const QHostAddress *>;
102template <
typename State>
103bool setIpv6NameServer(State *state,
104 EnableIfIPv6<
sizeof(std::declval<State>()._u._ext.nsaddrs) != 0> addr,
108 struct sockaddr_in6 *ns = state->_u._ext.nsaddrs[0];
114 ns =
static_cast<
struct sockaddr_in6*>(calloc(1,
sizeof(
struct sockaddr_in6)));
116 state->_u._ext.nsaddrs[0] = ns;
119 setNsMap(state->_u._ext, MAXNS + 1);
120 state->_u._ext.nscount6 = 1;
121 setSockaddr(ns, *addr, port);
125template <
typename State>
bool setIpv6NameServer(State *,
const void *, quint16)
131static bool applyNameServer(res_state state,
const QHostAddress &nameserver, quint16 port)
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);
143prepareQueryBuffer(res_state state, QueryBuffer &buffer,
const char *label, ns_rcode type)
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))
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);
157 return queryLength +
sizeof(Edns0Record);
160static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<
unsigned char> qbuffer,
161 ReplyBuffer &buffer,
const QHostAddress &nameserver, quint16 port)
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"));
172 reinterpret_cast<HEADER *>(buffer.data())->ad =
true;
177 state->options |= RES_TRUSTAD;
181 auto attemptToSend = [&]() {
182 std::memset(buffer.data(), 0, HFIXEDSZ);
183 int responseLength = res_nsend(state, qbuffer.data(), qbuffer.size(), buffer.data(), buffer.size());
184 if (responseLength >= 0)
185 return responseLength;
188 if (errno == ECONNREFUSED)
189 reply->setError(QDnsLookup::ServerRefusedError, qt_error_string());
190 else if (errno != ETIMEDOUT)
191 reply->makeResolverSystemError();
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);
198 reply->makeTimeoutError();
203 state->options |= RES_IGNTC;
204 int responseLength = attemptToSend();
205 if (responseLength < 0)
206 return responseLength;
209 auto header =
reinterpret_cast<HEADER *>(buffer.data());
210 if (header->rcode == NOERROR && header->tc) {
212 buffer.resize(std::numeric_limits<quint16>::max());
213 header =
reinterpret_cast<HEADER *>(buffer.data());
216 reinterpret_cast<HEADER *>(qbuffer.data())->arcount = 0;
217 qbuffer = qbuffer.first(qbuffer.size() -
sizeof(Edns0Record));
220 state->options |= RES_USEVC;
221 responseLength = attemptToSend();
222 if (Q_UNLIKELY(responseLength > buffer.size())) {
224 reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr(
"Reply was too large"));
233 if (nameserver.isNull())
236 reply->authenticData = header->ad;
238 return responseLength;
241void QDnsLookupRunnable::query(QDnsLookupReply *reply)
244 std::remove_pointer_t<res_state> state = {};
245 if (res_ninit(&state) < 0) {
247 qErrnoWarning(error,
"QDnsLookup: Resolver initialization failed");
248 return reply->makeResolverSystemError(error);
250 auto guard = qScopeGuard([&] { res_nclose(&state); });
252#ifdef QDNSLOOKUP_DEBUG
253 state.options |= RES_DEBUG;
258 int queryLength = prepareQueryBuffer(&state, qbuffer, requestName.constData(), ns_rcode(requestType));
259 if (Q_UNLIKELY(queryLength < 0))
260 return reply->makeResolverSystemError();
263 QSpan query(qbuffer.data(), queryLength);
264 ReplyBuffer buffer(ReplyBufferSize);
265 int responseLength = -1;
267 case QDnsLookup::Standard:
268 responseLength = sendStandardDns(reply, &state, query, buffer, nameserver, port);
270 case QDnsLookup::DnsOverTls:
271 if (!sendDnsOverTls(reply, query, buffer))
273 responseLength = buffer.size();
277 if (responseLength < 0)
280 parseResponse(reply, buffer, responseLength);
289 std::memset(buffer.data(), 0, HFIXEDSZ);
290 auto header =
new (buffer.data()) HEADER;
291 header->id = QRandomGenerator::system()->generate();
292 header->opcode = QUERY;
294 header->qdcount = qToBigEndian<quint16>(1);
295 header->arcount = qToBigEndian<quint16>(1);
299 unsigned char *ptr = buffer.data() + HFIXEDSZ;
300 int labelLength = dn_comp(label, ptr,
301 int(buffer.size() - HFIXEDSZ - QFIXEDSZ -
sizeof(Edns0Record)),
303 if (Q_UNLIKELY(labelLength < 0))
306 qToBigEndian<quint16>(type, ptr);
307 qToBigEndian<quint16>(C_IN, ptr +
sizeof(quint16));
310 Q_ASSERT(ptr +
sizeof(Edns0Record) <= buffer.data() + buffer.size());
313 return ptr - buffer.data();
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")));
322__attribute__((weakref(
"android_res_nresult")));
326 return local_res_nsend && local_res_nresult;
330 ReplyBuffer &buffer,
const QHostAddress &nameserver)
333 reply->setError(QDnsLookup::ResolverError,
334 QDnsLookup::tr(
"DNS lookups require Android 10 or later"));
339 if (!nameserver.isNull()) {
340 reply->setError(QDnsLookup::ResolverError,
341 QDnsLookup::tr(
"Setting a nameserver is currently not supported on this OS"));
347 auto attemptToSend = [&]() {
348 int fd = local_res_nsend(NETWORK_UNSPECIFIED, qbuffer.data(), qbuffer.size(), 0);
352 return local_res_nresult(fd, &rcode, buffer.data(), buffer.size());
355 int responseLength = attemptToSend();
356 if (responseLength == -EMSGSIZE) {
360 buffer.resize(std::numeric_limits<quint16>::max());
361 responseLength = attemptToSend();
363 if (responseLength < 0) {
364 if (responseLength == -ETIMEDOUT)
373 if (responseLength >=
int(
sizeof(HEADER)))
374 reinterpret_cast<HEADER *>(buffer.data())->ad =
false;
376 return responseLength;
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"));
389 QSpan query(qbuffer.data(), queryLength);
390 ReplyBuffer buffer(ReplyBufferSize);
391 int responseLength = -1;
393 case QDnsLookup::Standard:
394 responseLength = sendStandardDns(reply, query, buffer, nameserver);
396 case QDnsLookup::DnsOverTls:
397 if (!sendDnsOverTls(reply, query, buffer))
399 responseLength = buffer.size();
403 if (responseLength < 0)
406 parseResponse(reply, buffer, responseLength);
415 if (responseLength <
int(
sizeof(HEADER)))
416 return reply->makeInvalidReplyError();
419 auto header =
reinterpret_cast<HEADER *>(buffer.data());
421 return reply->makeDnsRcodeError(header->rcode);
423 qptrdiff offset =
sizeof(HEADER);
424 unsigned char *response = buffer.data();
427 auto expandHost = [&, cache = Cache{}](qptrdiff offset)
mutable {
428 if (uchar n = response[offset]; n & NS_CMPRSFLGS) {
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()) {
442 char host[MAXCDNAME + 1];
443 status = dn_expand(response, response + responseLength, response + offset,
446 return cache.emplaceBack(decodeLabel(QLatin1StringView(host)), offset).name;
449 reply->makeInvalidReplyError(QDnsLookup::tr(
"Could not expand domain name"));
453 if (ntohs(header->qdcount) == 1) {
458 if (offset + status + 4 > responseLength)
459 header->qdcount = 0xffff;
461 offset += status + 4;
463 if (ntohs(header->qdcount) > 1)
464 return reply->makeInvalidReplyError();
467 const int answerCount = ntohs(header->ancount);
469 while ((offset < responseLength) && (answerIndex < answerCount)) {
470 const QString name = expandHost(offset);
475 if (offset + RRFIXEDSZ > responseLength) {
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);
484 if (offset + size > responseLength)
489 if (type == QDnsLookup::A) {
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) {
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);
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);
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);
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;
538 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid mail exchange record"));
539 reply->mailExchangeRecords.append(record);
540 }
else if (type == QDnsLookup::SRV) {
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;
554 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid service record"));
555 reply->serviceRecords.append(record);
556 }
else if (type == QDnsLookup::TLSA) {
559 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid TLS association record"));
561 const quint8 usage = response[offset];
562 const quint8 selector = response[offset + 1];
563 const quint8 matchType = response[offset + 2];
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];
581 if (txt + length > offset + size)
582 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid text record"));
583 record.d->values << QByteArrayView(response + txt, length).toByteArray();
586 reply->textRecords.append(record);
void makeResolverSystemError(int code=-1)
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[]
Q_DECLARE_TYPEINFO(QDnsCachedName, Q_RELOCATABLE_TYPE)
static constexpr qsizetype QueryBufferSize