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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
NameResolver::Error error;
463
NameResolver::Error localError;
465
Item(QObject *parent = 0) :
481
static JDnsNameProvider *create(JDnsGlobal *global, Mode mode, QObject *parent = 0)
485
if(!global->ensure_uni_net())
490
if(!global->ensure_uni_local())
494
return new JDnsNameProvider(global, mode, parent);
497
JDnsNameProvider(JDnsGlobal *_global, Mode _mode, QObject *parent = 0) :
509
Item *getItemById(int id)
511
for(int n = 0; n < items.count(); ++n)
513
if(items[n]->id == id)
520
Item *getItemByReq(JDnsSharedRequest *req)
522
for(int n = 0; n < items.count(); ++n)
524
if(items[n]->req == req)
531
void releaseItem(Item *i)
533
idman.releaseId(i->id);
538
void tryError(Item *i)
540
// if we are doing dual resolves, make sure both are done
541
if(!i->longLived && (i->req || (i->useLocal && !i->localResult)))
545
NameResolver::Error error = i->error;
547
emit resolve_error(id, error);
550
virtual bool supportsSingle() const
555
virtual bool supportsLongLived() const
558
return true; // we support long-lived local queries
560
return false; // we do NOT support long-lived internet queries
563
virtual bool supportsRecordType(int type) const
565
// all record types supported
570
virtual int resolve_start(const QByteArray &name, int qType, bool longLived)
574
bool isLocalName = false;
575
if(name.right(6) == ".local" || name.right(7) == ".local.")
578
// if query ends in .local, switch to local resolver
581
Item *i = new Item(this);
582
i->id = idman.reserveId();
583
i->longLived = longLived;
585
i->sess.defer(this, "do_local", Q_ARG(int, i->id), Q_ARG(QByteArray, name));
589
// we don't support long-lived internet queries
592
// but we do support long-lived local queries
595
Item *i = new Item(this);
596
i->id = idman.reserveId();
597
i->longLived = longLived;
600
i->sess.defer(this, "do_local", Q_ARG(int, i->id), Q_ARG(QByteArray, name));
604
Item *i = new Item(this);
605
i->id = idman.reserveId();
607
i->sess.defer(this, "do_error", Q_ARG(int, i->id),
608
Q_ARG(XMPP::NameResolver::Error, NameResolver::ErrorNoLongLived));
613
Item *i = new Item(this);
614
i->id = idman.reserveId();
615
i->req = new JDnsSharedRequest(global->uni_net);
616
connect(i->req, SIGNAL(resultsReady()), SLOT(req_resultsReady()));
618
i->longLived = false;
622
i->req->query(name, qType);
623
// if query ends in .local, simultaneously do local resolve
625
i->sess.defer(this, "do_local", Q_ARG(int, i->id), Q_ARG(QByteArray, name));
630
Item *i = new Item(this);
631
i->id = idman.reserveId();
635
if(!global->ensure_mul())
638
i->sess.defer(this, "do_error", Q_ARG(int, i->id),
639
Q_ARG(XMPP::NameResolver::Error, NameResolver::ErrorNoLocal));
643
i->req = new JDnsSharedRequest(global->mul);
648
i->req = new JDnsSharedRequest(global->uni_local);
649
i->longLived = false;
651
connect(i->req, SIGNAL(resultsReady()), SLOT(req_resultsReady()));
653
i->req->query(name, qType);
658
virtual void resolve_stop(int id)
660
Item *i = getItemById(id);
668
virtual void resolve_localResultsReady(int id, const QList<XMPP::NameRecord> &results)
670
Item *i = getItemById(id);
672
Q_ASSERT(!i->localResult);
674
i->localResult = true;
675
i->sess.defer(this, "do_local_ready", Q_ARG(int, id),
676
Q_ARG(QList<XMPP::NameRecord>, results));
679
virtual void resolve_localError(int id, XMPP::NameResolver::Error e)
681
Item *i = getItemById(id);
683
Q_ASSERT(!i->localResult);
685
i->localResult = true;
686
i->sess.defer(this, "do_local_error", Q_ARG(int, id),
687
Q_ARG(XMPP::NameResolver::Error, e));
691
void req_resultsReady()
693
JDnsSharedRequest *req = (JDnsSharedRequest *)sender();
694
Item *i = getItemByReq(req);
699
NameResolver::Error error;
703
QList<NameRecord> out;
704
foreach(const QJDns::Record &r, req->results())
706
// unless we are asking for all types, only
707
// accept the type we asked for
708
if(i->type == QJDns::Any || r.type == i->type)
710
NameRecord rec = importJDNSRecord(r);
716
// don't report anything if long-lived gives no results
717
if(i->longLived && out.isEmpty())
720
// only emit success if we have at least 1 result
723
// FIXME: need a way to cancel related local
724
// query if still active
728
emit resolve_resultsReady(id, out);
733
error = NameResolver::ErrorGeneric;
738
JDnsSharedRequest::Error e = req->error();
740
error = NameResolver::ErrorGeneric;
741
if(e == JDnsSharedRequest::ErrorNXDomain)
742
error = NameResolver::ErrorNoName;
743
else if(e == JDnsSharedRequest::ErrorTimeout)
744
error = NameResolver::ErrorTimeout;
745
else // ErrorGeneric or ErrorNoNet
746
error = NameResolver::ErrorGeneric;
755
void do_error(int id, XMPP::NameResolver::Error e)
757
Item *i = getItemById(id);
760
// note: for an internet resolve, this slot is not called in
761
// any case where a local subquery is invoked as well (yet?)
764
emit resolve_error(id, e);
767
void do_local(int id, const QByteArray &name)
769
//Item *i = getItemById(id);
772
/*// resolve_useLocal has two behaviors:
773
// - if longlived, then it indicates a hand-off
774
// - if non-longlived, then it indicates we want a subquery
778
emit resolve_useLocal(id, name);
781
void do_local_ready(int id, const QList<XMPP::NameRecord> &results)
783
Item *i = getItemById(id);
788
// stop any simultaneous internet resolve
792
// for non-longlived, we're done
796
emit resolve_resultsReady(id, results);
799
void do_local_error(int id, XMPP::NameResolver::Error e)
801
Item *i = getItemById(id);
809
//----------------------------------------------------------------------------
811
//----------------------------------------------------------------------------
812
class JDnsBrowse : public QObject
817
QByteArray type, typeAndDomain;
818
JDnsSharedRequest req;
820
JDnsBrowse(JDnsShared *_jdns, QObject *parent = 0) :
824
connect(&req, SIGNAL(resultsReady()), SLOT(jdns_resultsReady()));
827
void start(const QByteArray &_type)
830
Q_ASSERT(validServiceType(type));
831
typeAndDomain = type + ".local.";
832
req.query(typeAndDomain, QJDns::Ptr);
836
void available(const QByteArray &instance);
837
void unavailable(const QByteArray &instance);
840
QByteArray parseInstanceName(const QByteArray &name)
842
// needs to be at least X + '.' + typeAndDomain
843
if(name.length() < typeAndDomain.length() + 2)
846
// index of the '.' character
847
int at = name.length() - typeAndDomain.length() - 1;
851
if(name.mid(at + 1) != typeAndDomain)
854
QByteArray friendlyName = unescapeDomainPart(name.mid(0, at));
855
if(friendlyName.isEmpty())
862
void jdns_resultsReady()
868
QJDns::Record rec = req.results().first();
870
Q_ASSERT(rec.type == QJDns::Ptr);
872
QByteArray name = rec.name;
873
QByteArray instance = parseInstanceName(name);
874
if(instance.isEmpty())
879
emit unavailable(instance);
883
emit available(instance);
887
//----------------------------------------------------------------------------
888
// JDnsServiceResolve
889
//----------------------------------------------------------------------------
891
// 5 second timeout waiting for both A and AAAA
892
// 8 second timeout waiting for at least one record
893
class JDnsServiceResolve : public QObject
905
JDnsSharedRequest reqtxt; // for TXT
906
JDnsSharedRequest req; // for SRV/A
907
JDnsSharedRequest req6; // for AAAA
913
QList<QByteArray> attribs;
917
QHostAddress addr4, addr6;
919
JDnsServiceResolve(JDnsShared *_jdns, QObject *parent = 0) :
925
connect(&reqtxt, SIGNAL(resultsReady()), SLOT(reqtxt_ready()));
926
connect(&req, SIGNAL(resultsReady()), SLOT(req_ready()));
927
connect(&req6, SIGNAL(resultsReady()), SLOT(req6_ready()));
929
opTimer = new QTimer(this);
930
connect(opTimer, SIGNAL(timeout()), SLOT(op_timeout()));
931
opTimer->setSingleShot(true);
934
~JDnsServiceResolve()
936
opTimer->disconnect(this);
937
opTimer->setParent(0);
938
opTimer->deleteLater();
941
void start(const QByteArray name)
948
opTimer->start(8000);
950
reqtxt.query(name, QJDns::Txt);
951
req.query(name, QJDns::Srv);
956
void error(JDnsSharedRequest::Error e);
961
if(opTimer->isActive())
965
if(srvState == Srv || !have4)
967
if(srvState >= AddressWait && !have6)
973
// we're done when we have txt and addresses
974
if(haveTxt && ( (have4 && have6) || (srvState == AddressFirstCome && (have4 || have6)) ))
987
if(!reqtxt.success())
990
emit error(reqtxt.error());
994
QJDns::Record rec = reqtxt.results().first();
997
Q_ASSERT(rec.type == QJDns::Txt);
1000
if(!rec.texts.isEmpty())
1002
// if there is only 1 text, it needs to be
1003
// non-empty for us to care
1004
if(rec.texts.count() != 1 || !rec.texts[0].isEmpty())
1005
attribs = rec.texts;
1018
emit error(req.error());
1022
QJDns::Record rec = req.results().first();
1027
// in Srv state, req is used for SRV records
1029
Q_ASSERT(rec.type == QJDns::Srv);
1034
srvState = AddressWait;
1035
opTimer->start(5000);
1037
req.query(host, QJDns::A);
1038
req6.query(host, QJDns::Aaaa);
1042
// in the other states, req is used for A records
1044
Q_ASSERT(rec.type == QJDns::A);
1046
addr4 = rec.address;
1058
emit error(req6.error());
1062
QJDns::Record rec = req6.results().first();
1065
Q_ASSERT(rec.type == QJDns::Aaaa);
1067
addr6 = rec.address;
1077
// timeout getting SRV. it is possible that we could
1078
// have obtained the TXT record, but if SRV times
1079
// out then we consider the whole job to have
1082
emit error(JDnsSharedRequest::ErrorTimeout);
1084
else if(srvState == AddressWait)
1086
// timeout while waiting for both A and AAAA. we now
1087
// switch to the AddressFirstCome state, where an
1088
// answer for either will do
1090
srvState = AddressFirstCome;
1092
// if we have at least one of these, we're done
1095
// well, almost. we might still be waiting
1096
// for the TXT record
1101
// if we are here, then it means we are missing TXT
1102
// still, or we have neither A nor AAAA.
1104
// wait 3 more seconds
1105
opTimer->start(3000);
1107
else // AddressFirstCome
1113
emit error(JDnsSharedRequest::ErrorTimeout);
1119
//----------------------------------------------------------------------------
1120
// JDnsPublishAddresses
1121
//----------------------------------------------------------------------------
1123
// helper class for JDnsPublishAddresses. publishes A+PTR or AAAA+PTR pair.
1124
class JDnsPublishAddress : public QObject
1137
JDnsSharedRequest pub_addr;
1138
JDnsSharedRequest pub_ptr;
1141
JDnsPublishAddress(JDnsShared *_jdns, QObject *parent = 0) :
1143
pub_addr(_jdns, this),
1144
pub_ptr(_jdns, this)
1146
connect(&pub_addr, SIGNAL(resultsReady()), SLOT(pub_addr_ready()));
1147
connect(&pub_ptr, SIGNAL(resultsReady()), SLOT(pub_ptr_ready()));
1150
void start(Type _type, const QByteArray &_host)
1158
rec.type = QJDns::Aaaa;
1160
rec.type = QJDns::A;
1163
rec.haveKnown = true;
1164
rec.address = QHostAddress(); // null address, will be filled in
1165
pub_addr.publish(QJDns::Unique, rec);
1174
bool success() const
1180
void resultsReady();
1183
void pub_addr_ready()
1185
if(pub_addr.success())
1188
rec.type = QJDns::Ptr;
1190
rec.owner = ".ip6.arpa.";
1192
rec.owner = ".in-addr.arpa.";
1194
rec.haveKnown = true;
1196
pub_ptr.publish(QJDns::Shared, rec);
1200
pub_ptr.cancel(); // needed if addr fails during or after ptr
1202
emit resultsReady();
1206
void pub_ptr_ready()
1208
if(pub_ptr.success())
1218
emit resultsReady();
1222
// This class publishes A/AAAA records for the machine, using a derived
1223
// hostname (it will use QHostInfo::localHostName(), but append a unique
1224
// suffix if necessary). If there is ever a record conflict, it will
1225
// republish under a unique name.
1227
// The hostName() signal is emitted when a hostname is successfully
1228
// published as. When there is a conflict, hostName() is emitted with
1229
// an empty value, and will again be emitted with a non-empty value
1230
// once the conflict is resolved. A missing hostname is considered a
1231
// temporary problem, and so other publish operations that depend on a
1232
// hostname (SRV, etc) should block until a hostname is available.
1233
class JDnsPublishAddresses : public QObject
1240
JDnsPublishAddress pub6;
1241
JDnsPublishAddress pub4;
1248
JDnsPublishAddresses(JDnsShared *_jdns, QObject *parent = 0) :
1257
connect(&pub6, SIGNAL(resultsReady()), SLOT(pub6_ready()));
1258
connect(&pub4, SIGNAL(resultsReady()), SLOT(pub4_ready()));
1271
bool isStarted() const
1276
// comments in this method apply to setUseIPv4 as well.
1277
void setUseIPv6(bool b)
1286
// a "deferred call to doDisable" and "publish operations"
1287
// are mutually exclusive. thus, a deferred call is only
1288
// invoked when both publishes are canceled, and the
1289
// deferred call is canceled if any of the publishes are
1296
// if the other is already active, then
1297
// just activate this one without
1298
// recomputing the hostname
1305
// otherwise, recompute the hostname
1314
sess.defer(this, "doDisable");
1318
void setUseIPv4(bool b)
1344
sess.defer(this, "doDisable");
1349
void hostName(const QByteArray &str);
1354
QString me = QHostInfo::localHostName();
1356
// some hosts may already have ".local" in their name
1357
if(me.endsWith(".local"))
1358
me.truncate(me.length() - 6);
1360
// prefix our hostname so we don't conflict with a system
1362
me.prepend("jdns-");
1365
me += QString("-%1").arg(counter);
1367
host = escapeDomainPart(me.toUtf8()) + ".local.";
1377
pub6.start(JDnsPublishAddress::IPv6, host);
1382
pub4.start(JDnsPublishAddress::IPv4, host);
1396
emit hostName(host);
1402
// we get here if we fail to publish at all, or if we
1403
// successfully publish but then fail later on. in the
1404
// latter case it means we "lost" our host records.
1406
bool lostHost = success; // as in earlier publish success
1409
// if we lost a hostname with a suffix, or counter is
1410
// at 99, then start counter over at 1 (no suffix).
1411
if((lostHost && counter > 1) || counter >= 99)
1418
// only emit lost host signal once
1420
emit hostName(QByteArray());
1426
bool lostHost = success;
1430
emit hostName(QByteArray());
1466
//----------------------------------------------------------------------------
1468
//----------------------------------------------------------------------------
1471
class JDnsPublishExtra : public QObject
1476
JDnsPublishExtra(JDnsPublish *_jdnsPub);
1477
~JDnsPublishExtra();
1479
void start(const QJDns::Record &_rec);
1480
void update(const QJDns::Record &_rec);
1484
void error(JDnsSharedRequest::Error e);
1487
friend class JDnsPublish;
1489
JDnsPublish *jdnsPub;
1491
JDnsSharedRequest pub;
1497
// This class publishes SRV/TXT/PTR for a service. if a hostName is not
1498
// is not available (see JDnsPublishAddresses) then the publish action
1499
// will be deferred until one is available. SRV and TXT are published
1500
// as unique records, and once they both succeed then the PTR record
1501
// is published. once the PTR succeeds, then published() is emitted.
1502
// if a conflict occurs with any action, then the whole thing fails and
1503
// error() is emitted. if, at any time, the hostName is lost, then
1504
// then the SRV operation is canceled, but no error is emitted. when the
1505
// hostName is regained, then the SRV record is republished.
1507
// It's important to note that published() is only emitted once ever, even
1508
// if a hostName change causes a republishing. this way, hostName changes
1509
// are completely transparent.
1510
class JDnsPublish : public QObject
1516
JDnsSharedRequest pub_srv;
1517
JDnsSharedRequest pub_txt;
1518
JDnsSharedRequest pub_ptr;
1520
bool have_srv, have_txt, have_ptr;
1521
bool need_update_txt;
1523
QByteArray fullname;
1524
QByteArray instance;
1528
QList<QByteArray> attribs;
1530
QSet<JDnsPublishExtra*> extraList;
1532
JDnsPublish(JDnsShared *_jdns, QObject *parent = 0) :
1535
pub_srv(_jdns, this),
1536
pub_txt(_jdns, this),
1537
pub_ptr(_jdns, this)
1539
connect(&pub_srv, SIGNAL(resultsReady()), SLOT(pub_srv_ready()));
1540
connect(&pub_txt, SIGNAL(resultsReady()), SLOT(pub_txt_ready()));
1541
connect(&pub_ptr, SIGNAL(resultsReady()), SLOT(pub_ptr_ready()));
1546
qDeleteAll(extraList);
1549
void start(const QString &_instance, const QByteArray &_type, const QByteArray &localHost, int _port, const QMap<QString,QByteArray> &attributes)
1552
Q_ASSERT(validServiceType(type));
1554
instance = escapeDomainPart(_instance.toUtf8());
1555
fullname = instance + '.' + type + ".local.";
1558
attribs = makeTxtList(attributes);
1563
need_update_txt = false;
1565
// no host? defer publishing till we have one
1572
void update(const QMap<QString,QByteArray> &attributes)
1574
attribs = makeTxtList(attributes);
1576
// still publishing the initial txt?
1579
// flag that we want to update once the publish
1581
need_update_txt = true;
1585
// no SRV, but have TXT? this means we lost SRV due to
1586
// a hostname change.
1589
// in that case, revoke the TXT. it'll get
1590
// republished after SRV then.
1600
// pass empty host if host lost
1601
void hostChanged(const QByteArray &_host)
1603
bool changed = (host != _host);
1611
// cancel srv record momentarily
1617
// we now have a host, publish
1625
void error(JDnsSharedRequest::Error e);
1628
friend class JDnsPublishExtra;
1630
static QList<QByteArray> makeTxtList(const QMap<QString,QByteArray> &attributes)
1632
QList<QByteArray> out;
1634
QMapIterator<QString,QByteArray> it(attributes);
1638
out += it.key().toLatin1() + '=' + it.value();
1641
out += QByteArray();
1650
rec.type = QJDns::Srv;
1651
rec.owner = fullname;
1653
rec.haveKnown = true;
1658
pub_srv.publish(QJDns::Unique, rec);
1660
// if we're just republishing SRV after losing/regaining
1661
// our hostname, then TXT is already published
1665
// publish extra records as needed
1666
foreach(JDnsPublishExtra *extra, extraList)
1669
doPublishExtra(extra);
1677
rec.type = QJDns::Txt;
1678
rec.owner = fullname;
1680
rec.haveKnown = true;
1681
rec.texts = attribs;
1684
pub_txt.publish(QJDns::Unique, rec);
1686
pub_txt.publishUpdate(rec);
1691
if(have_srv && have_txt)
1695
rec.type = QJDns::Ptr;
1696
rec.owner = type + ".local.";
1698
rec.haveKnown = true;
1699
rec.name = fullname;
1700
pub_ptr.publish(QJDns::Shared, rec);
1706
foreach(JDnsPublishExtra *extra, extraList)
1707
cleanupExtra(extra);
1708
qDeleteAll(extraList);
1719
void publishExtra(JDnsPublishExtra *extra)
1721
Q_ASSERT(!extraList.contains(extra));
1723
connect(&extra->pub, SIGNAL(resultsReady()), SLOT(pub_extra_ready()));
1726
// defer publishing until SRV is ready
1730
doPublishExtra(extra);
1733
void publishExtraUpdate(JDnsPublishExtra *extra)
1737
extra->need_update = true;
1743
extra->have = false;
1744
extra->pub.cancel();
1748
doPublishExtra(extra);
1751
void unpublishExtra(JDnsPublishExtra *extra)
1753
extraList.remove(extra);
1756
void doPublishExtra(JDnsPublishExtra *extra)
1759
extra->pub.publish(QJDns::Unique, extra->rec);
1761
extra->pub.publishUpdate(extra->rec);
1764
void cleanupExtra(JDnsPublishExtra *extra)
1766
extra->pub.cancel();
1767
extra->disconnect(this);
1768
extra->started = false;
1769
extra->have = false;
1773
void pub_srv_ready()
1775
if(pub_srv.success())
1782
JDnsSharedRequest::Error e = pub_srv.error();
1788
void pub_txt_ready()
1790
if(pub_txt.success())
1796
need_update_txt = false;
1804
JDnsSharedRequest::Error e = pub_txt.error();
1810
void pub_ptr_ready()
1812
if(pub_ptr.success())
1819
JDnsSharedRequest::Error e = pub_ptr.error();
1825
void pub_extra_ready()
1827
JDnsSharedRequest *req = (JDnsSharedRequest *)sender();
1828
JDnsPublishExtra *extra = 0;
1829
foreach(JDnsPublishExtra *e, extraList)
1839
if(extra->pub.success())
1843
if(extra->need_update)
1845
extra->need_update = false;
1846
doPublishExtra(extra);
1849
emit extra->published();
1853
JDnsSharedRequest::Error e = extra->pub.error();
1854
cleanupExtra(extra);
1855
emit extra->error(e);
1860
JDnsPublishExtra::JDnsPublishExtra(JDnsPublish *_jdnsPub) :
1864
pub(_jdnsPub->jdns, this)
1868
JDnsPublishExtra::~JDnsPublishExtra()
1871
jdnsPub->unpublishExtra(this);
1874
void JDnsPublishExtra::start(const QJDns::Record &_rec)
1879
need_update = false;
1880
jdnsPub->publishExtra(this);
1883
void JDnsPublishExtra::update(const QJDns::Record &_rec)
1886
jdnsPub->publishExtraUpdate(this);
1889
//----------------------------------------------------------------------------
1890
// JDnsServiceProvider
1891
//----------------------------------------------------------------------------
1896
JDnsBrowse * const browse;
1897
ObjectSession *sess;
1899
BrowseItem(int _id, JDnsBrowse *_browse) :
1913
class BrowseItemList
1916
QSet<BrowseItem*> items;
1917
QHash<int,BrowseItem*> indexById;
1918
QHash<JDnsBrowse*,BrowseItem*> indexByBrowse;
1929
return idman.reserveId();
1932
void insert(BrowseItem *item)
1935
indexById.insert(item->id, item);
1936
indexByBrowse.insert(item->browse, item);
1939
void remove(BrowseItem *item)
1941
indexById.remove(item->id);
1942
indexByBrowse.remove(item->browse);
1945
idman.releaseId(item->id);
1949
BrowseItem *itemById(int id) const
1951
return indexById.value(id);
1954
BrowseItem *itemByBrowse(JDnsBrowse *browse) const
1956
return indexByBrowse.value(browse);
1964
JDnsServiceResolve * const resolve;
1965
ObjectSession *sess;
1967
ResolveItem(int _id, JDnsServiceResolve *_resolve) :
1981
class ResolveItemList
1984
QSet<ResolveItem*> items;
1985
QHash<int,ResolveItem*> indexById;
1986
QHash<JDnsServiceResolve*,ResolveItem*> indexByResolve;
1997
return idman.reserveId();
2000
void insert(ResolveItem *item)
2003
indexById.insert(item->id, item);
2004
indexByResolve.insert(item->resolve, item);
2007
void remove(ResolveItem *item)
2009
indexById.remove(item->id);
2010
indexByResolve.remove(item->resolve);
2013
idman.releaseId(item->id);
2017
ResolveItem *itemById(int id) const
2019
return indexById.value(id);
2022
ResolveItem *itemByResolve(JDnsServiceResolve *resolve) const
2024
return indexByResolve.value(resolve);
2032
JDnsPublish * const publish;
2033
ObjectSession *sess;
2035
PublishItem(int _id, JDnsPublish *_publish) :
2049
class PublishItemList
2052
QSet<PublishItem*> items;
2055
QHash<int,PublishItem*> indexById;
2056
QHash<JDnsPublish*,PublishItem*> indexByPublish;
2067
return idman.reserveId();
2070
void insert(PublishItem *item)
2073
indexById.insert(item->id, item);
2074
indexByPublish.insert(item->publish, item);
2077
void remove(PublishItem *item)
2079
indexById.remove(item->id);
2080
indexByPublish.remove(item->publish);
2083
idman.releaseId(item->id);
2087
PublishItem *itemById(int id) const
2089
return indexById.value(id);
2092
PublishItem *itemByPublish(JDnsPublish *publish) const
2094
return indexByPublish.value(publish);
2098
class PublishExtraItem
2102
JDnsPublishExtra * const publish;
2103
ObjectSession *sess;
2105
PublishExtraItem(int _id, JDnsPublishExtra *_publish) :
2119
class PublishExtraItemList
2122
QSet<PublishExtraItem*> items;
2125
QHash<int,PublishExtraItem*> indexById;
2126
QHash<JDnsPublishExtra*,PublishExtraItem*> indexByPublish;
2130
~PublishExtraItemList()
2140
indexByPublish.clear();
2146
return idman.reserveId();
2149
void insert(PublishExtraItem *item)
2152
indexById.insert(item->id, item);
2153
indexByPublish.insert(item->publish, item);
2156
void remove(PublishExtraItem *item)
2158
indexById.remove(item->id);
2159
indexByPublish.remove(item->publish);
2162
idman.releaseId(item->id);
2166
PublishExtraItem *itemById(int id) const
2168
return indexById.value(id);
2171
PublishExtraItem *itemByPublish(JDnsPublishExtra *publish) const
2173
return indexByPublish.value(publish);
2177
class JDnsServiceProvider : public ServiceProvider
2185
BrowseItemList browseItemList;
2186
QHash<QByteArray,ServiceInstance> items;
2189
ResolveItemList resolveItemList;
2192
JDnsPublishAddresses *pub_addresses;
2193
QByteArray localHost;
2194
PublishItemList publishItemList;
2195
PublishExtraItemList publishExtraItemList;
2197
static JDnsServiceProvider *create(JDnsGlobal *global, QObject *parent = 0)
2199
return new JDnsServiceProvider(global, parent);
2202
JDnsServiceProvider(JDnsGlobal *_global, QObject *parent = 0) :
2203
ServiceProvider(parent),
2207
connect(global, SIGNAL(interfacesChanged()), SLOT(interfacesChanged()));
2210
~JDnsServiceProvider()
2212
// make sure extra items are deleted before normal ones
2213
publishExtraItemList.clear();
2216
virtual int browse_start(const QString &_type, const QString &_domain)
2219
if(_domain.isEmpty() || _domain == ".")
2224
if(domain[domain.length() - 1] != '.')
2227
Q_ASSERT(domain.length() >= 2 && domain[domain.length() - 1] == '.');
2229
int id = browseItemList.reserveId();
2231
// no support for non-local domains
2232
if(domain != "local.")
2234
BrowseItem *i = new BrowseItem(id, 0);
2235
i->sess = new ObjectSession(this);
2236
browseItemList.insert(i);
2237
i->sess->defer(this, "do_browse_error", Q_ARG(int, i->id),
2238
Q_ARG(XMPP::ServiceBrowser::Error, ServiceBrowser::ErrorNoWide));
2242
if(!global->ensure_mul())
2244
BrowseItem *i = new BrowseItem(id, 0);
2245
i->sess = new ObjectSession(this);
2246
browseItemList.insert(i);
2247
i->sess->defer(this, "do_browse_error", Q_ARG(int, i->id),
2248
Q_ARG(XMPP::ServiceBrowser::Error, ServiceBrowser::ErrorNoLocal));
2252
QByteArray type = _type.toUtf8();
2253
if(!validServiceType(type))
2255
BrowseItem *i = new BrowseItem(id, 0);
2256
i->sess = new ObjectSession(this);
2257
browseItemList.insert(i);
2258
i->sess->defer(this, "do_browse_error", Q_ARG(int, i->id),
2259
Q_ARG(XMPP::ServiceBrowser::Error, ServiceBrowser::ErrorGeneric));
2263
BrowseItem *i = new BrowseItem(id, new JDnsBrowse(global->mul, this));
2264
connect(i->browse, SIGNAL(available(const QByteArray &)), SLOT(jb_available(const QByteArray &)));
2265
connect(i->browse, SIGNAL(unavailable(const QByteArray &)), SLOT(jb_unavailable(const QByteArray &)));
2266
browseItemList.insert(i);
2267
i->browse->start(type);
2271
virtual void browse_stop(int id)
2273
BrowseItem *i = browseItemList.itemById(id);
2276
browseItemList.remove(i);
2279
virtual int resolve_start(const QByteArray &name)
2281
int id = resolveItemList.reserveId();
2283
if(!global->ensure_mul())
2285
ResolveItem *i = new ResolveItem(id, 0);
2286
i->sess = new ObjectSession(this);
2287
resolveItemList.insert(i);
2288
i->sess->defer(this, "do_resolve_error", Q_ARG(int, i->id),
2289
Q_ARG(XMPP::ServiceResolver::Error, ServiceResolver::ErrorNoLocal));
2293
ResolveItem *i = new ResolveItem(id, new JDnsServiceResolve(global->mul, this));
2294
connect(i->resolve, SIGNAL(finished()), SLOT(jr_finished()));
2295
connect(i->resolve, SIGNAL(error(JDnsSharedRequest::Error)), SLOT(jr_error(JDnsSharedRequest::Error)));
2296
resolveItemList.insert(i);
2297
i->resolve->start(name);
2301
virtual void resolve_stop(int id)
2303
ResolveItem *i = resolveItemList.itemById(id);
2306
resolveItemList.remove(i);
2309
virtual int publish_start(const QString &instance, const QString &_type, int port, const QMap<QString,QByteArray> &attributes)
2311
int id = publishItemList.reserveId();
2313
if(!global->ensure_mul())
2315
PublishItem *i = new PublishItem(id, 0);
2316
i->sess = new ObjectSession(this);
2317
publishItemList.insert(i);
2318
i->sess->defer(this, "do_publish_error", Q_ARG(int, i->id),
2319
Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorNoLocal));
2323
QByteArray type = _type.toUtf8();
2324
if(!validServiceType(type))
2326
PublishItem *i = new PublishItem(id, 0);
2327
i->sess = new ObjectSession(this);
2328
publishItemList.insert(i);
2329
i->sess->defer(this, "do_publish_error", Q_ARG(int, i->id),
2330
Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorGeneric));
2334
// make sure A/AAAA records are published
2337
pub_addresses = new JDnsPublishAddresses(global->mul, this);
2338
connect(pub_addresses, SIGNAL(hostName(const QByteArray &)), SLOT(pub_addresses_hostName(const QByteArray &)));
2339
pub_addresses->setUseIPv6(global->haveMulticast6());
2340
pub_addresses->setUseIPv4(global->haveMulticast4());
2341
pub_addresses->start();
2344
// it's okay to attempt to publish even if pub_addresses
2345
// hasn't succeeded yet. JDnsPublish is smart enough to
2346
// defer the operation until a host is acquired.
2347
PublishItem *i = new PublishItem(id, new JDnsPublish(global->mul, this));
2348
connect(i->publish, SIGNAL(published()), SLOT(jp_published()));
2349
connect(i->publish, SIGNAL(error(JDnsSharedRequest::Error)), SLOT(jp_error(JDnsSharedRequest::Error)));
2350
publishItemList.insert(i);
2351
i->publish->start(instance, type, localHost, port, attributes);
2355
virtual void publish_update(int id, const QMap<QString,QByteArray> &attributes)
2357
PublishItem *i = publishItemList.itemById(id);
2360
// if we already have an error queued, do nothing
2361
if(i->sess->isDeferred(this, "do_publish_error"))
2364
i->publish->update(attributes);
2367
virtual void publish_stop(int id)
2369
PublishItem *i = publishItemList.itemById(id);
2373
publishItemList.remove(i);
2376
virtual int publish_extra_start(int pub_id, const NameRecord &name)
2378
PublishItem *pi = publishItemList.itemById(pub_id);
2381
int id = publishItemList.reserveId();
2383
QJDns::Record rec = exportJDNSRecord(name);
2386
PublishExtraItem *i = new PublishExtraItem(id, 0);
2387
i->sess = new ObjectSession(this);
2388
publishExtraItemList.insert(i);
2389
i->sess->defer(this, "do_publish_extra_error", Q_ARG(int, i->id),
2390
Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorGeneric));
2394
// fill in owner if necessary
2395
if(rec.owner.isEmpty())
2396
rec.owner = pi->publish->fullname;
2398
// fill in the ttl if necessary
2402
PublishExtraItem *i = new PublishExtraItem(id, new JDnsPublishExtra(pi->publish));
2403
connect(i->publish, SIGNAL(published()), SLOT(jpe_published()));
2404
connect(i->publish, SIGNAL(error(JDnsSharedRequest::Error)), SLOT(jpe_error(JDnsSharedRequest::Error)));
2405
publishExtraItemList.insert(i);
2406
i->publish->start(rec);
2410
virtual void publish_extra_update(int id, const NameRecord &name)
2412
PublishExtraItem *i = publishExtraItemList.itemById(id);
2415
// if we already have an error queued, do nothing
2416
if(i->sess->isDeferred(this, "do_publish_extra_error"))
2419
QJDns::Record rec = exportJDNSRecord(name);
2422
i->sess = new ObjectSession(this);
2423
i->sess->defer(this, "do_publish_extra_error", Q_ARG(int, i->id),
2424
Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorGeneric));
2428
// fill in owner if necessary
2429
if(rec.owner.isEmpty())
2430
rec.owner = static_cast<JDnsPublish*>(i->publish->parent())->fullname;
2432
// fill in the ttl if necessary
2436
i->publish->update(rec);
2439
virtual void publish_extra_stop(int id)
2441
PublishExtraItem *i = publishExtraItemList.itemById(id);
2444
publishExtraItemList.remove(i);
2448
void cleanupExtra(PublishItem *pi)
2450
// remove all extra publishes associated with this publish.
2451
// the association can be checked via QObject parenting.
2452
QSet<PublishExtraItem*> remove;
2453
foreach(PublishExtraItem *i, publishExtraItemList.items)
2455
if(static_cast<JDnsPublish*>(i->publish->parent()) == pi->publish)
2459
foreach(PublishExtraItem *i, remove)
2460
publishExtraItemList.remove(i);
2464
void interfacesChanged()
2468
pub_addresses->setUseIPv6(global->haveMulticast6());
2469
pub_addresses->setUseIPv4(global->haveMulticast4());
2473
void jb_available(const QByteArray &instance)
2475
JDnsBrowse *jb = (JDnsBrowse *)sender();
2476
BrowseItem *i = browseItemList.itemByBrowse(jb);
2479
QByteArray name = instance + '.' + jb->typeAndDomain;
2480
ServiceInstance si(QString::fromLatin1(instance), QString::fromLatin1(jb->type), "local.", QMap<QString,QByteArray>());
2481
items.insert(name, si);
2483
emit browse_instanceAvailable(i->id, si);
2486
void jb_unavailable(const QByteArray &instance)
2488
JDnsBrowse *jb = (JDnsBrowse *)sender();
2489
BrowseItem *i = browseItemList.itemByBrowse(jb);
2492
QByteArray name = instance + '.' + jb->typeAndDomain;
2493
Q_ASSERT(items.contains(name));
2495
ServiceInstance si = items.value(name);
2498
emit browse_instanceUnavailable(i->id, si);
2501
void do_browse_error(int id, XMPP::ServiceBrowser::Error e)
2503
BrowseItem *i = browseItemList.itemById(id);
2506
browseItemList.remove(i);
2507
emit browse_error(id, e);
2512
JDnsServiceResolve *jr = (JDnsServiceResolve *)sender();
2513
ResolveItem *i = resolveItemList.itemByResolve(jr);
2516
// parse TXT list into attribute map
2517
QMap<QString,QByteArray> attribs;
2518
for(int n = 0; n < jr->attribs.count(); ++n)
2520
const QByteArray &a = jr->attribs[n];
2523
int x = a.indexOf('=');
2526
key = QString::fromLatin1(a.mid(0, x));
2527
value = a.mid(x + 1);
2531
key = QString::fromLatin1(a);
2534
attribs.insert(key, value);
2537
// one of these must be true
2538
Q_ASSERT(jr->have4 || jr->have6);
2540
QList<ResolveResult> results;
2544
r.attributes = attribs;
2545
r.address = jr->addr6;
2547
r.hostName = jr->host;
2553
r.attributes = attribs;
2554
r.address = jr->addr4;
2556
r.hostName = jr->host;
2561
resolveItemList.remove(i);
2562
emit resolve_resultsReady(id, results);
2565
void jr_error(JDnsSharedRequest::Error e)
2567
JDnsServiceResolve *jr = (JDnsServiceResolve *)sender();
2568
ResolveItem *i = resolveItemList.itemByResolve(jr);
2571
ServiceResolver::Error err;
2572
if(e == JDnsSharedRequest::ErrorTimeout)
2573
err = ServiceResolver::ErrorTimeout;
2575
err = ServiceResolver::ErrorGeneric;
2578
resolveItemList.remove(i);
2579
emit resolve_error(id, err);
2582
void do_resolve_error(int id, XMPP::ServiceResolver::Error e)
2584
ResolveItem *i = resolveItemList.itemById(id);
2587
resolveItemList.remove(i);
2588
emit resolve_error(id, e);
2591
void pub_addresses_hostName(const QByteArray &name)
2593
// tell all active publishes about the change
2594
foreach(PublishItem *item, publishItemList.items)
2595
item->publish->hostChanged(name);
2600
JDnsPublish *jp = (JDnsPublish *)sender();
2601
PublishItem *i = publishItemList.itemByPublish(jp);
2604
emit publish_published(i->id);
2607
void jp_error(JDnsSharedRequest::Error e)
2609
JDnsPublish *jp = (JDnsPublish *)sender();
2610
PublishItem *i = publishItemList.itemByPublish(jp);
2613
ServiceLocalPublisher::Error err;
2614
if(e == JDnsSharedRequest::ErrorConflict)
2615
err = ServiceLocalPublisher::ErrorConflict;
2617
err = ServiceLocalPublisher::ErrorGeneric;
2621
publishItemList.remove(i);
2622
emit publish_error(id, err);
2625
void do_publish_error(int id, XMPP::ServiceLocalPublisher::Error e)
2627
PublishItem *i = publishItemList.itemById(id);
2631
publishItemList.remove(i);
2632
emit publish_error(id, e);
2635
void jpe_published()
2637
JDnsPublishExtra *jp = (JDnsPublishExtra *)sender();
2638
PublishExtraItem *i = publishExtraItemList.itemByPublish(jp);
2641
emit publish_extra_published(i->id);
2644
void jpe_error(JDnsSharedRequest::Error e)
2646
JDnsPublishExtra *jp = (JDnsPublishExtra *)sender();
2647
PublishExtraItem *i = publishExtraItemList.itemByPublish(jp);
2650
ServiceLocalPublisher::Error err;
2651
if(e == JDnsSharedRequest::ErrorConflict)
2652
err = ServiceLocalPublisher::ErrorConflict;
2654
err = ServiceLocalPublisher::ErrorGeneric;
2657
publishExtraItemList.remove(i);
2658
emit publish_extra_error(id, err);
2661
void do_publish_extra_error(int id, XMPP::ServiceLocalPublisher::Error e)
2663
PublishExtraItem *i = publishExtraItemList.itemById(id);
2666
publishExtraItemList.remove(i);
2667
emit publish_extra_error(id, e);
2671
//----------------------------------------------------------------------------
2673
//----------------------------------------------------------------------------
2674
class JDnsProvider : public IrisNetProvider
2677
Q_INTERFACES(XMPP::IrisNetProvider)
2692
void ensure_global()
2695
global = new JDnsGlobal;
2698
virtual NameProvider *createNameProviderInternet()
2701
return JDnsNameProvider::create(global, JDnsNameProvider::Internet);
2704
virtual NameProvider *createNameProviderLocal()
2707
return JDnsNameProvider::create(global, JDnsNameProvider::Local);
2710
virtual ServiceProvider *createServiceProvider()
2713
return JDnsServiceProvider::create(global);
2717
IrisNetProvider *irisnet_createJDnsProvider()
2719
return new JDnsProvider;
2724
#include "netnames_jdns.moc"