diff --git a/src/network/kernel/qdnslookup_p.h b/src/network/kernel/qdnslookup_p.h index c1082863cd4..fa7e6cd7172 100644 --- a/src/network/kernel/qdnslookup_p.h +++ b/src/network/kernel/qdnslookup_p.h @@ -31,6 +31,8 @@ QT_BEGIN_NAMESPACE //#define QDNSLOOKUP_DEBUG +constexpr quint16 DnsPort = 53; + class QDnsLookupRunnable; class QDnsLookupReply diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp index ceab943a04f..240ace3c3c0 100644 --- a/src/network/kernel/qdnslookup_unix.cpp +++ b/src/network/kernel/qdnslookup_unix.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2012 Jeremy Lainé +// 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 #include #include -#include // for SetSALen +#include // for setSockAddr #include QT_REQUIRE_CONFIG(res_ninit); @@ -23,6 +24,67 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +template void setNsMap(T &ext, std::enable_if_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 void setNsMap(T &, ...) +{ + // fallback +} + +template +using EnableIfIPv6 = std::enable_if_t; + +template +bool setIpv6NameServer(State *state, + EnableIfIPv6()._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(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 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; diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt index 6c8cce89496..035e14d4c86 100644 --- a/tests/manual/CMakeLists.txt +++ b/tests/manual/CMakeLists.txt @@ -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) diff --git a/tests/manual/qdnslookup/CMakeLists.txt b/tests/manual/qdnslookup/CMakeLists.txt new file mode 100644 index 00000000000..646e972b302 --- /dev/null +++ b/tests/manual/qdnslookup/CMakeLists.txt @@ -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 +) diff --git a/tests/manual/qdnslookup/main.cpp b/tests/manual/qdnslookup/main.cpp new file mode 100644 index 00000000000..3ddf84063ef --- /dev/null +++ b/tests/manual/qdnslookup/main.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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(); + + 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 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().valueToKey(error), + qPrintable(lookup.errorString())); + else + printf(";; status: NoError\n"); + + QMetaEnum me = QMetaEnum::fromType(); + 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(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(); + 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; +}