QDnsLookup: add support for setting the port number of the server

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 <marten.nordheim@qt.io>
This commit is contained in:
Thiago Macieira 2023-05-08 21:54:59 -07:00
parent 029e0bf552
commit bce7009f55
7 changed files with 191 additions and 21 deletions

View File

@ -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<QHostAddress> 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<quint16> 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)
{
}

View File

@ -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<QHostAddress> bindableNameserver();
quint16 nameserverPort() const;
void setNameserverPort(quint16 port);
QBindable<quint16> bindableNameserverPort();
void setNameserver(const QHostAddress &nameserver, quint16 port);
QList<QDnsDomainNameRecord> canonicalNameRecords() const;
QList<QDnsHostAddressRecord> 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)

View File

@ -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

View File

@ -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"));

View File

@ -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;
}

View File

@ -7,7 +7,11 @@
#include <QSignalSpy>
#include <QtNetwork/QDnsLookup>
#include <QtCore/QRandomGenerator>
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QNetworkDatagram>
#include <QtNetwork/QUdpSocket>
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<QHostAddress> 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)

View File

@ -6,6 +6,7 @@
#include <QtCore/QElapsedTimer>
#include <QtCore/QMetaEnum>
#include <QtCore/QTimer>
#include <QtCore/QUrl>
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QDnsLookup>
@ -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<milliseconds>(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