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/qdnslookup_unix.cpp | 324 +++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 src/network/kernel/qdnslookup_unix.cpp (limited to 'src/network/kernel/qdnslookup_unix.cpp') 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 -- cgit v1.2.3