From bce7009f552ed1e28ac2687ef004203f285ee21c Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 8 May 2023 21:54:59 -0700 Subject: [PATCH] QDnsLookup: add support for setting the port number of the server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I couldn't make my Windows 10 or 11 query a non-standard port. It kept complaining about "The parameter is incorrect.", so as a result the unit test doesn't actually test the new feature there. I can't find a single example of this on the Internet; my speculation is that the backend API that DnsQueryEx uses does not support setting port numbers (DnsQuery_{A,W} didn't offer that option). [ChangeLog][QtNetwork][QDnsLookup] Added setNameserverPort(). Change-Id: I3e3bfef633af4130a03afffd175d60a581cc0a9c Reviewed-by: MÃ¥rten Nordheim --- src/network/kernel/qdnslookup.cpp | 70 ++++++++++++++++++- src/network/kernel/qdnslookup.h | 9 +++ src/network/kernel/qdnslookup_p.h | 28 +++++--- src/network/kernel/qdnslookup_unix.cpp | 2 +- src/network/kernel/qdnslookup_win.cpp | 2 +- .../kernel/qdnslookup/tst_qdnslookup.cpp | 70 +++++++++++++++++++ tests/manual/qdnslookup/main.cpp | 31 ++++++-- 7 files changed, 191 insertions(+), 21 deletions(-) diff --git a/src/network/kernel/qdnslookup.cpp b/src/network/kernel/qdnslookup.cpp index 06f6f83a49b..645ed31b023 100644 --- a/src/network/kernel/qdnslookup.cpp +++ b/src/network/kernel/qdnslookup.cpp @@ -244,16 +244,39 @@ QDnsLookup::QDnsLookup(Type type, const QString &name, QObject *parent) /*! \fn QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, QObject *parent) \since 5.4 - Constructs a QDnsLookup object for the given \a type, \a name and - \a nameserver and sets \a parent as the parent object. + + Constructs a QDnsLookup object to issue a query for \a name of record type + \a type, using the DNS server \a nameserver running on the default DNS port, + and sets \a parent as the parent object. */ QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, QObject *parent) + : QDnsLookup(type, name, nameserver, DnsPort, parent) +{ +} + +/*! + \fn QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, quint16 port, QObject *parent) + \since 6.6 + + Constructs a QDnsLookup object to issue a query for \a name of record type + \a type, using the DNS server \a nameserver running on port \a port, and + sets \a parent as the parent object. + +//! [nameserver-port] + \note Setting the port number to any value other than the default (53) can + cause the name resolution to fail, depending on the operating system + limitations and firewalls. Notably, the Windows API used by QDnsLookup is + unable to handle alternate port numbers. +//! [nameserver-port] +*/ +QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, quint16 port, QObject *parent) : QObject(*new QDnsLookupPrivate, parent) { Q_D(QDnsLookup); d->name = name; d->type = type; + d->port = port; d->nameserver = nameserver; } @@ -366,6 +389,46 @@ QBindable QDnsLookup::bindableNameserver() return &d->nameserver; } +/*! + \property QDnsLookup::nameserverPort + \since 6.6 + \brief the port number of nameserver to use for DNS lookup. + \include qdnslookup.cpp nameserver-port +*/ + +quint16 QDnsLookup::nameserverPort() const +{ + return d_func()->port; +} + +void QDnsLookup::setNameserverPort(quint16 nameserverPort) +{ + Q_D(QDnsLookup); + d->port = nameserverPort; +} + +QBindable QDnsLookup::bindableNameserverPort() +{ + Q_D(QDnsLookup); + return &d->port; +} + +/*! + \since 6.6 + Sets the nameserver to \a nameserver and the port to \a port. + + \include qdnslookup.cpp nameserver-port + + \sa QDnsLookup::nameserver, QDnsLookup::nameserverPort +*/ +void QDnsLookup::setNameserver(const QHostAddress &nameserver, quint16 port) +{ + Qt::beginPropertyUpdateGroup(); + setNameserver(nameserver); + setNameserverPort(port); + Qt::endPropertyUpdateGroup(); +} + /*! Returns the list of canonical name records associated with this lookup. */ @@ -965,7 +1028,8 @@ void QDnsLookupPrivate::_q_lookupFinished(const QDnsLookupReply &_reply) inline QDnsLookupRunnable::QDnsLookupRunnable(const QDnsLookupPrivate *d) : requestName(QUrl::toAce(d->name)), nameserver(d->nameserver), - requestType(d->type) + requestType(d->type), + port(d->port) { } diff --git a/src/network/kernel/qdnslookup.h b/src/network/kernel/qdnslookup.h index bf31b81da1a..ca7cb617ccb 100644 --- a/src/network/kernel/qdnslookup.h +++ b/src/network/kernel/qdnslookup.h @@ -146,6 +146,8 @@ class Q_NETWORK_EXPORT QDnsLookup : public QObject Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged BINDABLE bindableType) Q_PROPERTY(QHostAddress nameserver READ nameserver WRITE setNameserver NOTIFY nameserverChanged BINDABLE bindableNameserver) + Q_PROPERTY(quint16 nameserverPort READ nameserverPort WRITE setNameserverPort + NOTIFY nameserverPortChanged BINDABLE bindableNameserverPort) public: enum Error @@ -178,6 +180,8 @@ public: explicit QDnsLookup(QObject *parent = nullptr); QDnsLookup(Type type, const QString &name, QObject *parent = nullptr); QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, QObject *parent = nullptr); + QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, quint16 port, + QObject *parent = nullptr); ~QDnsLookup(); Error error() const; @@ -195,6 +199,10 @@ public: QHostAddress nameserver() const; void setNameserver(const QHostAddress &nameserver); QBindable bindableNameserver(); + quint16 nameserverPort() const; + void setNameserverPort(quint16 port); + QBindable bindableNameserverPort(); + void setNameserver(const QHostAddress &nameserver, quint16 port); QList canonicalNameRecords() const; QList hostAddressRecords() const; @@ -214,6 +222,7 @@ Q_SIGNALS: void nameChanged(const QString &name); void typeChanged(Type type); void nameserverChanged(const QHostAddress &nameserver); + void nameserverPortChanged(quint16 port); private: Q_DECLARE_PRIVATE(QDnsLookup) diff --git a/src/network/kernel/qdnslookup_p.h b/src/network/kernel/qdnslookup_p.h index 45403050edd..a1ffb5f20b0 100644 --- a/src/network/kernel/qdnslookup_p.h +++ b/src/network/kernel/qdnslookup_p.h @@ -120,15 +120,12 @@ class QDnsLookupPrivate : public QObjectPrivate { public: QDnsLookupPrivate() - : isFinished(false) - , type(QDnsLookup::A) - , runnable(nullptr) + : type(QDnsLookup::A) + , port(DnsPort) { } void _q_lookupFinished(const QDnsLookupReply &reply); - bool isFinished; - void nameChanged() { emit q_func()->nameChanged(name); @@ -136,6 +133,13 @@ public: Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, QString, name, &QDnsLookupPrivate::nameChanged); + void nameserverChanged() + { + emit q_func()->nameserverChanged(nameserver); + } + Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, QHostAddress, nameserver, + &QDnsLookupPrivate::nameserverChanged); + void typeChanged() { emit q_func()->typeChanged(type); @@ -144,15 +148,18 @@ public: Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, QDnsLookup::Type, type, &QDnsLookupPrivate::typeChanged); - void nameserverChanged() + void nameserverPortChanged() { - emit q_func()->nameserverChanged(nameserver); + emit q_func()->nameserverPortChanged(port); } - Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, QHostAddress, nameserver, - &QDnsLookupPrivate::nameserverChanged); + + Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, quint16, + port, &QDnsLookupPrivate::nameserverPortChanged); + QDnsLookupReply reply; - QDnsLookupRunnable *runnable; + QDnsLookupRunnable *runnable = nullptr; + bool isFinished = false; Q_DECLARE_PUBLIC(QDnsLookup) }; @@ -173,6 +180,7 @@ private: QByteArray requestName; QHostAddress nameserver; QDnsLookup::Type requestType; + quint16 port; }; class QDnsLookupThreadPool : public QThreadPool diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp index af2ef73a22b..9d12b75b31b 100644 --- a/src/network/kernel/qdnslookup_unix.cpp +++ b/src/network/kernel/qdnslookup_unix.cpp @@ -109,7 +109,7 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) auto guard = qScopeGuard([&] { res_nclose(&state); }); //Check if a nameserver was set. If so, use it - if (!applyNameServer(&state, nameserver, DnsPort)) { + if (!applyNameServer(&state, nameserver, port)) { qWarning("QDnsLookup: %s", "IPv6 nameservers are currently not supported on this OS"); return reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS")); diff --git a/src/network/kernel/qdnslookup_win.cpp b/src/network/kernel/qdnslookup_win.cpp index ac0252f2cc1..24169545919 100644 --- a/src/network/kernel/qdnslookup_win.cpp +++ b/src/network/kernel/qdnslookup_win.cpp @@ -82,7 +82,7 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) request.pDnsServerList->MaxCount = sizeof(dnsAddresses); request.pDnsServerList->AddrCount = 1; // ### setting port 53 seems to cause some systems to fail - setSockaddr(sa, nameserver, 0); + setSockaddr(sa, nameserver, port == DnsPort ? 0 : port); request.pDnsServerList->Family = sa->sa_family; } diff --git a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp index 1d0da9dbe35..568f0d84b86 100644 --- a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp +++ b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp @@ -7,7 +7,11 @@ #include #include + +#include #include +#include +#include using namespace Qt::StringLiterals; static const int Timeout = 15000; // 15s @@ -35,6 +39,7 @@ private slots: void lookupReuse(); void lookupAbortRetry(); + void setNameserverLoopback(); void bindingsAndProperties(); }; @@ -351,6 +356,62 @@ void tst_QDnsLookup::lookupAbortRetry() QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:db8::1")); } +void tst_QDnsLookup::setNameserverLoopback() +{ +#ifdef Q_OS_WIN + // Windows doesn't like sending DNS requests to ports other than 53, so + // let's try it first. + constexpr quint16 DesiredPort = 53; +#else + // Trying to bind to port 53 will fail on Unix systems unless this test is + // run as root, so we try mDNS's port (to help decoding in a packet capture). + constexpr quint16 DesiredPort = 5353; // mDNS +#endif + // random loopback address so multiple copies of this test can run + QHostAddress desiredAddress(0x7f000000 | QRandomGenerator::system()->bounded(0xffffff)); + + QUdpSocket server; + if (!server.bind(desiredAddress, DesiredPort)) { + // port in use, try a random one + server.bind(QHostAddress::LocalHost, 0); + } + QCOMPARE(server.state(), QUdpSocket::BoundState); + + QDnsLookup lookup(QDnsLookup::Type::A, u"somelabel.somedomain"_s); + QSignalSpy spy(&lookup, SIGNAL(finished())); + lookup.setNameserver(server.localAddress(), server.localPort()); + + // QDnsLookup is threaded, so we can answer on the main thread + QObject::connect(&server, &QUdpSocket::readyRead, + &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); + QObject::connect(&lookup, &QDnsLookup::finished, + &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); + lookup.lookup(); + QTestEventLoop::instance().enterLoop(5); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY2(spy.isEmpty(), qPrintable(lookup.errorString())); + + QNetworkDatagram dgram = server.receiveDatagram(); + QByteArray data = dgram.data(); + QCOMPARE_GT(data.size(), HeaderSize); + + quint8 opcode = (quint8(data.at(3)) >> 4) & 0xF; + QCOMPARE(opcode, 0); // standard query + + // send an NXDOMAIN reply to release the lookup thread + QByteArray reply = data; + reply[2] = 0x80; // header->qr = true; + reply[3] = 3; // header->rcode = NXDOMAIN; + server.writeDatagram(dgram.makeReply(reply)); + server.close(); + + // now check that the QDnsLookup finished + QTestEventLoop::instance().enterLoop(5); + QVERIFY(!QTestEventLoop::instance().timeout()); + QCOMPARE(spy.size(), 1); + QCOMPARE(lookup.error(), QDnsLookup::NotFoundError); +} + void tst_QDnsLookup::bindingsAndProperties() { QDnsLookup lookup; @@ -383,14 +444,23 @@ void tst_QDnsLookup::bindingsAndProperties() QProperty nameserverProp; lookup.bindableNameserver().setBinding(Qt::makePropertyBinding(nameserverProp)); const QSignalSpy nameserverChangeSpy(&lookup, &QDnsLookup::nameserverChanged); + const QSignalSpy nameserverPortChangeSpy(&lookup, &QDnsLookup::nameserverPortChanged); nameserverProp = QHostAddress::LocalHost; QCOMPARE(nameserverChangeSpy.size(), 1); + QCOMPARE(nameserverPortChangeSpy.size(), 0); QCOMPARE(lookup.nameserver(), QHostAddress::LocalHost); nameserverProp.setBinding(lookup.bindableNameserver().makeBinding()); lookup.setNameserver(QHostAddress::Any); QCOMPARE(nameserverProp.value(), QHostAddress::Any); + QCOMPARE(nameserverChangeSpy.size(), 2); + QCOMPARE(nameserverPortChangeSpy.size(), 0); + + lookup.setNameserver(QHostAddress::LocalHostIPv6, 10053); + QCOMPARE(nameserverProp.value(), QHostAddress::LocalHostIPv6); + QCOMPARE(nameserverChangeSpy.size(), 3); + QCOMPARE(nameserverPortChangeSpy.size(), 1); } QTEST_MAIN(tst_QDnsLookup) diff --git a/tests/manual/qdnslookup/main.cpp b/tests/manual/qdnslookup/main.cpp index 3ddf84063ef..19aeeeb4b80 100644 --- a/tests/manual/qdnslookup/main.cpp +++ b/tests/manual/qdnslookup/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,24 @@ static int showHelp(const char *argv0, int exitcode) return exitcode; } +static auto parseServerAddress(QString server) +{ + struct R { + QHostAddress address; + int port = -1; + } r; + + // let's use QUrl to help us + QUrl url; + url.setAuthority(server); + if (!url.isValid() || !url.userInfo().isNull()) + return r; // failed + + r.port = url.port(); + r.address.setAddress(url.host()); + return r; +} + static void printAnswers(const QDnsLookup &lookup) { printf("\n;; ANSWER:\n"); @@ -96,7 +115,7 @@ static void printResults(const QDnsLookup &lookup, QElapsedTimer::Duration durat 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())); + printf(";; SERVER: %s#%d\n", qPrintable(server.toString()), lookup.nameserverPort()); } int main(int argc, char *argv[]) @@ -138,15 +157,15 @@ int main(int argc, char *argv[]) QDnsLookup lookup(type, domain); if (!server.isEmpty()) { - QHostAddress addr(server); - if (addr.isNull()) - addr = QHostInfo::fromName(server).addresses().value(0); - if (addr.isNull()) { + auto addr = parseServerAddress(server); + if (addr.address.isNull()) { fprintf(stderr, "%s: could not parse name server address '%s'\n", argv[0], qPrintable(server)); return EXIT_FAILURE; } - lookup.setNameserver(addr); + lookup.setNameserver(addr.address); + if (addr.port > 0) + lookup.setNameserverPort(addr.port); } // execute the lookup