11#include <qvarlengtharray.h>
12#include <private/qnativesocketengine_p.h>
13#include <private/qtnetwork-config_p.h>
18#include <netinet/in.h>
19#include <arpa/nameser.h>
21# include <arpa/nameser_compat.h>
30# define T_OPT ns_t_opt
35using namespace Qt::StringLiterals;
36using ReplyBuffer = QDnsLookupRunnable::ReplyBuffer;
42 ReplyBuffer::PreallocatedSize >> 8, ReplyBuffer::PreallocatedSize & 0xff,
51 HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1 +
sizeof(Edns0Record);
52using QueryBuffer = std::array<
unsigned char, (QueryBufferSize + 15) / 16 * 16>;
59 QDnsCachedName(
const QString &name,
int code) : name(name), code(code) {}
63using Cache = QList<QDnsCachedName>;
65#if QT_CONFIG(res_setservers)
68static bool applyNameServer(res_state state,
const QHostAddress &nameserver, quint16 port)
70 union res_sockaddr_union u;
71 setSockaddr(
reinterpret_cast<sockaddr *>(&u.sin), nameserver, port);
72 res_setservers(state, &u, 1);
76template <
typename T>
void setNsMap(T &ext, std::enable_if_t<
sizeof(T::nsmap) != 0, uint16_t> v)
89template <
bool Condition>
90using EnableIfIPv6 = std::enable_if_t<Condition,
const QHostAddress *>;
92template <
typename State>
94 EnableIfIPv6<
sizeof(std::declval<State>()._u._ext.nsaddrs) != 0> addr,
98 struct sockaddr_in6 *ns = state->_u._ext.nsaddrs[0];
104 ns =
static_cast<
struct sockaddr_in6*>(calloc(1,
sizeof(
struct sockaddr_in6)));
106 state->_u._ext.nsaddrs[0] = ns;
109 setNsMap(state->_u._ext, MAXNS + 1);
110 state->_u._ext.nscount6 = 1;
111 setSockaddr(ns, *addr, port);
121static bool applyNameServer(res_state state,
const QHostAddress &nameserver, quint16 port)
124 state->nsaddr_list[0].sin_family = AF_UNSPEC;
125 if (nameserver.protocol() == QAbstractSocket::IPv6Protocol)
126 return setIpv6NameServer(state, &nameserver, port);
127 setSockaddr(&state->nsaddr_list[0], nameserver, port);
136 int queryLength = res_nmkquery(state, QUERY, label, C_IN, type,
nullptr, 0,
nullptr,
137 buffer.data(), buffer.size());
138 Q_ASSERT(queryLength <
int(buffer.size()));
139 if (Q_UNLIKELY(queryLength < 0))
143 Q_ASSERT(queryLength +
sizeof(Edns0Record) < buffer.size());
144 std::copy_n(std::begin(Edns0Record),
sizeof(Edns0Record), buffer.begin() + queryLength);
145 reinterpret_cast<HEADER *>(buffer.data())->arcount = qToBigEndian<quint16>(1);
147 return queryLength +
sizeof(Edns0Record);
151 ReplyBuffer &buffer,
const QHostAddress &nameserver, quint16 port)
154 if (!nameserver.isNull()) {
155 if (!applyNameServer(state, nameserver, port)) {
156 reply->setError(QDnsLookup::ResolverError,
157 QDnsLookup::tr(
"IPv6 nameservers are currently not supported on this OS"));
162 reinterpret_cast<HEADER *>(buffer.data())->ad =
true;
167 state->options |= RES_TRUSTAD;
171 auto attemptToSend = [&]() {
172 std::memset(buffer.data(), 0, HFIXEDSZ);
173 int responseLength = res_nsend(state, qbuffer.data(), qbuffer.size(), buffer.data(), buffer.size());
174 if (responseLength >= 0)
175 return responseLength;
178 if (errno == ECONNREFUSED)
179 reply->setError(QDnsLookup::ServerRefusedError, qt_error_string());
180 else if (errno != ETIMEDOUT)
183 auto query =
reinterpret_cast<HEADER *>(qbuffer.data());
184 auto header =
reinterpret_cast<HEADER *>(buffer.data());
185 if (query->id == header->id && header->qr)
186 reply->makeDnsRcodeError(header->rcode);
193 state->options |= RES_IGNTC;
194 int responseLength = attemptToSend();
195 if (responseLength < 0)
196 return responseLength;
199 auto header =
reinterpret_cast<HEADER *>(buffer.data());
200 if (header->rcode == NOERROR && header->tc) {
202 buffer.resize(std::numeric_limits<quint16>::max());
203 header =
reinterpret_cast<HEADER *>(buffer.data());
206 reinterpret_cast<HEADER *>(qbuffer.data())->arcount = 0;
207 qbuffer = qbuffer.first(qbuffer.size() -
sizeof(Edns0Record));
210 state->options |= RES_USEVC;
211 responseLength = attemptToSend();
212 if (Q_UNLIKELY(responseLength > buffer.size())) {
214 reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr(
"Reply was too large"));
223 if (nameserver.isNull())
228 return responseLength;
234 std::remove_pointer_t<res_state> state = {};
235 if (res_ninit(&state) < 0) {
237 qErrnoWarning(error,
"QDnsLookup: Resolver initialization failed");
240 auto guard = qScopeGuard([&] { res_nclose(&state); });
242#ifdef QDNSLOOKUP_DEBUG
243 state.options |= RES_DEBUG;
248 int queryLength = prepareQueryBuffer(&state, qbuffer, requestName.constData(), ns_rcode(requestType));
249 if (Q_UNLIKELY(queryLength < 0))
253 QSpan query(qbuffer.data(), queryLength);
254 ReplyBuffer buffer(ReplyBufferSize);
255 int responseLength = -1;
257 case QDnsLookup::Standard:
258 responseLength = sendStandardDns(reply, &state, query, buffer, nameserver, port);
260 case QDnsLookup::DnsOverTls:
261 if (!sendDnsOverTls(reply, query, buffer))
263 responseLength = buffer.size();
267 if (responseLength < 0)
271 if (responseLength <
int(
sizeof(HEADER)))
272 return reply->makeInvalidReplyError();
275 auto header =
reinterpret_cast<HEADER *>(buffer.data());
277 return reply->makeDnsRcodeError(header->rcode);
279 qptrdiff offset =
sizeof(HEADER);
280 unsigned char *response = buffer.data();
283 auto expandHost = [&, cache = Cache{}](qptrdiff offset)
mutable {
284 if (uchar n = response[offset]; n & NS_CMPRSFLGS) {
286 if (offset + 1 < responseLength) {
287 int id = ((n & ~NS_CMPRSFLGS) << 8) | response[offset + 1];
288 auto it = std::find_if(cache.constBegin(), cache.constEnd(),
289 [id](
const QDnsCachedName &n) {
return n.code == id; });
290 if (it != cache.constEnd()) {
298 char host[MAXCDNAME + 1];
299 status = dn_expand(response, response + responseLength, response + offset,
302 return cache.emplaceBack(decodeLabel(QLatin1StringView(host)), offset).name;
305 reply->makeInvalidReplyError(QDnsLookup::tr(
"Could not expand domain name"));
309 if (ntohs(header->qdcount) == 1) {
314 if (offset + status + 4 > responseLength)
315 header->qdcount = 0xffff;
317 offset += status + 4;
319 if (ntohs(header->qdcount) > 1)
320 return reply->makeInvalidReplyError();
323 const int answerCount = ntohs(header->ancount);
325 while ((offset < responseLength) && (answerIndex < answerCount)) {
326 const QString name = expandHost(offset);
331 if (offset + RRFIXEDSZ > responseLength) {
335 const quint16 type = qFromBigEndian<quint16>(response + offset);
336 const qint16 rrclass = qFromBigEndian<quint16>(response + offset + 2);
337 const quint32 ttl = qFromBigEndian<quint32>(response + offset + 4);
338 const quint16 size = qFromBigEndian<quint16>(response + offset + 8);
340 if (offset + size > responseLength)
345 if (type == QDnsLookup::A) {
347 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid IPv4 address record"));
348 const quint32 addr = qFromBigEndian<quint32>(response + offset);
349 QDnsHostAddressRecord record;
350 record.d->name = name;
351 record.d->timeToLive = ttl;
352 record.d->value = QHostAddress(addr);
353 reply->hostAddressRecords.append(record);
354 }
else if (type == QDnsLookup::AAAA) {
356 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid IPv6 address record"));
357 QDnsHostAddressRecord record;
358 record.d->name = name;
359 record.d->timeToLive = ttl;
360 record.d->value = QHostAddress(response + offset);
361 reply->hostAddressRecords.append(record);
362 }
else if (type == QDnsLookup::CNAME) {
363 QDnsDomainNameRecord record;
364 record.d->name = name;
365 record.d->timeToLive = ttl;
366 record.d->value = expandHost(offset);
368 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid canonical name record"));
369 reply->canonicalNameRecords.append(record);
370 }
else if (type == QDnsLookup::NS) {
371 QDnsDomainNameRecord record;
372 record.d->name = name;
373 record.d->timeToLive = ttl;
374 record.d->value = expandHost(offset);
376 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid name server record"));
377 reply->nameServerRecords.append(record);
378 }
else if (type == QDnsLookup::PTR) {
379 QDnsDomainNameRecord record;
380 record.d->name = name;
381 record.d->timeToLive = ttl;
382 record.d->value = expandHost(offset);
384 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid pointer record"));
385 reply->pointerRecords.append(record);
386 }
else if (type == QDnsLookup::MX) {
387 const quint16 preference = qFromBigEndian<quint16>(response + offset);
388 QDnsMailExchangeRecord record;
389 record.d->exchange = expandHost(offset + 2);
390 record.d->name = name;
391 record.d->preference = preference;
392 record.d->timeToLive = ttl;
394 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid mail exchange record"));
395 reply->mailExchangeRecords.append(record);
396 }
else if (type == QDnsLookup::SRV) {
398 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid service record"));
399 const quint16 priority = qFromBigEndian<quint16>(response + offset);
400 const quint16 weight = qFromBigEndian<quint16>(response + offset + 2);
401 const quint16 port = qFromBigEndian<quint16>(response + offset + 4);
402 QDnsServiceRecord record;
403 record.d->name = name;
404 record.d->target = expandHost(offset + 6);
405 record.d->port = port;
406 record.d->priority = priority;
407 record.d->timeToLive = ttl;
408 record.d->weight = weight;
410 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid service record"));
411 reply->serviceRecords.append(record);
412 }
else if (type == QDnsLookup::TLSA) {
415 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid TLS association record"));
417 const quint8 usage = response[offset];
418 const quint8 selector = response[offset + 1];
419 const quint8 matchType = response[offset + 2];
421 QDnsTlsAssociationRecord record;
422 record.d->name = name;
423 record.d->timeToLive = ttl;
424 record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage);
425 record.d->selector = QDnsTlsAssociationRecord::Selector(selector);
426 record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType);
427 record.d->value.assign(response + offset + 3, response + offset + size);
428 reply->tlsAssociationRecords.append(std::move(record));
429 }
else if (type == QDnsLookup::TXT) {
430 QDnsTextRecord record;
431 record.d->name = name;
432 record.d->timeToLive = ttl;
433 qptrdiff txt = offset;
434 while (txt < offset + size) {
435 const unsigned char length = response[txt];
437 if (txt + length > offset + size)
438 return reply->makeInvalidReplyError(QDnsLookup::tr(
"Invalid text record"));
439 record.d->values << QByteArrayView(response + txt, length).toByteArray();
442 reply->textRecords.append(record);
void makeResolverSystemError(int code=-1)
static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan< unsigned char > qbuffer, ReplyBuffer &buffer, const QHostAddress &nameserver, quint16 port)
static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
static int prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_rcode type)
bool setIpv6NameServer(State *state, EnableIfIPv6< sizeof(std::declval< State >()._u._ext.nsaddrs) !=0 > addr, quint16 port)
static constexpr unsigned char Edns0Record[]
bool setIpv6NameServer(State *, const void *, quint16)
Q_DECLARE_TYPEINFO(QDnsCachedName, Q_RELOCATABLE_TYPE)
static constexpr qsizetype QueryBufferSize
void setNsMap(T &ext, std::enable_if_t< sizeof(T::nsmap) !=0, uint16_t > v)
QT_REQUIRE_CONFIG(thread)