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 --- 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 ++++++ 6 files changed, 1943 insertions(+), 3 deletions(-) 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 (limited to 'src/network/kernel') 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 -- cgit v1.2.3