2
* Copyright (C) 2005-2008 Justin Karneges
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2.1 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public
15
* License along with this library; if not, write to the Free Software
16
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
#include "irisnetplugin.h"
22
#include "objectsession.h"
23
#include "jdnsshared.h"
24
#include "netinterface.h"
28
Q_DECLARE_METATYPE(XMPP::NameRecord)
29
Q_DECLARE_METATYPE(XMPP::NameResolver::Error)
30
Q_DECLARE_METATYPE(XMPP::ServiceBrowser::Error)
31
Q_DECLARE_METATYPE(XMPP::ServiceResolver::Error)
32
Q_DECLARE_METATYPE(XMPP::ServiceLocalPublisher::Error)
36
static NameRecord importJDNSRecord(const QJDns::Record &in)
41
case QJDns::A: out.setAddress(in.address); break;
42
case QJDns::Aaaa: out.setAddress(in.address); break;
43
case QJDns::Mx: out.setMx(in.name, in.priority); break;
44
case QJDns::Srv: out.setSrv(in.name, in.port, in.priority, in.weight); break;
45
case QJDns::Cname: out.setCname(in.name); break;
46
case QJDns::Ptr: out.setPtr(in.name); break;
47
case QJDns::Txt: out.setTxt(in.texts); break;
48
case QJDns::Hinfo: out.setHinfo(in.cpu, in.os); break;
49
case QJDns::Ns: out.setNs(in.name); break;
50
case 10: out.setNull(in.rdata); break;
54
out.setOwner(in.owner);
59
static QJDns::Record exportJDNSRecord(const NameRecord &in)
67
out.address = in.address();
69
case NameRecord::Aaaa:
70
out.type = QJDns::Aaaa;
72
out.address = in.address();
78
out.priority = in.priority();
81
out.type = QJDns::Srv;
85
out.priority = in.priority();
86
out.weight = in.weight();
88
case NameRecord::Cname:
89
out.type = QJDns::Cname;
94
out.type = QJDns::Ptr;
99
out.type = QJDns::Txt;
100
out.haveKnown = true;
101
out.texts = in.texts();
103
case NameRecord::Hinfo:
104
out.type = QJDns::Hinfo;
105
out.haveKnown = true;
110
out.type = QJDns::Ns;
111
out.haveKnown = true;
112
out.name = in.name();
114
case NameRecord::Null:
116
out.rdata = in.rawData();
121
out.owner = in.owner();
126
static bool validServiceType(const QByteArray &in)
128
// can't be empty, or start/end with a dot
129
if(in.isEmpty() || in[0] == '.' || in[in.length() - 1] == '.')
132
// must contain exactly one dot
134
for(int n = 0; n < in.length(); ++n)
140
// no need to count more than 2
151
static QByteArray escapeDomainPart(const QByteArray &in)
154
for(int n = 0; n < in.length(); ++n)
158
else if(in[n] == '.')
166
static QByteArray unescapeDomainPart(const QByteArray &in)
169
for(int n = 0; n < in.length(); ++n)
173
if(n + 1 >= in.length())
190
inline void bump_at()
208
if(!set.contains(at))
220
void releaseId(int id)
232
//----------------------------------------------------------------------------
234
//----------------------------------------------------------------------------
235
class JDnsGlobal : public QObject
241
JDnsShared *uni_net, *uni_local, *mul;
242
QHostAddress mul_addr4, mul_addr6;
243
NetInterfaceManager netman;
244
QList<NetInterface*> ifaces;
254
qRegisterMetaType<NameRecord>();
255
qRegisterMetaType<NameResolver::Error>();
256
qRegisterMetaType<ServiceBrowser::Error>();
257
qRegisterMetaType<ServiceResolver::Error>();
258
qRegisterMetaType<ServiceLocalPublisher::Error>();
260
connect(&db, SIGNAL(readyRead()), SLOT(jdns_debugReady()));
262
updateTimer = new QTimer(this);
263
connect(updateTimer, SIGNAL(timeout()), SLOT(doUpdateMulticastInterfaces()));
264
updateTimer->setSingleShot(true);
269
updateTimer->disconnect(this);
270
updateTimer->setParent(0);
271
updateTimer->deleteLater();
275
QList<JDnsShared*> list;
283
// calls shutdown on the list, waits for shutdownFinished, deletes
284
JDnsShared::waitForShutdown(list);
290
JDnsShared *ensure_uni_net()
294
uni_net = new JDnsShared(JDnsShared::UnicastInternet, this);
295
uni_net->setDebug(&db, "U");
296
bool ok4 = uni_net->addInterface(QHostAddress::Any);
297
bool ok6 = uni_net->addInterface(QHostAddress::AnyIPv6);
307
JDnsShared *ensure_uni_local()
311
uni_local = new JDnsShared(JDnsShared::UnicastLocal, this);
312
uni_local->setDebug(&db, "L");
313
bool ok4 = uni_local->addInterface(QHostAddress::Any);
314
bool ok6 = uni_local->addInterface(QHostAddress::AnyIPv6);
324
JDnsShared *ensure_mul()
328
mul = new JDnsShared(JDnsShared::Multicast, this);
329
mul->setDebug(&db, "M");
331
connect(&netman, SIGNAL(interfaceAvailable(const QString &)), SLOT(iface_available(const QString &)));
333
// get the current network interfaces. this initial
334
// fetching should not trigger any calls to
335
// updateMulticastInterfaces(). only future
336
// activity should do that.
337
foreach(const QString &id, netman.interfaces())
339
NetInterface *iface = new NetInterface(id, &netman);
340
connect(iface, SIGNAL(unavailable()), SLOT(iface_unavailable()));
344
updateMulticastInterfaces(false);
349
bool haveMulticast4() const
351
return !mul_addr4.isNull();
354
bool haveMulticast6() const
356
return !mul_addr6.isNull();
360
void interfacesChanged();
363
void jdns_debugReady()
365
QStringList lines = db.readDebugLines();
367
for(int n = 0; n < lines.count(); ++n)
368
printf("jdns: %s\n", qPrintable(lines[n]));
374
void iface_available(const QString &id)
376
NetInterface *iface = new NetInterface(id, &netman);
377
connect(iface, SIGNAL(unavailable()), SLOT(iface_unavailable()));
380
updateTimer->start(100);
383
void iface_unavailable()
385
NetInterface *iface = (NetInterface *)sender();
386
ifaces.removeAll(iface);
389
updateTimer->start(100);
392
void doUpdateMulticastInterfaces()
394
updateMulticastInterfaces(true);
398
void updateMulticastInterfaces(bool useSignals)
400
QHostAddress addr4 = QJDns::detectPrimaryMulticast(QHostAddress::Any);
401
QHostAddress addr6 = QJDns::detectPrimaryMulticast(QHostAddress::AnyIPv6);
403
bool had4 = !mul_addr4.isNull();
404
bool had6 = !mul_addr6.isNull();
406
updateMulticastInterface(&mul_addr4, addr4);
407
updateMulticastInterface(&mul_addr6, addr6);
409
bool have4 = !mul_addr4.isNull();
410
bool have6 = !mul_addr6.isNull();
412
// did we gain/lose something?
413
if(had4 != have4 || had6 != have6)
416
emit interfacesChanged();
420
void updateMulticastInterface(QHostAddress *curaddr, const QHostAddress &newaddr)
422
if(!(newaddr == *curaddr)) // QHostAddress doesn't have operator!=
424
if(!curaddr->isNull())
425
mul->removeInterface(*curaddr);
427
if(!curaddr->isNull())
429
if(!mul->addInterface(*curaddr))
430
*curaddr = QHostAddress();
436
//----------------------------------------------------------------------------
438
//----------------------------------------------------------------------------
439
class JDnsNameProvider : public NameProvider
442
Q_INTERFACES(XMPP::NameProvider)
445
enum Mode { Internet, Local };
456
JDnsSharedRequest *req;
462
Item(QObject *parent = 0) :
477
static JDnsNameProvider *create(JDnsGlobal *global, Mode mode, QObject *parent = 0)
481
if(!global->ensure_uni_net())
486
if(!global->ensure_uni_local())
490
return new JDnsNameProvider(global, mode, parent);
493
JDnsNameProvider(JDnsGlobal *_global, Mode _mode, QObject *parent = 0) :
505
Item *getItemById(int id)
507
for(int n = 0; n < items.count(); ++n)
509
if(items[n]->id == id)
516
Item *getItemByReq(JDnsSharedRequest *req)
518
for(int n = 0; n < items.count(); ++n)
520
if(items[n]->req == req)
527
void releaseItem(Item *i)
529
idman.releaseId(i->id);
534
virtual bool supportsSingle() const
539
virtual bool supportsLongLived() const
542
return true; // we support long-lived local queries
544
return false; // we do NOT support long-lived internet queries
547
virtual bool supportsRecordType(int type) const
549
// all record types supported
554
virtual int resolve_start(const QByteArray &name, int qType, bool longLived)
558
// if query ends in .local, switch to local resolver
559
if(name.right(6) == ".local" || name.right(7) == ".local.")
561
Item *i = new Item(this);
562
i->id = idman.reserveId();
563
i->longLived = longLived;
565
i->sess.defer(this, "do_local", Q_ARG(int, i->id), Q_ARG(QByteArray, name));
569
// we don't support long-lived internet queries
572
Item *i = new Item(this);
573
i->id = idman.reserveId();
575
i->sess.defer(this, "do_error", Q_ARG(int, i->id),
576
Q_ARG(XMPP::NameResolver::Error, NameResolver::ErrorNoLongLived));
581
Item *i = new Item(this);
582
i->id = idman.reserveId();
583
i->req = new JDnsSharedRequest(global->uni_net);
584
connect(i->req, SIGNAL(resultsReady()), SLOT(req_resultsReady()));
586
i->longLived = false;
588
i->req->query(name, qType);
593
Item *i = new Item(this);
594
i->id = idman.reserveId();
598
if(!global->ensure_mul())
601
i->sess.defer(this, "do_error", Q_ARG(int, i->id),
602
Q_ARG(XMPP::NameResolver::Error, NameResolver::ErrorNoLocal));
606
i->req = new JDnsSharedRequest(global->mul);
611
i->req = new JDnsSharedRequest(global->uni_local);
612
i->longLived = false;
614
connect(i->req, SIGNAL(resultsReady()), SLOT(req_resultsReady()));
616
i->req->query(name, qType);
621
virtual void resolve_stop(int id)
623
Item *i = getItemById(id);
631
virtual void resolve_localResultsReady(int id, const QList<XMPP::NameRecord> &results)
633
Item *i = getItemById(id);
635
Q_ASSERT(!i->localResult);
637
i->localResult = true;
638
i->sess.defer(this, "do_local_ready", Q_ARG(int, id),
639
Q_ARG(QList<XMPP::NameRecord>, results));
642
virtual void resolve_localError(int id, XMPP::NameResolver::Error e)
644
Item *i = getItemById(id);
646
Q_ASSERT(!i->localResult);
648
i->localResult = true;
649
i->sess.defer(this, "do_local_error", Q_ARG(int, id),
650
Q_ARG(XMPP::NameResolver::Error, e));
654
void req_resultsReady()
656
JDnsSharedRequest *req = (JDnsSharedRequest *)sender();
657
Item *i = getItemByReq(req);
664
QList<NameRecord> out;
665
foreach(const QJDns::Record &r, req->results())
667
// unless we are asking for all types, only
668
// accept the type we asked for
669
if(i->type == QJDns::Any || r.type == i->type)
671
NameRecord rec = importJDNSRecord(r);
677
// don't report anything if long-lived gives no results
678
if(i->longLived && out.isEmpty())
681
// only emit success if we have at least 1 result
686
emit resolve_resultsReady(id, out);
691
emit resolve_error(id, NameResolver::ErrorGeneric);
696
JDnsSharedRequest::Error e = req->error();
699
NameResolver::Error error = NameResolver::ErrorGeneric;
700
if(e == JDnsSharedRequest::ErrorNXDomain)
701
error = NameResolver::ErrorNoName;
702
else if(e == JDnsSharedRequest::ErrorTimeout)
703
error = NameResolver::ErrorTimeout;
704
else // ErrorGeneric or ErrorNoNet
705
error = NameResolver::ErrorGeneric;
706
emit resolve_error(id, error);
710
void do_error(int id, XMPP::NameResolver::Error e)
712
Item *i = getItemById(id);
716
emit resolve_error(id, e);
719
void do_local(int id, const QByteArray &name)
721
Item *i = getItemById(id);
724
// resolve_useLocal has two behaviors:
725
// - if longlived, then it indicates a hand-off
726
// - if non-longlived, then it indicates we want a subquery
730
emit resolve_useLocal(id, name);
733
void do_local_ready(int id, const QList<XMPP::NameRecord> &results)
735
Item *i = getItemById(id);
738
// only non-longlived queries come through here, so we're done
740
emit resolve_resultsReady(id, results);
743
void do_local_error(int id, XMPP::NameResolver::Error e)
745
Item *i = getItemById(id);
749
emit resolve_error(id, e);
753
//----------------------------------------------------------------------------
755
//----------------------------------------------------------------------------
756
class JDnsBrowse : public QObject
761
QByteArray type, typeAndDomain;
762
JDnsSharedRequest req;
764
JDnsBrowse(JDnsShared *_jdns, QObject *parent = 0) :
768
connect(&req, SIGNAL(resultsReady()), SLOT(jdns_resultsReady()));
771
void start(const QByteArray &_type)
774
Q_ASSERT(validServiceType(type));
775
typeAndDomain = type + ".local.";
776
req.query(typeAndDomain, QJDns::Ptr);
780
void available(const QByteArray &instance);
781
void unavailable(const QByteArray &instance);
784
QByteArray parseInstanceName(const QByteArray &name)
786
// needs to be at least X + '.' + typeAndDomain
787
if(name.length() < typeAndDomain.length() + 2)
790
// index of the '.' character
791
int at = name.length() - typeAndDomain.length() - 1;
795
if(name.mid(at + 1) != typeAndDomain)
798
QByteArray friendlyName = unescapeDomainPart(name.mid(0, at));
799
if(friendlyName.isEmpty())
806
void jdns_resultsReady()
812
QJDns::Record rec = req.results().first();
814
Q_ASSERT(rec.type == QJDns::Ptr);
816
QByteArray name = rec.name;
817
QByteArray instance = parseInstanceName(name);
818
if(instance.isEmpty())
823
emit unavailable(instance);
827
emit available(instance);
831
//----------------------------------------------------------------------------
832
// JDnsServiceResolve
833
//----------------------------------------------------------------------------
835
// 5 second timeout waiting for both A and AAAA
836
// 8 second timeout waiting for at least one record
837
class JDnsServiceResolve : public QObject
849
JDnsSharedRequest reqtxt; // for TXT
850
JDnsSharedRequest req; // for SRV/A
851
JDnsSharedRequest req6; // for AAAA
857
QList<QByteArray> attribs;
861
QHostAddress addr4, addr6;
863
JDnsServiceResolve(JDnsShared *_jdns, QObject *parent = 0) :
869
connect(&reqtxt, SIGNAL(resultsReady()), SLOT(reqtxt_ready()));
870
connect(&req, SIGNAL(resultsReady()), SLOT(req_ready()));
871
connect(&req6, SIGNAL(resultsReady()), SLOT(req6_ready()));
873
opTimer = new QTimer(this);
874
connect(opTimer, SIGNAL(timeout()), SLOT(op_timeout()));
875
opTimer->setSingleShot(true);
878
~JDnsServiceResolve()
880
opTimer->disconnect(this);
881
opTimer->setParent(0);
882
opTimer->deleteLater();
885
void start(const QByteArray name)
892
opTimer->start(8000);
894
reqtxt.query(name, QJDns::Txt);
895
req.query(name, QJDns::Srv);
900
void error(JDnsSharedRequest::Error e);
905
if(opTimer->isActive())
909
if(srvState == Srv || !have4)
911
if(srvState >= AddressWait && !have6)
917
// we're done when we have txt and addresses
918
if(haveTxt && ( (have4 && have6) || (srvState == AddressFirstCome && (have4 || have6)) ))
931
if(!reqtxt.success())
934
emit error(reqtxt.error());
938
QJDns::Record rec = reqtxt.results().first();
941
Q_ASSERT(rec.type == QJDns::Txt);
944
if(!rec.texts.isEmpty())
946
// if there is only 1 text, it needs to be
947
// non-empty for us to care
948
if(rec.texts.count() != 1 || !rec.texts[0].isEmpty())
962
emit error(req.error());
966
QJDns::Record rec = req.results().first();
971
// in Srv state, req is used for SRV records
973
Q_ASSERT(rec.type == QJDns::Srv);
978
srvState = AddressWait;
979
opTimer->start(5000);
981
req.query(host, QJDns::A);
982
req6.query(host, QJDns::Aaaa);
986
// in the other states, req is used for A records
988
Q_ASSERT(rec.type == QJDns::A);
1002
emit error(req6.error());
1006
QJDns::Record rec = req6.results().first();
1009
Q_ASSERT(rec.type == QJDns::Aaaa);
1011
addr6 = rec.address;
1021
// timeout getting SRV. it is possible that we could
1022
// have obtained the TXT record, but if SRV times
1023
// out then we consider the whole job to have
1026
emit error(JDnsSharedRequest::ErrorTimeout);
1028
else if(srvState == AddressWait)
1030
// timeout while waiting for both A and AAAA. we now
1031
// switch to the AddressFirstCome state, where an
1032
// answer for either will do
1034
srvState = AddressFirstCome;
1036
// if we have at least one of these, we're done
1039
// well, almost. we might still be waiting
1040
// for the TXT record
1045
// if we are here, then it means we are missing TXT
1046
// still, or we have neither A nor AAAA.
1048
// wait 3 more seconds
1049
opTimer->start(3000);
1051
else // AddressFirstCome
1057
emit error(JDnsSharedRequest::ErrorTimeout);
1063
//----------------------------------------------------------------------------
1064
// JDnsPublishAddresses
1065
//----------------------------------------------------------------------------
1067
// helper class for JDnsPublishAddresses. publishes A+PTR or AAAA+PTR pair.
1068
class JDnsPublishAddress : public QObject
1081
JDnsSharedRequest pub_addr;
1082
JDnsSharedRequest pub_ptr;
1085
JDnsPublishAddress(JDnsShared *_jdns, QObject *parent = 0) :
1087
pub_addr(_jdns, this),
1088
pub_ptr(_jdns, this)
1090
connect(&pub_addr, SIGNAL(resultsReady()), SLOT(pub_addr_ready()));
1091
connect(&pub_ptr, SIGNAL(resultsReady()), SLOT(pub_ptr_ready()));
1094
void start(Type _type, const QByteArray &_host)
1102
rec.type = QJDns::Aaaa;
1104
rec.type = QJDns::A;
1107
rec.haveKnown = true;
1108
rec.address = QHostAddress(); // null address, will be filled in
1109
pub_addr.publish(QJDns::Unique, rec);
1118
bool success() const
1124
void resultsReady();
1127
void pub_addr_ready()
1129
if(pub_addr.success())
1132
rec.type = QJDns::Ptr;
1134
rec.owner = ".ip6.arpa.";
1136
rec.owner = ".in-addr.arpa.";
1138
rec.haveKnown = true;
1140
pub_ptr.publish(QJDns::Shared, rec);
1144
pub_ptr.cancel(); // needed if addr fails during or after ptr
1146
emit resultsReady();
1150
void pub_ptr_ready()
1152
if(pub_ptr.success())
1162
emit resultsReady();
1166
// This class publishes A/AAAA records for the machine, using a derived
1167
// hostname (it will use QHostInfo::localHostName(), but append a unique
1168
// suffix if necessary). If there is ever a record conflict, it will
1169
// republish under a unique name.
1171
// The hostName() signal is emitted when a hostname is successfully
1172
// published as. When there is a conflict, hostName() is emitted with
1173
// an empty value, and will again be emitted with a non-empty value
1174
// once the conflict is resolved. A missing hostname is considered a
1175
// temporary problem, and so other publish operations that depend on a
1176
// hostname (SRV, etc) should block until a hostname is available.
1177
class JDnsPublishAddresses : public QObject
1184
JDnsPublishAddress pub6;
1185
JDnsPublishAddress pub4;
1192
JDnsPublishAddresses(JDnsShared *_jdns, QObject *parent = 0) :
1201
connect(&pub6, SIGNAL(resultsReady()), SLOT(pub6_ready()));
1202
connect(&pub4, SIGNAL(resultsReady()), SLOT(pub4_ready()));
1215
bool isStarted() const
1220
// comments in this method apply to setUseIPv4 as well.
1221
void setUseIPv6(bool b)
1230
// a "deferred call to doDisable" and "publish operations"
1231
// are mutually exclusive. thus, a deferred call is only
1232
// invoked when both publishes are canceled, and the
1233
// deferred call is canceled if any of the publishes are
1240
// if the other is already active, then
1241
// just activate this one without
1242
// recomputing the hostname
1249
// otherwise, recompute the hostname
1258
sess.defer(this, "doDisable");
1262
void setUseIPv4(bool b)
1288
sess.defer(this, "doDisable");
1293
void hostName(const QByteArray &str);
1298
QString me = QHostInfo::localHostName();
1300
// some hosts may already have ".local" in their name
1301
if(me.endsWith(".local"))
1302
me.truncate(me.length() - 6);
1304
// prefix our hostname so we don't conflict with a system
1306
me.prepend("jdns-");
1309
me += QString("-%1").arg(counter);
1311
host = escapeDomainPart(me.toUtf8()) + ".local.";
1321
pub6.start(JDnsPublishAddress::IPv6, host);
1326
pub4.start(JDnsPublishAddress::IPv4, host);
1340
emit hostName(host);
1346
// we get here if we fail to publish at all, or if we
1347
// successfully publish but then fail later on. in the
1348
// latter case it means we "lost" our host records.
1350
bool lostHost = success; // as in earlier publish success
1353
// if we lost a hostname with a suffix, or counter is
1354
// at 99, then start counter over at 1 (no suffix).
1355
if((lostHost && counter > 1) || counter >= 99)
1362
// only emit lost host signal once
1364
emit hostName(QByteArray());
1370
bool lostHost = success;
1374
emit hostName(QByteArray());
1410
//----------------------------------------------------------------------------
1412
//----------------------------------------------------------------------------
1415
class JDnsPublishExtra : public QObject
1420
JDnsPublishExtra(JDnsPublish *_jdnsPub);
1421
~JDnsPublishExtra();
1423
void start(const QJDns::Record &_rec);
1424
void update(const QJDns::Record &_rec);
1428
void error(JDnsSharedRequest::Error e);
1431
friend class JDnsPublish;
1433
JDnsPublish *jdnsPub;
1435
JDnsSharedRequest pub;
1441
// This class publishes SRV/TXT/PTR for a service. if a hostName is not
1442
// is not available (see JDnsPublishAddresses) then the publish action
1443
// will be deferred until one is available. SRV and TXT are published
1444
// as unique records, and once they both succeed then the PTR record
1445
// is published. once the PTR succeeds, then published() is emitted.
1446
// if a conflict occurs with any action, then the whole thing fails and
1447
// error() is emitted. if, at any time, the hostName is lost, then
1448
// then the SRV operation is canceled, but no error is emitted. when the
1449
// hostName is regained, then the SRV record is republished.
1451
// It's important to note that published() is only emitted once ever, even
1452
// if a hostName change causes a republishing. this way, hostName changes
1453
// are completely transparent.
1454
class JDnsPublish : public QObject
1460
JDnsSharedRequest pub_srv;
1461
JDnsSharedRequest pub_txt;
1462
JDnsSharedRequest pub_ptr;
1464
bool have_srv, have_txt, have_ptr;
1465
bool need_update_txt;
1467
QByteArray fullname;
1468
QByteArray instance;
1472
QList<QByteArray> attribs;
1474
QSet<JDnsPublishExtra*> extraList;
1476
JDnsPublish(JDnsShared *_jdns, QObject *parent = 0) :
1479
pub_srv(_jdns, this),
1480
pub_txt(_jdns, this),
1481
pub_ptr(_jdns, this)
1483
connect(&pub_srv, SIGNAL(resultsReady()), SLOT(pub_srv_ready()));
1484
connect(&pub_txt, SIGNAL(resultsReady()), SLOT(pub_txt_ready()));
1485
connect(&pub_ptr, SIGNAL(resultsReady()), SLOT(pub_ptr_ready()));
1490
qDeleteAll(extraList);
1493
void start(const QString &_instance, const QByteArray &_type, const QByteArray &localHost, int _port, const QMap<QString,QByteArray> &attributes)
1496
Q_ASSERT(validServiceType(type));
1498
instance = escapeDomainPart(_instance.toUtf8());
1499
fullname = instance + '.' + type + ".local.";
1502
attribs = makeTxtList(attributes);
1507
need_update_txt = false;
1509
// no host? defer publishing till we have one
1516
void update(const QMap<QString,QByteArray> &attributes)
1518
attribs = makeTxtList(attributes);
1520
// still publishing the initial txt?
1523
// flag that we want to update once the publish
1525
need_update_txt = true;
1529
// no SRV, but have TXT? this means we lost SRV due to
1530
// a hostname change.
1533
// in that case, revoke the TXT. it'll get
1534
// republished after SRV then.
1544
// pass empty host if host lost
1545
void hostChanged(const QByteArray &_host)
1547
bool changed = (host != _host);
1555
// cancel srv record momentarily
1561
// we now have a host, publish
1569
void error(JDnsSharedRequest::Error e);
1572
friend class JDnsPublishExtra;
1574
static QList<QByteArray> makeTxtList(const QMap<QString,QByteArray> &attributes)
1576
QList<QByteArray> out;
1578
QMapIterator<QString,QByteArray> it(attributes);
1582
out += it.key().toLatin1() + '=' + it.value();
1585
out += QByteArray();
1594
rec.type = QJDns::Srv;
1595
rec.owner = fullname;
1597
rec.haveKnown = true;
1602
pub_srv.publish(QJDns::Unique, rec);
1604
// if we're just republishing SRV after losing/regaining
1605
// our hostname, then TXT is already published
1609
// publish extra records as needed
1610
foreach(JDnsPublishExtra *extra, extraList)
1613
doPublishExtra(extra);
1621
rec.type = QJDns::Txt;
1622
rec.owner = fullname;
1624
rec.haveKnown = true;
1625
rec.texts = attribs;
1628
pub_txt.publish(QJDns::Unique, rec);
1630
pub_txt.publishUpdate(rec);
1635
if(have_srv && have_txt)
1639
rec.type = QJDns::Ptr;
1640
rec.owner = type + ".local.";
1642
rec.haveKnown = true;
1643
rec.name = fullname;
1644
pub_ptr.publish(QJDns::Shared, rec);
1650
foreach(JDnsPublishExtra *extra, extraList)
1651
cleanupExtra(extra);
1652
qDeleteAll(extraList);
1663
void publishExtra(JDnsPublishExtra *extra)
1665
Q_ASSERT(!extraList.contains(extra));
1667
connect(&extra->pub, SIGNAL(resultsReady()), SLOT(pub_extra_ready()));
1670
// defer publishing until SRV is ready
1674
doPublishExtra(extra);
1677
void publishExtraUpdate(JDnsPublishExtra *extra)
1681
extra->need_update = true;
1687
extra->have = false;
1688
extra->pub.cancel();
1692
doPublishExtra(extra);
1695
void unpublishExtra(JDnsPublishExtra *extra)
1697
extraList.remove(extra);
1700
void doPublishExtra(JDnsPublishExtra *extra)
1703
extra->pub.publish(QJDns::Unique, extra->rec);
1705
extra->pub.publishUpdate(extra->rec);
1708
void cleanupExtra(JDnsPublishExtra *extra)
1710
extra->pub.cancel();
1711
extra->disconnect(this);
1712
extra->started = false;
1713
extra->have = false;
1717
void pub_srv_ready()
1719
if(pub_srv.success())
1726
JDnsSharedRequest::Error e = pub_srv.error();
1732
void pub_txt_ready()
1734
if(pub_txt.success())
1740
need_update_txt = false;
1748
JDnsSharedRequest::Error e = pub_txt.error();
1754
void pub_ptr_ready()
1756
if(pub_ptr.success())
1763
JDnsSharedRequest::Error e = pub_ptr.error();
1769
void pub_extra_ready()
1771
JDnsSharedRequest *req = (JDnsSharedRequest *)sender();
1772
JDnsPublishExtra *extra = 0;
1773
foreach(JDnsPublishExtra *e, extraList)
1783
if(extra->pub.success())
1787
if(extra->need_update)
1789
extra->need_update = false;
1790
doPublishExtra(extra);
1793
emit extra->published();
1797
JDnsSharedRequest::Error e = extra->pub.error();
1798
cleanupExtra(extra);
1799
emit extra->error(e);
1804
JDnsPublishExtra::JDnsPublishExtra(JDnsPublish *_jdnsPub) :
1808
pub(_jdnsPub->jdns, this)
1812
JDnsPublishExtra::~JDnsPublishExtra()
1815
jdnsPub->unpublishExtra(this);
1818
void JDnsPublishExtra::start(const QJDns::Record &_rec)
1823
need_update = false;
1824
jdnsPub->publishExtra(this);
1827
void JDnsPublishExtra::update(const QJDns::Record &_rec)
1830
jdnsPub->publishExtraUpdate(this);
1833
//----------------------------------------------------------------------------
1834
// JDnsServiceProvider
1835
//----------------------------------------------------------------------------
1840
JDnsBrowse * const browse;
1841
ObjectSession *sess;
1843
BrowseItem(int _id, JDnsBrowse *_browse) :
1857
class BrowseItemList
1860
QSet<BrowseItem*> items;
1861
QHash<int,BrowseItem*> indexById;
1862
QHash<JDnsBrowse*,BrowseItem*> indexByBrowse;
1873
return idman.reserveId();
1876
void insert(BrowseItem *item)
1879
indexById.insert(item->id, item);
1880
indexByBrowse.insert(item->browse, item);
1883
void remove(BrowseItem *item)
1885
indexById.remove(item->id);
1886
indexByBrowse.remove(item->browse);
1889
idman.releaseId(item->id);
1893
BrowseItem *itemById(int id) const
1895
return indexById.value(id);
1898
BrowseItem *itemByBrowse(JDnsBrowse *browse) const
1900
return indexByBrowse.value(browse);
1908
JDnsServiceResolve * const resolve;
1909
ObjectSession *sess;
1911
ResolveItem(int _id, JDnsServiceResolve *_resolve) :
1925
class ResolveItemList
1928
QSet<ResolveItem*> items;
1929
QHash<int,ResolveItem*> indexById;
1930
QHash<JDnsServiceResolve*,ResolveItem*> indexByResolve;
1941
return idman.reserveId();
1944
void insert(ResolveItem *item)
1947
indexById.insert(item->id, item);
1948
indexByResolve.insert(item->resolve, item);
1951
void remove(ResolveItem *item)
1953
indexById.remove(item->id);
1954
indexByResolve.remove(item->resolve);
1957
idman.releaseId(item->id);
1961
ResolveItem *itemById(int id) const
1963
return indexById.value(id);
1966
ResolveItem *itemByResolve(JDnsServiceResolve *resolve) const
1968
return indexByResolve.value(resolve);
1976
JDnsPublish * const publish;
1977
ObjectSession *sess;
1979
PublishItem(int _id, JDnsPublish *_publish) :
1993
class PublishItemList
1996
QSet<PublishItem*> items;
1999
QHash<int,PublishItem*> indexById;
2000
QHash<JDnsPublish*,PublishItem*> indexByPublish;
2011
return idman.reserveId();
2014
void insert(PublishItem *item)
2017
indexById.insert(item->id, item);
2018
indexByPublish.insert(item->publish, item);
2021
void remove(PublishItem *item)
2023
indexById.remove(item->id);
2024
indexByPublish.remove(item->publish);
2027
idman.releaseId(item->id);
2031
PublishItem *itemById(int id) const
2033
return indexById.value(id);
2036
PublishItem *itemByPublish(JDnsPublish *publish) const
2038
return indexByPublish.value(publish);
2042
class PublishExtraItem
2046
JDnsPublishExtra * const publish;
2047
ObjectSession *sess;
2049
PublishExtraItem(int _id, JDnsPublishExtra *_publish) :
2063
class PublishExtraItemList
2066
QSet<PublishExtraItem*> items;
2069
QHash<int,PublishExtraItem*> indexById;
2070
QHash<JDnsPublishExtra*,PublishExtraItem*> indexByPublish;
2074
~PublishExtraItemList()
2084
indexByPublish.clear();
2090
return idman.reserveId();
2093
void insert(PublishExtraItem *item)
2096
indexById.insert(item->id, item);
2097
indexByPublish.insert(item->publish, item);
2100
void remove(PublishExtraItem *item)
2102
indexById.remove(item->id);
2103
indexByPublish.remove(item->publish);
2106
idman.releaseId(item->id);
2110
PublishExtraItem *itemById(int id) const
2112
return indexById.value(id);
2115
PublishExtraItem *itemByPublish(JDnsPublishExtra *publish) const
2117
return indexByPublish.value(publish);
2121
class JDnsServiceProvider : public ServiceProvider
2129
BrowseItemList browseItemList;
2130
QHash<QByteArray,ServiceInstance> items;
2133
ResolveItemList resolveItemList;
2136
JDnsPublishAddresses *pub_addresses;
2137
QByteArray localHost;
2138
PublishItemList publishItemList;
2139
PublishExtraItemList publishExtraItemList;
2141
static JDnsServiceProvider *create(JDnsGlobal *global, QObject *parent = 0)
2143
return new JDnsServiceProvider(global, parent);
2146
JDnsServiceProvider(JDnsGlobal *_global, QObject *parent = 0) :
2147
ServiceProvider(parent),
2151
connect(global, SIGNAL(interfacesChanged()), SLOT(interfacesChanged()));
2154
~JDnsServiceProvider()
2156
// make sure extra items are deleted before normal ones
2157
publishExtraItemList.clear();
2160
virtual int browse_start(const QString &_type, const QString &_domain)
2163
if(_domain.isEmpty() || _domain == ".")
2168
if(domain[domain.length() - 1] != '.')
2171
Q_ASSERT(domain.length() >= 2 && domain[domain.length() - 1] == '.');
2173
int id = browseItemList.reserveId();
2175
// no support for non-local domains
2176
if(domain != "local.")
2178
BrowseItem *i = new BrowseItem(id, 0);
2179
i->sess = new ObjectSession(this);
2180
browseItemList.insert(i);
2181
i->sess->defer(this, "do_browse_error", Q_ARG(int, i->id),
2182
Q_ARG(XMPP::ServiceBrowser::Error, ServiceBrowser::ErrorNoWide));
2186
if(!global->ensure_mul())
2188
BrowseItem *i = new BrowseItem(id, 0);
2189
i->sess = new ObjectSession(this);
2190
browseItemList.insert(i);
2191
i->sess->defer(this, "do_browse_error", Q_ARG(int, i->id),
2192
Q_ARG(XMPP::ServiceBrowser::Error, ServiceBrowser::ErrorNoLocal));
2196
QByteArray type = _type.toUtf8();
2197
if(!validServiceType(type))
2199
BrowseItem *i = new BrowseItem(id, 0);
2200
i->sess = new ObjectSession(this);
2201
browseItemList.insert(i);
2202
i->sess->defer(this, "do_browse_error", Q_ARG(int, i->id),
2203
Q_ARG(XMPP::ServiceBrowser::Error, ServiceBrowser::ErrorGeneric));
2207
BrowseItem *i = new BrowseItem(id, new JDnsBrowse(global->mul, this));
2208
connect(i->browse, SIGNAL(available(const QByteArray &)), SLOT(jb_available(const QByteArray &)));
2209
connect(i->browse, SIGNAL(unavailable(const QByteArray &)), SLOT(jb_unavailable(const QByteArray &)));
2210
browseItemList.insert(i);
2211
i->browse->start(type);
2215
virtual void browse_stop(int id)
2217
BrowseItem *i = browseItemList.itemById(id);
2220
browseItemList.remove(i);
2223
virtual int resolve_start(const QByteArray &name)
2225
int id = resolveItemList.reserveId();
2227
if(!global->ensure_mul())
2229
ResolveItem *i = new ResolveItem(id, 0);
2230
i->sess = new ObjectSession(this);
2231
resolveItemList.insert(i);
2232
i->sess->defer(this, "do_resolve_error", Q_ARG(int, i->id),
2233
Q_ARG(XMPP::ServiceResolver::Error, ServiceResolver::ErrorNoLocal));
2237
ResolveItem *i = new ResolveItem(id, new JDnsServiceResolve(global->mul, this));
2238
connect(i->resolve, SIGNAL(finished()), SLOT(jr_finished()));
2239
connect(i->resolve, SIGNAL(error(JDnsSharedRequest::Error)), SLOT(jr_error(JDnsSharedRequest::Error)));
2240
resolveItemList.insert(i);
2241
i->resolve->start(name);
2245
virtual void resolve_stop(int id)
2247
ResolveItem *i = resolveItemList.itemById(id);
2250
resolveItemList.remove(i);
2253
virtual int publish_start(const QString &instance, const QString &_type, int port, const QMap<QString,QByteArray> &attributes)
2255
int id = publishItemList.reserveId();
2257
if(!global->ensure_mul())
2259
PublishItem *i = new PublishItem(id, 0);
2260
i->sess = new ObjectSession(this);
2261
publishItemList.insert(i);
2262
i->sess->defer(this, "do_publish_error", Q_ARG(int, i->id),
2263
Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorNoLocal));
2267
QByteArray type = _type.toUtf8();
2268
if(!validServiceType(type))
2270
PublishItem *i = new PublishItem(id, 0);
2271
i->sess = new ObjectSession(this);
2272
publishItemList.insert(i);
2273
i->sess->defer(this, "do_publish_error", Q_ARG(int, i->id),
2274
Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorGeneric));
2278
// make sure A/AAAA records are published
2281
pub_addresses = new JDnsPublishAddresses(global->mul, this);
2282
connect(pub_addresses, SIGNAL(hostName(const QByteArray &)), SLOT(pub_addresses_hostName(const QByteArray &)));
2283
pub_addresses->setUseIPv6(global->haveMulticast6());
2284
pub_addresses->setUseIPv4(global->haveMulticast4());
2285
pub_addresses->start();
2288
// it's okay to attempt to publish even if pub_addresses
2289
// hasn't succeeded yet. JDnsPublish is smart enough to
2290
// defer the operation until a host is acquired.
2291
PublishItem *i = new PublishItem(id, new JDnsPublish(global->mul, this));
2292
connect(i->publish, SIGNAL(published()), SLOT(jp_published()));
2293
connect(i->publish, SIGNAL(error(JDnsSharedRequest::Error)), SLOT(jp_error(JDnsSharedRequest::Error)));
2294
publishItemList.insert(i);
2295
i->publish->start(instance, type, localHost, port, attributes);
2299
virtual void publish_update(int id, const QMap<QString,QByteArray> &attributes)
2301
PublishItem *i = publishItemList.itemById(id);
2304
// if we already have an error queued, do nothing
2305
if(i->sess->isDeferred(this, "do_publish_error"))
2308
i->publish->update(attributes);
2311
virtual void publish_stop(int id)
2313
PublishItem *i = publishItemList.itemById(id);
2317
publishItemList.remove(i);
2320
virtual int publish_extra_start(int pub_id, const NameRecord &name)
2322
PublishItem *pi = publishItemList.itemById(pub_id);
2325
int id = publishItemList.reserveId();
2327
QJDns::Record rec = exportJDNSRecord(name);
2330
PublishExtraItem *i = new PublishExtraItem(id, 0);
2331
i->sess = new ObjectSession(this);
2332
publishExtraItemList.insert(i);
2333
i->sess->defer(this, "do_publish_extra_error", Q_ARG(int, i->id),
2334
Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorGeneric));
2338
// fill in owner if necessary
2339
if(rec.owner.isEmpty())
2340
rec.owner = pi->publish->fullname;
2342
// fill in the ttl if necessary
2346
PublishExtraItem *i = new PublishExtraItem(id, new JDnsPublishExtra(pi->publish));
2347
connect(i->publish, SIGNAL(published()), SLOT(jpe_published()));
2348
connect(i->publish, SIGNAL(error(JDnsSharedRequest::Error)), SLOT(jpe_error(JDnsSharedRequest::Error)));
2349
publishExtraItemList.insert(i);
2350
i->publish->start(rec);
2354
virtual void publish_extra_update(int id, const NameRecord &name)
2356
PublishExtraItem *i = publishExtraItemList.itemById(id);
2359
// if we already have an error queued, do nothing
2360
if(i->sess->isDeferred(this, "do_publish_extra_error"))
2363
QJDns::Record rec = exportJDNSRecord(name);
2366
i->sess = new ObjectSession(this);
2367
i->sess->defer(this, "do_publish_extra_error", Q_ARG(int, i->id),
2368
Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorGeneric));
2372
// fill in owner if necessary
2373
if(rec.owner.isEmpty())
2374
rec.owner = static_cast<JDnsPublish*>(i->publish->parent())->fullname;
2376
// fill in the ttl if necessary
2380
i->publish->update(rec);
2383
virtual void publish_extra_stop(int id)
2385
PublishExtraItem *i = publishExtraItemList.itemById(id);
2388
publishExtraItemList.remove(i);
2392
void cleanupExtra(PublishItem *pi)
2394
// remove all extra publishes associated with this publish.
2395
// the association can be checked via QObject parenting.
2396
QSet<PublishExtraItem*> remove;
2397
foreach(PublishExtraItem *i, publishExtraItemList.items)
2399
if(static_cast<JDnsPublish*>(i->publish->parent()) == pi->publish)
2403
foreach(PublishExtraItem *i, remove)
2404
publishExtraItemList.remove(i);
2408
void interfacesChanged()
2412
pub_addresses->setUseIPv6(global->haveMulticast6());
2413
pub_addresses->setUseIPv4(global->haveMulticast4());
2417
void jb_available(const QByteArray &instance)
2419
JDnsBrowse *jb = (JDnsBrowse *)sender();
2420
BrowseItem *i = browseItemList.itemByBrowse(jb);
2423
QByteArray name = instance + '.' + jb->typeAndDomain;
2424
ServiceInstance si(QString::fromLatin1(instance), QString::fromLatin1(jb->type), "local.", QMap<QString,QByteArray>());
2425
items.insert(name, si);
2427
emit browse_instanceAvailable(i->id, si);
2430
void jb_unavailable(const QByteArray &instance)
2432
JDnsBrowse *jb = (JDnsBrowse *)sender();
2433
BrowseItem *i = browseItemList.itemByBrowse(jb);
2436
QByteArray name = instance + '.' + jb->typeAndDomain;
2437
Q_ASSERT(items.contains(name));
2439
ServiceInstance si = items.value(name);
2442
emit browse_instanceUnavailable(i->id, si);
2445
void do_browse_error(int id, XMPP::ServiceBrowser::Error e)
2447
BrowseItem *i = browseItemList.itemById(id);
2450
browseItemList.remove(i);
2451
emit browse_error(id, e);
2456
JDnsServiceResolve *jr = (JDnsServiceResolve *)sender();
2457
ResolveItem *i = resolveItemList.itemByResolve(jr);
2460
// parse TXT list into attribute map
2461
QMap<QString,QByteArray> attribs;
2462
for(int n = 0; n < jr->attribs.count(); ++n)
2464
const QByteArray &a = jr->attribs[n];
2467
int x = a.indexOf('=');
2470
key = QString::fromLatin1(a.mid(0, x));
2471
value = a.mid(x + 1);
2475
key = QString::fromLatin1(a);
2478
attribs.insert(key, value);
2481
// one of these must be true
2482
Q_ASSERT(jr->have4 || jr->have6);
2484
QList<ResolveResult> results;
2488
r.attributes = attribs;
2489
r.address = jr->addr6;
2491
r.hostName = jr->host;
2497
r.attributes = attribs;
2498
r.address = jr->addr4;
2500
r.hostName = jr->host;
2505
resolveItemList.remove(i);
2506
emit resolve_resultsReady(id, results);
2509
void jr_error(JDnsSharedRequest::Error e)
2511
JDnsServiceResolve *jr = (JDnsServiceResolve *)sender();
2512
ResolveItem *i = resolveItemList.itemByResolve(jr);
2515
ServiceResolver::Error err;
2516
if(e == JDnsSharedRequest::ErrorTimeout)
2517
err = ServiceResolver::ErrorTimeout;
2519
err = ServiceResolver::ErrorGeneric;
2522
resolveItemList.remove(i);
2523
emit resolve_error(id, err);
2526
void do_resolve_error(int id, XMPP::ServiceResolver::Error e)
2528
ResolveItem *i = resolveItemList.itemById(id);
2531
resolveItemList.remove(i);
2532
emit resolve_error(id, e);
2535
void pub_addresses_hostName(const QByteArray &name)
2537
// tell all active publishes about the change
2538
foreach(PublishItem *item, publishItemList.items)
2539
item->publish->hostChanged(name);
2544
JDnsPublish *jp = (JDnsPublish *)sender();
2545
PublishItem *i = publishItemList.itemByPublish(jp);
2548
emit publish_published(i->id);
2551
void jp_error(JDnsSharedRequest::Error e)
2553
JDnsPublish *jp = (JDnsPublish *)sender();
2554
PublishItem *i = publishItemList.itemByPublish(jp);
2557
ServiceLocalPublisher::Error err;
2558
if(e == JDnsSharedRequest::ErrorConflict)
2559
err = ServiceLocalPublisher::ErrorConflict;
2561
err = ServiceLocalPublisher::ErrorGeneric;
2565
publishItemList.remove(i);
2566
emit publish_error(id, err);
2569
void do_publish_error(int id, XMPP::ServiceLocalPublisher::Error e)
2571
PublishItem *i = publishItemList.itemById(id);
2575
publishItemList.remove(i);
2576
emit publish_error(id, e);
2579
void jpe_published()
2581
JDnsPublishExtra *jp = (JDnsPublishExtra *)sender();
2582
PublishExtraItem *i = publishExtraItemList.itemByPublish(jp);
2585
emit publish_extra_published(i->id);
2588
void jpe_error(JDnsSharedRequest::Error e)
2590
JDnsPublishExtra *jp = (JDnsPublishExtra *)sender();
2591
PublishExtraItem *i = publishExtraItemList.itemByPublish(jp);
2594
ServiceLocalPublisher::Error err;
2595
if(e == JDnsSharedRequest::ErrorConflict)
2596
err = ServiceLocalPublisher::ErrorConflict;
2598
err = ServiceLocalPublisher::ErrorGeneric;
2601
publishExtraItemList.remove(i);
2602
emit publish_extra_error(id, err);
2605
void do_publish_extra_error(int id, XMPP::ServiceLocalPublisher::Error e)
2607
PublishExtraItem *i = publishExtraItemList.itemById(id);
2610
publishExtraItemList.remove(i);
2611
emit publish_extra_error(id, e);
2615
//----------------------------------------------------------------------------
2617
//----------------------------------------------------------------------------
2618
class JDnsProvider : public IrisNetProvider
2621
Q_INTERFACES(XMPP::IrisNetProvider)
2636
void ensure_global()
2639
global = new JDnsGlobal;
2642
virtual NameProvider *createNameProviderInternet()
2645
return JDnsNameProvider::create(global, JDnsNameProvider::Internet);
2648
virtual NameProvider *createNameProviderLocal()
2651
return JDnsNameProvider::create(global, JDnsNameProvider::Local);
2654
virtual ServiceProvider *createServiceProvider()
2657
return JDnsServiceProvider::create(global);
2661
IrisNetProvider *irisnet_createJDnsProvider()
2663
return new JDnsProvider;
2668
#include "netnames_jdns.moc"