diff --git a/src/libtomahawk/utils/jdnsshared.cpp b/src/libtomahawk/utils/jdnsshared.cpp new file mode 100644 index 000000000..b6e8a1c31 --- /dev/null +++ b/src/libtomahawk/utils/jdnsshared.cpp @@ -0,0 +1,1420 @@ +/* + * Copyright (C) 2006-2008 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +// Note: JDnsShared supports multiple interfaces for multicast, but only one +// for IPv4 and one for IPv6. Sharing multiple interfaces of the same IP +// version for multicast is unfortunately not possible without reworking +// the jdns subsystem. +// +// The reason for this limitation is that in order to do multi-interface +// multicast, you have to do a single bind to Any, and then use special +// functions to determine which interface a packet came from and to +// specify which interface a packet should go out on. Again this is just +// not possible with the current system and the assumptions made by jdns. + +// Note: When quering against multiple interfaces with multicast, it is +// possible that different answers for a unique record may be reported +// on each interface. We don't do anything about this. + +#include "jdnsshared.h" + +namespace { + +// safeobj stuff, from qca + +void releaseAndDeleteLater(QObject *owner, QObject *obj) +{ + obj->disconnect(owner); + obj->setParent(0); + obj->deleteLater(); +} + +class SafeTimer : public QObject +{ + Q_OBJECT +public: + SafeTimer(QObject *parent = 0) : + QObject(parent) + { + t = new QTimer(this); + connect(t, SIGNAL(timeout()), SIGNAL(timeout())); + } + + ~SafeTimer() + { + releaseAndDeleteLater(this, t); + } + + int interval() const { return t->interval(); } + bool isActive() const { return t->isActive(); } + bool isSingleShot() const { return t->isSingleShot(); } + void setInterval(int msec) { t->setInterval(msec); } + void setSingleShot(bool singleShot) { t->setSingleShot(singleShot); } + int timerId() const { return t->timerId(); } + +public slots: + void start(int msec) { t->start(msec); } + void start() { t->start(); } + void stop() { t->stop(); } + +signals: + void timeout(); + +private: + QTimer *t; +}; + +// for caching system info + +class SystemInfoCache +{ +public: + QJDns::SystemInfo info; + QTime time; +}; + +} + +Q_GLOBAL_STATIC(QMutex, jdnsshared_mutex) +Q_GLOBAL_STATIC(SystemInfoCache, jdnsshared_infocache) + +static QJDns::SystemInfo get_sys_info() +{ + QMutexLocker locker(jdnsshared_mutex()); + SystemInfoCache *c = jdnsshared_infocache(); + + // cache info for 1/2 second, enough to prevent re-reading of sys + // info 20 times because of all the different resolves + if(c->time.isNull() || c->time.elapsed() >= 500) + { + c->info = QJDns::systemInfo(); + c->time.start(); + } + + return c->info; +} + +static bool domainCompare(const QByteArray &a, const QByteArray &b) +{ + return (qstricmp(a.data(), b.data()) == 0) ? true: false; +} + +// adapted from jdns_mdnsd.c, _a_match() +static bool matchRecordExceptTtl(const QJDns::Record &a, const QJDns::Record &b) +{ + if(a.type != b.type || !domainCompare(a.owner, b.owner)) + return false; + + if(a.type == QJDns::Srv) + { + if(domainCompare(a.name, b.name) + && a.port == b.port + && a.priority == b.priority + && a.weight == b.weight) + { + return true; + } + } + else if(a.type == QJDns::Ptr || a.type == QJDns::Ns || a.type == QJDns::Cname) + { + if(domainCompare(a.name, b.name)) + return true; + } + else if(a.rdata == b.rdata) + return true; + + return false; +} + +static void getHex(unsigned char in, char *hi, char *lo) +{ + QString str; + str.sprintf("%02x", in); + *hi = str[0].toLatin1(); + *lo = str[1].toLatin1(); +} + +static QByteArray getDec(int in) +{ + return QString::number(in).toLatin1(); +} + +static QByteArray makeReverseName(const QHostAddress &addr) +{ + QByteArray out; + + if(addr.protocol() == QAbstractSocket::IPv6Protocol) + { + Q_IPV6ADDR raw = addr.toIPv6Address(); + for(int n = 0; n < 32; ++n) + { + char hi, lo; + getHex(raw.c[31 - n], &hi, &lo); + out += lo; + out += '.'; + out += hi; + out += '.'; + } + out += "ip6.arpa."; + } + else + { + quint32 rawi = addr.toIPv4Address(); + int raw[4]; + raw[0] = (rawi >> 24) & 0xff; + raw[1] = (rawi >> 16) & 0xff; + raw[2] = (rawi >> 8) & 0xff; + raw[3] = rawi & 0xff; + for(int n = 0; n < 4; ++n) + { + out += getDec(raw[3 - n]); + out += '.'; + } + out += "in-addr.arpa."; + } + + return out; +} + +//---------------------------------------------------------------------------- +// Handle +//---------------------------------------------------------------------------- + +namespace { + +// QJDns uses integer handle ids, but they are only unique within +// the relevant QJDns instance. Since we want our handles to be +// unique across all instances, we'll make an instance/id pair. +class Handle +{ +public: + QJDns *jdns; + int id; + + Handle() : jdns(0), id(-1) + { + } + + Handle(QJDns *_jdns, int _id) : jdns(_jdns), id(_id) + { + } + + bool operator==(const Handle &a) const + { + if(a.jdns == jdns && a.id == id) + return true; + return false; + } + + bool operator!=(const Handle &a) const + { + return !(operator==(a)); + } +}; + +// adapted from qHash +static inline uint qHash(const Handle &key) +{ + uint h1 = ::qHash(key.jdns); + uint h2 = ::qHash(key.id); + return ((h1 << 16) | (h1 >> 16)) ^ h2; +} + +} + +//---------------------------------------------------------------------------- +// JDnsShutdown +//---------------------------------------------------------------------------- +namespace { + +class JDnsShutdownAgent : public QObject +{ + Q_OBJECT +public: + void start() + { + QMetaObject::invokeMethod(this, "started", Qt::QueuedConnection); + } + +signals: + void started(); +}; + +class JDnsShutdownWorker : public QObject +{ + Q_OBJECT +public: + QList list; + + JDnsShutdownWorker(const QList &_list) : QObject(0), list(_list) + { + foreach(JDnsShared *i, list) + { + connect(i, SIGNAL(shutdownFinished()), SLOT(jdns_shutdownFinished())); + i->shutdown(); // MUST support DOR-DS, and it does + } + } + +signals: + void finished(); + +private slots: + void jdns_shutdownFinished() + { + JDnsShared *i = (JDnsShared *)sender(); + list.removeAll(i); + delete i; + if(list.isEmpty()) + emit finished(); + } +}; + +class JDnsShutdown : public QThread +{ + Q_OBJECT +public: + QMutex m; + QWaitCondition w; + QList list; + JDnsShutdownAgent *agent; + JDnsShutdownWorker *worker; + int phase; + + void waitForShutdown(const QList &_list) + { + list = _list; + phase = 0; + + m.lock(); + start(); + w.wait(&m); + + foreach(JDnsShared *i, list) + { + i->setParent(0); + i->moveToThread(this); + } + + phase = 1; + agent->start(); + wait(); + } + +protected: + virtual void run() + { + m.lock(); + agent = new JDnsShutdownAgent; + connect(agent, SIGNAL(started()), SLOT(agent_started()), Qt::DirectConnection); + agent->start(); + exec(); + delete agent; + } + +private slots: + void agent_started() + { + if(phase == 0) + { + w.wakeOne(); + m.unlock(); + } + else + { + worker = new JDnsShutdownWorker(list); + connect(worker, SIGNAL(finished()), SLOT(worker_finished()), Qt::DirectConnection); + } + } + + void worker_finished() + { + delete worker; + worker = 0; + + quit(); + } +}; + +} + +//---------------------------------------------------------------------------- +// JDnsSharedDebug +//---------------------------------------------------------------------------- +class JDnsSharedDebugPrivate : public QObject +{ + Q_OBJECT +public: + JDnsSharedDebug *q; + QMutex m; + QStringList lines; + bool dirty; + + JDnsSharedDebugPrivate(JDnsSharedDebug *_q) : QObject(_q), q(_q) + { + dirty = false; + } + + void addDebug(const QString &name, const QStringList &_lines) + { + if(!_lines.isEmpty()) + { + QMutexLocker locker(&m); + for(int n = 0; n < _lines.count(); ++n) + lines += name + ": " + _lines[n]; + if(!dirty) + { + dirty = true; + QMetaObject::invokeMethod(this, "doUpdate", Qt::QueuedConnection); + } + } + } + +private slots: + void doUpdate() + { + { + QMutexLocker locker(&m); + if(!dirty) + return; + } + emit q->readyRead(); + } +}; + +JDnsSharedDebug::JDnsSharedDebug(QObject *parent) +:QObject(parent) +{ + d = new JDnsSharedDebugPrivate(this); +} + +JDnsSharedDebug::~JDnsSharedDebug() +{ + delete d; +} + +QStringList JDnsSharedDebug::readDebugLines() +{ + QMutexLocker locker(&d->m); + QStringList tmplines = d->lines; + d->lines.clear(); + d->dirty = false; + return tmplines; +} + +//---------------------------------------------------------------------------- +// JDnsSharedRequest +//---------------------------------------------------------------------------- +class JDnsSharedPrivate : public QObject +{ + Q_OBJECT +public: + class Instance + { + public: + QJDns *jdns; + QHostAddress addr; + int index; + + Instance() : jdns(0) + { + } + }; + + enum PreprocessMode + { + None, // don't muck with anything + FillInAddress, // for A/AAAA + FillInPtrOwner6, // for PTR, IPv6 + FillInPtrOwner4, // for PTR, IPv4 + }; + + JDnsShared *q; + JDnsShared::Mode mode; + bool shutting_down; + JDnsSharedDebug *db; + QString dbname; + + QList instances; + QHash instanceForQJDns; + + QSet requests; + QHash requestForHandle; + + JDnsSharedPrivate(JDnsShared *_q) : QObject(_q), q(_q) + { + } + + JDnsSharedRequest *findRequest(QJDns *jdns, int id) const + { + Handle h(jdns, id); + return requestForHandle.value(h); + } + + void jdns_link(QJDns *jdns) + { + connect(jdns, SIGNAL(resultsReady(int, const QJDns::Response &)), SLOT(jdns_resultsReady(int, const QJDns::Response &))); + connect(jdns, SIGNAL(published(int)), SLOT(jdns_published(int))); + connect(jdns, SIGNAL(error(int, QJDns::Error)), SLOT(jdns_error(int, QJDns::Error))); + connect(jdns, SIGNAL(shutdownFinished()), SLOT(jdns_shutdownFinished())); + connect(jdns, SIGNAL(debugLinesReady()), SLOT(jdns_debugLinesReady())); + } + + int getNewIndex() const + { + // find lowest unused value + for(int n = 0;; ++n) + { + bool found = false; + foreach(Instance *i, instances) + { + if(i->index == n) + { + found = true; + break; + } + } + if(!found) + return n; + } + } + + void addDebug(int index, const QString &line) + { + if(db) + db->d->addDebug(dbname + QString::number(index), QStringList() << line); + } + + void doDebug(QJDns *jdns, int index) + { + QStringList lines = jdns->debugLines(); + if(db) + db->d->addDebug(dbname + QString::number(index), lines); + } + + PreprocessMode determinePpMode(const QJDns::Record &in) + { + // Note: since our implementation only allows 1 ipv4 and 1 ipv6 + // interface to exist, it is safe to publish both kinds of + // records on both interfaces, with the same values. For + // example, an A record can be published on both interfaces, + // with the value set to the ipv4 interface. If we supported + // multiple ipv4 interfaces, then this wouldn't work, because + // we wouldn't know which value to use for the A record when + // publishing on the ipv6 interface. + + // publishing our own IP address? null address means the user + // wants us to fill in the blank with our address. + if((in.type == QJDns::Aaaa || in.type == QJDns::A) && in.address.isNull()) + { + return FillInAddress; + } + // publishing our own reverse lookup? partial owner means + // user wants us to fill in the rest. + else if(in.type == QJDns::Ptr && in.owner == ".ip6.arpa.") + { + return FillInPtrOwner6; + } + else if(in.type == QJDns::Ptr && in.owner == ".in-addr.arpa.") + { + return FillInPtrOwner4; + } + + return None; + } + + QJDns::Record manipulateRecord(const QJDns::Record &in, PreprocessMode ppmode, bool *modified = 0) + { + if(ppmode == FillInAddress) + { + QJDns::Record out = in; + + if(in.type == QJDns::Aaaa) + { + // are we operating on ipv6? + foreach(Instance *i, instances) + { + if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) + { + if(modified && !(out.address == i->addr)) + *modified = true; + out.address = i->addr; + break; + } + } + } + else // A + { + // are we operating on ipv4? + foreach(Instance *i, instances) + { + if(i->addr.protocol() == QAbstractSocket::IPv4Protocol) + { + if(modified && !(out.address == i->addr)) + *modified = true; + out.address = i->addr; + break; + } + } + } + + return out; + } + else if(ppmode == FillInPtrOwner6) + { + QJDns::Record out = in; + + // are we operating on ipv6? + foreach(Instance *i, instances) + { + if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) + { + QByteArray newOwner = makeReverseName(i->addr); + if(modified && !(out.owner == newOwner)) + *modified = true; + out.owner = newOwner; + break; + } + } + + return out; + } + else if(ppmode == FillInPtrOwner4) + { + QJDns::Record out = in; + + // are we operating on ipv4? + foreach(Instance *i, instances) + { + if(i->addr.protocol() == QAbstractSocket::IPv4Protocol) + { + QByteArray newOwner = makeReverseName(i->addr); + if(modified && !(out.owner == newOwner)) + *modified = true; + out.owner = newOwner; + break; + } + } + + return out; + } + + if(modified) + *modified = false; + return in; + } + + bool addInterface(const QHostAddress &addr); + void removeInterface(const QHostAddress &addr); + + void queryStart(JDnsSharedRequest *obj, const QByteArray &name, int qType); + void queryCancel(JDnsSharedRequest *obj); + void publishStart(JDnsSharedRequest *obj, QJDns::PublishMode m, const QJDns::Record &record); + void publishUpdate(JDnsSharedRequest *obj, const QJDns::Record &record); + void publishCancel(JDnsSharedRequest *obj); + +public slots: + void late_shutdown() + { + shutting_down = false; + emit q->shutdownFinished(); + } + +private slots: + void jdns_resultsReady(int id, const QJDns::Response &results); + void jdns_published(int id); + void jdns_error(int id, QJDns::Error e); + void jdns_shutdownFinished(); + void jdns_debugLinesReady(); +}; + +class JDnsSharedRequestPrivate : public QObject +{ + Q_OBJECT +public: + JDnsSharedRequest *q; + JDnsSharedPrivate *jsp; + + // current action + JDnsSharedRequest::Type type; + QByteArray name; + int qType; + QJDns::PublishMode pubmode; + JDnsSharedPrivate::PreprocessMode ppmode; + QJDns::Record pubrecord; + + // a single request might have to perform multiple QJDns operations + QList handles; + + // keep a list of handles that successfully publish + QList published; + + // use to weed out dups for multicast + QList queryCache; + + bool success; + JDnsSharedRequest::Error error; + QList results; + SafeTimer lateTimer; + + JDnsSharedRequestPrivate(JDnsSharedRequest *_q) : QObject(_q), q(_q), lateTimer(this) + { + connect(&lateTimer, SIGNAL(timeout()), SLOT(lateTimer_timeout())); + } + + void resetSession() + { + name = QByteArray(); + pubrecord = QJDns::Record(); + handles.clear(); + published.clear(); + queryCache.clear(); + } + +private slots: + void lateTimer_timeout() + { + emit q->resultsReady(); + } +}; + +JDnsSharedRequest::JDnsSharedRequest(JDnsShared *jdnsShared, QObject *parent) +:QObject(parent) +{ + d = new JDnsSharedRequestPrivate(this); + d->jsp = jdnsShared->d; +} + +JDnsSharedRequest::~JDnsSharedRequest() +{ + cancel(); + delete d; +} + +JDnsSharedRequest::Type JDnsSharedRequest::type() +{ + return d->type; +} + +void JDnsSharedRequest::query(const QByteArray &name, int type) +{ + cancel(); + d->jsp->queryStart(this, name, type); +} + +void JDnsSharedRequest::publish(QJDns::PublishMode m, const QJDns::Record &record) +{ + cancel(); + d->jsp->publishStart(this, m, record); +} + +void JDnsSharedRequest::publishUpdate(const QJDns::Record &record) +{ + // only allowed to update if we have an active publish + if(!d->handles.isEmpty() && d->type == Publish) + d->jsp->publishUpdate(this, record); +} + +void JDnsSharedRequest::cancel() +{ + d->lateTimer.stop(); + if(!d->handles.isEmpty()) + { + if(d->type == Query) + d->jsp->queryCancel(this); + else + d->jsp->publishCancel(this); + } + d->resetSession(); +} + +bool JDnsSharedRequest::success() const +{ + return d->success; +} + +JDnsSharedRequest::Error JDnsSharedRequest::error() const +{ + return d->error; +} + +QList JDnsSharedRequest::results() const +{ + return d->results; +} + +//---------------------------------------------------------------------------- +// JDnsShared +//---------------------------------------------------------------------------- +JDnsShared::JDnsShared(Mode mode, QObject *parent) +:QObject(parent) +{ + d = new JDnsSharedPrivate(this); + d->mode = mode; + d->shutting_down = false; + d->db = 0; +} + +JDnsShared::~JDnsShared() +{ + foreach(JDnsSharedPrivate::Instance *i, d->instances) + { + delete i->jdns; + delete i; + } + delete d; +} + +void JDnsShared::setDebug(JDnsSharedDebug *db, const QString &name) +{ + d->db = db; + d->dbname = name; +} + +bool JDnsShared::addInterface(const QHostAddress &addr) +{ + return d->addInterface(addr); +} + +void JDnsShared::removeInterface(const QHostAddress &addr) +{ + d->removeInterface(addr); +} + +void JDnsShared::shutdown() +{ + d->shutting_down = true; + if(!d->instances.isEmpty()) + { + foreach(JDnsSharedPrivate::Instance *i, d->instances) + i->jdns->shutdown(); + } + else + QMetaObject::invokeMethod(d, "late_shutdown", Qt::QueuedConnection); +} + +QList JDnsShared::domains() +{ + return get_sys_info().domains; +} + +void JDnsShared::waitForShutdown(const QList &instances) +{ + JDnsShutdown s; + s.waitForShutdown(instances); +} + +bool JDnsSharedPrivate::addInterface(const QHostAddress &addr) +{ + if(shutting_down) + return false; + + // make sure we don't have this one already + foreach(Instance *i, instances) + { + if(i->addr == addr) + return false; + } + + int index = getNewIndex(); + addDebug(index, QString("attempting to use interface %1").arg(addr.toString())); + + QJDns *jdns; + + if(mode == JDnsShared::UnicastInternet || mode == JDnsShared::UnicastLocal) + { + jdns = new QJDns(this); + jdns_link(jdns); + if(!jdns->init(QJDns::Unicast, addr)) + { + doDebug(jdns, index); + delete jdns; + return false; + } + + if(mode == JDnsShared::UnicastLocal) + { + QJDns::NameServer host; + if(addr.protocol() == QAbstractSocket::IPv6Protocol) + host.address = QHostAddress("FF02::FB"); + else + host.address = QHostAddress("224.0.0.251"); + host.port = 5353; + jdns->setNameServers(QList() << host); + } + } + else // Multicast + { + // only one multicast interface allowed per IP protocol version. + // this is because we bind to INADDR_ANY. + + bool have_v6 = false; + bool have_v4 = false; + foreach(Instance *i, instances) + { + if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) + have_v6 = true; + else + have_v4 = true; + } + + bool is_v6 = (addr.protocol() == QAbstractSocket::IPv6Protocol) ? true : false; + + if(is_v6 && have_v6) + { + addDebug(index, "already have an ipv6 interface"); + return false; + } + + if(!is_v6 && have_v4) + { + addDebug(index, "already have an ipv4 interface"); + return false; + } + + QHostAddress actualBindAddress; + if(is_v6) + actualBindAddress = QHostAddress::AnyIPv6; + else + actualBindAddress = QHostAddress::Any; + + jdns = new QJDns(this); + jdns_link(jdns); + if(!jdns->init(QJDns::Multicast, actualBindAddress)) + { + doDebug(jdns, index); + delete jdns; + return false; + } + } + + Instance *i = new Instance; + i->jdns = jdns; + i->addr = addr; + i->index = index; + instances += i; + instanceForQJDns.insert(i->jdns, i); + + addDebug(index, "interface ready"); + + if(mode == JDnsShared::Multicast) + { + // extend active requests to this interface + foreach(JDnsSharedRequest *obj, requests) + { + if(obj->d->type == JDnsSharedRequest::Query) + { + Handle h(i->jdns, i->jdns->queryStart(obj->d->name, obj->d->qType)); + obj->d->handles += h; + requestForHandle.insert(h, obj); + } + else // Publish + { + bool modified; + obj->d->pubrecord = manipulateRecord(obj->d->pubrecord, obj->d->ppmode, &modified); + // if the record changed, update on the other (existing) interfaces + if(modified) + { + foreach(Handle h, obj->d->handles) + h.jdns->publishUpdate(h.id, obj->d->pubrecord); + } + + // publish the record on the new interface + Handle h(i->jdns, i->jdns->publishStart(obj->d->pubmode, obj->d->pubrecord)); + obj->d->handles += h; + requestForHandle.insert(h, obj); + } + } + } + + return true; +} + +void JDnsSharedPrivate::removeInterface(const QHostAddress &addr) +{ + Instance *i = 0; + for(int n = 0; n < instances.count(); ++n) + { + if(instances[n]->addr == addr) + { + i = instances[n]; + break; + } + } + if(!i) + return; + + int index = i->index; + + // we don't cancel operations or shutdown jdns, we simply + // delete our references. this is because if the interface + // is gone, then we have nothing to send on anyway. + + foreach(JDnsSharedRequest *obj, requests) + { + for(int n = 0; n < obj->d->handles.count(); ++n) + { + Handle h = obj->d->handles[n]; + if(h.jdns == i->jdns) + { + // see above, no need to cancel the operation + obj->d->handles.removeAt(n); + requestForHandle.remove(h); + break; + } + } + + // remove published reference + if(obj->d->type == JDnsSharedRequest::Publish) + { + for(int n = 0; n < obj->d->published.count(); ++n) + { + Handle h = obj->d->published[n]; + if(h.jdns == i->jdns) + { + obj->d->published.removeAt(n); + break; + } + } + } + } + + // see above, no need to shutdown jdns + instanceForQJDns.remove(i->jdns); + instances.removeAll(i); + delete i->jdns; + delete i; + + // if that was the last interface to be removed, then there should + // be no more handles left. let's take action with these + // handleless requests. + foreach(JDnsSharedRequest *obj, requests) + { + if(obj->d->handles.isEmpty()) + { + if(mode == JDnsShared::UnicastInternet || mode == JDnsShared::UnicastLocal) + { + // for unicast, we'll invalidate with ErrorNoNet + obj->d->success = false; + obj->d->error = JDnsSharedRequest::ErrorNoNet; + obj->d->lateTimer.start(); + } + else // Multicast + { + // for multicast, we'll keep all requests alive. + // activity will resume when an interface is + // added. + } + } + } + + addDebug(index, QString("removing from %1").arg(addr.toString())); +} + +void JDnsSharedPrivate::queryStart(JDnsSharedRequest *obj, const QByteArray &name, int qType) +{ + obj->d->type = JDnsSharedRequest::Query; + obj->d->success = false; + obj->d->results.clear(); + obj->d->name = name; + obj->d->qType = qType; + + // is the input an IP address and the qType is an address record? + if(qType == QJDns::Aaaa || qType == QJDns::A) + { + QHostAddress addr; + if(addr.setAddress(QString::fromLocal8Bit(name))) + { + if(qType == QJDns::Aaaa && addr.protocol() == QAbstractSocket::IPv6Protocol) + { + QJDns::Record rec; + rec.owner = name; + rec.type = QJDns::Aaaa; + rec.ttl = 120; + rec.haveKnown = true; + rec.address = addr; + obj->d->success = true; + obj->d->results = QList() << rec; + obj->d->lateTimer.start(); + return; + } + else if(qType == QJDns::A && addr.protocol() == QAbstractSocket::IPv4Protocol) + { + QJDns::Record rec; + rec.owner = name; + rec.type = QJDns::A; + rec.ttl = 120; + rec.haveKnown = true; + rec.address = addr; + obj->d->success = true; + obj->d->results = QList() << rec; + obj->d->lateTimer.start(); + return; + } + } + } + + QJDns::SystemInfo sysInfo = get_sys_info(); + + // is the input name a known host and the qType is an address record? + if(qType == QJDns::Aaaa || qType == QJDns::A) + { + QByteArray lname = name.toLower(); + QList known = sysInfo.hosts; + foreach(QJDns::DnsHost host, known) + { + if(((qType == QJDns::Aaaa && host.address.protocol() == QAbstractSocket::IPv6Protocol) + || (qType == QJDns::A && host.address.protocol() == QAbstractSocket::IPv4Protocol)) + && host.name.toLower() == lname) + { + QJDns::Record rec; + rec.owner = name; + rec.type = qType; + rec.ttl = 120; + rec.haveKnown = true; + rec.address = host.address; + obj->d->success = true; + obj->d->results = QList() << rec; + obj->d->lateTimer.start(); + return; + } + } + } + + // if we have no QJDns instances to operate on, then error + if(instances.isEmpty()) + { + obj->d->error = JDnsSharedRequest::ErrorNoNet; + obj->d->lateTimer.start(); + return; + } + + if(mode == JDnsShared::UnicastInternet) + { + // get latest nameservers, split into ipv6/v4, apply to jdns instances + QList ns_v6; + QList ns_v4; + { + QList nameServers = sysInfo.nameServers; + foreach(QJDns::NameServer ns, nameServers) + { + if(ns.address.protocol() == QAbstractSocket::IPv6Protocol) + ns_v6 += ns; + else + ns_v4 += ns; + } + } + foreach(Instance *i, instances) + { + if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) + i->jdns->setNameServers(ns_v6); + else + i->jdns->setNameServers(ns_v4); + } + } + + // keep track of this request + requests += obj; + + // query on all jdns instances + foreach(Instance *i, instances) + { + Handle h(i->jdns, i->jdns->queryStart(name, qType)); + obj->d->handles += h; + + // keep track of this handle for this request + requestForHandle.insert(h, obj); + } +} + +void JDnsSharedPrivate::queryCancel(JDnsSharedRequest *obj) +{ + if(!requests.contains(obj)) + return; + + foreach(Handle h, obj->d->handles) + { + h.jdns->queryCancel(h.id); + requestForHandle.remove(h); + } + + obj->d->handles.clear(); + requests.remove(obj); +} + +void JDnsSharedPrivate::publishStart(JDnsSharedRequest *obj, QJDns::PublishMode m, const QJDns::Record &record) +{ + obj->d->type = JDnsSharedRequest::Publish; + obj->d->success = false; + obj->d->results.clear(); + obj->d->pubmode = m; + obj->d->ppmode = determinePpMode(record); + obj->d->pubrecord = manipulateRecord(record, obj->d->ppmode); + + // if we have no QJDns instances to operate on, then error + if(instances.isEmpty()) + { + obj->d->error = JDnsSharedRequest::ErrorNoNet; + obj->d->lateTimer.start(); + return; + } + + // keep track of this request + requests += obj; + + // attempt to publish on all jdns instances + foreach(JDnsSharedPrivate::Instance *i, instances) + { + Handle h(i->jdns, i->jdns->publishStart(m, obj->d->pubrecord)); + obj->d->handles += h; + + // keep track of this handle for this request + requestForHandle.insert(h, obj); + } +} + +void JDnsSharedPrivate::publishUpdate(JDnsSharedRequest *obj, const QJDns::Record &record) +{ + if(!requests.contains(obj)) + return; + + obj->d->ppmode = determinePpMode(record); + obj->d->pubrecord = manipulateRecord(record, obj->d->ppmode); + + // publish update on all handles for this request + foreach(Handle h, obj->d->handles) + h.jdns->publishUpdate(h.id, obj->d->pubrecord); +} + +void JDnsSharedPrivate::publishCancel(JDnsSharedRequest *obj) +{ + if(!requests.contains(obj)) + return; + + foreach(Handle h, obj->d->handles) + { + h.jdns->publishCancel(h.id); + requestForHandle.remove(h); + } + + obj->d->handles.clear(); + obj->d->published.clear(); + requests.remove(obj); +} + +void JDnsSharedPrivate::jdns_resultsReady(int id, const QJDns::Response &results) +{ + QJDns *jdns = (QJDns *)sender(); + JDnsSharedRequest *obj = findRequest(jdns, id); + Q_ASSERT(obj); + + obj->d->success = true; + obj->d->results = results.answerRecords; + + if(mode == JDnsShared::UnicastInternet || mode == JDnsShared::UnicastLocal) + { + // only one response, so "cancel" it + for(int n = 0; n < obj->d->handles.count(); ++n) + { + Handle h = obj->d->handles[n]; + if(h.jdns == jdns && h.id == id) + { + obj->d->handles.removeAt(n); + requestForHandle.remove(h); + break; + } + } + + // cancel related handles + foreach(Handle h, obj->d->handles) + { + h.jdns->queryCancel(h.id); + requestForHandle.remove(h); + } + + obj->d->handles.clear(); + requests.remove(obj); + } + else // Multicast + { + // check our cache to see how we should report these results + for(int n = 0; n < obj->d->results.count(); ++n) + { + QJDns::Record &r = obj->d->results[n]; + + // do we have this answer already in our cache? + QJDns::Record *c = 0; + int c_at = -1; + for(int k = 0; k < obj->d->queryCache.count(); ++k) + { + QJDns::Record &tmp = obj->d->queryCache[k]; + if(matchRecordExceptTtl(r, tmp)) + { + c = &tmp; + c_at = k; + break; + } + } + + // don't report duplicates or unknown removals + if((c && r.ttl != 0) || (!c && r.ttl == 0)) + { + obj->d->results.removeAt(n); + --n; // adjust position + continue; + } + + // if we have it, and it is removed, remove from cache + if(c && r.ttl == 0) + { + obj->d->queryCache.removeAt(c_at); + } + // otherwise, if we don't have it, add it to the cache + else if(!c) + { + obj->d->queryCache += r; + } + } + + if(obj->d->results.isEmpty()) + return; + } + + emit obj->resultsReady(); +} + +void JDnsSharedPrivate::jdns_published(int id) +{ + QJDns *jdns = (QJDns *)sender(); + JDnsSharedRequest *obj = findRequest(jdns, id); + Q_ASSERT(obj); + + // find handle + Handle handle; + for(int n = 0; n < obj->d->handles.count(); ++n) + { + Handle h = obj->d->handles[n]; + if(h.jdns == jdns && h.id == id) + { + handle = h; + break; + } + } + + obj->d->published += handle; + + // if this publish has already been considered successful, then + // a publish has succeeded on a new interface and there's no + // need to report success for this request again + if(obj->d->success) + return; + + // all handles published? + if(obj->d->published.count() == obj->d->handles.count()) + { + obj->d->success = true; + emit obj->resultsReady(); + } +} + +void JDnsSharedPrivate::jdns_error(int id, QJDns::Error e) +{ + QJDns *jdns = (QJDns *)sender(); + JDnsSharedRequest *obj = findRequest(jdns, id); + Q_ASSERT(obj); + + // "cancel" it + for(int n = 0; n < obj->d->handles.count(); ++n) + { + Handle h = obj->d->handles[n]; + if(h.jdns == jdns && h.id == id) + { + obj->d->handles.removeAt(n); + requestForHandle.remove(h); + break; + } + } + + if(obj->d->type == JDnsSharedRequest::Query) + { + // ignore the error if it is not the last error + if(!obj->d->handles.isEmpty()) + return; + + requests.remove(obj); + + obj->d->success = false; + JDnsSharedRequest::Error x = JDnsSharedRequest::ErrorGeneric; + if(e == QJDns::ErrorNXDomain) + x = JDnsSharedRequest::ErrorNXDomain; + else if(e == QJDns::ErrorTimeout) + x = JDnsSharedRequest::ErrorTimeout; + else // ErrorGeneric + x = JDnsSharedRequest::ErrorGeneric; + obj->d->error = x; + emit obj->resultsReady(); + } + else // Publish + { + // cancel related handles + foreach(Handle h, obj->d->handles) + { + h.jdns->publishCancel(h.id); + requestForHandle.remove(h); + } + + obj->d->handles.clear(); + obj->d->published.clear(); + requests.remove(obj); + + obj->d->success = false; + JDnsSharedRequest::Error x = JDnsSharedRequest::ErrorGeneric; + if(e == QJDns::ErrorConflict) + x = JDnsSharedRequest::ErrorConflict; + else // ErrorGeneric + x = JDnsSharedRequest::ErrorGeneric; + obj->d->error = x; + emit obj->resultsReady(); + } +} + +void JDnsSharedPrivate::jdns_shutdownFinished() +{ + QJDns *jdns = (QJDns *)sender(); + + addDebug(instanceForQJDns.value(jdns)->index, "jdns_shutdownFinished, removing interface"); + + Instance *instance = instanceForQJDns.value(jdns); + delete instance->jdns; + delete instance; + instanceForQJDns.remove(jdns); + instances.removeAll(instance); + + if(instances.isEmpty()) + late_shutdown(); +} + +void JDnsSharedPrivate::jdns_debugLinesReady() +{ + QJDns *jdns = (QJDns *)sender(); + + doDebug(jdns, instanceForQJDns.value(jdns)->index); +} + +#include "jdnsshared.moc" diff --git a/src/libtomahawk/utils/jdnsshared.h b/src/libtomahawk/utils/jdnsshared.h new file mode 100644 index 000000000..81ac215cb --- /dev/null +++ b/src/libtomahawk/utils/jdnsshared.h @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2006,2007 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef JDNSSHARED_H +#define JDNSSHARED_H + +#include "qjdns.h" + +class JDnsShared; +class JDnsSharedPrivate; +class JDnsSharedRequestPrivate; +class JDnsSharedDebugPrivate; + +/** + \brief Collects debugging information from JDnsShared + + \note Iris users should utilize NetNames for DNS capabilities, not JDnsSharedDebug. See the JDnsShared documentation for more information. + + JDnsSharedDebug is used to collect debugging information from one or many JDnsShared objects. To use it, simply create it and pass it to JDnsShared::setDebug(). + + Example use: + +\code +JDnsSharedDebug *db = new JDnsSharedDebug; +connect(db, SIGNAL(debugLinesReady(const QStringList &)), + SLOT(db_debugLinesReady(const QStringList &))); + +JDnsShared *jdnsShared1 = new JDnsShared(JDnsShared::UnicastInternet); +jdnsShared1->setDebug(db, "U"); + +JDnsShared *jdnsShared2 = new JDnsShared(JDnsShared::UnicastLocal); +jdnsShared2->setDebug(db, "L"); +... +void db_debugLinesReady(const QStringList &lines) +{ + foreach(QString line, lines) + printf("%s\n", qPrintable(line)); +} +\endcode + + JDnsShared reports debug lines with the name and interface number prepended to each line. For example, if there is debug information to report about the second interface added to \a jdnsShared2 in the above example, the lines would be prepended with "L1: ". + + Do not destroy JDnsSharedDebug until all of the JDnsShared objects associated with it have been destroyed. + + \sa JDnsShared JDnsSharedRequest +*/ +class JDnsSharedDebug : public QObject +{ + Q_OBJECT +public: + /** + \brief Constructs a new object with the given \a parent + */ + JDnsSharedDebug(QObject *parent = 0); + + /** + \brief Destroys the object + */ + ~JDnsSharedDebug(); + + /** + \brief Read the available debug information + + Debug information is reported as a series of lines. The lines are of reasonable length, and so if you're storing a backlog of the most recent debug information, it should be safe to make the cut-off point based on lines. + + \sa readyRead + */ + QStringList readDebugLines(); + +signals: + /** + \brief Emitted when there is debug information to report + + \sa readDebugLines + */ + void readyRead(); + +private: + friend class JDnsShared; + friend class JDnsSharedPrivate; + friend class JDnsSharedDebugPrivate; + JDnsSharedDebugPrivate *d; +}; + +/** + \brief Performs a DNS operation using JDnsShared + + \note Iris users should utilize NetNames for DNS capabilities, not JDnsSharedRequest. See the JDnsShared documentation for more information. + + JDnsSharedRequest is used to perform DNS operations on a JDnsShared object. Many requests may be performed simultaneously, such that a single JDnsShared object can be "shared" across the application. Please see the JDnsShared documentation for more complete information about how the overall system works. + + Call query() to perform a query. Call publish() (or publishUpdate()) to make DNS records available on the local network (JDnsShared::Multicast mode only). When the operation has something to report, the resultsReady() signal is emitted. Call success() to determine the status of the operation. If success() returns false, then the operation has failed and the reason for the failure can be determined with error(). If success() returns true, then the meaning differs depending on the type of operation being performed: +
    +
  • For JDnsShared::UnicastInternet and JDnsShared::UnicastLocal modes, call results() to obtain the records obtained by the query. In these modes, resultsReady() is only emitted once, at which point the operation is no longer active.
  • +
  • For JDnsShared::Multicast, operations are long-lived. Query operations never timeout, and resultsReady() may be emitted multiple times. In order to stop the query, either call cancel() or destroy the JDnsSharedRequest object. Similarly, publishing is long-lived. The record stays published as long as the JDnsSharedRequest has not been cancelled or destroyed.
  • +
+ + Here is how you might look up an A record: + +\code +JDnsSharedRequest *req = new JDnsSharedRequest(jdnsShared); +connect(req, SIGNAL(resultsReady()), SLOT(req_resultsReady())); +req->query("psi-im.org", QJDns::A); +... +void req_resultsReady() +{ + if(req->success()) + { + // print all of the IP addresses obtained + QList results = req->results(); + foreach(QJDns::Record r, results) + { + if(r.type == QJDns::A) + printf("%s\n", qPrintable(r.address.toString()); + } + } + else + printf("Error resolving!\n"); +} +\endcode + + Here is an example of publishing a record: + +\code +JDnsSharedRequest *pub = new JDnsSharedRequest(jdnsShared); +connect(pub, SIGNAL(resultsReady()), SLOT(pub_resultsReady())); + +// let's publish an A record +QJDns::Record rec; +rec.owner = "SomeComputer.local."; +rec.type = QJDns::A; +rec.ttl = 120; +rec.haveKnown = true; +rec.address = QHostAddress("192.168.0.32"); + +pub->publish(QJDns::Unique, rec); +... +void pub_resultsReady() +{ + if(pub->success()) + printf("Record published\n"); + else + printf("Error publishing!\n"); +} +\endcode + + To update an existing record, use publishUpdate(): + +\code +// the IP address of the host changed, so make a new record +QJDns::Record rec; +rec.owner = "SomeComputer.local."; +rec.type = QJDns::A; +rec.ttl = 120; +rec.haveKnown = true; +rec.address = QHostAddress("192.168.0.64"); + +// update it +pub->publishUpdate(rec); +\endcode + + As a special exception, the address value can be left unspecified for A and Aaaa record types, which tells JDnsShared to substitute the address value with the address of whatever interfaces the record gets published on. This is the preferred way to publish the IP address of your own machine, and in fact it is the only way to do so if you have multiple interfaces, because there will likely be a different IP address value for each interface (the record resolves to a different answer depending on which interface a query comes from). + +\code +// let's publish our own A record +QJDns::Record rec; +rec.owner = "MyComputer.local."; +rec.type = QJDns::A; +rec.ttl = 120; +rec.haveKnown = true; +rec.address = QHostAddress(); + +pub->publish(QJDns::Unique, rec); +\endcode + + When you want to unpublish, call cancel() or destroy the JDnsSharedRequest. + + \sa JDnsShared +*/ +class JDnsSharedRequest : public QObject +{ + Q_OBJECT +public: + /** + \brief Operation type + */ + enum Type + { + Query, ///< Query operation, initiated by query() + Publish ///< Publish operation, initiated by publish() or publishUpdate() + }; + + /** + \brief Request error + */ + enum Error + { + ErrorNoNet, ///< There are no available network interfaces to operate on. This happens if JDnsShared::addInterface() was not called. + ErrorGeneric, ///< Generic error during the operation. + ErrorNXDomain, ///< The name looked up does not exist. + ErrorTimeout, ///< The operation timed out. + ErrorConflict ///< Attempt to publish an already published unique record. + }; + + /** + \brief Constructs a new object with the given \a jdnsShared and \a parent + */ + JDnsSharedRequest(JDnsShared *jdnsShared, QObject *parent = 0); + + /** + \brief Destroys the object + + If there is an active operation, it is cancelled. + */ + ~JDnsSharedRequest(); + + /** + \brief The type of operation being performed + */ + Type type(); + + /** + \brief Perform a query operation + */ + void query(const QByteArray &name, int type); + + /** + \brief Perform a publish operation + */ + void publish(QJDns::PublishMode m, const QJDns::Record &record); + + /** + \brief Update a record that is currently published + */ + void publishUpdate(const QJDns::Record &record); + + /** + \brief Cancels the current operation + */ + void cancel(); + + /** + \brief Indicates whether or not the operation was successful + */ + bool success() const; + + /** + \brief Returns the reason for error + */ + Error error() const; + + /** + \brief Returns the results of the operation + */ + QList results() const; + +signals: + /** + \brief Indicates that the operation has something to report + + After receiving this signal, call success() to check on the status of the operation, followed by results() or error() as appropriate. + */ + void resultsReady(); + +private: + friend class JDnsShared; + friend class JDnsSharedPrivate; + friend class JDnsSharedRequestPrivate; + JDnsSharedRequestPrivate *d; +}; + +/** + \brief Abstraction layer on top of QJDns + + \note Iris users should utilize NetNames for DNS capabilities, not JDnsShared. JDnsShared is provided for non-Iris users (and it is also used internally by NetNames). To use JDnsShared by itself, simply drop the jdnsshared.h and jdnsshared.cpp files, along with JDNS, into your project. It is not a full replacement for Qt's Q3Dns, as some tasks are left to you, but it covers most of it. + + QJDns supports everything a typical application should ever need in DNS. However, it is expected that modern applications will need to maintain multiple QJDns instances at the same time, and this is where things can get complicated. For example, most applications will want at least two QJDns instances: one for IPv4 unicast and one for IPv6 unicast. + + A single JDnsShared object encapsulates multiple instances of QJDns that are related. For example, an IPv4 unicast instance and an IPv6 unicast instance could be coupled within JDnsShared. Then, when a unicast operation is performed on the JDnsShared object, both underlying instances will be queried as appropriate. The application would not need to perform two resolutions itself, nor deal with any related complexity. + + Further, individual operations are performed using a separate class called JDnsSharedRequest, eliminating the need for the application to directly interface with a central QJDns object or track integer handles. This makes it easier for individual parts of the application to "share" the same instance (or set of instances) of QJDns, hence the name. + + JDnsShared is a thin abstraction. QJDns subtypes (e.g. QJDns::Type, QJDns::Record, etc) are still used with JDnsShared. Because of the duplication of documentation effort between NetNames and QJDns, there is no formal documentation for QJDns. Users of JDnsShared will need to read qjdns.h, although a basic explanation of the elements can be found below. + + Types: + + + + +
QJDns::TypeThis is a convenience enumeration for common DNS record types. For example: A, Aaaa, Srv, etc. The values directly map to the integer values of the DNS protocol (e.g. Srv = 33). See qjdns.h for all of the types and values.
QJDns::RecordThis class holds a DNS record. The main fields are type (integer type, probably something listed in QJDns::Type), rdata (QByteArray of the record value), and haveKnown (boolean to indicate if a decoded form of the record value is also available). See qjdns.h for the possible known fields. You will most-likely always work with known types. Received records that have a type listed in QJDns::Type are guaranteed to be known and will provide a decoded value. If you are creating a record for publishing, you will need to set owner, ttl, and type. If the type to be published is listed in QJDns::Type, then you will need to set haveKnown to true and set the known fields as appropriate, otherwise you need to set rdata. You do not need to supply an encoded form in rdata for known types, it can be left empty in that case.
QJDns::PublishModeThis is for Multicast DNS, and can either be Unique or Shared. A shared record can be published by multiple owners (for example, a "_ssh._tcp.local." PTR record might resolve to many different SSH services owned by different machines). A unique record can only have one owner (for example, a "mycomputer.local." A record would resolve to the IP address of the machine that published it). Attempting to publish a record on a network where a unique record is already present will result in a conflict error.
+ + Functions: + + +
QJDns::detectPrimaryMulticast()Detects a multicast interface. Pass QHostAddress::Any or QHostAddress::AnyIPv6, depending on which type of interface is desired.
+ + To use JDnsShared, first create an instance of it, set it up by calling addInterface() as necessary, and then use JDnsSharedRequest to perform operations on it. + + Here is an example of how to create and set up a JDnsShared object for typical DNS resolution: + +\code +// construct +JDnsShared *dns = new JDnsShared(JDnsShared::UnicastInternet); + +// add IPv4 and IPv6 interfaces +dns->addInterface(QHostAddress::Any); +dns->addInterface(QHostAddress::AnyIPv6); + +// at this point, the object is ready for operation +\endcode + + Perform a resolution like this: + +\code +JDnsSharedRequest *req = new JDnsSharedRequest(dns); +connect(req, SIGNAL(resultsReady()), SLOT(req_resultsReady())); +req->query("psi-im.org", QJDns::A); +... +void req_resultsReady() +{ + if(req->success()) + { + // print all of the IP addresses obtained + QList results = req->results(); + foreach(QJDns::Record r, results) + { + if(r.type == QJDns::A) + printf("%s\n", qPrintable(r.address.toString()); + } + } + else + printf("Error resolving!\n"); +} +\endcode + + It is important to filter the results as shown in the above example. QJDns guarantees at least one record in the results will be of the type queried for, but there may also be CNAME records present (of course, if the query was for a CNAME type, then the results will only be CNAME records). The recommended approach is to simply filter for the record types desired, as shown, rather than single out CNAME specifically. + + When you are finished with a JDnsShared object, it should be shut down before deleting: + +\code +connect(dns, SIGNAL(shutdownFinished()), SLOT(dns_shutdownFinished())); +dns->shutdown(); +... +void dns_shutdownFinished() +{ + delete dns; +} +\endcode + + Setting up JDnsShared for UnicastLocal and Multicast mode is done the same way as with UnicastInternet. + + For example, here is how Multicast mode could be set up: + +\code +// construct +JDnsShared *dns = new JDnsShared(JDnsShared::Multicast); + +// add IPv4 interface +QHostAddress addr = QJDns::detectPrimaryMulticast(QHostAddress::Any); +dns->addInterface(addr); + +// at this point, the object is ready for operation +\endcode + + JDnsShared provides a lot of functionality, but certain aspects of DNS are deemed out of its scope. Below are the responsibilities of the user of JDnsShared, if a more complete DNS behavior is desired: +
    +
  • Querying for several "qualified" names. You should first query for the name as provided, and if that fails then query for name + ".domain" (for every domain the computer is in). See domains().
  • +
  • Detecting for ".local" in the name to be queried, and using that to decide whether to query via Multicast/UnicastLocal or UnicastInternet.
  • +
  • For zeroconf/Bonjour, keep in mind that JDnsShared only provides low-level record queries. DNS-SD and any higher layers would be your job.
  • +
+ + Using a custom DNS implementation, such as JDnsShared, has the drawback that it is difficult to take advantage of platform-specific features (for example, an OS-wide DNS cache or LDAP integration). An application strategy for normal DNS should probably be: +
    +
  • If an A or AAAA record is desired, use a native lookup.
  • +
  • Else, if the platform has advanced DNS features already (ie, res_query), use those.
  • +
  • Else, use JDnsShared.
  • +
+ + For Multicast DNS, awareness of the platform is doubly important. There should only be one Multicast DNS "Responder" per computer, and using JDnsShared in Multicast mode at the same time could result in a conflict. An application strategy for Multicast DNS should be: +
    +
  • If the platform has a Multicast DNS daemon installed already, use it somehow.
  • +
  • Else, use JDnsShared.
  • +
+ + \sa JDnsSharedRequest +*/ +class JDnsShared : public QObject +{ + Q_OBJECT +public: + /** + \brief The mode to operate in + */ + enum Mode + { + /** + For regular DNS resolution. In this mode, lookups are performed on all interfaces, and the first returned result is used. + */ + UnicastInternet, + + /** + Perform regular DNS resolution using the Multicast DNS address. This is used to resolve large and/or known Multicast DNS names without actually multicasting anything. + */ + UnicastLocal, + + /** + Multicast DNS querying and publishing. + + \note For Multicast mode, JDnsShared supports up to one interface for each IP version (e.g. one IPv4 interface and one IPv6 interface), and expects the default/primary multicast interface for that IP version to be used. + */ + Multicast + }; + + /** + \brief Constructs a new object with the given \a mode and \a parent + */ + JDnsShared(Mode mode, QObject *parent = 0); + + /** + \brief Destroys the object + */ + ~JDnsShared(); + + /** + \brief Sets the debug object to report to + + If a debug object is set using this function, then JDnsShared will send output text to it, prefixing each line with \a name. + */ + void setDebug(JDnsSharedDebug *db, const QString &name); + + /** + \brief Adds an interface to operate on + + For UnicastInternet and UnicastLocal, these will almost always be QHostAddress::Any or QHostAddress::AnyIPv6 (operate on the default interface for IPv4 or IPv6, respectively). + + For Multicast, it is expected that the default/primary multicast interface will be used here. Do not pass QHostAddress::Any (or AnyIPv6) with Multicast mode. + + Returns true if the interface was successfully added, otherwise returns false. + */ + bool addInterface(const QHostAddress &addr); + + /** + \brief Removes a previously-added interface + */ + void removeInterface(const QHostAddress &addr); + + /** + \brief Shuts down the object + + This operation primarily exists for Multicast mode, so that any published records have a chance to be unpublished. If the JDnsShared object is simply deleted without performing a shutdown, then published records will linger on the network until their TTLs expire. + + When shutdown is complete, the shutdownFinished() signal will be emitted. + */ + void shutdown(); + + /** + \brief The domains to search in + + You should perform a separate resolution for every domain configured on this machine. + */ + static QList domains(); + + /** + \brief Performs a blocking shutdown of many JDnsShared instances + + This function is a convenient way to shutdown multiple JDnsShared instances synchronously. The internal shutdown procedure uses no more than a few cycles of the eventloop, so it should be safe to call without worry of the application being overly stalled. This function takes ownership of the instances passed to it, and will delete them upon completion. + + It is worth noting that this function is implemented without the use of a nested eventloop. All of the JDnsShared instances are moved into a temporary thread to perform the shutdown procedure, which should not cause any unexpected behavior in the current thread. + + \code +QList list; +list += jdnsShared_unicast; +list += jdnsShared_multicast; +JDnsShared::waitForShutdown(list); + +// collect remaining debug information +QStringList finalDebugLines = jdnsSharedDebug.readDebugLines(); + \endcode + */ + static void waitForShutdown(const QList &instances); + +signals: + /** + \brief Indicates the object has been shut down + */ + void shutdownFinished(); + +private: + friend class JDnsSharedRequest; + friend class JDnsSharedPrivate; + JDnsSharedPrivate *d; +}; + +#endif