/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "servicebrowser.h" #include "servicebrowser_p.h" #include #include #include #include #include #ifdef Q_OS_LINUX #define AVAHI_LIB #endif #ifdef AVAHI_LIB #include #include #include #include #include #include #include namespace ZeroConf { namespace Internal { extern "C" void cAvahiResolveReply( AvahiServiceResolver * r, AvahiIfIndex interface, AvahiProtocol /*protocol*/, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *hostName, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags /*flags*/, void* context); extern "C" void cAvahiResolveEmptyReply( AvahiServiceResolver * r, AvahiIfIndex, AvahiProtocol, AvahiResolverEvent, const char *, const char *, const char *, const char *, const AvahiAddress *, uint16_t, AvahiStringList *, AvahiLookupResultFlags, void*); extern "C" void cAvahiClientReply (AvahiClient * /*s*/, AvahiClientState state, void* context); extern "C" void cAvahiBrowseReply( AvahiServiceBrowser * /*b*/, AvahiIfIndex interface, AvahiProtocol /*protocol*/, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags /*flags*/, void* context); extern "C" int cAvahiPollFunction(struct pollfd *ufds, unsigned int nfds, int timeout, void *userdata); extern "C" { typedef const AvahiPoll* (*AvahiSimplePollGet)(AvahiSimplePoll *s); typedef AvahiSimplePoll *(*AvahiSimplePollNewPtr)(void); typedef int (*AvahiSimplePollIteratePtr)(AvahiSimplePoll *s, int sleep_time); typedef void (*AvahiSimplePollQuitPtr)(AvahiSimplePoll *s); typedef void (*AvahiSimplePollFreePtr)(AvahiSimplePoll *s); typedef void (*AvahiSimplePollSetFuncPtr)(AvahiSimplePoll *s, AvahiPollFunc func, void *userdata); typedef AvahiClient* (*AvahiClientNewPtr)( const AvahiPoll *poll_api, AvahiClientFlags flags, AvahiClientCallback callback, void *userdata, int *error); typedef void (*AvahiClientFreePtr)(AvahiClient *client); typedef AvahiServiceBrowser* (*AvahiServiceBrowserNewPtr)( AvahiClient *client, AvahiIfIndex interface, AvahiProtocol protocol, const char *type, const char *domain, AvahiLookupFlags flags, AvahiServiceBrowserCallback callback, void *userdata); typedef int (*AvahiServiceBrowserFreePtr)(AvahiServiceBrowser *); typedef AvahiServiceResolver * (*AvahiServiceResolverNewPtr)( AvahiClient *client, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain, AvahiProtocol aprotocol, AvahiLookupFlags flags, AvahiServiceResolverCallback callback, void *userdata); typedef int (*AvahiServiceResolverFreePtr)(AvahiServiceResolver *r); } class AvahiZConfLib; struct MyAvahiConnection { AvahiClient *client; AvahiSimplePoll *simple_poll; AvahiZConfLib *lib; MyAvahiConnection() : client(0), simple_poll(0), lib(0) { } }; // represents an avahi library class AvahiZConfLib : public ZConfLib { private: AvahiSimplePollGet m_simplePollGet; AvahiSimplePollNewPtr m_simplePollNew; AvahiSimplePollIteratePtr m_simplePollIterate; AvahiSimplePollQuitPtr m_simplePollQuit; AvahiSimplePollFreePtr m_simplePollFree; AvahiSimplePollSetFuncPtr m_simplePollSetFunc; AvahiClientNewPtr m_clientNew; AvahiClientFreePtr m_clientFree; AvahiServiceBrowserNewPtr m_serviceBrowserNew; AvahiServiceBrowserFreePtr m_serviceBrowserFree; AvahiServiceResolverNewPtr m_serviceResolverNew; AvahiServiceResolverFreePtr m_serviceResolverFree; QLibrary nativeLib; public: AvahiZConfLib(const QString &libName = QLatin1String("avahi-client"), const QString &version = QLatin1String("3"), const ZConfLib::Ptr &fallBack = ZConfLib::Ptr(0)) : ZConfLib(fallBack), nativeLib(libName, version) { #ifndef ZCONF_AVAHI_STATIC_LINKING // dynamic linking if (!nativeLib.load()) { setError(true, ZConfLib::tr("AvahiZConfLib could not load the native library '%1': %2").arg(libName, nativeLib.errorString())); } m_simplePollGet = reinterpret_cast(nativeLib.resolve("avahi_simple_poll_get")); m_simplePollNew = reinterpret_cast(nativeLib.resolve("avahi_simple_poll_new")); m_simplePollIterate = reinterpret_cast(nativeLib.resolve("avahi_simple_poll_iterate")); m_simplePollQuit = reinterpret_cast(nativeLib.resolve("avahi_simple_poll_quit")); m_simplePollFree = reinterpret_cast(nativeLib.resolve("avahi_simple_poll_free")); m_simplePollSetFunc = reinterpret_cast(nativeLib.resolve("avahi_simple_poll_set_func")); m_clientNew = reinterpret_cast(nativeLib.resolve("avahi_client_new")); m_clientFree = reinterpret_cast(nativeLib.resolve("avahi_client_free")); m_serviceBrowserNew = reinterpret_cast(nativeLib.resolve("avahi_service_browser_new")); m_serviceBrowserFree = reinterpret_cast(nativeLib.resolve("avahi_service_browser_free")); m_serviceResolverNew = reinterpret_cast(nativeLib.resolve("avahi_service_resolver_new")); m_serviceResolverFree = reinterpret_cast(nativeLib.resolve("avahi_service_resolver_free")); #else m_simplePollGet = reinterpret_cast(&avahi_simple_poll_get); m_simplePollNew = reinterpret_cast(&avahi_simple_poll_new); m_simplePollIterate = reinterpret_cast(&avahi_simple_poll_iterate); m_simplePollQuit = reinterpret_cast(&avahi_simple_poll_quit); m_simplePollFree = reinterpret_cast(&avahi_simple_poll_free); m_simplePollSetFunc = reinterpret_cast(&avahi_simple_poll_set_func); m_clientNew = reinterpret_cast(&avahi_client_new); m_clientFree = reinterpret_cast(&avahi_client_free); m_serviceBrowserNew = reinterpret_cast(&avahi_service_browser_new); m_serviceBrowserFree = reinterpret_cast(&avahi_service_browser_free); m_serviceResolverNew = reinterpret_cast(&avahi_service_resolver_new); m_serviceResolverFree = reinterpret_cast(&avahi_service_resolver_free); #endif if (DEBUG_ZEROCONF) { if (!m_simplePollGet) qDebug() << name() << " has null m_simplePollGet"; if (!m_simplePollNew) qDebug() << name() << " has null m_simplePollNew"; if (!m_simplePollIterate) qDebug() << name() << " has null m_simplePollIterate"; if (!m_simplePollQuit) qDebug() << name() << " has null m_simplePollQuit"; if (!m_simplePollFree) qDebug() << name() << " has null m_simplePollFree"; if (!m_simplePollSetFunc) qDebug() << name() << " has null m_simplePollSetFunc"; if (!m_clientNew) qDebug() << name() << " has null m_clientNew"; if (!m_clientFree) qDebug() << name() << " has null m_clientFree"; if (!m_serviceBrowserNew) qDebug() << name() << " has null m_serviceBrowserNew"; if (!m_serviceBrowserFree) qDebug() << name() << " has null m_serviceBrowserFree"; if (!m_serviceResolverNew) qDebug() << name() << " has null m_serviceResolverNew"; if (!m_serviceResolverFree) qDebug() << name() << " has null m_serviceResolverFree"; } } ~AvahiZConfLib() { } QString name(){ return QString::fromLatin1("Avahi Library"); } // bool tryStartDaemon(); void refDeallocate(DNSServiceRef /*sdRef*/) { } void browserDeallocate(BrowserRef *sdRef) { if (sdRef && (*sdRef) && m_serviceBrowserFree) { m_serviceBrowserFree(*reinterpret_cast(sdRef)); *sdRef = 0; } } void destroyConnection(ConnectionRef *sdRef) { MyAvahiConnection **connection = reinterpret_cast(sdRef); if (connection && (*connection)) { if ((*connection)->client && m_clientFree) m_clientFree((*connection)->client); if ((*connection)->simple_poll && m_simplePollFree) m_simplePollFree((*connection)->simple_poll); delete(*connection); *connection = 0; } } DNSServiceErrorType resolve(ConnectionRef cRef, DNSServiceRef *sdRef, uint32_t interfaceIndex, ZK_IP_Protocol protocol, const char *name, const char *regtype, const char *domain, ServiceGatherer *gatherer) { if (!sdRef) { qDebug() << "Error: sdRef is null in resolve"; return kDNSServiceErr_Unknown; } MyAvahiConnection *connection = reinterpret_cast(cRef); if (!m_serviceResolverNew) return kDNSServiceErr_Unknown; AvahiProtocol avahiProtocol = AVAHI_PROTO_UNSPEC; switch (protocol) { case ZK_PROTO_IPv4: avahiProtocol = AVAHI_PROTO_INET; break; case ZK_PROTO_IPv6: avahiProtocol = AVAHI_PROTO_INET6; break; case ZK_PROTO_IPv4_OR_IPv6: avahiProtocol = AVAHI_PROTO_UNSPEC; break; default: qDebug() << "Zeroconf: unexpected value " << static_cast(protocol) << " for protocol in avahiLib.resolve"; } AvahiServiceResolver *resolver = m_serviceResolverNew( connection->client, interfaceIndex, avahiProtocol, name, regtype, domain, avahiProtocol, AvahiLookupFlags(0), &cAvahiResolveReply, gatherer); //*sdRef = reinterpret_cast(resolver); // add for restart? if (!resolver) return kDNSServiceErr_Unknown; // avahi_strerror(avahi_client_errno(connection->client)); return kDNSServiceErr_NoError; } DNSServiceErrorType queryRecord(ConnectionRef, DNSServiceRef *, uint32_t, const char *, ServiceGatherer *) { return kDNSServiceErr_NoError; } DNSServiceErrorType getAddrInfo(ConnectionRef, DNSServiceRef *, uint32_t, DNSServiceProtocol, const char *, ServiceGatherer *) { return kDNSServiceErr_NoError; } DNSServiceErrorType reconfirmRecord(ConnectionRef cRef, uint32_t interfaceIndex, const char *name, const char *type, const char *domain, const char * /*fullname */) { MyAvahiConnection *connection = reinterpret_cast(cRef); if (!connection) return kDNSServiceErr_Unknown; AvahiServiceResolver *resolver = m_serviceResolverNew( connection->client, interfaceIndex, AVAHI_PROTO_INET, name, type, domain, AVAHI_PROTO_INET, AvahiLookupFlags(AVAHI_LOOKUP_USE_WIDE_AREA| AVAHI_LOOKUP_USE_MULTICAST), &cAvahiResolveEmptyReply, this); return ((resolver == 0)?kDNSServiceErr_Unknown:kDNSServiceErr_NoError); } DNSServiceErrorType browse(ConnectionRef cRef, BrowserRef *sdRef, uint32_t interfaceIndex, const char *regtype, const char *domain, ServiceBrowserPrivate *browser) { if (!sdRef) { qDebug() << "Error: sdRef is null in browse"; return kDNSServiceErr_Unknown; } if (!m_serviceBrowserNew) return kDNSServiceErr_Unknown; MyAvahiConnection *connection = reinterpret_cast(cRef); AvahiServiceBrowser *sb = m_serviceBrowserNew( connection->client, ((interfaceIndex == 0)?static_cast(AVAHI_IF_UNSPEC): static_cast(interfaceIndex)), AVAHI_PROTO_UNSPEC, regtype, domain, AvahiLookupFlags(0), &cAvahiBrowseReply, browser); *sdRef = reinterpret_cast(sb); if (!sb) { return kDNSServiceErr_Unknown; //avahi_strerror(avahi_client_errno(client)); } browser->activateAutoRefresh(); return kDNSServiceErr_NoError; } DNSServiceErrorType getProperty(const char * /*property*/, void * /*result*/, uint32_t * /*size*/ ) { return 0; } RunLoopStatus processOneEvent(MainConnection *, ConnectionRef cRef, qint64 maxLockMs) { if (!m_simplePollIterate) return ProcessedFailure; MyAvahiConnection *connection = reinterpret_cast(cRef); if (!connection) return ProcessedError; if (m_simplePollIterate(connection->simple_poll, static_cast(maxLockMs))) return ProcessedError; return ProcessedOk; } RunLoopStatus processOneEventBlock(ConnectionRef cRef) { return processOneEvent(NULL,cRef,-1); } DNSServiceErrorType createConnection(MainConnection *mainConnection, ConnectionRef *sdRef) { if (!m_simplePollNew || !m_clientNew || !m_simplePollSetFunc) return kDNSServiceErr_Unknown; if (sdRef == 0) { qDebug() << "Error: sdRef is null in createConnection"; return kDNSServiceErr_Unknown; } MyAvahiConnection *connection = new MyAvahiConnection; connection->lib = this; /* Allocate main loop object */ connection->simple_poll = m_simplePollNew(); if (!connection->simple_poll) { delete connection; return kDNSServiceErr_Unknown; } typedef void (*AvahiSimplePollSetFuncPtr)(AvahiSimplePoll *s, AvahiPollFunc func, void *userdata); m_simplePollSetFunc(connection->simple_poll, &cAvahiPollFunction, mainConnection); /* Allocate a new client */ int error; connection->client = m_clientNew(m_simplePollGet(connection->simple_poll), AvahiClientFlags(0), &cAvahiClientReply, connection, &error); if (!connection->client) { if (m_simplePollFree) m_simplePollFree(connection->simple_poll); delete connection; setError(true, ZConfLib::tr("%1 cannot create a client. The daemon is probably not running.").arg(name())); return kDNSServiceErr_Unknown; } *sdRef = reinterpret_cast(connection); return ((isOk())?kDNSServiceErr_NoError:kDNSServiceErr_Unknown); } void stopConnection(ConnectionRef cRef) { MyAvahiConnection *connection = reinterpret_cast(cRef); if (m_simplePollQuit && connection) m_simplePollQuit(connection->simple_poll); } int refSockFD(ConnectionRef) { return -1; } void serviceResolverFree(AvahiServiceResolver *r) { if (m_serviceResolverFree) m_serviceResolverFree(r); } }; ZConfLib::Ptr ZConfLib::createAvahiLib(const QString &libName, const QString &version, const ZConfLib::Ptr &fallback) { return ZConfLib::Ptr(new AvahiZConfLib(libName, version, fallback)); } extern "C" void cAvahiResolveReply( AvahiServiceResolver * r, AvahiIfIndex interface, AvahiProtocol /*protocol*/, AvahiResolverEvent event, const char * /*name*/, const char * /*type*/, const char * /*domain*/, const char *hostName, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags /*flags*/, void* context) { enum { defaultTtl = 0 }; ServiceGatherer *sg = reinterpret_cast(context); if (!sg){ qDebug() << "context was null in cAvahiResolveReply"; return; } AvahiStringList *txtAtt = 0; const unsigned char emptyTxt[1]=""; switch (event) { case AVAHI_RESOLVER_FAILURE: sg->serviceResolveReply(0, interface, kDNSServiceErr_Timeout, 0, QString(), 0, 0); break; case AVAHI_RESOLVER_FOUND: sg->serviceResolveReply( kDNSServiceFlagsAdd | ((txtAtt || address)?kDNSServiceFlagsMoreComing:0), interface, kDNSServiceErr_NoError, hostName, QString::number(port), 0, emptyTxt); txtAtt = txt; while (txtAtt) { sg->txtFieldReply( kDNSServiceFlagsAdd | ((txtAtt->next || address)?kDNSServiceFlagsMoreComing:0), kDNSServiceErr_NoError, static_cast(txtAtt->size), txtAtt->text, ~0); txtAtt = txtAtt->next; } if (address){ sockaddr_in ipv4; sockaddr_in6 ipv6; //#ifdef HAVE_SA_LEN switch (address->proto){ case (AVAHI_PROTO_INET): memset(&ipv4, 0, sizeof(ipv4)); ipv4.sin_family = AF_INET; memcpy(&(ipv4.sin_addr),&(address->data.ipv4.address), sizeof(ipv4.sin_addr)); sg->addrReply(kDNSServiceFlagsAdd, kDNSServiceErr_NoError, hostName, reinterpret_cast(&ipv4), defaultTtl); break; case (AVAHI_PROTO_INET6): memset(&ipv6, 0, sizeof(ipv6)); ipv6.sin6_family = AF_INET6; memcpy(&(ipv6.sin6_addr), &(address->data.ipv6.address), sizeof(ipv6.sin6_addr)); sg->addrReply(kDNSServiceFlagsAdd, kDNSServiceErr_NoError, hostName, reinterpret_cast(&ipv6), defaultTtl); break; default: if (DEBUG_ZEROCONF) qDebug() << "Warning: ignoring address with protocol " << address->proto << " for service " << sg->fullName(); } } break; default: qDebug() << "Error: unexpected avahi event " << event << " in cAvahiResolveReply"; break; } ZConfLib::Ptr libBase = sg->serviceBrowser->mainConnection->lib; AvahiZConfLib *lib = dynamic_cast(libBase.data()); if (lib) lib->serviceResolverFree(r); } extern "C" void cAvahiResolveEmptyReply( AvahiServiceResolver * r, AvahiIfIndex, AvahiProtocol, AvahiResolverEvent, const char *, const char *, const char *, const char *, const AvahiAddress *, uint16_t, AvahiStringList *, AvahiLookupResultFlags, void* context) { AvahiZConfLib *lib = reinterpret_cast(context); if (lib) lib->serviceResolverFree(r); } extern "C" void cAvahiClientReply (AvahiClient * /*s*/, AvahiClientState state, void* context) { MyAvahiConnection *connection = reinterpret_cast(context); ZConfLib *lib = connection->lib; if (!lib) { qDebug() << "Error: context was null in cAvahiClientReply, ignoring state " << state; return; } if (DEBUG_ZEROCONF) qDebug() << "cAvahiClientReply called with state " << state; switch (state){ case (AVAHI_CLIENT_S_REGISTERING): /* Server state: REGISTERING */ break; case (AVAHI_CLIENT_S_RUNNING): /* Server state: RUNNING */ lib->setError(false, QString()); break; case (AVAHI_CLIENT_S_COLLISION): /* Server state: COLLISION */ lib->setError(true, ZConfLib::tr("cAvahiClient, server collision.")); break; case (AVAHI_CLIENT_FAILURE): lib->setError(true, ZConfLib::tr("cAvahiClient, an error occurred on the client side.")); break; case (AVAHI_CLIENT_CONNECTING): lib->setError(false, ZConfLib::tr("cAvahiClient, still connecting, no server available.")); break; default: lib->setError(true, ZConfLib::tr("Unexpected state %1 in cAvahiClientReply.") .arg(state)); } } extern "C" void cAvahiBrowseReply( AvahiServiceBrowser * /*b*/, AvahiIfIndex interface, AvahiProtocol avahiProtocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags /*flags*/, void* context) { ZK_IP_Protocol protocol = ZK_PROTO_IPv4_OR_IPv6; switch (avahiProtocol) { case AVAHI_PROTO_INET: protocol = ZK_PROTO_IPv4; break; case AVAHI_PROTO_INET6: protocol = ZK_PROTO_IPv6; break; case AVAHI_PROTO_UNSPEC: protocol = ZK_PROTO_IPv4_OR_IPv6; break; default: qDebug() << "Error unexpected protocol value " << avahiProtocol << " in cAvahiBrowseReply"; } ServiceBrowserPrivate *browser = reinterpret_cast(context); if (browser == 0) qDebug() << "Error context is null in cAvahiBrowseReply"; switch (event) { case AVAHI_BROWSER_FAILURE: browser->browseReply(kDNSServiceFlagsMoreComing, 0, protocol, kDNSServiceErr_Unknown, 0, 0, 0); break; case AVAHI_BROWSER_NEW: browser->browseReply(kDNSServiceFlagsAdd|kDNSServiceFlagsMoreComing, static_cast(interface), protocol, kDNSServiceErr_NoError, name, type, domain); break; case AVAHI_BROWSER_REMOVE: browser->browseReply(kDNSServiceFlagsMoreComing, static_cast(interface), protocol, kDNSServiceErr_NoError, name, type, domain); break; case AVAHI_BROWSER_ALL_FOR_NOW: browser->updateFlowStatusForFlags(0); break; case AVAHI_BROWSER_CACHE_EXHAUSTED: browser->updateFlowStatusForFlags(0); break; default: browser->mainConnection->lib->setError(true, ZConfLib::tr( "Unexpected state %1 in cAvahiBrowseReply.") .arg(event)); } } extern "C" int cAvahiPollFunction(struct pollfd *ufds, unsigned int nfds, int timeout, void *userdata) { MainConnection *mainConnection = static_cast(userdata); QMutex *lock = 0; if (mainConnection) lock = mainConnection->mainThreadLock(); if (lock) lock->unlock(); int res=poll(ufds,nfds,timeout); if (lock) lock->lock(); return res; } } // namespace Internal } // namespace ZeroConf #else // no native lib namespace ZeroConf { namespace Internal { ZConfLib::Ptr ZConfLib::createAvahiLib(const QString &/*lib*/, const QString &/*version*/, const ZConfLib::Ptr &fallback) { return fallback; } } // namespace Internal } // namespace ZeroConf #endif