From e54dc7c2b5b9aa14989f26a718eb99d7516af4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Mon, 23 Jan 2012 18:25:39 +0100 Subject: Add support for DNS lookups using native APIs The QDnsLookup class provides asynchronous APIs for performing DNS lookups. For now, the following lookups are supported: - A and AAAA - CNAME as defined per RFC 1035 - MX as defined per RFC 1035 - NS as defined per RFC 1035 - PTR as defined per RFC 1035 - SRV as defined per RFC 2782 - TXT as defined per RFC 1035 Task-number: QTBUG-10481 Change-Id: I46c1741ec23615863eeca3a1231d5e3f8942495e Reviewed-by: Thiago Macieira --- .../code/src_network_kernel_qdnslookup.cpp | 73 ++ examples/network/dnslookup/dnslookup.cpp | 155 ++++ examples/network/dnslookup/dnslookup.h | 61 ++ examples/network/dnslookup/dnslookup.pro | 12 + examples/network/network.pro | 1 + src/network/kernel/kernel.pri | 9 +- src/network/kernel/qdnslookup.cpp | 988 +++++++++++++++++++++ src/network/kernel/qdnslookup.h | 235 +++++ src/network/kernel/qdnslookup_p.h | 213 +++++ src/network/kernel/qdnslookup_unix.cpp | 324 +++++++ src/network/kernel/qdnslookup_win.cpp | 177 ++++ tests/auto/network/kernel/kernel.pro | 2 + .../auto/network/kernel/qdnslookup/qdnslookup.pro | 7 + .../network/kernel/qdnslookup/tst_qdnslookup.cpp | 274 ++++++ .../qdnslookup_appless/qdnslookup_appless.pro | 7 + .../qdnslookup_appless/tst_qdnslookup_appless.cpp | 92 ++ 16 files changed, 2627 insertions(+), 3 deletions(-) create mode 100644 doc/src/snippets/code/src_network_kernel_qdnslookup.cpp create mode 100644 examples/network/dnslookup/dnslookup.cpp create mode 100644 examples/network/dnslookup/dnslookup.h create mode 100644 examples/network/dnslookup/dnslookup.pro create mode 100644 src/network/kernel/qdnslookup.cpp create mode 100644 src/network/kernel/qdnslookup.h create mode 100644 src/network/kernel/qdnslookup_p.h create mode 100644 src/network/kernel/qdnslookup_unix.cpp create mode 100644 src/network/kernel/qdnslookup_win.cpp create mode 100644 tests/auto/network/kernel/qdnslookup/qdnslookup.pro create mode 100644 tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp create mode 100644 tests/auto/network/kernel/qdnslookup_appless/qdnslookup_appless.pro create mode 100644 tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp diff --git a/doc/src/snippets/code/src_network_kernel_qdnslookup.cpp b/doc/src/snippets/code/src_network_kernel_qdnslookup.cpp new file mode 100644 index 0000000000..f2e530ff96 --- /dev/null +++ b/doc/src/snippets/code/src_network_kernel_qdnslookup.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [0] +void MyObject::lookupServers() +{ + // Create a DNS lookup. + dns = new QDnsLookup(this); + connect(dns, SIGNAL(finished()), + this, SLOT(handleServers())); + + // Find the XMPP servers for gmail.com + dns->setType(QDnsLookup::SRV); + dns->setName("_xmpp-client._tcp.gmail.com"); + dns->lookup(); +} +//! [0] + + +//! [1] +void MyObject::handleServers() +{ + // Check the lookup succeeded. + if (dns->error() != QDnsLookup::NoError) { + qWarning("DNS lookup failed"); + dns->deleteLater(); + return; + } + + // Handle the results. + foreach (const QDnsServiceRecord &record, dns->serviceRecords()) { + ... + } + dns->deleteLater(); +} +//! [1] diff --git a/examples/network/dnslookup/dnslookup.cpp b/examples/network/dnslookup/dnslookup.cpp new file mode 100644 index 0000000000..90cf914629 --- /dev/null +++ b/examples/network/dnslookup/dnslookup.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "dnslookup.h" + +#include +#include +#include +#include +#include + +#include + +static void usage() { + printf("Qt DNS example - performs DNS lookups\n" + "Usage: dnslookup [-t ] name\n\n"); +} + +DnsManager::DnsManager() +{ + dns = new QDnsLookup(this); + connect(dns, SIGNAL(finished()), this, SLOT(showResults())); +} + +void DnsManager::execute() +{ + QStringList args = QCoreApplication::instance()->arguments(); + args.takeFirst(); + + // lookup type + dns->setType(QDnsLookup::A); + if (args.size() > 1 && args.first() == "-t") { + args.takeFirst(); + const QString type = args.takeFirst().toLower(); + if (type == "a") + dns->setType(QDnsLookup::A); + else if (type == "aaaa") + dns->setType(QDnsLookup::AAAA); + else if (type == "any") + dns->setType(QDnsLookup::ANY); + else if (type == "cname") + dns->setType(QDnsLookup::CNAME); + else if (type == "mx") + dns->setType(QDnsLookup::MX); + else if (type == "ns") + dns->setType(QDnsLookup::NS); + else if (type == "ptr") + dns->setType(QDnsLookup::PTR); + else if (type == "srv") + dns->setType(QDnsLookup::SRV); + else if (type == "txt") + dns->setType(QDnsLookup::TXT); + else { + printf("Bad record type: %s\n", qPrintable(type)); + QCoreApplication::instance()->quit(); + return; + } + } + if (args.isEmpty()) { + usage(); + QCoreApplication::instance()->quit(); + return; + } + dns->setName(args.takeFirst()); + dns->lookup(); +} + +void DnsManager::showResults() +{ + if (dns->error() != QDnsLookup::NoError) + printf("Error: %i (%s)\n", dns->error(), qPrintable(dns->errorString())); + + // CNAME records + foreach (const QDnsDomainNameRecord &record, dns->canonicalNameRecords()) + printf("%s\t%i\tIN\tCNAME\t%s\n", qPrintable(record.name()), record.timeToLive(), qPrintable(record.value())); + + // A and AAAA records + foreach (const QDnsHostAddressRecord &record, dns->hostAddressRecords()) { + const char *type = (record.value().protocol() == QAbstractSocket::IPv6Protocol) ? "AAAA" : "A"; + printf("%s\t%i\tIN\t%s\t%s\n", qPrintable(record.name()), record.timeToLive(), type, qPrintable(record.value().toString())); + } + + // MX records + foreach (const QDnsMailExchangeRecord &record, dns->mailExchangeRecords()) + printf("%s\t%i\tIN\tMX\t%u %s\n", qPrintable(record.name()), record.timeToLive(), record.preference(), qPrintable(record.exchange())); + + // NS records + foreach (const QDnsDomainNameRecord &record, dns->nameServerRecords()) + printf("%s\t%i\tIN\tNS\t%s\n", qPrintable(record.name()), record.timeToLive(), qPrintable(record.value())); + + // PTR records + foreach (const QDnsDomainNameRecord &record, dns->pointerRecords()) + printf("%s\t%i\tIN\tPTR\t%s\n", qPrintable(record.name()), record.timeToLive(), qPrintable(record.value())); + + // SRV records + foreach (const QDnsServiceRecord &record, dns->serviceRecords()) + printf("%s\t%i\tIN\tSRV\t%u %u %u %s\n", qPrintable(record.name()), record.timeToLive(), record.priority(), record.weight(), record.port(), qPrintable(record.target())); + + // TXT records + foreach (const QDnsTextRecord &record, dns->textRecords()) { + QStringList values; + foreach (const QByteArray &ba, record.values()) + values << "\"" + QString::fromAscii(ba) + "\""; + printf("%s\t%i\tIN\tTXT\t%s\n", qPrintable(record.name()), record.timeToLive(), qPrintable(values.join(" "))); + } + + QCoreApplication::instance()->quit(); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + DnsManager manager; + QTimer::singleShot(0, &manager, SLOT(execute())); + + return app.exec(); +} diff --git a/examples/network/dnslookup/dnslookup.h b/examples/network/dnslookup/dnslookup.h new file mode 100644 index 0000000000..3c59173fe5 --- /dev/null +++ b/examples/network/dnslookup/dnslookup.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +QT_BEGIN_NAMESPACE +class QDnsLookup; +QT_END_NAMESPACE + +class DnsManager : public QObject +{ + Q_OBJECT + +public: + DnsManager(); + +public slots: + void execute(); + void showResults(); + +private: + QDnsLookup *dns; +}; + diff --git a/examples/network/dnslookup/dnslookup.pro b/examples/network/dnslookup/dnslookup.pro new file mode 100644 index 0000000000..160666de7c --- /dev/null +++ b/examples/network/dnslookup/dnslookup.pro @@ -0,0 +1,12 @@ +TEMPLATE = app +QT = core network +mac:CONFIG -= app_bundle +win32:CONFIG += console +HEADERS += dnslookup.h +SOURCES += dnslookup.cpp + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/qtbase/network/dnslookup +sources.files = $$SOURCES $$HEADERS $$FORMS $$RESOURCES *.pro +sources.path = $$[QT_INSTALL_EXAMPLES]/qtbase/network/dnslookup +INSTALLS += target sources diff --git a/examples/network/network.pro b/examples/network/network.pro index 0496cbb242..4342c81895 100644 --- a/examples/network/network.pro +++ b/examples/network/network.pro @@ -1,5 +1,6 @@ TEMPLATE = subdirs SUBDIRS = \ + dnslookup \ download \ downloadmanager diff --git a/src/network/kernel/kernel.pri b/src/network/kernel/kernel.pri index d6e099701d..ea937da518 100644 --- a/src/network/kernel/kernel.pri +++ b/src/network/kernel/kernel.pri @@ -5,6 +5,8 @@ INCLUDEPATH += $$PWD HEADERS += kernel/qauthenticator.h \ kernel/qauthenticator_p.h \ + kernel/qdnslookup.h \ + kernel/qdnslookup_p.h \ kernel/qhostaddress.h \ kernel/qhostinfo.h \ kernel/qhostinfo_p.h \ @@ -14,15 +16,16 @@ HEADERS += kernel/qauthenticator.h \ kernel/qnetworkinterface_p.h SOURCES += kernel/qauthenticator.cpp \ + kernel/qdnslookup.cpp \ kernel/qhostaddress.cpp \ kernel/qhostinfo.cpp \ kernel/qurlinfo.cpp \ kernel/qnetworkproxy.cpp \ kernel/qnetworkinterface.cpp -unix:SOURCES += kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp -win32:SOURCES += kernel/qhostinfo_win.cpp kernel/qnetworkinterface_win.cpp -integrity:SOURCES += kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp +unix:SOURCES += kernel/qdnslookup_unix.cpp kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp +win32:SOURCES += kernel/qdnslookup_win.cpp kernel/qhostinfo_win.cpp kernel/qnetworkinterface_win.cpp +integrity:SOURCES += kernel/qdnslookup_unix.cpp kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp mac:LIBS_PRIVATE += -framework SystemConfiguration -framework CoreFoundation mac:SOURCES += kernel/qnetworkproxy_mac.cpp diff --git a/src/network/kernel/qdnslookup.cpp b/src/network/kernel/qdnslookup.cpp new file mode 100644 index 0000000000..f4b143a5fc --- /dev/null +++ b/src/network/kernel/qdnslookup.cpp @@ -0,0 +1,988 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnslookup.h" +#include "qdnslookup_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QDnsLookupThreadPool, theDnsLookupThreadPool); +Q_GLOBAL_STATIC(QThreadStorage, theDnsLookupSeedStorage); + +static bool qt_qdnsmailexchangerecord_less_than(const QDnsMailExchangeRecord &r1, const QDnsMailExchangeRecord &r2) +{ + // Lower numbers are more preferred than higher ones. + return r1.preference() < r2.preference(); +} + +/*! + Sorts a list of QDnsMailExchangeRecord objects according to RFC 5321. +*/ + +static void qt_qdnsmailexchangerecord_sort(QList &records) +{ + // If we have no more than one result, we are done. + if (records.size() <= 1) + return; + + // Order the records by preference. + qSort(records.begin(), records.end(), qt_qdnsmailexchangerecord_less_than); + + int i = 0; + while (i < records.size()) { + + // Determine the slice of records with the current preference. + QList slice; + const quint16 slicePreference = records[i].preference(); + for (int j = i; j < records.size(); ++j) { + if (records[j].preference() != slicePreference) + break; + slice << records[j]; + } + + // Randomize the slice of records. + while (!slice.isEmpty()) { + const unsigned int pos = qrand() % slice.size(); + records[i++] = slice.takeAt(pos); + } + } +} + +static bool qt_qdnsservicerecord_less_than(const QDnsServiceRecord &r1, const QDnsServiceRecord &r2) +{ + // Order by priority, or if the priorities are equal, + // put zero weight records first. + return r1.priority() < r2.priority() + || (r1.priority() == r2.priority() + && r1.weight() == 0 && r2.weight() > 0); +} + +/*! + Sorts a list of QDnsServiceRecord objects according to RFC 2782. +*/ + +static void qt_qdnsservicerecord_sort(QList &records) +{ + // If we have no more than one result, we are done. + if (records.size() <= 1) + return; + + // Order the records by priority, and for records with an equal + // priority, put records with a zero weight first. + qSort(records.begin(), records.end(), qt_qdnsservicerecord_less_than); + + int i = 0; + while (i < records.size()) { + + // Determine the slice of records with the current priority. + QList slice; + const quint16 slicePriority = records[i].priority(); + unsigned int sliceWeight = 0; + for (int j = i; j < records.size(); ++j) { + if (records[j].priority() != slicePriority) + break; + sliceWeight += records[j].weight(); + slice << records[j]; + } +#ifdef QDNSLOOKUP_DEBUG + qDebug("qt_qdnsservicerecord_sort() : priority %i (size: %i, total weight: %i)", + slicePriority, slice.size(), sliceWeight); +#endif + + // Order the slice of records. + while (!slice.isEmpty()) { + const unsigned int weightThreshold = qrand() % (sliceWeight + 1); + unsigned int summedWeight = 0; + for (int j = 0; j < slice.size(); ++j) { + summedWeight += slice[j].weight(); + if (summedWeight >= weightThreshold) { +#ifdef QDNSLOOKUP_DEBUG + qDebug("qt_qdnsservicerecord_sort() : adding %s %i (weight: %i)", + qPrintable(slice[j].target()), slice[j].port(), + slice[j].weight()); +#endif + // Adjust the slice weight and take the current record. + sliceWeight -= slice[j].weight(); + records[i++] = slice.takeAt(j); + break; + } + } + } + } +} + +/*! + \class QDnsLookup + \brief The QDnsLookup class represents a DNS lookup. + + \inmodule QtNetwork + \ingroup network + + QDnsLookup uses the mechanisms provided by the operating system to perform + DNS lookups. To perform a lookup you need to specify a \l name and \l type + then invoke the \l{QDnsLookup::lookup()}{lookup()} slot. The + \l{QDnsLookup::finished()}{finished()} signal will be emitted upon + completion. + + For example, you can determine which servers an XMPP chat client should + connect to for a given domain with: + + \snippet doc/src/snippets/code/src_network_kernel_qdnslookup.cpp 0 + + Once the request finishes you can handle the results with: + + \snippet doc/src/snippets/code/src_network_kernel_qdnslookup.cpp 1 + + \note If you simply want to find the IP address(es) associated with a host + name, or the host name associated with an IP address you should use + QHostInfo instead. +*/ + +/*! + \enum QDnsLookup::Error + + Indicates all possible error conditions found during the + processing of the DNS lookup. + + \value NoError no error condition. + + \value ResolverError there was an error initializing the system's + DNS resolver. + + \value OperationCancelledError the lookup was aborted using the abort() + method. + + \value InvalidRequestError the requested DNS lookup was invalid. + + \value InvalidReplyError the reply returned by the server was invalid. + + \value ServerFailureError the server encountered an internal failure + while processing the request (SERVFAIL). + + \value ServerRefusedError the server refused to process the request for + security or policy reasons (REFUSED). + + \value NotFoundError the requested domain name does not exist + (NXDOMAIN). +*/ + +/*! + \enum QDnsLookup::Type + + Indicates the type of DNS lookup that was performed. + + \value A IPv4 address records. + + \value AAAA IPv6 address records. + + \value ANY any records. + + \value CNAME canonical name records. + + \value MX mail exchange records. + + \value NS name server records. + + \value PTR pointer records. + + \value SRV service records. + + \value TXT text records. +*/ + +/*! + \fn void QDnsLookup::finished() + + This signal is emitted when the reply has finished processing. +*/ + +/*! + \fn void QDnsLookup::nameChanged(const QString &name) + + This signal is emitted when the lookup \l name changes. + \a name is the new lookup name. +*/ + +/*! + \fn void QDnsLookup::typeChanged(Type type) + + This signal is emitted when the lookup \l type changes. + \a type is the new lookup type. +*/ + +/*! + Constructs a QDnsLookup object and sets \a parent as the parent object. + + The \l type property will default to QDnsLookup::A. +*/ + +QDnsLookup::QDnsLookup(QObject *parent) + : QObject(*new QDnsLookupPrivate, parent) +{ + qRegisterMetaType(); +} +/*! + Constructs a QDnsLookup object for the given \a type and \a name and sets + \a parent as the parent object. +*/ + +QDnsLookup::QDnsLookup(Type type, const QString &name, QObject *parent) + : QObject(*new QDnsLookupPrivate, parent) +{ + Q_D(QDnsLookup); + qRegisterMetaType(); + d->name = name; + d->type = type; +} + +/*! + Destroys the QDnsLookup object. + + It is safe to delete a QDnsLookup object even if it is not finished, you + will simply never receive its results. +*/ + +QDnsLookup::~QDnsLookup() +{ +} + +/*! + \property QDnsLookup::error + \brief the type of error that occurred if the DNS lookup failed, or NoError. +*/ + +QDnsLookup::Error QDnsLookup::error() const +{ + return d_func()->reply.error; +} + +/*! + \property QDnsLookup::errorString + \brief a human-readable description of the error if the DNS lookup failed. +*/ + +QString QDnsLookup::errorString() const +{ + return d_func()->reply.errorString; +} + +/*! + \property QDnsLookup::finished + \brief whether the reply has finished or was aborted. +*/ + +bool QDnsLookup::isFinished() const +{ + return d_func()->isFinished; +} + +/*! + \property QDnsLookup::name + \brief the name to lookup. + + \note The name will be encoded using IDNA, which means it's unsuitable for + querying SRV records compatible with the DNS-SD specification. +*/ + +QString QDnsLookup::name() const +{ + return d_func()->name; +} + +void QDnsLookup::setName(const QString &name) +{ + Q_D(QDnsLookup); + if (name != d->name) { + d->name = name; + emit nameChanged(name); + } +} + +/*! + \property QDnsLookup::type + \brief the type of DNS lookup. +*/ + +QDnsLookup::Type QDnsLookup::type() const +{ + return d_func()->type; +} + +void QDnsLookup::setType(Type type) +{ + Q_D(QDnsLookup); + if (type != d->type) { + d->type = type; + emit typeChanged(type); + } +} + +/*! + Returns the list of canonical name records associated with this lookup. +*/ + +QList QDnsLookup::canonicalNameRecords() const +{ + return d_func()->reply.canonicalNameRecords; +} + +/*! + Returns the list of host address records associated with this lookup. +*/ + +QList QDnsLookup::hostAddressRecords() const +{ + return d_func()->reply.hostAddressRecords; +} + +/*! + Returns the list of mail exchange records associated with this lookup. + + The records are sorted according to + \l{http://www.rfc-editor.org/rfc/rfc5321.txt}{RFC 5321}, so if you use them + to connect to servers, you should try them in the order they are listed. +*/ + +QList QDnsLookup::mailExchangeRecords() const +{ + return d_func()->reply.mailExchangeRecords; +} + +/*! + Returns the list of name server records associated with this lookup. +*/ + +QList QDnsLookup::nameServerRecords() const +{ + return d_func()->reply.nameServerRecords; +} + +/*! + Returns the list of pointer records associated with this lookup. +*/ + +QList QDnsLookup::pointerRecords() const +{ + return d_func()->reply.pointerRecords; +} + +/*! + Returns the list of service records associated with this lookup. + + The records are sorted according to + \l{http://www.rfc-editor.org/rfc/rfc2782.txt}{RFC 2782}, so if you use them + to connect to servers, you should try them in the order they are listed. +*/ + +QList QDnsLookup::serviceRecords() const +{ + return d_func()->reply.serviceRecords; +} + +/*! + Returns the list of text records associated with this lookup. +*/ + +QList QDnsLookup::textRecords() const +{ + return d_func()->reply.textRecords; +} + +/*! + Aborts the DNS lookup operation. + + If the lookup is already finished, does nothing. +*/ + +void QDnsLookup::abort() +{ + Q_D(QDnsLookup); + if (d->runnable) { + d->runnable = 0; + d->reply = QDnsLookupReply(); + d->reply.error = QDnsLookup::OperationCancelledError; + d->reply.errorString = tr("Operation cancelled"); + d->isFinished = true; + emit finished(); + } +} + +/*! + Performs the DNS lookup. + + The \l{QDnsLookup::finished()}{finished()} signal is emitted upon completion. +*/ + +void QDnsLookup::lookup() +{ + Q_D(QDnsLookup); + d->isFinished = false; + d->reply = QDnsLookupReply(); + d->runnable = new QDnsLookupRunnable(d->type, QUrl::toAce(d->name)); + connect(d->runnable, SIGNAL(finished(QDnsLookupReply)), + this, SLOT(_q_lookupFinished(QDnsLookupReply)), + Qt::BlockingQueuedConnection); + theDnsLookupThreadPool()->start(d->runnable); +} + +/*! + \class QDnsDomainNameRecord + \brief The QDnsDomainNameRecord class stores information about a domain + name record. + + \inmodule QtNetwork + \ingroup network + + When performing a name server lookup, zero or more records will be returned. + Each record is represented by a QDnsDomainNameRecord instance. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty domain name record object. +*/ + +QDnsDomainNameRecord::QDnsDomainNameRecord() + : d(new QDnsDomainNameRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsDomainNameRecord::QDnsDomainNameRecord(const QDnsDomainNameRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a domain name record. +*/ + +QDnsDomainNameRecord::~QDnsDomainNameRecord() +{ +} + +/*! + Returns the name for this record. +*/ + +QString QDnsDomainNameRecord::name() const +{ + return d->name; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsDomainNameRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Returns the value for this domain name record. +*/ + +QString QDnsDomainNameRecord::value() const +{ + return d->value; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsDomainNameRecord &QDnsDomainNameRecord::operator=(const QDnsDomainNameRecord &other) +{ + d = other.d; + return *this; +} + +/*! + \class QDnsHostAddressRecord + \brief The QDnsHostAddressRecord class stores information about a host + address record. + + \inmodule QtNetwork + \ingroup network + + When performing an address lookup, zero or more records will be + returned. Each record is represented by a QDnsHostAddressRecord instance. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty host address record object. +*/ + +QDnsHostAddressRecord::QDnsHostAddressRecord() + : d(new QDnsHostAddressRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsHostAddressRecord::QDnsHostAddressRecord(const QDnsHostAddressRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a host address record. +*/ + +QDnsHostAddressRecord::~QDnsHostAddressRecord() +{ +} + +/*! + Returns the name for this record. +*/ + +QString QDnsHostAddressRecord::name() const +{ + return d->name; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsHostAddressRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Returns the value for this host address record. +*/ + +QHostAddress QDnsHostAddressRecord::value() const +{ + return d->value; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsHostAddressRecord &QDnsHostAddressRecord::operator=(const QDnsHostAddressRecord &other) +{ + d = other.d; + return *this; +} + +/*! + \class QDnsMailExchangeRecord + \brief The QDnsMailExchangeRecord class stores information about a DNS MX record. + + \inmodule QtNetwork + \ingroup network + + When performing a lookup on a service, zero or more records will be + returned. Each record is represented by a QDnsMailExchangeRecord instance. + + The meaning of the fields is defined in + \l{http://www.rfc-editor.org/rfc/rfc1035.txt}{RFC 1035}. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty mail exchange record object. +*/ + +QDnsMailExchangeRecord::QDnsMailExchangeRecord() + : d(new QDnsMailExchangeRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsMailExchangeRecord::QDnsMailExchangeRecord(const QDnsMailExchangeRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a mail exchange record. +*/ + +QDnsMailExchangeRecord::~QDnsMailExchangeRecord() +{ +} + +/*! + Returns the domain name of the mail exchange for this record. +*/ + +QString QDnsMailExchangeRecord::exchange() const +{ + return d->exchange; +} + +/*! + Returns the name for this record. +*/ + +QString QDnsMailExchangeRecord::name() const +{ + return d->name; +} + +/*! + Returns the preference for this record. +*/ + +quint16 QDnsMailExchangeRecord::preference() const +{ + return d->preference; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsMailExchangeRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsMailExchangeRecord &QDnsMailExchangeRecord::operator=(const QDnsMailExchangeRecord &other) +{ + d = other.d; + return *this; +} + +/*! + \class QDnsServiceRecord + \brief The QDnsServiceRecord class stores information about a DNS SRV record. + + \inmodule QtNetwork + \ingroup network + + When performing a lookup on a service, zero or more records will be + returned. Each record is represented by a QDnsServiceRecord instance. + + The meaning of the fields is defined in + \l{http://www.rfc-editor.org/rfc/rfc2782.txt}{RFC 2782}. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty service record object. +*/ + +QDnsServiceRecord::QDnsServiceRecord() + : d(new QDnsServiceRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsServiceRecord::QDnsServiceRecord(const QDnsServiceRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a service record. +*/ + +QDnsServiceRecord::~QDnsServiceRecord() +{ +} + +/*! + Returns the name for this record. +*/ + +QString QDnsServiceRecord::name() const +{ + return d->name; +} + +/*! + Returns the port on the target host for this service record. +*/ + +quint16 QDnsServiceRecord::port() const +{ + return d->port; +} + +/*! + Returns the priority for this service record. + + A client must attempt to contact the target host with the lowest-numbered + priority. +*/ + +quint16 QDnsServiceRecord::priority() const +{ + return d->priority; +} + +/*! + Returns the domain name of the target host for this service record. +*/ + +QString QDnsServiceRecord::target() const +{ + return d->target; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsServiceRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Returns the weight for this service record. + + The weight field specifies a relative weight for entries with the same + priority. Entries with higher weights should be selected with a higher + probability. +*/ + +quint16 QDnsServiceRecord::weight() const +{ + return d->weight; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsServiceRecord &QDnsServiceRecord::operator=(const QDnsServiceRecord &other) +{ + d = other.d; + return *this; +} + +/*! + \class QDnsTextRecord + \brief The QDnsTextRecord class stores information about a DNS TXT record. + + \inmodule QtNetwork + \ingroup network + + When performing a text lookup, zero or more records will be + returned. Each record is represented by a QDnsTextRecord instance. + + The meaning of the fields is defined in + \l{http://www.rfc-editor.org/rfc/rfc1035.txt}{RFC 1035}. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty text record object. +*/ + +QDnsTextRecord::QDnsTextRecord() + : d(new QDnsTextRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsTextRecord::QDnsTextRecord(const QDnsTextRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a text record. +*/ + +QDnsTextRecord::~QDnsTextRecord() +{ +} + +/*! + Returns the name for this text record. +*/ + +QString QDnsTextRecord::name() const +{ + return d->name; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsTextRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Returns the values for this text record. +*/ + +QList QDnsTextRecord::values() const +{ + return d->values; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsTextRecord &QDnsTextRecord::operator=(const QDnsTextRecord &other) +{ + d = other.d; + return *this; +} + +void QDnsLookupPrivate::_q_lookupFinished(const QDnsLookupReply &_reply) +{ + Q_Q(QDnsLookup); + if (runnable == q->sender()) { +#ifdef QDNSLOOKUP_DEBUG + qDebug("DNS reply for %s: %i (%s)", qPrintable(name), _reply.error, qPrintable(_reply.errorString)); +#endif + reply = _reply; + runnable = 0; + isFinished = true; + emit q->finished(); + } +} + +void QDnsLookupRunnable::run() +{ + QDnsLookupReply reply; + + // Validate input. + if (requestName.isEmpty()) { + reply.error = QDnsLookup::InvalidRequestError; + reply.errorString = tr("Invalid domain name"); + emit finished(reply); + return; + } + + // Perform request. + query(requestType, requestName, &reply); + + // Sort results. + if (!theDnsLookupSeedStorage()->hasLocalData()) { + qsrand(QTime(0,0,0).msecsTo(QTime::currentTime()) ^ reinterpret_cast(this)); + theDnsLookupSeedStorage()->setLocalData(new bool(true)); + } + qt_qdnsmailexchangerecord_sort(reply.mailExchangeRecords); + qt_qdnsservicerecord_sort(reply.serviceRecords); + + emit finished(reply); +} + +QDnsLookupThreadPool::QDnsLookupThreadPool() + : signalsConnected(false) +{ + // Run up to 5 lookups in parallel. + setMaxThreadCount(5); +} + +void QDnsLookupThreadPool::start(QRunnable *runnable) +{ + // Ensure threads complete at application destruction. + if (!signalsConnected) { + QMutexLocker signalsLocker(&signalsMutex); + if (!signalsConnected) { + QCoreApplication *app = QCoreApplication::instance(); + if (!app) { + qWarning("QDnsLookup requires a QCoreApplication"); + delete runnable; + return; + } + + moveToThread(app->thread()); + connect(app, SIGNAL(destroyed()), + SLOT(_q_applicationDestroyed()), Qt::DirectConnection); + signalsConnected = true; + } + } + + QThreadPool::start(runnable); +} + +void QDnsLookupThreadPool::_q_applicationDestroyed() +{ + waitForDone(); + signalsConnected = false; +} + +QT_END_NAMESPACE + +#include "moc_qdnslookup.cpp" diff --git a/src/network/kernel/qdnslookup.h b/src/network/kernel/qdnslookup.h new file mode 100644 index 0000000000..198b19d8a8 --- /dev/null +++ b/src/network/kernel/qdnslookup.h @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDNSLOOKUP_H +#define QDNSLOOKUP_H + +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QHostAddress; +class QDnsLookupPrivate; +class QDnsDomainNameRecordPrivate; +class QDnsHostAddressRecordPrivate; +class QDnsMailExchangeRecordPrivate; +class QDnsServiceRecordPrivate; +class QDnsTextRecordPrivate; + +class Q_NETWORK_EXPORT QDnsDomainNameRecord +{ +public: + QDnsDomainNameRecord(); + QDnsDomainNameRecord(const QDnsDomainNameRecord &other); + ~QDnsDomainNameRecord(); + + QString name() const; + quint32 timeToLive() const; + QString value() const; + + QDnsDomainNameRecord &operator=(const QDnsDomainNameRecord &other); + +private: + QSharedDataPointer d; + friend class QDnsLookupRunnable; +}; + +class Q_NETWORK_EXPORT QDnsHostAddressRecord +{ +public: + QDnsHostAddressRecord(); + QDnsHostAddressRecord(const QDnsHostAddressRecord &other); + ~QDnsHostAddressRecord(); + + QString name() const; + quint32 timeToLive() const; + QHostAddress value() const; + + QDnsHostAddressRecord &operator=(const QDnsHostAddressRecord &other); + +private: + QSharedDataPointer d; + friend class QDnsLookupRunnable; +}; + +class Q_NETWORK_EXPORT QDnsMailExchangeRecord +{ +public: + QDnsMailExchangeRecord(); + QDnsMailExchangeRecord(const QDnsMailExchangeRecord &other); + ~QDnsMailExchangeRecord(); + + QString exchange() const; + QString name() const; + quint16 preference() const; + quint32 timeToLive() const; + + QDnsMailExchangeRecord &operator=(const QDnsMailExchangeRecord &other); + +private: + QSharedDataPointer d; + friend class QDnsLookupRunnable; +}; + +class Q_NETWORK_EXPORT QDnsServiceRecord +{ +public: + QDnsServiceRecord(); + QDnsServiceRecord(const QDnsServiceRecord &other); + ~QDnsServiceRecord(); + + QString name() const; + quint16 port() const; + quint16 priority() const; + QString target() const; + quint32 timeToLive() const; + quint16 weight() const; + + QDnsServiceRecord &operator=(const QDnsServiceRecord &other); + +private: + QSharedDataPointer d; + friend class QDnsLookupRunnable; +}; + +class Q_NETWORK_EXPORT QDnsTextRecord +{ +public: + QDnsTextRecord(); + QDnsTextRecord(const QDnsTextRecord &other); + ~QDnsTextRecord(); + + QString name() const; + quint32 timeToLive() const; + QList values() const; + + QDnsTextRecord &operator=(const QDnsTextRecord &other); + +private: + QSharedDataPointer d; + friend class QDnsLookupRunnable; +}; + +class Q_NETWORK_EXPORT QDnsLookup : public QObject +{ + Q_OBJECT + Q_ENUMS(Error Type) + Q_PROPERTY(Error error READ error NOTIFY finished) + Q_PROPERTY(QString errorString READ errorString NOTIFY finished) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged) + +public: + enum Error + { + NoError = 0, + ResolverError, + OperationCancelledError, + InvalidRequestError, + InvalidReplyError, + ServerFailureError, + ServerRefusedError, + NotFoundError + }; + + enum Type + { + A = 1, + AAAA = 28, + ANY = 255, + CNAME = 5, + MX = 15, + NS = 2, + PTR = 12, + SRV = 33, + TXT = 16 + }; + + QDnsLookup(QObject *parent = 0); + QDnsLookup(Type type, const QString &name, QObject *parent = 0); + ~QDnsLookup(); + + Error error() const; + QString errorString() const; + bool isFinished() const; + + QString name() const; + void setName(const QString &name); + + Type type() const; + void setType(QDnsLookup::Type); + + QList canonicalNameRecords() const; + QList hostAddressRecords() const; + QList mailExchangeRecords() const; + QList nameServerRecords() const; + QList pointerRecords() const; + QList serviceRecords() const; + QList textRecords() const; + + +public Q_SLOTS: + void abort(); + void lookup(); + +Q_SIGNALS: + void finished(); + void nameChanged(const QString &name); + void typeChanged(Type type); + +private: + Q_DECLARE_PRIVATE(QDnsLookup) + Q_PRIVATE_SLOT(d_func(), void _q_lookupFinished(const QDnsLookupReply &reply)) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDNSLOOKUP_H diff --git a/src/network/kernel/qdnslookup_p.h b/src/network/kernel/qdnslookup_p.h new file mode 100644 index 0000000000..8515620a93 --- /dev/null +++ b/src/network/kernel/qdnslookup_p.h @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDNSLOOKUP_P_H +#define QDNSLOOKUP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QDnsLookup class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qmutex.h" +#include "QtCore/qrunnable.h" +#include "QtCore/qsharedpointer.h" +#include "QtCore/qthreadpool.h" +#include "QtNetwork/qdnslookup.h" +#include "QtNetwork/qhostaddress.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +//#define QDNSLOOKUP_DEBUG + +class QDnsLookupRunnable; + +class QDnsLookupReply +{ +public: + QDnsLookupReply() + : error(QDnsLookup::NoError) + { } + + QDnsLookup::Error error; + QString errorString; + + QList canonicalNameRecords; + QList hostAddressRecords; + QList mailExchangeRecords; + QList nameServerRecords; + QList pointerRecords; + QList serviceRecords; + QList textRecords; +}; + +class QDnsLookupPrivate : public QObjectPrivate +{ +public: + QDnsLookupPrivate() + : isFinished(false) + , type(QDnsLookup::A) + , runnable(0) + { } + + void _q_lookupFinished(const QDnsLookupReply &reply); + + bool isFinished; + QString name; + QDnsLookup::Type type; + QDnsLookupReply reply; + QDnsLookupRunnable *runnable; + + Q_DECLARE_PUBLIC(QDnsLookup) +}; + +class QDnsLookupRunnable : public QObject, public QRunnable +{ + Q_OBJECT + +public: + QDnsLookupRunnable(QDnsLookup::Type type, const QByteArray &name) + : requestType(type) + , requestName(name) + { } + void run(); + +signals: + void finished(const QDnsLookupReply &reply); + +private: + static void query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply); + QDnsLookup::Type requestType; + QByteArray requestName; +}; + +class QDnsLookupThreadPool : public QThreadPool +{ + Q_OBJECT + +public: + QDnsLookupThreadPool(); + void start(QRunnable *runnable); + +private slots: + void _q_applicationDestroyed(); + +private: + QMutex signalsMutex; + bool signalsConnected; +}; + +class QDnsRecordPrivate : public QSharedData +{ +public: + QDnsRecordPrivate() + : timeToLive(0) + { } + + QString name; + quint32 timeToLive; +}; + +class QDnsDomainNameRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsDomainNameRecordPrivate() + { } + + QString value; +}; + +class QDnsHostAddressRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsHostAddressRecordPrivate() + { } + + QHostAddress value; +}; + +class QDnsMailExchangeRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsMailExchangeRecordPrivate() + : preference(0) + { } + + QString exchange; + quint16 preference; +}; + +class QDnsServiceRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsServiceRecordPrivate() + : port(0), + priority(0), + weight(0) + { } + + QString target; + quint16 port; + quint16 priority; + quint16 weight; +}; + +class QDnsTextRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsTextRecordPrivate() + { } + + QList values; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QDnsLookupReply) + +#endif // QDNSLOOKUP_P_H diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp new file mode 100644 index 0000000000..21a7135106 --- /dev/null +++ b/src/network/kernel/qdnslookup_unix.cpp @@ -0,0 +1,324 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnslookup_p.h" + +#include +#include +#include +#include + +#include +#include +#if defined(Q_OS_MAC) +#include +#include +#endif +#include + +QT_BEGIN_NAMESPACE + +typedef int (*dn_expand_proto)(const unsigned char *, const unsigned char *, const unsigned char *, char *, int); +static dn_expand_proto local_dn_expand = 0; +typedef void (*res_nclose_proto)(res_state); +static res_nclose_proto local_res_nclose = 0; +typedef int (*res_ninit_proto)(res_state); +static res_ninit_proto local_res_ninit = 0; +typedef int (*res_nquery_proto)(res_state, const char *, int, int, unsigned char *, int); +static res_nquery_proto local_res_nquery = 0; + +// Custom deleter to close resolver state. + +struct QDnsLookupStateDeleter +{ + static inline void cleanup(struct __res_state *pointer) + { + local_res_nclose(pointer); + } +}; + +static void resolveLibrary() +{ + QLibrary lib(QLatin1String("resolv")); + if (!lib.load()) + return; + + local_dn_expand = dn_expand_proto(lib.resolve("__dn_expand")); + if (!local_dn_expand) + local_dn_expand = dn_expand_proto(lib.resolve("dn_expand")); + + local_res_nclose = res_nclose_proto(lib.resolve("__res_nclose")); + if (!local_res_nclose) + local_res_nclose = res_nclose_proto(lib.resolve("res_9_nclose")); + if (!local_res_nclose) + local_res_nclose = res_nclose_proto(lib.resolve("res_nclose")); + + local_res_ninit = res_ninit_proto(lib.resolve("__res_ninit")); + if (!local_res_ninit) + local_res_ninit = res_ninit_proto(lib.resolve("res_9_ninit")); + if (!local_res_ninit) + local_res_ninit = res_ninit_proto(lib.resolve("res_ninit")); + + local_res_nquery = res_nquery_proto(lib.resolve("__res_nquery")); + if (!local_res_nquery) + local_res_nquery = res_nquery_proto(lib.resolve("res_9_nquery")); + if (!local_res_nquery) + local_res_nquery = res_nquery_proto(lib.resolve("res_nquery")); +} + +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply) +{ + // Load dn_expand, res_ninit and res_nquery on demand. + static volatile bool triedResolve = false; + if (!triedResolve) { + QMutexLocker locker(QMutexPool::globalInstanceGet(&local_res_ninit)); + if (!triedResolve) { + resolveLibrary(); + triedResolve = true; + } + } + + // If dn_expand, res_ninit or res_nquery is missing, fail. + if (!local_dn_expand || !local_res_nclose || !local_res_ninit || !local_res_nquery) { + reply->error = QDnsLookup::ResolverError; + reply->errorString = tr("Resolver functions not found"); + return; + } + + // Initialize state. + struct __res_state state; + memset(&state, 0, sizeof(state)); + if (local_res_ninit(&state) < 0) { + reply->error = QDnsLookup::ResolverError; + reply->errorString = tr("Resolver initialization failed"); + return; + } +#ifdef QDNSLOOKUP_DEBUG + state.options |= RES_DEBUG; +#endif + QScopedPointer state_ptr(&state); + + // Perform DNS query. + unsigned char response[PACKETSZ]; + memset(response, 0, sizeof(response)); + const int responseLength = local_res_nquery(&state, requestName, C_IN, requestType, response, sizeof(response)); + + // Check the response header. + HEADER *header = (HEADER*)response; + const int answerCount = ntohs(header->ancount); + switch (header->rcode) { + case NOERROR: + break; + case FORMERR: + reply->error = QDnsLookup::InvalidRequestError; + reply->errorString = tr("Server could not process query"); + return; + case SERVFAIL: + reply->error = QDnsLookup::ServerFailureError; + reply->errorString = tr("Server failure"); + return; + case NXDOMAIN: + reply->error = QDnsLookup::NotFoundError; + reply->errorString = tr("Non existent domain"); + return; + case REFUSED: + reply->error = QDnsLookup::ServerRefusedError; + reply->errorString = tr("Server refused to answer"); + return; + default: + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid reply received"); + return; + } + + // Check the reply is valid. + if (responseLength < int(sizeof(HEADER))) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid reply received"); + return; + } + + // Skip the query host, type (2 bytes) and class (2 bytes). + char host[PACKETSZ], answer[PACKETSZ]; + unsigned char *p = response + sizeof(HEADER); + int status = local_dn_expand(response, response + responseLength, p, host, sizeof(host)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Could not expand domain name"); + return; + } + p += status + 4; + + // Extract results. + int answerIndex = 0; + while ((p < response + responseLength) && (answerIndex < answerCount)) { + status = local_dn_expand(response, response + responseLength, p, host, sizeof(host)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Could not expand domain name"); + return; + } + const QString name = QUrl::fromAce(host); + + p += status; + const quint16 type = (p[0] << 8) | p[1]; + p += 2; // RR type + p += 2; // RR class + const quint32 ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + p += 4; + const quint16 size = (p[0] << 8) | p[1]; + p += 2; + + if (type == QDnsLookup::A) { + if (size != 4) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid IPv4 address record"); + return; + } + const quint32 addr = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QHostAddress(addr); + reply->hostAddressRecords.append(record); + } else if (type == QDnsLookup::AAAA) { + if (size != 16) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid IPv6 address record"); + return; + } + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QHostAddress(p); + reply->hostAddressRecords.append(record); + } else if (type == QDnsLookup::CNAME) { + status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid canonical name record"); + return; + } + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QUrl::fromAce(answer); + reply->canonicalNameRecords.append(record); + } else if (type == QDnsLookup::NS) { + status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid name server record"); + return; + } + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QUrl::fromAce(answer); + reply->nameServerRecords.append(record); + } else if (type == QDnsLookup::PTR) { + status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid pointer record"); + return; + } + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QUrl::fromAce(answer); + reply->pointerRecords.append(record); + } else if (type == QDnsLookup::MX) { + const quint16 preference = (p[0] << 8) | p[1]; + status = local_dn_expand(response, response + responseLength, p + 2, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid mail exchange record"); + return; + } + QDnsMailExchangeRecord record; + record.d->exchange = QUrl::fromAce(answer); + record.d->name = name; + record.d->preference = preference; + record.d->timeToLive = ttl; + reply->mailExchangeRecords.append(record); + } else if (type == QDnsLookup::SRV) { + const quint16 priority = (p[0] << 8) | p[1]; + const quint16 weight = (p[2] << 8) | p[3]; + const quint16 port = (p[4] << 8) | p[5]; + status = local_dn_expand(response, response + responseLength, p + 6, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid service record"); + return; + } + QDnsServiceRecord record; + record.d->name = name; + record.d->target = QUrl::fromAce(answer); + record.d->port = port; + record.d->priority = priority; + record.d->timeToLive = ttl; + record.d->weight = weight; + reply->serviceRecords.append(record); + } else if (type == QDnsLookup::TXT) { + unsigned char *txt = p; + QDnsTextRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + while (txt < p + size) { + const unsigned char length = *txt; + txt++; + if (txt + length > p + size) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid text record"); + return; + } + record.d->values << QByteArray((char*)txt, length); + txt += length; + } + reply->textRecords.append(record); + } + p += size; + answerIndex++; + } +} + +QT_END_NAMESPACE diff --git a/src/network/kernel/qdnslookup_win.cpp b/src/network/kernel/qdnslookup_win.cpp new file mode 100644 index 0000000000..97a82de43c --- /dev/null +++ b/src/network/kernel/qdnslookup_win.cpp @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnslookup_p.h" + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +typedef DNS_STATUS (*dns_query_utf8_proto)(PCSTR,WORD,DWORD,PIP4_ARRAY,PDNS_RECORD*,PVOID*); +static dns_query_utf8_proto local_dns_query_utf8 = 0; +typedef void (*dns_record_list_free_proto)(PDNS_RECORD,DNS_FREE_TYPE); +static dns_record_list_free_proto local_dns_record_list_free = 0; + +static void resolveLibrary() +{ + local_dns_query_utf8 = (dns_query_utf8_proto) QSystemLibrary::resolve(QLatin1String("dnsapi"), "DnsQuery_UTF8"); + local_dns_record_list_free = (dns_record_list_free_proto) QSystemLibrary::resolve(QLatin1String("dnsapi"), "DnsRecordListFree"); +} + +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply) +{ + // Load DnsQuery_UTF8 and DnsRecordListFree on demand. + static volatile bool triedResolve = false; + if (!triedResolve) { + QMutexLocker locker(QMutexPool::globalInstanceGet(&local_dns_query_utf8)); + if (!triedResolve) { + resolveLibrary(); + triedResolve = true; + } + } + + // If DnsQuery_UTF8 or DnsRecordListFree is missing, fail. + if (!local_dns_query_utf8 || !local_dns_record_list_free) { + reply->error = QDnsLookup::ResolverError, + reply->errorString = tr("Resolver functions not found"); + return; + } + + // Perform DNS query. + PDNS_RECORD dns_records; + const DNS_STATUS status = local_dns_query_utf8(requestName, requestType, DNS_QUERY_STANDARD, NULL, &dns_records, NULL); + switch (status) { + case ERROR_SUCCESS: + break; + case DNS_ERROR_RCODE_FORMAT_ERROR: + reply->error = QDnsLookup::InvalidRequestError; + reply->errorString = tr("Server could not process query"); + return; + case DNS_ERROR_RCODE_SERVER_FAILURE: + reply->error = QDnsLookup::ServerFailureError; + reply->errorString = tr("Server failure"); + return; + case DNS_ERROR_RCODE_NAME_ERROR: + reply->error = QDnsLookup::NotFoundError; + reply->errorString = tr("Non existent domain"); + return; + case DNS_ERROR_RCODE_REFUSED: + reply->error = QDnsLookup::ServerRefusedError; + reply->errorString = tr("Server refused to answer"); + return; + default: + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid reply received"); + return; + } + + // Extract results. + for (PDNS_RECORD ptr = dns_records; ptr != NULL; ptr = ptr->pNext) { + const QString name = QUrl::fromAce((char*)ptr->pName); + if (ptr->wType == QDnsLookup::A) { + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QHostAddress(ntohl(ptr->Data.A.IpAddress)); + reply->hostAddressRecords.append(record); + } else if (ptr->wType == QDnsLookup::AAAA) { + Q_IPV6ADDR addr; + memcpy(&addr, &ptr->Data.AAAA.Ip6Address, sizeof(Q_IPV6ADDR)); + + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QHostAddress(addr); + reply->hostAddressRecords.append(record); + } else if (ptr->wType == QDnsLookup::CNAME) { + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QUrl::fromAce((char*)ptr->Data.Cname.pNameHost); + reply->canonicalNameRecords.append(record); + } else if (ptr->wType == QDnsLookup::MX) { + QDnsMailExchangeRecord record; + record.d->name = name; + record.d->exchange = QUrl::fromAce((char*)ptr->Data.Mx.pNameExchange); + record.d->preference = ptr->Data.Mx.wPreference; + record.d->timeToLive = ptr->dwTtl; + reply->mailExchangeRecords.append(record); + } else if (ptr->wType == QDnsLookup::NS) { + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QUrl::fromAce((char*)ptr->Data.Ns.pNameHost); + reply->nameServerRecords.append(record); + } else if (ptr->wType == QDnsLookup::PTR) { + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QUrl::fromAce((char*)ptr->Data.Ptr.pNameHost); + reply->pointerRecords.append(record); + } else if (ptr->wType == QDnsLookup::SRV) { + QDnsServiceRecord record; + record.d->name = name; + record.d->target = QUrl::fromAce((char*)ptr->Data.Srv.pNameTarget); + record.d->port = ptr->Data.Srv.wPort; + record.d->priority = ptr->Data.Srv.wPriority; + record.d->timeToLive = ptr->dwTtl; + record.d->weight = ptr->Data.Srv.wWeight; + reply->serviceRecords.append(record); + } else if (ptr->wType == QDnsLookup::TXT) { + QDnsTextRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + for (unsigned int i = 0; i < ptr->Data.Txt.dwStringCount; ++i) { + record.d->values << QByteArray((char*)ptr->Data.Txt.pStringArray[i]); + } + reply->textRecords.append(record); + } + } + + local_dns_record_list_free(dns_records, DnsFreeRecordList); +} + +QT_END_NAMESPACE diff --git a/tests/auto/network/kernel/kernel.pro b/tests/auto/network/kernel/kernel.pro index d1c901d529..32ba8b41b7 100644 --- a/tests/auto/network/kernel/kernel.pro +++ b/tests/auto/network/kernel/kernel.pro @@ -1,5 +1,7 @@ TEMPLATE=subdirs SUBDIRS=\ + qdnslookup \ + qdnslookup_appless \ qhostinfo \ # qnetworkproxyfactory \ # Uses a hardcoded proxy configuration qauthenticator \ diff --git a/tests/auto/network/kernel/qdnslookup/qdnslookup.pro b/tests/auto/network/kernel/qdnslookup/qdnslookup.pro new file mode 100644 index 0000000000..f14ffd003e --- /dev/null +++ b/tests/auto/network/kernel/qdnslookup/qdnslookup.pro @@ -0,0 +1,7 @@ +CONFIG += testcase + +TARGET = tst_qdnslookup + +SOURCES += tst_qdnslookup.cpp + +QT = core network testlib diff --git a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp new file mode 100644 index 0000000000..3baca3c50b --- /dev/null +++ b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp @@ -0,0 +1,274 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include +#include +#include + +static bool waitForDone(QDnsLookup *lookup) +{ + if (lookup->isFinished()) + return true; + + QObject::connect(lookup, SIGNAL(finished()), + &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + return !QTestEventLoop::instance().timeout(); +} + +class tst_QDnsLookup: public QObject +{ + Q_OBJECT + +private slots: + void lookup_data(); + void lookup(); + void lookupReuse(); + void lookupAbortRetry(); +}; + +void tst_QDnsLookup::lookup_data() +{ + QTest::addColumn("type"); + QTest::addColumn("domain"); + QTest::addColumn("error"); + QTest::addColumn("cname"); + QTest::addColumn("host"); + QTest::addColumn("mx"); + QTest::addColumn("ns"); + QTest::addColumn("ptr"); + QTest::addColumn("srv"); + QTest::addColumn("txt"); + + QTest::newRow("a-empty") << int(QDnsLookup::A) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << ""<< "" << QByteArray(); + QTest::newRow("a-notfound") << int(QDnsLookup::A) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("a-idn") << int(QDnsLookup::A) << QString::fromUtf8("alqualondë.troll.no") << int(QDnsLookup::NoError) << "alqualonde.troll.no" << "10.3.3.55" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("a-single") << int(QDnsLookup::A) << "lupinella.troll.no" << int(QDnsLookup::NoError) << "" << "10.3.4.6" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("a-multi") << int(QDnsLookup::A) << "multi.dev.troll.no" << int(QDnsLookup::NoError) << "" << "1.2.3.4 1.2.3.5 10.3.3.31" << "" << "" << "" << "" << QByteArray(); + + QTest::newRow("aaaa-empty") << int(QDnsLookup::AAAA) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("aaaa-notfound") << int(QDnsLookup::AAAA) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("aaaa-single") << int(QDnsLookup::AAAA) << "dns6-test-dev.troll.no" << int(QDnsLookup::NoError) << "" << "2001:470:1f01:115::10" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("aaaa-multi") << int(QDnsLookup::AAAA) << "multi-dns6-test-dev.troll.no" << int(QDnsLookup::NoError) << "" << "2001:470:1f01:115::11 2001:470:1f01:115::12" << "" << "" << "" << "" << QByteArray(); + + QTest::newRow("any-empty") << int(QDnsLookup::ANY) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("any-notfound") << int(QDnsLookup::ANY) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("any-ascii") << int(QDnsLookup::ANY) << "fluke.troll.no" << int(QDnsLookup::NoError) << "" << "10.3.3.31" << "" << "" << "" << "" << QByteArray(); + + QTest::newRow("mx-empty") << int(QDnsLookup::MX) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("mx-notfound") << int(QDnsLookup::MX) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("mx-ascii") << int(QDnsLookup::MX) << "troll.no" << int(QDnsLookup::NoError) << "" << "" << "10 smtp.trolltech.com" << "" << "" << "" << QByteArray(); + // FIXME: we need an IDN MX record in the troll.no domain + QTest::newRow("mx-idn") << int(QDnsLookup::MX) << QString::fromUtf8("råkat.se") << int(QDnsLookup::NoError) << "" << "" << "10 mail.cdr.se" << "" << "" << "" << QByteArray(); + + QTest::newRow("ns-empty") << int(QDnsLookup::NS) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("ns-notfound") << int(QDnsLookup::NS) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("ns-ascii") << int(QDnsLookup::NS) << "troll.no" << int(QDnsLookup::NoError) << "" << "" << "" << "ns-0.trolltech.net ns-1.trolltech.net ns-i.trolltech.net" << "" << "" << QByteArray(); + + QTest::newRow("ptr-empty") << int(QDnsLookup::PTR) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("ptr-notfound") << int(QDnsLookup::PTR) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray(); + // FIXME: we need PTR records in the troll.no domain + QTest::newRow("ptr-ascii") << int(QDnsLookup::PTR) << "168.52.238.87.in-addr.arpa" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "gitorious.org" << "" << QByteArray(); + + QTest::newRow("srv-empty") << int(QDnsLookup::SRV) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("srv-notfound") << int(QDnsLookup::SRV) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray(); + // FIXME: we need SRV records in the troll.no domain + QTest::newRow("srv-idn") << int(QDnsLookup::SRV) << QString::fromUtf8("_xmpp-client._tcp.råkat.se") << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "5 0 5224 jabber.cdr.se" << QByteArray(); + + QTest::newRow("txt-empty") << int(QDnsLookup::TXT) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << QByteArray(); + QTest::newRow("txt-notfound") << int(QDnsLookup::TXT) << "invalid." << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << QByteArray(); + // FIXME: we need TXT records in the troll.no domain + QTest::newRow("txt-ascii") << int(QDnsLookup::TXT) << "gmail.com" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << QByteArray("v=spf1 redirect=_spf.google.com"); +} + +void tst_QDnsLookup::lookup() +{ + QFETCH(int, type); + QFETCH(QString, domain); + QFETCH(int, error); + QFETCH(QString, cname); + QFETCH(QString, host); + QFETCH(QString, mx); + QFETCH(QString, ns); + QFETCH(QString, ptr); + QFETCH(QString, srv); + QFETCH(QByteArray, txt); + + QDnsLookup lookup; + lookup.setType(static_cast(type)); + lookup.setName(domain); + lookup.lookup(); + QVERIFY(waitForDone(&lookup)); + QVERIFY(lookup.isFinished()); + QCOMPARE(int(lookup.error()), error); + if (error == QDnsLookup::NoError) + QVERIFY(lookup.errorString().isEmpty()); + QCOMPARE(int(lookup.type()), type); + QCOMPARE(lookup.name(), domain); + + // canonical names + if (!cname.isEmpty()) { + QVERIFY(!lookup.canonicalNameRecords().isEmpty()); + const QDnsDomainNameRecord cnameRecord = lookup.canonicalNameRecords().first(); + QCOMPARE(cnameRecord.name(), domain); + QCOMPARE(cnameRecord.value(), cname); + } else { + QVERIFY(lookup.canonicalNameRecords().isEmpty()); + } + + // host addresses + const QString hostName = cname.isEmpty() ? domain : cname; + QStringList addresses; + foreach (const QDnsHostAddressRecord &record, lookup.hostAddressRecords()) { + QCOMPARE(record.name(), hostName); + addresses << record.value().toString().toLower(); + } + addresses.sort(); + QCOMPARE(addresses.join(" "), host); + + // mail exchanges + QStringList mailExchanges; + foreach (const QDnsMailExchangeRecord &record, lookup.mailExchangeRecords()) { + QCOMPARE(record.name(), domain); + mailExchanges << QString("%1 %2").arg(QString::number(record.preference()), record.exchange()); + } + QCOMPARE(mailExchanges.join(" "), mx); + + // name servers + QStringList nameServers; + foreach (const QDnsDomainNameRecord &record, lookup.nameServerRecords()) { + QCOMPARE(record.name(), domain); + nameServers << record.value(); + } + nameServers.sort(); + QCOMPARE(nameServers.join(" "), ns); + + // pointers + if (!ptr.isEmpty()) { + QVERIFY(!lookup.pointerRecords().isEmpty()); + const QDnsDomainNameRecord ptrRecord = lookup.pointerRecords().first(); + QCOMPARE(ptrRecord.name(), domain); + QCOMPARE(ptrRecord.value(), ptr); + } else { + QVERIFY(lookup.pointerRecords().isEmpty()); + } + + // services + QStringList services; + foreach (const QDnsServiceRecord &record, lookup.serviceRecords()) { + QCOMPARE(record.name(), domain); + services << QString("%1 %2 %3 %4").arg( + QString::number(record.priority()), + QString::number(record.weight()), + QString::number(record.port()), + record.target()); + } + QCOMPARE(services.join(" "), srv); + + // text + if (!txt.isEmpty()) { + QVERIFY(!lookup.textRecords().isEmpty()); + const QDnsTextRecord firstRecord = lookup.textRecords().first(); + QCOMPARE(firstRecord.name(), domain); + QCOMPARE(firstRecord.values().size(), 1); + QCOMPARE(firstRecord.values().first(), txt); + } else { + QVERIFY(lookup.textRecords().isEmpty()); + } +} + +void tst_QDnsLookup::lookupReuse() +{ + QDnsLookup lookup; + + // first lookup + lookup.setType(QDnsLookup::A); + lookup.setName("lupinella.troll.no"); + lookup.lookup(); + QVERIFY(waitForDone(&lookup)); + QVERIFY(lookup.isFinished()); + QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); + QVERIFY(!lookup.hostAddressRecords().isEmpty()); + QCOMPARE(lookup.hostAddressRecords().first().name(), QString("lupinella.troll.no")); + QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("10.3.4.6")); + + // second lookup + lookup.setType(QDnsLookup::AAAA); + lookup.setName("dns6-test-dev.troll.no"); + lookup.lookup(); + QVERIFY(waitForDone(&lookup)); + QVERIFY(lookup.isFinished()); + QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); + QVERIFY(!lookup.hostAddressRecords().isEmpty()); + QCOMPARE(lookup.hostAddressRecords().first().name(), QString("dns6-test-dev.troll.no")); + QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:470:1f01:115::10")); +} + + +void tst_QDnsLookup::lookupAbortRetry() +{ + QDnsLookup lookup; + + // try and abort the lookup + lookup.setType(QDnsLookup::A); + lookup.setName("lupinella.troll.no"); + lookup.lookup(); + lookup.abort(); + QVERIFY(waitForDone(&lookup)); + QVERIFY(lookup.isFinished()); + QCOMPARE(int(lookup.error()), int(QDnsLookup::OperationCancelledError)); + QVERIFY(lookup.hostAddressRecords().isEmpty()); + + // retry a different lookup + lookup.setType(QDnsLookup::AAAA); + lookup.setName("dns6-test-dev.troll.no"); + lookup.lookup(); + QVERIFY(waitForDone(&lookup)); + QVERIFY(lookup.isFinished()); + QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); + QVERIFY(!lookup.hostAddressRecords().isEmpty()); + QCOMPARE(lookup.hostAddressRecords().first().name(), QString("dns6-test-dev.troll.no")); + QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:470:1f01:115::10")); +} + +QTEST_MAIN(tst_QDnsLookup) +#include "tst_qdnslookup.moc" diff --git a/tests/auto/network/kernel/qdnslookup_appless/qdnslookup_appless.pro b/tests/auto/network/kernel/qdnslookup_appless/qdnslookup_appless.pro new file mode 100644 index 0000000000..25d76b5739 --- /dev/null +++ b/tests/auto/network/kernel/qdnslookup_appless/qdnslookup_appless.pro @@ -0,0 +1,7 @@ +CONFIG += testcase + +TARGET = tst_qdnslookup_appless + +SOURCES += tst_qdnslookup_appless.cpp + +QT = core network testlib diff --git a/tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp b/tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp new file mode 100644 index 0000000000..a183cfdc8a --- /dev/null +++ b/tests/auto/network/kernel/qdnslookup_appless/tst_qdnslookup_appless.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé +** Contact: http://www.qt-project.org/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +class tst_QDnsLookup_Appless : public QObject +{ + Q_OBJECT + +private slots: + void noApplication(); + void recreateApplication(); + void destroyApplicationDuringLookup(); +}; + +void tst_QDnsLookup_Appless::noApplication() +{ + QTest::ignoreMessage(QtWarningMsg, "QDnsLookup requires a QCoreApplication"); + QDnsLookup dns(QDnsLookup::A, "troll.no"); + dns.lookup(); +} + +void tst_QDnsLookup_Appless::recreateApplication() +{ + int argc = 0; + char **argv = 0; + for (int i = 0; i < 10; ++i) { + QCoreApplication app(argc, argv); + QDnsLookup dns(QDnsLookup::A, "lupinella.troll.no"); + dns.lookup(); + if (!dns.isFinished()) { + QObject::connect(&dns, SIGNAL(finished()), + &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + } + QVERIFY(dns.isFinished()); + } +} + +void tst_QDnsLookup_Appless::destroyApplicationDuringLookup() +{ + int argc = 0; + char **argv = 0; + for (int i = 0; i < 10; ++i) { + QCoreApplication app(argc, argv); + QDnsLookup dns(QDnsLookup::A, "lupinella.troll.no"); + dns.lookup(); + } +} + +QTEST_APPLESS_MAIN(tst_QDnsLookup_Appless) +#include "tst_qdnslookup_appless.moc" -- cgit v1.2.3