summaryrefslogtreecommitdiffstats
path: root/src/network/kernel/qdnslookup_unix.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/kernel/qdnslookup_unix.cpp')
-rw-r--r--src/network/kernel/qdnslookup_unix.cpp252
1 files changed, 171 insertions, 81 deletions
diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp
index 296c703423..9de073b781 100644
--- a/src/network/kernel/qdnslookup_unix.cpp
+++ b/src/network/kernel/qdnslookup_unix.cpp
@@ -6,6 +6,7 @@
#include <qendian.h>
#include <qscopedpointer.h>
+#include <qspan.h>
#include <qurl.h>
#include <qvarlengtharray.h>
#include <private/qnativesocketengine_p.h> // for setSockAddr
@@ -32,15 +33,13 @@ QT_REQUIRE_CONFIG(libresolv);
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-
-// minimum IPv6 MTU (1280) minus the IPv6 (40) and UDP headers (8)
-static constexpr qsizetype ReplyBufferSize = 1280 - 40 - 8;
+using ReplyBuffer = QDnsLookupRunnable::ReplyBuffer;
// https://www.rfc-editor.org/rfc/rfc6891
static constexpr unsigned char Edns0Record[] = {
0x00, // root label
T_OPT >> 8, T_OPT & 0xff, // type OPT
- ReplyBufferSize >> 8, ReplyBufferSize & 0xff, // payload size
+ ReplyBuffer::PreallocatedSize >> 8, ReplyBuffer::PreallocatedSize & 0xff, // payload size
NOERROR, // extended rcode
0, // version
0x00, 0x00, // flags
@@ -52,16 +51,25 @@ static constexpr qsizetype QueryBufferSize =
HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1 + sizeof(Edns0Record);
using QueryBuffer = std::array<unsigned char, (QueryBufferSize + 15) / 16 * 16>;
+namespace {
+struct QDnsCachedName
+{
+ QString name;
+ int code = 0;
+ QDnsCachedName(const QString &name, int code) : name(name), code(code) {}
+};
+}
+Q_DECLARE_TYPEINFO(QDnsCachedName, Q_RELOCATABLE_TYPE);
+using Cache = QList<QDnsCachedName>; // QHash or QMap are overkill
+
#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
static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
{
- if (!nameserver.isNull()) {
- union res_sockaddr_union u;
- setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port);
- res_setservers(state, &u, 1);
- }
+ union res_sockaddr_union u;
+ setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port);
+ res_setservers(state, &u, 1);
return true;
}
#else
@@ -112,9 +120,6 @@ template <typename State> bool setIpv6NameServer(State *, const void *, quint16)
static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
{
- if (nameserver.isNull())
- return true;
-
state->nscount = 1;
state->nsaddr_list[0].sin_family = AF_UNSPEC;
if (nameserver.protocol() == QAbstractSocket::IPv6Protocol)
@@ -142,51 +147,53 @@ prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_r
return queryLength + sizeof(Edns0Record);
}
-void QDnsLookupRunnable::query(QDnsLookupReply *reply)
+static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<unsigned char> qbuffer,
+ ReplyBuffer &buffer, const QHostAddress &nameserver, quint16 port)
{
- // Initialize state.
- std::remove_pointer_t<res_state> state = {};
- if (res_ninit(&state) < 0) {
- int error = errno;
- qErrnoWarning(error, "QDnsLookup: Resolver initialization failed");
- return reply->makeResolverSystemError(error);
- }
- auto guard = qScopeGuard([&] { res_nclose(&state); });
+ // Check if a nameserver was set. If so, use it.
+ if (!nameserver.isNull()) {
+ if (!applyNameServer(state, nameserver, port)) {
+ reply->setError(QDnsLookup::ResolverError,
+ QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS"));
+ return -1;
+ }
- //Check if a nameserver was set. If so, use it
- if (!applyNameServer(&state, nameserver, port))
- return reply->setError(QDnsLookup::ResolverError,
- QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS"));
-#ifdef QDNSLOOKUP_DEBUG
- state.options |= RES_DEBUG;
-#endif
+ // Request the name server attempt to authenticate the reply.
+ reinterpret_cast<HEADER *>(buffer.data())->ad = true;
- // Prepare the DNS query.
- QueryBuffer qbuffer;
- int queryLength = prepareQueryBuffer(&state, qbuffer, requestName, ns_rcode(requestType));
- if (Q_UNLIKELY(queryLength < 0))
- return reply->makeResolverSystemError();
+#ifdef RES_TRUSTAD
+ // Need to set this option even though we set the AD bit, otherwise
+ // glibc turns it off.
+ state->options |= RES_TRUSTAD;
+#endif
+ }
- // Perform DNS query.
- 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
- if (errno == ETIMEDOUT)
- reply->makeTimeoutError();
- else
- reply->makeResolverSystemError();
- }
- return responseLength;
+ int responseLength = res_nsend(state, qbuffer.data(), qbuffer.size(), buffer.data(), buffer.size());
+ if (responseLength >= 0)
+ return responseLength; // success
+
+ // libresolv uses ETIMEDOUT for resolver errors ("no answer")
+ if (errno == ECONNREFUSED)
+ reply->setError(QDnsLookup::ServerRefusedError, qt_error_string());
+ else if (errno != ETIMEDOUT)
+ reply->makeResolverSystemError(); // some other error
+
+ auto query = reinterpret_cast<HEADER *>(qbuffer.data());
+ auto header = reinterpret_cast<HEADER *>(buffer.data());
+ if (query->id == header->id && header->qr)
+ reply->makeDnsRcodeError(header->rcode);
+ else
+ reply->makeTimeoutError(); // must really be a timeout
+ return -1;
};
// strictly use UDP, we'll deal with truncated replies ourselves
- state.options |= RES_IGNTC;
+ state->options |= RES_IGNTC;
int responseLength = attemptToSend();
if (responseLength < 0)
- return;
+ return responseLength;
// check if we need to use the virtual circuit (TCP)
auto header = reinterpret_cast<HEADER *>(buffer.data());
@@ -197,17 +204,65 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
// remove the EDNS record in the query
reinterpret_cast<HEADER *>(qbuffer.data())->arcount = 0;
- queryLength -= sizeof(Edns0Record);
+ qbuffer = qbuffer.first(qbuffer.size() - sizeof(Edns0Record));
// send using the virtual circuit
- state.options |= RES_USEVC;
+ state->options |= RES_USEVC;
responseLength = attemptToSend();
if (Q_UNLIKELY(responseLength > buffer.size())) {
// Ok, we give up.
- return reply->setError(QDnsLookup::ResolverError,
- QDnsLookup::tr("Reply was too large"));
+ reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr("Reply was too large"));
+ return -1;
}
}
+
+ // We only trust the AD bit in the reply if we're querying a custom name
+ // server or if we can tell the system administrator configured the resolver
+ // to trust replies.
+#ifndef RES_TRUSTAD
+ if (nameserver.isNull())
+ header->ad = false;
+#endif
+ reply->authenticData = header->ad;
+
+ return responseLength;
+}
+
+void QDnsLookupRunnable::query(QDnsLookupReply *reply)
+{
+ // Initialize state.
+ std::remove_pointer_t<res_state> state = {};
+ if (res_ninit(&state) < 0) {
+ int error = errno;
+ qErrnoWarning(error, "QDnsLookup: Resolver initialization failed");
+ return reply->makeResolverSystemError(error);
+ }
+ auto guard = qScopeGuard([&] { res_nclose(&state); });
+
+#ifdef QDNSLOOKUP_DEBUG
+ state.options |= RES_DEBUG;
+#endif
+
+ // Prepare the DNS query.
+ QueryBuffer qbuffer;
+ int queryLength = prepareQueryBuffer(&state, qbuffer, requestName.constData(), ns_rcode(requestType));
+ if (Q_UNLIKELY(queryLength < 0))
+ return reply->makeResolverSystemError();
+
+ // Perform DNS query.
+ ReplyBuffer buffer(ReplyBufferSize);
+ int responseLength = -1;
+ switch (protocol) {
+ case QDnsLookup::Standard:
+ responseLength = sendStandardDns(reply, &state, qbuffer, buffer, nameserver, port);
+ break;
+ case QDnsLookup::DnsOverTls:
+ if (!sendDnsOverTls(reply, qbuffer, buffer))
+ return;
+ responseLength = buffer.size();
+ break;
+ }
+
if (responseLength < 0)
return;
@@ -216,22 +271,46 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
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;
+ auto expandHost = [&, cache = Cache{}](qptrdiff offset) mutable {
+ if (uchar n = response[offset]; n & NS_CMPRSFLGS) {
+ // compressed name, see if we already have it cached
+ if (offset + 1 < responseLength) {
+ int id = ((n & ~NS_CMPRSFLGS) << 8) | response[offset + 1];
+ auto it = std::find_if(cache.constBegin(), cache.constEnd(),
+ [id](const QDnsCachedName &n) { return n.code == id; });
+ if (it != cache.constEnd()) {
+ status = 2;
+ return it->name;
+ }
+ }
+ }
+
+ // uncached, expand it
+ char host[MAXCDNAME + 1];
+ status = dn_expand(response, response + responseLength, response + offset,
+ host, sizeof(host));
+ if (status >= 0)
+ return cache.emplaceBack(decodeLabel(QLatin1StringView(host)), offset).name;
+
+ // failed
+ reply->makeInvalidReplyError(QDnsLookup::tr("Could not expand domain name"));
+ return QString();
+ };
+
if (ntohs(header->qdcount) == 1) {
// Skip the query host, type (2 bytes) and class (2 bytes).
- status = dn_expand(response, response + responseLength, response + offset, host, sizeof(host));
- if (status < 0) {
- reply->makeInvalidReplyError(QDnsLookup::tr("Could not expand domain name"));
+ expandHost(offset);
+ if (status < 0)
return;
- }
- if (offset + status + 4 >= responseLength)
+ if (offset + status + 4 > responseLength)
header->qdcount = 0xffff; // invalid reply below
else
offset += status + 4;
@@ -243,12 +322,9 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
const int answerCount = ntohs(header->ancount);
int answerIndex = 0;
while ((offset < responseLength) && (answerIndex < answerCount)) {
- status = dn_expand(response, response + responseLength, response + offset, host, sizeof(host));
- if (status < 0) {
- reply->makeInvalidReplyError(QDnsLookup::tr("Could not expand domain name"));
+ const QString name = expandHost(offset);
+ if (status < 0)
return;
- }
- const QString name = QUrl::fromAce(host);
offset += status;
if (offset + RRFIXEDSZ > responseLength) {
@@ -283,58 +359,72 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
record.d->value = QHostAddress(response + offset);
reply->hostAddressRecords.append(record);
} else if (type == QDnsLookup::CNAME) {
- status = dn_expand(response, response + responseLength, response + offset, answer, sizeof(answer));
- if (status < 0)
- return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid canonical name record"));
QDnsDomainNameRecord record;
record.d->name = name;
record.d->timeToLive = ttl;
- record.d->value = QUrl::fromAce(answer);
+ record.d->value = expandHost(offset);
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid canonical name record"));
reply->canonicalNameRecords.append(record);
} else if (type == QDnsLookup::NS) {
- status = dn_expand(response, response + responseLength, response + offset, answer, sizeof(answer));
- if (status < 0)
- return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid name server record"));
QDnsDomainNameRecord record;
record.d->name = name;
record.d->timeToLive = ttl;
- record.d->value = QUrl::fromAce(answer);
+ record.d->value = expandHost(offset);
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid name server record"));
reply->nameServerRecords.append(record);
} else if (type == QDnsLookup::PTR) {
- status = dn_expand(response, response + responseLength, response + offset, answer, sizeof(answer));
- if (status < 0)
- return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid pointer record"));
QDnsDomainNameRecord record;
record.d->name = name;
record.d->timeToLive = ttl;
- record.d->value = QUrl::fromAce(answer);
+ record.d->value = expandHost(offset);
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid pointer record"));
reply->pointerRecords.append(record);
} else if (type == QDnsLookup::MX) {
const quint16 preference = qFromBigEndian<quint16>(response + offset);
- status = dn_expand(response, response + responseLength, response + offset + 2, answer, sizeof(answer));
- if (status < 0)
- return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid mail exchange record"));
QDnsMailExchangeRecord record;
- record.d->exchange = QUrl::fromAce(answer);
+ record.d->exchange = expandHost(offset + 2);
record.d->name = name;
record.d->preference = preference;
record.d->timeToLive = ttl;
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid mail exchange record"));
reply->mailExchangeRecords.append(record);
} else if (type == QDnsLookup::SRV) {
+ if (size < 7)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
const quint16 priority = qFromBigEndian<quint16>(response + offset);
const quint16 weight = qFromBigEndian<quint16>(response + offset + 2);
const quint16 port = qFromBigEndian<quint16>(response + offset + 4);
- status = dn_expand(response, response + responseLength, response + offset + 6, answer, sizeof(answer));
- if (status < 0)
- return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
QDnsServiceRecord record;
record.d->name = name;
- record.d->target = QUrl::fromAce(answer);
+ record.d->target = expandHost(offset + 6);
record.d->port = port;
record.d->priority = priority;
record.d->timeToLive = ttl;
record.d->weight = weight;
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
reply->serviceRecords.append(record);
+ } else if (type == QDnsLookup::TLSA) {
+ // https://datatracker.ietf.org/doc/html/rfc6698#section-2.1
+ if (size < 3)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid TLS association record"));
+
+ const quint8 usage = response[offset];
+ const quint8 selector = response[offset + 1];
+ const quint8 matchType = response[offset + 2];
+
+ QDnsTlsAssociationRecord record;
+ record.d->name = name;
+ record.d->timeToLive = ttl;
+ record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage);
+ record.d->selector = QDnsTlsAssociationRecord::Selector(selector);
+ record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType);
+ record.d->value.assign(response + offset + 3, response + offset + size);
+ reply->tlsAssociationRecords.append(std::move(record));
} else if (type == QDnsLookup::TXT) {
QDnsTextRecord record;
record.d->name = name;