summaryrefslogtreecommitdiffstats
path: root/src/network/kernel/qdnslookup_unix.cpp
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2012-01-23 18:25:39 +0100
committerQt by Nokia <qt-info@nokia.com>2012-01-30 16:39:46 +0100
commite54dc7c2b5b9aa14989f26a718eb99d7516af4a0 (patch)
tree248372c55bede52c63ab0cc367cdfc8c350ba619 /src/network/kernel/qdnslookup_unix.cpp
parentd9468a975210ecb58ff199e931f47df5b99b267f (diff)
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 <thiago.macieira@intel.com>
Diffstat (limited to 'src/network/kernel/qdnslookup_unix.cpp')
-rw-r--r--src/network/kernel/qdnslookup_unix.cpp324
1 files changed, 324 insertions, 0 deletions
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é <jeremy.laine@m4x.org>
+** 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 <qlibrary.h>
+#include <qscopedpointer.h>
+#include <qurl.h>
+#include <private/qmutexpool_p.h>
+
+#include <sys/types.h>
+#include <netdb.h>
+#if defined(Q_OS_MAC)
+#include <arpa/nameser.h>
+#include <arpa/nameser_compat.h>
+#endif
+#include <resolv.h>
+
+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<struct __res_state, QDnsLookupStateDeleter> 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