QDnsLookup/Unix: modernize the setting of IPv6 server addresses
Instead of using #if, this now uses SFINAE to detect the presence of the glibc extensions to set IPv6 nameserver addresses. It's also possible that this fixes some bugs that have always been there, but never checked because we don't have a way to unit-test explicit name servers. To that effect, this commit adds a manual unit test that mimics the BIND tool "dig". When running: ./qdnslookup qt-project.org any @dns.google it printed for me: ; <<>> QDnsLookup 6.6.0 ;; status: NoError ;; QUESTION: ;qt-project.org IN ANY ;; ANSWER: qt-project.org 3600 IN MX 10 mx.qt-project.org qt-project.org 3600 IN NS ns14.cloudns.net qt-project.org 3600 IN NS ns11.cloudns.net qt-project.org 3600 IN NS ns12.cloudns.net qt-project.org 3600 IN NS ns13.cloudns.net qt-project.org 3600 IN A 52.18.144.254 qt-project.org 3600 IN TXT "v=spf1 mx ip4:193.209.87.4 include:spf.protection.outlook.com ~all" ;; Query time: 241 ms ;; SERVER: 2001:4860:4860::8844#53 strace confirms the DNS queries were sent to the correct address. Change-Id: I3e3bfef633af4130a03afffd175d56a92371ed16 Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
parent
df2131426e
commit
eb9f88a078
@ -31,6 +31,8 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
//#define QDNSLOOKUP_DEBUG
|
||||
|
||||
constexpr quint16 DnsPort = 53;
|
||||
|
||||
class QDnsLookupRunnable;
|
||||
|
||||
class QDnsLookupReply
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
|
||||
// Copyright (C) 2023 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "qdnslookup_p.h"
|
||||
@ -6,7 +7,7 @@
|
||||
#include <qscopedpointer.h>
|
||||
#include <qurl.h>
|
||||
#include <qvarlengtharray.h>
|
||||
#include <private/qnativesocketengine_p.h> // for SetSALen
|
||||
#include <private/qnativesocketengine_p.h> // for setSockAddr
|
||||
#include <private/qtnetwork-config_p.h>
|
||||
|
||||
QT_REQUIRE_CONFIG(res_ninit);
|
||||
@ -23,6 +24,67 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
template <typename T> void setNsMap(T &ext, std::enable_if_t<sizeof(T::nsmap) != 0, uint16_t> v)
|
||||
{
|
||||
// Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address
|
||||
// See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html
|
||||
// Unneeded since glibc 2.22 (2015), but doesn't hurt to set it
|
||||
// See: https://sourceware.org/git/?p=glibc.git;a=commit;h=2212c1420c92a33b0e0bd9a34938c9814a56c0f7
|
||||
ext.nsmap[0] = v;
|
||||
}
|
||||
template <typename T> void setNsMap(T &, ...)
|
||||
{
|
||||
// fallback
|
||||
}
|
||||
|
||||
template <bool Condition>
|
||||
using EnableIfIPv6 = std::enable_if_t<Condition, const QHostAddress *>;
|
||||
|
||||
template <typename State>
|
||||
bool setIpv6NameServer(State *state,
|
||||
EnableIfIPv6<sizeof(std::declval<State>()._u._ext.nsaddrs) != 0> addr,
|
||||
quint16 port)
|
||||
{
|
||||
// glibc-like API to set IPv6 name servers
|
||||
struct sockaddr_in6 *ns = state->_u._ext.nsaddrs[0];
|
||||
|
||||
// nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf
|
||||
if (!ns) {
|
||||
// Memory allocated here will be free()'d in res_close() as we
|
||||
// have done res_init() above.
|
||||
ns = static_cast<struct sockaddr_in6*>(calloc(1, sizeof(struct sockaddr_in6)));
|
||||
Q_CHECK_PTR(ns);
|
||||
state->_u._ext.nsaddrs[0] = ns;
|
||||
}
|
||||
|
||||
setNsMap(state->_u._ext, MAXNS + 1);
|
||||
state->_u._ext.nscount6 = 1;
|
||||
setSockaddr(ns, *addr, port);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename State> bool setIpv6NameServer(State *, const void *, quint16)
|
||||
{
|
||||
// fallback
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
|
||||
{
|
||||
if (nameserver.isNull())
|
||||
return nullptr;
|
||||
|
||||
state->nscount = 1;
|
||||
state->nsaddr_list[0].sin_family = AF_UNSPEC;
|
||||
if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
if (!setIpv6NameServer(state, &nameserver, port))
|
||||
return QDnsLookupPrivate::msgNoIpV6NameServerAdresses;
|
||||
} else {
|
||||
setSockaddr(&state->nsaddr_list[0], nameserver, port);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply)
|
||||
{
|
||||
// Initialize state.
|
||||
@ -35,43 +97,11 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
|
||||
auto guard = qScopeGuard([&] { res_nclose(&state); });
|
||||
|
||||
//Check if a nameserver was set. If so, use it
|
||||
if (!nameserver.isNull()) {
|
||||
if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
state.nsaddr_list[0].sin_addr.s_addr = htonl(nameserver.toIPv4Address());
|
||||
state.nscount = 1;
|
||||
} else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
#if defined(Q_OS_LINUX)
|
||||
struct sockaddr_in6 *ns;
|
||||
ns = state._u._ext.nsaddrs[0];
|
||||
// nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf
|
||||
if (!ns) {
|
||||
// Memory allocated here will be free'd in res_close() as we
|
||||
// have done res_init() above.
|
||||
ns = (struct sockaddr_in6*) calloc(1, sizeof(struct sockaddr_in6));
|
||||
Q_CHECK_PTR(ns);
|
||||
state._u._ext.nsaddrs[0] = ns;
|
||||
}
|
||||
#ifndef __UCLIBC__
|
||||
// Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address
|
||||
// See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html
|
||||
state._u._ext.nsmap[0] = MAXNS + 1;
|
||||
#endif
|
||||
state._u._ext.nscount6 = 1;
|
||||
ns->sin6_family = AF_INET6;
|
||||
ns->sin6_port = htons(53);
|
||||
SetSALen::set(ns, sizeof(*ns));
|
||||
|
||||
Q_IPV6ADDR ipv6Address = nameserver.toIPv6Address();
|
||||
for (int i=0; i<16; i++) {
|
||||
ns->sin6_addr.s6_addr[i] = ipv6Address[i];
|
||||
}
|
||||
#else
|
||||
qWarning("%s", QDnsLookupPrivate::msgNoIpV6NameServerAdresses);
|
||||
reply->error = QDnsLookup::ResolverError;
|
||||
reply->errorString = tr(QDnsLookupPrivate::msgNoIpV6NameServerAdresses);
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
if (const char *msg = applyNameServer(&state, nameserver, DnsPort)) {
|
||||
qWarning("%s", msg);
|
||||
reply->error = QDnsLookup::ResolverError;
|
||||
reply->errorString = tr(msg);
|
||||
return;
|
||||
}
|
||||
#ifdef QDNSLOOKUP_DEBUG
|
||||
state.options |= RES_DEBUG;
|
||||
|
@ -18,6 +18,7 @@ add_subdirectory(keypadnavigation)
|
||||
#add_subdirectory(lance) # qgl.h missing
|
||||
add_subdirectory(qcursor)
|
||||
add_subdirectory(qdesktopservices)
|
||||
add_subdirectory(qdnslookup)
|
||||
add_subdirectory(qgraphicsitem)
|
||||
add_subdirectory(qgraphicsitemgroup)
|
||||
add_subdirectory(qgraphicslayout/flicker)
|
||||
|
9
tests/manual/qdnslookup/CMakeLists.txt
Normal file
9
tests/manual/qdnslookup/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
# Copyright (C) 2023 Intel Corporation.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
qt_internal_add_manual_test(qdnslookup
|
||||
SOURCES
|
||||
main.cpp
|
||||
LIBRARIES
|
||||
Qt::Network
|
||||
)
|
165
tests/manual/qdnslookup/main.cpp
Normal file
165
tests/manual/qdnslookup/main.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
// Copyright (C) 2023 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QMetaEnum>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtNetwork/QHostAddress>
|
||||
#include <QtNetwork/QHostInfo>
|
||||
#include <QtNetwork/QDnsLookup>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
using namespace std::chrono;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static QDnsLookup::Type typeFromString(QString str)
|
||||
{
|
||||
// we can use the meta object
|
||||
QMetaEnum me = QMetaEnum::fromType<QDnsLookup::Type>();
|
||||
|
||||
bool ok;
|
||||
int value = me.keyToValue(str.toUpper().toLatin1(), &ok);
|
||||
if (!ok)
|
||||
return QDnsLookup::Type(0);
|
||||
return QDnsLookup::Type(value);
|
||||
}
|
||||
|
||||
static int showHelp(const char *argv0, int exitcode)
|
||||
{
|
||||
// like dig
|
||||
printf("%s [@global-server] [domain] [query-type]\n", argv0);
|
||||
return exitcode;
|
||||
}
|
||||
|
||||
static void printAnswers(const QDnsLookup &lookup)
|
||||
{
|
||||
printf("\n;; ANSWER:\n");
|
||||
static auto printRecordCommon = [](const auto &rr, const char *rrtype) {
|
||||
printf("%-23s %6d IN %s\t", qPrintable(rr.name()), rr.timeToLive(), rrtype);
|
||||
};
|
||||
auto printNameRecords = [](const char *rrtype, const QList<QDnsDomainNameRecord> list) {
|
||||
for (const QDnsDomainNameRecord &rr : list) {
|
||||
printRecordCommon(rr, rrtype);
|
||||
printf("%s\n", qPrintable(rr.value()));
|
||||
}
|
||||
};
|
||||
|
||||
for (const QDnsMailExchangeRecord &rr : lookup.mailExchangeRecords()) {
|
||||
printRecordCommon(rr, "MX");
|
||||
printf("%d %s\n", rr.preference(), qPrintable(rr.exchange()));
|
||||
}
|
||||
for (const QDnsServiceRecord &rr : lookup.serviceRecords()) {
|
||||
printRecordCommon(rr, "SRV");
|
||||
printf("%d %d %d %s\n", rr.priority(), rr.weight(), rr.port(),
|
||||
qPrintable(rr.target()));
|
||||
}
|
||||
printNameRecords("NS", lookup.nameServerRecords());
|
||||
printNameRecords("PTR", lookup.pointerRecords());
|
||||
printNameRecords("CNAME", lookup.canonicalNameRecords());
|
||||
|
||||
for (const QDnsHostAddressRecord &rr : lookup.hostAddressRecords()) {
|
||||
QHostAddress addr = rr.value();
|
||||
printRecordCommon(rr, addr.protocol() == QHostAddress::IPv6Protocol ? "AAAA" : "A");
|
||||
printf("%s\n", qPrintable(addr.toString()));
|
||||
}
|
||||
|
||||
for (const QDnsTextRecord &rr : lookup.textRecords()) {
|
||||
printRecordCommon(rr, "TXT");
|
||||
for (const QByteArray &data : rr.values())
|
||||
printf("%s ", qPrintable(QDebug::toString(data)));
|
||||
puts("");
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
qPrintable(lookup.errorString()));
|
||||
else
|
||||
printf(";; status: NoError\n");
|
||||
|
||||
QMetaEnum me = QMetaEnum::fromType<QDnsLookup::Type>();
|
||||
printf(";; QUESTION:\n");
|
||||
printf(";%-30s IN %s\n", qPrintable(lookup.name()),
|
||||
me.valueToKey(lookup.type()));
|
||||
|
||||
if (lookup.error() == QDnsLookup::NoError)
|
||||
printAnswers(lookup);
|
||||
|
||||
printf("\n;; Query time: %lld ms\n", qint64(duration_cast<milliseconds>(duration).count()));
|
||||
if (QHostAddress server = lookup.nameserver(); !server.isNull())
|
||||
printf(";; SERVER: %s#53\n", qPrintable(server.toString()));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
QDnsLookup::Type type = {};
|
||||
QString domain, server;
|
||||
const QStringList args = QCoreApplication::arguments().sliced(1);
|
||||
for (const QString &arg : args) {
|
||||
if (arg.startsWith(u'@')) {
|
||||
server = arg.mid(1);
|
||||
continue;
|
||||
}
|
||||
if (arg == u"-h")
|
||||
return showHelp(argv[0], EXIT_SUCCESS);
|
||||
if (domain.isNull()) {
|
||||
domain = arg;
|
||||
continue;
|
||||
}
|
||||
if (type != QDnsLookup::Type{})
|
||||
return showHelp(argv[0], EXIT_FAILURE);
|
||||
|
||||
type = typeFromString(arg);
|
||||
if (type == QDnsLookup::Type{}) {
|
||||
fprintf(stderr, "%s: unknown DNS record type '%s'. Valid types are:\n",
|
||||
argv[0], qPrintable(arg));
|
||||
QMetaEnum me = QMetaEnum::fromType<QDnsLookup::Type>();
|
||||
for (int i = 0; i < me.keyCount(); ++i)
|
||||
fprintf(stderr, " %s\n", me.key(i));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (domain.isEmpty())
|
||||
domain = u"qt-project.org"_s;
|
||||
if (type == QDnsLookup::Type{})
|
||||
type = QDnsLookup::A;
|
||||
|
||||
QDnsLookup lookup(type, domain);
|
||||
if (!server.isEmpty()) {
|
||||
QHostAddress addr(server);
|
||||
if (addr.isNull())
|
||||
addr = QHostInfo::fromName(server).addresses().value(0);
|
||||
if (addr.isNull()) {
|
||||
fprintf(stderr, "%s: could not parse name server address '%s'\n",
|
||||
argv[0], qPrintable(server));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
lookup.setNameserver(addr);
|
||||
}
|
||||
|
||||
// execute the lookup
|
||||
QObject::connect(&lookup, &QDnsLookup::finished, qApp, &QCoreApplication::quit);
|
||||
QTimer::singleShot(15s, []() { qApp->exit(EXIT_FAILURE); });
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
lookup.lookup();
|
||||
if (a.exec() == EXIT_FAILURE)
|
||||
return EXIT_FAILURE;
|
||||
printf("; <<>> QDnsLookup " QT_VERSION_STR " <<>> %s %s\n",
|
||||
qPrintable(QCoreApplication::applicationName()), qPrintable(args.join(u' ')));
|
||||
printResults(lookup, timer.durationElapsed());
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user