13#include <qvarlengtharray.h>
14#include <private/qnativesocketengine_p.h>
15#include <private/qtnetwork-config_p.h>
20#if !QT_CONFIG(libresolv) && !QT_CONFIG(android_dnsresolver)
21 && !QT_CONFIG(harmony_dnsresolver)
22# error "This file requires libresolv, Android's DnsResolver or the HarmonyOS stateless resolver"
26#include <netinet/in.h>
27#include <arpa/nameser.h>
29# include <arpa/nameser_compat.h>
34#if QT_CONFIG(android_dnsresolver)
35# include <android/multinetwork.h>
42# define T_OPT ns_t_opt
47using namespace Qt::StringLiterals;
48using ReplyBuffer = QDnsLookupRunnable::ReplyBuffer;
54 ReplyBuffer::PreallocatedSize >> 8, ReplyBuffer::PreallocatedSize & 0xff,
63 HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1 +
sizeof(Edns0Record);
64using QueryBuffer = std::array<
unsigned char, (QueryBufferSize + 15) / 16 * 16>;
71 QDnsCachedName(
const QString &name,
int code) : name(name), code(code) {}
75using Cache = QList<QDnsCachedName>;
77#if QT_CONFIG(libresolv)
78#if QT_CONFIG(res_setservers)
81static bool applyNameServer(res_state state,
const QHostAddress &nameserver, quint16 port)
83 union res_sockaddr_union u;
84 setSockaddr(
reinterpret_cast<sockaddr *>(&u.sin), nameserver, port);
85 res_setservers(state, &u, 1);
89template <
typename T>
void setNsMap(T &ext, std::enable_if_t<
sizeof(T::nsmap) != 0, uint16_t> v)
97template <
typename T>
void setNsMap(T &, ...)
102template <
bool Condition>
103using EnableIfIPv6 = std::enable_if_t<Condition,
const QHostAddress *>;
105template <
typename State>
106bool setIpv6NameServer(State *state,
107 EnableIfIPv6<
sizeof(std::declval<State>()._u._ext.nsaddrs) != 0> addr,
111 struct sockaddr_in6 *ns = state->_u._ext.nsaddrs[0];
117 ns =
static_cast<
struct sockaddr_in6*>(calloc(1,
sizeof(
struct sockaddr_in6)));
119 state->_u._ext.nsaddrs[0] = ns;
122 setNsMap(state->_u._ext, MAXNS + 1);
123 state->_u._ext.nscount6 = 1;
124 setSockaddr(ns, *addr, port);
128template <
typename State>
bool setIpv6NameServer(State *,
const void *, quint16)
134static bool applyNameServer(res_state state,
const QHostAddress &nameserver, quint16 port)
137 state->nsaddr_list[0].sin_family = AF_UNSPEC;
138 if (nameserver.protocol() == QAbstractSocket::IPv6Protocol)
139 return setIpv6NameServer(state, &nameserver, port);
140 setSockaddr(&state->nsaddr_list[0], nameserver, port);
146prepareQueryBuffer(res_state state, QueryBuffer &buffer,
const char *label, ns_rcode type)
149 int queryLength = res_nmkquery(state, QUERY, label, C_IN, type,
nullptr, 0,
nullptr,
150 buffer.data(), buffer.size());
151 Q_ASSERT(queryLength <
int(buffer.size()));
152 if (Q_UNLIKELY(queryLength < 0))
156 Q_ASSERT(queryLength +
sizeof(Edns0Record) < buffer.size());
157 std::copy_n(std::begin(Edns0Record),
sizeof(Edns0Record), buffer.begin() + queryLength);
158 reinterpret_cast<HEADER *>(buffer.data())->arcount = qToBigEndian<quint16>(1);
160 return queryLength +
sizeof(Edns0Record);
163static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<
unsigned char> qbuffer,
164 ReplyBuffer &buffer,
const QHostAddress &nameserver, quint16 port)
167 if (!nameserver.isNull()) {
168 if (!applyNameServer(state, nameserver, port)) {
169 reply->setError(QDnsLookup::ResolverError,
170 QDnsLookup::tr(
"IPv6 nameservers are currently not supported on this OS"));
175 reinterpret_cast<HEADER *>(buffer.data())->ad =
true;
180 state->options |= RES_TRUSTAD;
184 auto attemptToSend = [&]() {
185 std::memset(buffer.data(), 0, HFIXEDSZ);
186 int responseLength = res_nsend(state, qbuffer.data(), qbuffer.size(), buffer.data(), buffer.size());
187 if (responseLength >= 0)
188 return responseLength;
191 if (errno == ECONNREFUSED)
192 reply->setError(QDnsLookup::ServerRefusedError, qt_error_string());
193 else if (errno != ETIMEDOUT)
194 reply->makeResolverSystemError();
196 auto query =
reinterpret_cast<HEADER *>(qbuffer.data());
197 auto header =
reinterpret_cast<HEADER *>(buffer.data());
198 if (query->id == header->id && header->qr)
199 reply->makeDnsRcodeError(header->rcode);
201 reply->makeTimeoutError();
206 state->options |= RES_IGNTC;
207 int responseLength = attemptToSend();
208 if (responseLength < 0)
209 return responseLength;
212 auto header =
reinterpret_cast<HEADER *>(buffer.data());
213 if (header->rcode == NOERROR && header->tc) {
215 buffer.resize(std::numeric_limits<quint16>::max());
216 header =
reinterpret_cast<HEADER *>(buffer.data());
219 reinterpret_cast<HEADER *>(qbuffer.data())->arcount = 0;
220 qbuffer = qbuffer.first(qbuffer.size() -
sizeof(Edns0Record));
223 state->options |= RES_USEVC;
224 responseLength = attemptToSend();
225 if (Q_UNLIKELY(responseLength > buffer.size())) {
227 reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr(
"Reply was too large"));
236 if (nameserver.isNull())
239 reply->authenticData = header->ad;
241 return responseLength;
244void QDnsLookupRunnable::query(QDnsLookupReply *reply)
247 std::remove_pointer_t<res_state> state = {};
248 if (res_ninit(&state) < 0) {
250 qErrnoWarning(error,
"QDnsLookup: Resolver initialization failed");
251 return reply->makeResolverSystemError(error);
253 auto guard = qScopeGuard([&] { res_nclose(&state); });
255#ifdef QDNSLOOKUP_DEBUG
256 state.options |= RES_DEBUG;
261 int queryLength = prepareQueryBuffer(&state, qbuffer, requestName.constData(), ns_rcode(requestType));
262 if (Q_UNLIKELY(queryLength < 0))
263 return reply->makeResolverSystemError();
266 QSpan query(qbuffer.data(), queryLength);
267 ReplyBuffer buffer(ReplyBufferSize);
268 int responseLength = -1;
270 case QDnsLookup::Standard:
271 responseLength = sendStandardDns(reply, &state, query, buffer, nameserver, port);
273 case QDnsLookup::DnsOverTls:
274 if (!sendDnsOverTls(reply, query, buffer))
276 responseLength = buffer.size();
280 if (responseLength < 0)
283 parseResponse(reply, buffer, responseLength);
286#elif QT_CONFIG(android_dnsresolver) || QT_CONFIG(harmony_dnsresolver)
291static int prepareQueryBuffer(QueryBuffer &buffer,
const char *label, ns_type type)
293 std::memset(buffer.data(), 0, HFIXEDSZ);
294 auto header =
new (buffer.data()) HEADER;
295 header->id = QRandomGenerator::system()->generate();
296 header->opcode = QUERY;
298 header->qdcount = qToBigEndian<quint16>(1);
299 header->arcount = qToBigEndian<quint16>(1);
303 unsigned char *ptr = buffer.data() + HFIXEDSZ;
304 int labelLength = dn_comp(label, ptr,
305 int(buffer.size() - HFIXEDSZ - QFIXEDSZ -
sizeof(Edns0Record)),
307 if (Q_UNLIKELY(labelLength < 0))
310 qToBigEndian<quint16>(type, ptr);
311 qToBigEndian<quint16>(C_IN, ptr +
sizeof(quint16));
314 Q_ASSERT(ptr +
sizeof(Edns0Record) <= buffer.data() + buffer.size());
315 ptr = std::copy_n(std::begin(Edns0Record),
sizeof(Edns0Record), ptr);
317 return ptr - buffer.data();
320#if QT_CONFIG(android_dnsresolver)
323static int local_res_nsend(net_handle_t network,
const uint8_t *msg, size_t msglen, uint32_t flags)
324__attribute__((weakref(
"android_res_nsend")));
326static int local_res_nresult(
int fd,
int *rcode, uint8_t *answer, size_t anslen)
327__attribute__((weakref(
"android_res_nresult")));
329bool qt_androidDnsResolverAvailable()
331 return local_res_nsend && local_res_nresult;
334static int sendStandardDns(QDnsLookupReply *reply, QSpan<
unsigned char> qbuffer,
337 if (!qt_androidDnsResolverAvailable()) {
338 reply->setError(QDnsLookup::ResolverError,
339 QDnsLookup::tr(
"DNS lookups require Android 10 or later"));
345 auto attemptToSend = [&]() {
346 int fd = local_res_nsend(NETWORK_UNSPECIFIED, qbuffer.data(), qbuffer.size(), 0);
350 return local_res_nresult(fd, &rcode, buffer.data(), buffer.size());
353 int responseLength = attemptToSend();
354 if (responseLength == -EMSGSIZE) {
358 buffer.resize(std::numeric_limits<quint16>::max());
359 responseLength = attemptToSend();
361 if (responseLength < 0) {
362 if (responseLength == -ETIMEDOUT)
363 reply->makeTimeoutError();
365 reply->makeResolverSystemError(-responseLength);
369 return responseLength;
374#if QT_CONFIG(harmony_dnsresolver)
376static int sendStandardDns(QDnsLookupReply *reply, QSpan<
unsigned char> qbuffer,
379 auto attemptToSend = [&]() {
380 std::memset(buffer.data(), 0, HFIXEDSZ);
381 int responseLength = res_send(qbuffer.data(), qbuffer.size(), buffer.data(), buffer.size());
382 if (responseLength >= 0)
383 return responseLength;
385 if (errno == ECONNREFUSED)
386 reply->setError(QDnsLookup::ServerRefusedError, qt_error_string());
387 else if (errno != EAGAIN && errno != ETIMEDOUT)
388 reply->makeResolverSystemError();
390 auto query =
reinterpret_cast<
const HEADER *>(qbuffer.data());
391 auto header =
reinterpret_cast<
const HEADER *>(buffer.data());
392 if (query->id == header->id && header->qr)
393 reply->makeDnsRcodeError(header->rcode);
395 reply->makeTimeoutError();
399 int responseLength = attemptToSend();
400 if (responseLength > buffer.size()) {
401 buffer.resize(std::numeric_limits<quint16>::max());
402 responseLength = attemptToSend();
403 if (Q_UNLIKELY(responseLength > buffer.size())) {
404 reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr(
"Reply was too large"));
409 return responseLength;
414void QDnsLookupRunnable::query(QDnsLookupReply *reply)
418 int queryLength = prepareQueryBuffer(qbuffer, requestName.constData(), ns_type(requestType));
419 if (Q_UNLIKELY(queryLength < 0))
420 return reply->setError(QDnsLookup::InvalidRequestError,
421 QDnsLookup::tr(
"Invalid domain name"));
424 QSpan query(qbuffer.data(), queryLength);
425 ReplyBuffer buffer(ReplyBufferSize);
426 int responseLength = -1;
428 case QDnsLookup::Standard:
429 if (!nameserver.isNull()) {
430 reply->setError(QDnsLookup::ResolverError,
431 QDnsLookup::tr(
"Setting a nameserver is currently not supported on this OS"));
434 responseLength = sendStandardDns(reply, query, buffer);
438 if (responseLength >=
int(
sizeof(HEADER)))
439 reinterpret_cast<HEADER *>(buffer.data())->ad =
false;
441 case QDnsLookup::DnsOverTls:
442 if (!sendDnsOverTls(reply, query, buffer))
444 responseLength = buffer.size();
448 if (responseLength < 0)
451 parseResponse(reply, buffer, responseLength);
460 if (responseLength <
int(
sizeof(HEADER)))
461 return reply->makeInvalidReplyError();
464 auto header =
reinterpret_cast<HEADER *>(buffer.data());
466 return reply->makeDnsRcodeError(header->rcode);
468 qptrdiff offset =
sizeof(HEADER);
469 unsigned char *response = buffer.data();
472 auto expandHost = [&, cache = Cache{}](qptrdiff offset)
mutable {
473 if (uchar n = response[offset]; n & NS_CMPRSFLGS) {
475 if (offset + 1 < responseLength) {
476 int id = ((n & ~NS_CMPRSFLGS) << 8) | response[offset + 1];
477 auto it = std::find_if(cache.constBegin(), cache.constEnd(),
478 [id](
const QDnsCachedName &n) {
return n.code == id; });
479 if (it != cache.constEnd()) {
487 char host[MAXCDNAME + 1];
488 status = dn_expand(response, response + responseLength, response + offset,
491 return cache.emplaceBack(decodeLabel(QLatin1StringView(host)), offset).name;
494 reply->makeInvalidReplyError(QDnsLookup::tr(
"Could not expand domain name"));
498 if (ntohs(header->qdcount) == 1) {
503 if (offset + status + 4 > responseLength)
504 header->qdcount = 0xffff;
506 offset += status + 4;
508 if (ntohs(header->qdcount) > 1)
509 return reply->makeInvalidReplyError();
512 const int answerCount = ntohs(header->ancount);
514 while ((offset < responseLength) && (answerIndex < answerCount)) {
515 const QString name = expandHost(offset);
520 if (offset + RRFIXEDSZ > responseLength) {
524 const quint16 type = qFromBigEndian<quint16>(response + offset);
525 const qint16 rrclass = qFromBigEndian<quint16>(response + offset + 2);
526 const quint32 ttl = qFromBigEndian<quint32>(response + offset + 4);
527 const quint16 size = qFromBigEndian<quint16>(response + offset + 8);
529 if (offset + size > responseLength)
534 if (type == QDnsLookup::A) {
536 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid IPv4 address record"));
537 const quint32 addr = qFromBigEndian<quint32>(response + offset);
538 QDnsHostAddressRecord record;
539 record.d->name = name;
540 record.d->timeToLive = ttl;
541 record.d->value = QHostAddress(addr);
542 reply->hostAddressRecords.append(record);
543 }
else if (type == QDnsLookup::AAAA) {
545 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid IPv6 address record"));
546 QDnsHostAddressRecord record;
547 record.d->name = name;
548 record.d->timeToLive = ttl;
549 record.d->value = QHostAddress(response + offset);
550 reply->hostAddressRecords.append(record);
551 }
else if (type == QDnsLookup::CNAME) {
552 QDnsDomainNameRecord record;
553 record.d->name = name;
554 record.d->timeToLive = ttl;
555 record.d->value = expandHost(offset);
557 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid canonical name record"));
558 reply->canonicalNameRecords.append(record);
559 }
else if (type == QDnsLookup::NS) {
560 QDnsDomainNameRecord record;
561 record.d->name = name;
562 record.d->timeToLive = ttl;
563 record.d->value = expandHost(offset);
565 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid name server record"));
566 reply->nameServerRecords.append(record);
567 }
else if (type == QDnsLookup::PTR) {
568 QDnsDomainNameRecord record;
569 record.d->name = name;
570 record.d->timeToLive = ttl;
571 record.d->value = expandHost(offset);
573 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid pointer record"));
574 reply->pointerRecords.append(record);
575 }
else if (type == QDnsLookup::MX) {
576 const quint16 preference = qFromBigEndian<quint16>(response + offset);
577 QDnsMailExchangeRecord record;
578 record.d->exchange = expandHost(offset + 2);
579 record.d->name = name;
580 record.d->preference = preference;
581 record.d->timeToLive = ttl;
583 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid mail exchange record"));
584 reply->mailExchangeRecords.append(record);
585 }
else if (type == QDnsLookup::SRV) {
587 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid service record"));
588 const quint16 priority = qFromBigEndian<quint16>(response + offset);
589 const quint16 weight = qFromBigEndian<quint16>(response + offset + 2);
590 const quint16 port = qFromBigEndian<quint16>(response + offset + 4);
591 QDnsServiceRecord record;
592 record.d->name = name;
593 record.d->target = expandHost(offset + 6);
594 record.d->port = port;
595 record.d->priority = priority;
596 record.d->timeToLive = ttl;
597 record.d->weight = weight;
599 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid service record"));
600 reply->serviceRecords.append(record);
601 }
else if (type == QDnsLookup::TLSA) {
604 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid TLS association record"));
606 const quint8 usage = response[offset];
607 const quint8 selector = response[offset + 1];
608 const quint8 matchType = response[offset + 2];
610 QDnsTlsAssociationRecord record;
611 record.d->name = name;
612 record.d->timeToLive = ttl;
613 record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage);
614 record.d->selector = QDnsTlsAssociationRecord::Selector(selector);
615 record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType);
616 record.d->value.assign(response + offset + 3, response + offset + size);
617 reply->tlsAssociationRecords.append(std::move(record));
618 }
else if (type == QDnsLookup::TXT) {
619 QDnsTextRecord record;
620 record.d->name = name;
621 record.d->timeToLive = ttl;
622 qptrdiff txt = offset;
623 while (txt < offset + size) {
624 const unsigned char length = response[txt];
626 if (txt + length > offset + size)
627 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid text record"));
628 record.d->values << QByteArrayView(response + txt, length).toByteArray();
631 reply->textRecords.append(record);
static constexpr unsigned char Edns0Record[]
Q_DECLARE_TYPEINFO(QDnsCachedName, Q_RELOCATABLE_TYPE)
static constexpr qsizetype QueryBufferSize