mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 10:52:49 +00:00
469 lines
16 KiB
C++
469 lines
16 KiB
C++
/* This file is part of the KDE libraries
|
|
Copyright (C) 2022 Ivailo Monev <xakepa10@gmail.com>
|
|
|
|
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 <avahi-client/client.h>
|
|
# include <avahi-client/publish.h>
|
|
# include <avahi-client/lookup.h>
|
|
# include <avahi-common/simple-watch.h>
|
|
# include <avahi-common/error.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_AVAHI)
|
|
// NOTE: resolving to and publishing IPv4 addresses since curl, ping, etc. cannot handle URLs with
|
|
// IPv6 addresses as host along with port (e.g. [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<KDNSSDService> 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<KDNSSDService> m_services;
|
|
QList<QByteArray> 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)
|
|
#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);
|
|
return (avahiresult >= 0);
|
|
#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<QByteArray> 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<KDNSSDService> KDNSSDPrivate::services() const
|
|
{
|
|
#if defined(HAVE_AVAHI)
|
|
return m_services;
|
|
#else
|
|
static const QList<KDNSSDService> 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<KDNSSDPrivate*>(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<KDNSSDPrivate*>(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<KDNSSDPrivate*>(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<KDNSSD*>(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<KDNSSDPrivate*>(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<KDNSSDPrivate*>(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<KDNSSDService> 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"
|