/* This file is part of the KDE libraries Copyright (C) 2022 Ivailo Monev This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2, as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdnssd.h" #include "kurl.h" #include "kdebug.h" #if defined(HAVE_AVAHI) # include # include # include # include # include #endif #if defined(HAVE_AVAHI) // NOTE: resolving to and publishing IPv4 addresses since curl, ping, etc. cannot handle URLs with // IPv6 address as host along with port (e.g. http://[fe80::fe4d:d4ff:fe4c:5575]:7287) static const AvahiProtocol s_avahiproto = AVAHI_PROTO_INET; static QString getAvahiError(const int avahierror) { kWarning() << avahi_strerror(avahierror); return QString::fromAscii(avahi_strerror(avahierror)); } static QString getAvahiClientError(AvahiClient *avahiclient) { return getAvahiError(avahi_client_errno(avahiclient)); } #endif class KDNSSDPrivate : public QObject { Q_OBJECT public: KDNSSDPrivate(QObject *parent); ~KDNSSDPrivate(); bool publishService(const QByteArray &servicetype, const uint serviceport, const QString &servicename); bool unpublishService(); void startBrowse(const QByteArray &servicetype); QList services() const; QString errorString() const; #if defined(HAVE_AVAHI) static void clientCallback(AvahiClient *avahiclient, AvahiClientState avahistate, void *userdata); static void groupCallback(AvahiEntryGroup *avahigroup, AvahiEntryGroupState avahistate, void *userdata); static void browseCallback(AvahiServiceBrowser *avahibrowser, AvahiIfIndex avahiinterface, AvahiProtocol avahiprotocol, AvahiBrowserEvent avahievent, const char *avahiname, const char *avahitype, const char *avahidomain, AvahiLookupResultFlags avahiflags, void* userdata); static void resolveCallback(AvahiServiceResolver *avahiresolver, AvahiIfIndex avahiinterface, AvahiProtocol avahiprotocol, AvahiResolverEvent avahievent, const char *avahiname, const char *avahitype, const char *avahidomain, const char *avahihostname, const AvahiAddress *avahiaddress, uint16_t avahiport, AvahiStringList *avahitxt, AvahiLookupResultFlags avahiflags, void* userdata); static void serviceCallback(AvahiServiceTypeBrowser *avahiservice, AvahiIfIndex avahiinterface, AvahiProtocol avahiprotocol, AvahiBrowserEvent avahievent, const char *avahitype, const char *avahidomain, AvahiLookupResultFlags avahiflags, void *userdata); private: int m_pollcounter; AvahiSimplePoll *m_avahipoll; AvahiClient *m_avahiclient; AvahiEntryGroup *m_avahigroup; QList m_services; QList m_servicetypes; #endif // HAVE_AVAHI private: QString m_errorstring; }; KDNSSDPrivate::KDNSSDPrivate(QObject *parent) : QObject(parent) #if defined(HAVE_AVAHI) , m_pollcounter(0), m_avahipoll(nullptr), m_avahiclient(nullptr), m_avahigroup(nullptr) #else , m_errorstring(QString::fromLatin1("Built without Avahi")) #endif { #if defined(HAVE_AVAHI) m_avahipoll = avahi_simple_poll_new(); if (!m_avahipoll) { m_errorstring = QString::fromLatin1("Could not create Avahi poll"); return; } int avahierror = 0; m_avahiclient = avahi_client_new( avahi_simple_poll_get(m_avahipoll), AVAHI_CLIENT_NO_FAIL, KDNSSDPrivate::clientCallback, this, &avahierror ); if (!m_avahiclient) { m_errorstring = getAvahiError(avahierror); return; } #endif // HAVE_AVAHI } KDNSSDPrivate::~KDNSSDPrivate() { #if defined(HAVE_AVAHI) m_pollcounter = 0; if (m_avahigroup) { avahi_entry_group_reset(m_avahigroup); } if (m_avahiclient) { avahi_client_free(m_avahiclient); } if (m_avahipoll) { avahi_simple_poll_free(m_avahipoll); } #endif // HAVE_AVAHI } bool KDNSSDPrivate::publishService(const QByteArray &servicetype, const uint serviceport, const QString &servicename) { #if defined(HAVE_AVAHI) if (m_avahigroup) { avahi_entry_group_reset(m_avahigroup); } m_avahigroup = avahi_entry_group_new(m_avahiclient, KDNSSDPrivate::groupCallback, this); if (!m_avahigroup) { m_errorstring = getAvahiClientError(m_avahiclient); return false; } const QByteArray servicenamebytes = servicename.toUtf8(); // qDebug() << Q_FUNC_INFO << servicenamebytes << servicetype; int avahiresult = avahi_entry_group_add_service( m_avahigroup, AVAHI_IF_UNSPEC, s_avahiproto, AvahiPublishFlags(0), servicenamebytes.constData(), servicetype.constData(), NULL, NULL, serviceport, NULL ); if (avahiresult < 0) { m_errorstring = getAvahiError(avahiresult); return false; } avahiresult = avahi_entry_group_commit(m_avahigroup); if (avahiresult < 0) { m_errorstring = getAvahiError(avahiresult); return false; } return true; #else return false; #endif } bool KDNSSDPrivate::unpublishService() { #if defined(HAVE_AVAHI) if (!m_avahigroup) { return true; } const int avahiresult = avahi_entry_group_reset(m_avahigroup); if (avahiresult < 0) { m_errorstring = getAvahiError(avahiresult); return false; } return true; #else return false; #endif } void KDNSSDPrivate::startBrowse(const QByteArray &servicetype) { #if defined(HAVE_AVAHI) // qDebug() << Q_FUNC_INFO << servicetype; m_pollcounter = 0; QList servicetypes; if (servicetype.isEmpty()) { AvahiServiceTypeBrowser* avahiservice = avahi_service_type_browser_new( m_avahiclient, AVAHI_IF_UNSPEC, s_avahiproto, NULL, AvahiLookupFlags(0), KDNSSDPrivate::serviceCallback, this ); if (!avahiservice) { m_errorstring = getAvahiClientError(m_avahiclient); return; } m_pollcounter++; m_servicetypes.clear(); while (m_pollcounter) { // qDebug() << Q_FUNC_INFO << m_pollcounter; avahi_simple_poll_iterate(m_avahipoll, 0); } avahi_service_type_browser_free(avahiservice); servicetypes = m_servicetypes; } else { servicetypes.append(servicetype); } m_pollcounter = 0; m_services.clear(); foreach (const QByteArray &servicetypeit, servicetypes) { AvahiServiceBrowser *avahibrowser = avahi_service_browser_new( m_avahiclient, AVAHI_IF_UNSPEC, s_avahiproto, servicetypeit.constData(), NULL, AvahiLookupFlags(0), KDNSSDPrivate::browseCallback, this ); if (!avahibrowser) { m_errorstring = getAvahiClientError(m_avahiclient); return; } m_pollcounter++; while (m_pollcounter) { // qDebug() << Q_FUNC_INFO << m_pollcounter; avahi_simple_poll_iterate(m_avahipoll, 0); } avahi_service_browser_free(avahibrowser); } #endif // HAVE_AVAHI } QList KDNSSDPrivate::services() const { #if defined(HAVE_AVAHI) return m_services; #else static const QList result; return result; #endif } QString KDNSSDPrivate::errorString() const { return m_errorstring; } #if defined(HAVE_AVAHI) void KDNSSDPrivate::groupCallback(AvahiEntryGroup *avahigroup, AvahiEntryGroupState avahistate, void *userdata) { // qDebug() << Q_FUNC_INFO << avahigroup << avahistate << userdata; if (avahistate == AVAHI_ENTRY_GROUP_FAILURE) { KDNSSDPrivate *kdnssdprivate = static_cast(userdata); kdnssdprivate->m_errorstring = getAvahiClientError(avahi_entry_group_get_client(avahigroup)); } } void KDNSSDPrivate::clientCallback(AvahiClient *avahiclient, AvahiClientState avahistate, void *userdata) { // qDebug() << Q_FUNC_INFO << avahistate << userdata; if (avahistate == AVAHI_CLIENT_FAILURE) { KDNSSDPrivate *kdnssdprivate = static_cast(userdata); kdnssdprivate->m_errorstring = getAvahiClientError(avahiclient); } } void KDNSSDPrivate::browseCallback(AvahiServiceBrowser *avahibrowser, AvahiIfIndex avahiinterface, AvahiProtocol avahiprotocol, AvahiBrowserEvent avahievent, const char *avahiname, const char *avahitype, const char *avahidomain, AvahiLookupResultFlags avahiflags, void* userdata) { // qDebug() << Q_FUNC_INFO << avahievent << avahiname << avahitype << avahidomain << userdata; KDNSSDPrivate *kdnssdprivate = static_cast(userdata); AvahiClient *avahiclient = avahi_service_browser_get_client(avahibrowser); switch (avahievent) { case AVAHI_BROWSER_NEW: { kDebug() << "New service" << avahiname << avahitype << avahidomain; kdnssdprivate->m_pollcounter++; AvahiServiceResolver *avahiresolver = avahi_service_resolver_new( avahiclient, avahiinterface, avahiprotocol, avahiname, avahitype, avahidomain, s_avahiproto, AvahiLookupFlags(0), KDNSSDPrivate::resolveCallback, userdata ); if (!avahiresolver) { kdnssdprivate->m_errorstring = getAvahiClientError(avahiclient); } break; } case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: { kDebug() << "Done browsing"; kdnssdprivate->m_pollcounter--; KDNSSD *kdnssd= qobject_cast(kdnssdprivate->parent()); emit kdnssd->finished(); break; } case AVAHI_BROWSER_FAILURE: { kdnssdprivate->m_errorstring = getAvahiClientError(avahiclient); break; } } } void KDNSSDPrivate::resolveCallback(AvahiServiceResolver *avahiresolver, AvahiIfIndex avahiinterface, AvahiProtocol avahiprotocol, AvahiResolverEvent avahievent, const char *avahiname, const char *avahitype, const char *avahidomain, const char *avahihostname, const AvahiAddress *avahiaddress, uint16_t avahiport, AvahiStringList *avahitxt, AvahiLookupResultFlags avahiflags, void* userdata) { // qDebug() << Q_FUNC_INFO << avahievent << avahiname << avahitype << avahidomain << userdata; KDNSSDPrivate *kdnssdprivate = static_cast(userdata); switch (avahievent) { case AVAHI_RESOLVER_FOUND: { kDebug() << "Resolved service" << avahiname << avahitype << avahidomain; const QByteArray kdnssdservicetype = QByteArray(avahitype); const QString kdnssdservicedomain = QString::fromUtf8(avahidomain); const QString kdnssdservicehostname = QString::fromUtf8(avahihostname); const uint kdnssdserviceport = avahiport; // first comes, first serves bool isduplicate = false; foreach (const KDNSSDService &kdnssdserviceit, kdnssdprivate->m_services) { if (kdnssdserviceit.type == kdnssdservicetype && kdnssdserviceit.domain == kdnssdservicedomain && kdnssdserviceit.hostname == kdnssdservicehostname && kdnssdserviceit.port == kdnssdserviceport) { isduplicate = true; break; } } if (!isduplicate) { char avahiaddressbuff[AVAHI_ADDRESS_STR_MAX]; ::memset(avahiaddressbuff, 0, sizeof(avahiaddressbuff) * sizeof(char)); avahi_address_snprint(avahiaddressbuff, sizeof(avahiaddressbuff), avahiaddress); QString kdnssdserviceprotocol = QString::fromLatin1(avahitype); kdnssdserviceprotocol = kdnssdserviceprotocol.mid(1); const int dotindex = kdnssdserviceprotocol.indexOf(QLatin1Char('.')); kdnssdserviceprotocol = kdnssdserviceprotocol.left(dotindex); KUrl kdnssdserviceurl; kdnssdserviceurl.setScheme(kdnssdserviceprotocol); kdnssdserviceurl.setHost(QString::fromLatin1(avahiaddressbuff)); kdnssdserviceurl.setPort(kdnssdserviceport); KDNSSDService kdnssdservice; kdnssdservice.name = QString::fromUtf8(avahiname); kdnssdservice.type = kdnssdservicetype; kdnssdservice.domain = kdnssdservicedomain; kdnssdservice.hostname = kdnssdservicehostname; kdnssdservice.url = kdnssdserviceurl.prettyUrl(); kdnssdservice.port = kdnssdserviceport; kdnssdprivate->m_services.append(kdnssdservice); } break; } case AVAHI_RESOLVER_FAILURE: { kdnssdprivate->m_errorstring = getAvahiClientError(avahi_service_resolver_get_client(avahiresolver)); break; } } kdnssdprivate->m_pollcounter--; avahi_service_resolver_free(avahiresolver); } void KDNSSDPrivate::serviceCallback(AvahiServiceTypeBrowser *avahiservice, AvahiIfIndex avahiinterface, AvahiProtocol avahiprotocol, AvahiBrowserEvent avahievent, const char *avahitype, const char *avahidomain, AvahiLookupResultFlags avahiflags, void *userdata) { // qDebug() << Q_FUNC_INFO << avahievent << avahitype << avahidomain << userdata; KDNSSDPrivate *kdnssdprivate = static_cast(userdata); AvahiClient *avahiclient = avahi_service_type_browser_get_client(avahiservice); switch (avahievent) { case AVAHI_BROWSER_NEW: { kDebug() << "New service type" << avahitype << avahidomain; const QByteArray kdnsservicetype(avahitype); if (!kdnssdprivate->m_servicetypes.contains(kdnsservicetype)) { kdnssdprivate->m_servicetypes.append(kdnsservicetype); } break; } case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: { kDebug() << "Done browsing service types"; kdnssdprivate->m_pollcounter--; break; } case AVAHI_BROWSER_FAILURE: { kdnssdprivate->m_errorstring = getAvahiClientError(avahiclient); break; } } } #endif // HAVE_AVAHI KDNSSD::KDNSSD(QObject *parent) : QObject(parent), d(new KDNSSDPrivate(this)) { } KDNSSD::~KDNSSD() { delete d; } bool KDNSSD::publishService(const QByteArray &servicetype, const uint serviceport, const QString &servicename) { return d->publishService(servicetype, serviceport, servicename); } bool KDNSSD::unpublishService() { return d->unpublishService(); } QList KDNSSD::services() const { return d->services(); } void KDNSSD::startBrowse(const QByteArray &servicetype) { d->startBrowse(servicetype); } QString KDNSSD::errorString() const { return d->errorString(); } #include "kdnssd.moc"