summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThiago Macieira <[email protected]>2024-04-19 09:41:40 -0700
committerThiago Macieira <[email protected]>2024-05-08 21:13:45 -0700
commit503fd609881fb220ac5abe7da2fe367efd90ed4b (patch)
tree0a4efedaf0508a308166d861d689572c6f9b569c
parentf2f00b2a4632aaeb58e6cdae7faef9e0bafaff49 (diff)
QDnsLookup: add the ability to tell if the reply was authenticated
This is implemented for DNS-over-TLS and for the native Unix resolver, because I can find no way to get the state of the reply on Windows with the WinDNS.h API. Change-Id: I455fe22ef4ad4b2f9b01fffd17c7bc022ded2363 Reviewed-by: MÃ¥rten Nordheim <[email protected]>
-rw-r--r--src/network/kernel/qdnslookup.cpp55
-rw-r--r--src/network/kernel/qdnslookup.h2
-rw-r--r--src/network/kernel/qdnslookup_p.h1
-rw-r--r--src/network/kernel/qdnslookup_unix.cpp41
-rw-r--r--tests/manual/qdnslookup/main.cpp7
5 files changed, 89 insertions, 17 deletions
diff --git a/src/network/kernel/qdnslookup.cpp b/src/network/kernel/qdnslookup.cpp
index 9a35bd3365a..a3ebbe04db8 100644
--- a/src/network/kernel/qdnslookup.cpp
+++ b/src/network/kernel/qdnslookup.cpp
@@ -168,7 +168,7 @@ static void qt_qdnsservicerecord_sort(QList<QDnsServiceRecord> &records)
name, or the host name associated with an IP address you should use
QHostInfo instead.
- \section1 DNS-over-TLS
+ \section1 DNS-over-TLS and Authentic Data
QDnsLookup supports DNS-over-TLS (DoT, as specified by \l{RFC 7858}) on
some platforms. That currently includes all Unix platforms where regular
@@ -182,6 +182,27 @@ static void qt_qdnsservicerecord_sort(QList<QDnsServiceRecord> &records)
server being connected to. Clients may use setSslConfiguration() to impose
additional restrictions and sslConfiguration() to obtain information after
the query is complete.
+
+ QDnsLookup will request DNS servers queried over TLS to perform
+ authentication on the data they return. If they confirm the data is valid,
+ the \l authenticData property will be set to true. QDnsLookup does not
+ verify the integrity of the data by itself, so applications should only
+ trust this property on servers they have confirmed through other means to
+ be trustworthy.
+
+ \section2 Authentic Data without TLS
+
+ QDnsLookup request Authentic Data for any server set with setNameserver(),
+ even if TLS encryption is not required. This is useful when querying a
+ caching nameserver on the same host as the application or on a trusted
+ network. Though similar to the TLS case, the application is responsible for
+ determining if the server it chose to use is trustworthy, and if the
+ unencrypted connection cannot be tampered with.
+
+ QDnsLookup obeys the system configuration to request Authentic Data on the
+ default nameserver (that is, if setNameserver() is not called). This is
+ currently only supported on Linux systems using glibc 2.31 or later. On any
+ other systems, QDnsLookup will ignore the AD bit in the query header.
*/
/*!
@@ -419,6 +440,28 @@ QDnsLookup::~QDnsLookup()
}
/*!
+ \since 6.8
+ \property QDnsLookup::authenticData
+ \brief whether the reply was authenticated by the resolver.
+
+ QDnsLookup does not perform the authentication itself. Instead, it trusts
+ the name server that was queried to perform the authentication and report
+ it. The application is responsible for determining if any servers it
+ configured with setNameserver() are trustworthy; if no server was set,
+ QDnsLookup obeys system configuration on whether responses should be
+ trusted.
+
+ This property may be set even if error() indicates a resolver error
+ occurred.
+
+ \sa setNameserver(), nameserverProtocol()
+*/
+bool QDnsLookup::isAuthenticData() const
+{
+ return d_func()->reply.authenticData;
+}
+
+/*!
\property QDnsLookup::error
\brief the type of error that occurred if the DNS lookup failed, or NoError.
*/
@@ -1308,6 +1351,7 @@ inline QDebug operator<<(QDebug &d, QDnsLookupRunnable *r)
#if QT_CONFIG(ssl)
static constexpr std::chrono::milliseconds DnsOverTlsConnectTimeout(15'000);
static constexpr std::chrono::milliseconds DnsOverTlsTimeout(120'000);
+static constexpr quint8 DnsAuthenticDataBit = 0x20;
static int makeReplyErrorFromSocket(QDnsLookupReply *reply, const QAbstractSocket *socket)
{
@@ -1334,6 +1378,9 @@ bool QDnsLookupRunnable::sendDnsOverTls(QDnsLookupReply *reply, QSpan<unsigned c
socket.setProtocolTag("domain-s"_L1);
# endif
+ // Request the name server attempt to authenticate the reply.
+ query[3] |= DnsAuthenticDataBit;
+
do {
quint16 size = qToBigEndian<quint16>(query.size());
QDeadlineTimer timeout(DnsOverTlsTimeout);
@@ -1363,8 +1410,12 @@ bool QDnsLookupRunnable::sendDnsOverTls(QDnsLookupReply *reply, QSpan<unsigned c
// the maximum allocation is small.
size = qFromBigEndian(size);
response.resize(size);
- if (waitForBytes(response.data(), size))
+ if (waitForBytes(response.data(), size)) {
+ // check if the AD bit is set; we'll trust it over TLS requests
+ if (size >= 4)
+ reply->authenticData = response[3] & DnsAuthenticDataBit;
return true;
+ }
} while (false);
// handle errors
diff --git a/src/network/kernel/qdnslookup.h b/src/network/kernel/qdnslookup.h
index 2a56dc16e8c..ad98f8ae5c2 100644
--- a/src/network/kernel/qdnslookup.h
+++ b/src/network/kernel/qdnslookup.h
@@ -142,6 +142,7 @@ class Q_NETWORK_EXPORT QDnsLookup : public QObject
{
Q_OBJECT
Q_PROPERTY(Error error READ error NOTIFY finished)
+ Q_PROPERTY(bool authenticData READ isAuthenticData NOTIFY finished)
Q_PROPERTY(QString errorString READ errorString NOTIFY finished)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged BINDABLE bindableName)
Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged BINDABLE bindableType)
@@ -196,6 +197,7 @@ public:
quint16 port = 0, QObject *parent = nullptr);
~QDnsLookup();
+ bool isAuthenticData() const;
Error error() const;
QString errorString() const;
bool isFinished() const;
diff --git a/src/network/kernel/qdnslookup_p.h b/src/network/kernel/qdnslookup_p.h
index 574b279fe6b..259dca0b2e4 100644
--- a/src/network/kernel/qdnslookup_p.h
+++ b/src/network/kernel/qdnslookup_p.h
@@ -48,6 +48,7 @@ class QDnsLookupReply
{
public:
QDnsLookup::Error error = QDnsLookup::NoError;
+ bool authenticData = false;
QString errorString;
QList<QDnsDomainNameRecord> canonicalNameRecords;
diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp
index 7a283d4f7fd..38a1dce5d9b 100644
--- a/src/network/kernel/qdnslookup_unix.cpp
+++ b/src/network/kernel/qdnslookup_unix.cpp
@@ -67,11 +67,9 @@ using Cache = QList<QDnsCachedName>; // QHash or QMap are overkill
// 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
@@ -122,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)
@@ -155,11 +150,22 @@ prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_r
static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<unsigned char> qbuffer,
ReplyBuffer &buffer, const QHostAddress &nameserver, quint16 port)
{
- //Check if a nameserver was set. If so, use it
- 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 (!nameserver.isNull()) {
+ if (!applyNameServer(state, nameserver, port)) {
+ reply->setError(QDnsLookup::ResolverError,
+ QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS"));
+ return -1;
+ }
+
+ // Request the name server attempt to authenticate the reply.
+ reinterpret_cast<HEADER *>(buffer.data())->ad = true;
+
+#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
}
auto attemptToSend = [&]() {
@@ -210,6 +216,15 @@ static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<unsign
}
}
+ // 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;
}
diff --git a/tests/manual/qdnslookup/main.cpp b/tests/manual/qdnslookup/main.cpp
index 129c79bb442..59ce2ab284d 100644
--- a/tests/manual/qdnslookup/main.cpp
+++ b/tests/manual/qdnslookup/main.cpp
@@ -118,10 +118,13 @@ static void printAnswers(const QDnsLookup &lookup)
static void printResults(const QDnsLookup &lookup, QElapsedTimer::Duration duration)
{
if (QDnsLookup::Error error = lookup.error())
- printf(";; status: %s (%s)\n", QMetaEnum::fromType<QDnsLookup::Error>().valueToKey(error),
+ printf(";; status: %s (%s)", QMetaEnum::fromType<QDnsLookup::Error>().valueToKey(error),
qPrintable(lookup.errorString()));
else
- printf(";; status: NoError\n");
+ printf(";; status: NoError");
+ if (lookup.isAuthenticData())
+ printf("; AuthenticData");
+ puts("");
QMetaEnum me = QMetaEnum::fromType<QDnsLookup::Type>();
printf(";; QUESTION:\n");