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 <marten.nordheim@qt.io>
This commit is contained in:
parent
f2f00b2a46
commit
503fd60988
@ -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.
|
||||
*/
|
||||
|
||||
/*!
|
||||
@ -418,6 +439,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
|
||||
|
@ -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;
|
||||
|
@ -48,6 +48,7 @@ class QDnsLookupReply
|
||||
{
|
||||
public:
|
||||
QDnsLookup::Error error = QDnsLookup::NoError;
|
||||
bool authenticData = false;
|
||||
QString errorString;
|
||||
|
||||
QList<QDnsDomainNameRecord> canonicalNameRecords;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user