21 #include <QCoreApplication> 35 #include <arpa/inet.h> 41 DNSServiceFlags Flags,
42 DNSServiceErrorType Errorcode,
48 DNSServiceFlags Flags,
49 uint32_t InterfaceIndex,
50 DNSServiceErrorType ErrorCode,
56 DNSServiceFlags Flags,
57 uint32_t InterfaceIndex,
58 DNSServiceErrorType ErrorCode,
60 const char *HostTarget,
63 const unsigned char *TxtRecord,
75 : m_serviceType(Service),
80 m_domain(QByteArray()),
83 m_ipAddresses(QList<QHostAddress>()),
87 m_socketNotifier(nullptr)
90 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Invalid TorcBonjourService object"));
146 const QByteArray &Name,
const QByteArray &Type,
147 const QByteArray &Domain, uint32_t InterfaceIndex)
169 if (FileDescriptor != -1 && Object)
171 m_fd = FileDescriptor;
172 m_socketNotifier =
new QSocketNotifier(FileDescriptor, QSocketNotifier::Read, Object);
196 LOG(VB_NETWORK, LOG_WARNING, QStringLiteral(
"Host lookup for '%1' is not finished - cancelling").arg(
m_host.constData()));
206 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Cancelling browse for '%1'")
207 .arg(
m_type.constData()));
211 LOG(VB_GENERAL, LOG_INFO,
212 QStringLiteral(
"De-registering service '%1' on '%2'")
217 LOG(VB_NETWORK, LOG_INFO, QStringLiteral(
"Cancelling resolve for '%1'")
218 .arg(
m_type.constData()));
234 DNSServiceErrorType Errorcode,
235 const char *Name,
const char *Type,
236 const char *Domain,
void *Object)
243 if (kDNSServiceErr_NoError != Errorcode)
245 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Callback Error: %1").arg(Errorcode));
249 LOG(VB_GENERAL, LOG_INFO,
250 QStringLiteral(
"Service registration complete: name '%1' type '%2'")
251 .arg(QString::fromUtf8(Name), QString::fromUtf8(Type)));
255 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"BonjourRegisterCallback for unknown object."));
263 uint32_t InterfaceIndex,
264 DNSServiceErrorType ErrorCode,
const char *Name,
265 const char *Type,
const char *Domain,
void *Object)
270 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Received browse callback for unknown object"));
274 if (ErrorCode != kDNSServiceErr_NoError)
276 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Browse callback error"));
281 if (Flags & kDNSServiceFlagsAdd)
286 if (!(Flags & kDNSServiceFlagsMoreComing))
296 uint32_t InterfaceIndex, DNSServiceErrorType ErrorCode,
297 const char *Fullname,
const char *HostTarget,
298 uint16_t Port, uint16_t TxtLen,
299 const unsigned char *TxtRecord,
void *Object)
302 (void) InterfaceIndex;
307 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Received resolve callback for unknown object"));
311 bonjour->
Resolve(Ref, ErrorCode, Fullname, HostTarget, Port, TxtLen, TxtRecord);
351 QMetaObject::invokeMethod(gBonjour,
"SuspendPriv", Qt::AutoConnection, Q_ARG(
bool, Suspend));
371 QMap<QByteArray,QByteArray>::const_iterator it = Map.constBegin();
372 for ( ; it != Map.constEnd(); ++it)
374 QByteArray record(1, it.key().size() + it.value().size() + 1);
375 record.append(it.key() +
"=" + it.value());
376 result.append(record);
387 QMap<QByteArray,QByteArray> result;
390 while (position < TxtRecord.size())
392 int size = TxtRecord[position++];
393 QList<QByteArray> records = TxtRecord.mid(position, size).split(
'=');
396 if (records.size() == 2)
397 result.insert(records[0], records[1]);
406 m_serviceLock(QMutex::Recursive),
407 m_services(QMap<quint32,TorcBonjourService>()),
408 m_discoveredLock(QMutex::Recursive),
409 m_discoveredServices(QMap<quint32,TorcBonjourService>())
412 qputenv(
"AVAHI_COMPAT_NOWARN",
"1");
423 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Closing Bonjour"));
427 QMutexLocker locker(&m_serviceLock);
436 QMutexLocker locker(&m_discoveredLock);
439 m_discoveredServices.clear();
464 const QMap<QByteArray, QByteArray> &TxtRecords, quint32 Reference)
466 QByteArray txt = MapToTxtRecord(TxtRecords);
467 return Register(Port, Type, Name, txt, Reference);
470 quint32
TorcBonjour::Register(quint16 Port,
const QByteArray &Type,
const QByteArray &Name,
const QByteArray &Txt, quint32 Reference)
474 QMutexLocker locker(&m_serviceLock);
479 quint32 reference = Reference;
481 while (!reference || m_services.contains(reference))
483 m_services.insert(reference, service);
485 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Bonjour service registration deferred until Bonjour resumed"));
489 DNSServiceRef dnssref =
nullptr;
490 DNSServiceErrorType result = kDNSServiceErr_NameConflict;
494 while (tries < 20 && kDNSServiceErr_NameConflict == result)
496 QByteArray name(Name);
498 name.append(QStringLiteral(
" [%1]").arg(tries + 1));
499 result = DNSServiceRegister(&dnssref, 0,
500 0, (
const char*)name.constData(),
501 (
const char*)Type.constData(),
502 nullptr,
nullptr, htons(Port), Txt.size(), (
void*)Txt.constData(),
507 if (kDNSServiceErr_NoError != result)
509 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Register error: %1").arg(result));
511 DNSServiceRefDeallocate(dnssref);
515 QMutexLocker locker(&m_serviceLock);
516 quint32 reference = Reference;
517 while (!reference || m_services.contains(reference))
522 m_services.insert(reference, service);
523 m_services[reference].SetFileDescriptor(DNSServiceRefSockFD(dnssref),
this);
527 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Failed to register service."));
545 static QByteArray dummy(
"browser");
549 QMutexLocker locker(&m_serviceLock);
552 quint32 reference = Reference;
553 while (!reference || m_services.contains(reference))
555 m_services.insert(reference, service);
557 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Bonjour browse request deferred until Bonjour resumed"));
561 DNSServiceRef dnssref =
nullptr;
562 DNSServiceErrorType result = DNSServiceBrowse(&dnssref, 0,
563 kDNSServiceInterfaceIndexAny,
565 if (kDNSServiceErr_NoError != result)
567 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Browse error: %1").arg(result));
569 DNSServiceRefDeallocate(dnssref);
573 QMutexLocker locker(&m_serviceLock);
574 quint32 reference = Reference;
575 while (!reference || m_services.contains(reference))
578 m_services.insert(reference, service);
579 m_services[reference].SetFileDescriptor(DNSServiceRefSockFD(dnssref),
this);
580 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Browsing for '%1'").arg(Type.constData()));
584 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Failed to browse for '%1'").arg(Type.constData()));
603 QMutexLocker locker(&m_serviceLock);
604 if (m_services.contains(Reference))
606 type = m_services[Reference].m_type;
607 m_services[Reference].Deregister();
608 m_services.remove(Reference);
614 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Trying to de-register an unknown service."));
620 QMutexLocker locker(&m_discoveredLock);
621 QMutableMapIterator<quint32,TorcBonjourService> it(m_discoveredServices);
625 if (it.value().m_type == type)
627 it.value().Deregister();
644 QMutexLocker lock(&m_serviceLock);
645 QMap<quint32,TorcBonjourService>::const_iterator it = m_services.constBegin();
646 for ( ; it != m_services.constEnd(); ++it)
648 if ((*it).m_fd == Socket)
650 DNSServiceErrorType res = DNSServiceProcessResult((*it).m_dnssRef);
651 if (kDNSServiceErr_NoError != res)
652 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Read Error: %1").arg(res));
660 QMutexLocker lock(&m_discoveredLock);
661 QMap<quint32,TorcBonjourService>::const_iterator it = m_discoveredServices.constBegin();
662 for ( ; it != m_discoveredServices.constEnd(); ++it)
664 if ((*it).m_fd == Socket)
666 DNSServiceErrorType res = DNSServiceProcessResult((*it).m_dnssRef);
667 if (kDNSServiceErr_NoError != res)
668 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Read Error: %1").arg(res));
674 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Read request on unknown socket"));
686 void TorcBonjour::SuspendPriv(
bool Suspend)
692 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Bonjour already suspended"));
696 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Suspending bonjour activities"));
701 QMutexLocker locker(&m_serviceLock);
704 QMap<quint32,TorcBonjourService> services;
705 QMap<quint32,TorcBonjourService>::const_iterator it = m_services.constBegin();
706 for (; it != m_services.constEnd(); ++it)
709 service.
m_txt = (*it).m_txt;
710 service.
m_port = (*it).m_port;
711 services.insert(it.key(), service);
716 m_services = services;
723 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Cannot resume - not suspended"));
727 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Resuming bonjour activities"));
730 QMutexLocker locker(&m_serviceLock);
734 QMap<quint32,TorcBonjourService> services = m_services;
737 QMap<quint32,TorcBonjourService>::const_iterator it = services.constBegin();
738 for (; it != services.constEnd(); ++it)
741 (void)Register((*it).m_port, (*it).m_type, (*it).m_name, (*it).m_txt, it.key());
743 (
void)
Browse((*it).m_type, it.key());
759 QMutexLocker locker(&m_serviceLock);
761 QMap<quint32,TorcBonjourService>::const_iterator it = m_services.constBegin();
762 for ( ; it != m_services.constEnd(); ++it)
764 if ((*it).m_dnssRef == Reference)
773 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Browser result for unknown browser"));
780 QMutexLocker locker(&m_discoveredLock);
781 QMap<quint32,TorcBonjourService>::const_iterator it = m_discoveredServices.constBegin();
782 for( ; it != m_discoveredServices.constEnd(); ++it)
784 if ((*it).m_name == Service.
m_name &&
785 (*it).m_type == Service.
m_type &&
786 (*it).m_domain == Service.
m_domain &&
789 LOG(VB_NETWORK, LOG_INFO, QStringLiteral(
"Service '%1' already discovered - ignoring")
790 .arg(Service.
m_name.constData()));
796 DNSServiceRef reference =
nullptr;
797 DNSServiceErrorType error =
802 if (error != kDNSServiceErr_NoError)
804 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Service resolution call failed"));
806 DNSServiceRefDeallocate(reference);
813 while (m_discoveredServices.contains(ref))
816 m_discoveredServices.insert(ref, service);
817 m_discoveredServices[ref].SetFileDescriptor(DNSServiceRefSockFD(reference),
this);
818 LOG(VB_NETWORK, LOG_INFO, QStringLiteral(
"Resolving '%1'").arg(service.
m_name.constData()));
831 QMutexLocker locker(&m_serviceLock);
833 QMap<quint32,TorcBonjourService>::const_iterator it = m_services.constBegin();
834 for ( ; it != m_services.constEnd(); ++it)
836 if ((*it).m_dnssRef == Reference)
845 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Browser result for unknown browser"));
852 QMutexLocker locker(&m_discoveredLock);
853 QMutableMapIterator<quint32,TorcBonjourService> it(m_discoveredServices);
857 if (it.value().m_type == Service.
m_type &&
858 it.value().m_name == Service.
m_name &&
859 it.value().m_domain == Service.
m_domain &&
870 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Service '%1' on '%2' went away")
871 .arg(it.value().m_type.constData(),
872 it.value().m_host.isEmpty() ? it.value().m_name.constData() : it.value().m_host.constData()));
873 it.value().Deregister();
890 const char *HostTarget, uint16_t Port, uint16_t TxtLen,
891 const unsigned char *TxtRecord)
899 QMutexLocker locker(&m_discoveredLock);
904 if (ErrorType != kDNSServiceErr_NoError)
906 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Failed to resolve '%1' (Error %2)").arg(service.
m_name.constData()).arg(ErrorType));
920 uint16_t port = ntohs(Port);
921 service.
m_host = HostTarget;
923 LOG(VB_NETWORK, LOG_INFO, QStringLiteral(
"%1 (%2) resolved to %3:%4")
924 .arg(service.
m_name.constData(), service.
m_type.constData(), HostTarget).arg(port));
925 QString name(HostTarget);
926 service.
m_lookupID = QHostInfo::lookupHost(name,
this, SLOT(HostLookup(QHostInfo)));
927 service.
m_txt = QByteArray((
const char *)TxtRecord, TxtLen);
940 void TorcBonjour::HostLookup(
const QHostInfo &HostInfo)
943 QMutexLocker locker(&m_discoveredLock);
946 if (service.
m_lookupID == HostInfo.lookupId())
951 if (HostInfo.error() != QHostInfo::NoError)
953 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Lookup failed for '%1' with error '%2'").arg(HostInfo.hostName(), HostInfo.errorString()));
958 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Service '%1' on '%2:%3' resolved to %4 address(es) on interface %5")
959 .arg(service.
m_type.constData(), service.
m_host.constData())
964 QStringList addresses;
968 LOG(VB_NETWORK, LOG_INFO, QStringLiteral(
"Address: %1").arg(address.toString()));
969 addresses << address.toString();
979 if (!addresses.isEmpty())
986 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"No valid addresses resolved for Service '%1' on '%2:%3'")
987 .arg(service.
m_type.constData(), service.
m_host.constData())
QList< QHostAddress > m_ipAddresses
Address resolution for a discovered service.
uint32_t m_interfaceIndex
TorcLocalContext * gLocalContext
void Deregister(quint32 Reference)
Cancel a Bonjour service registration or browse request.
static QByteArray MapToTxtRecord(const QMap< QByteArray, QByteArray > &Map)
Serialises a QMap into a Bonjour Txt record format.
void socketReadyRead(int Socket)
Read from a socket.
void RemoveBrowseResult(DNSServiceRef Reference, const TorcBonjourService &Service)
Handle removed services.
QSocketNotifier * m_socketNotifier
quint32 Browse(const QByteArray &Type, quint32 Reference=0)
Search for a service advertised via Bonjour.
bool IsResolved(void)
Returns true when the service has been fully resolved to an IP address and port.
QMutex * gBonjourLock
Lock around access to gBonjour.
#define TORC_BONJOUR_ADDRESSES
#define TORC_BONJOUR_NAME
void Deregister(void)
Release all resources associated with this service.
TorcBonjour * gBonjour
Global TorcBonjour singleton.
void AddBrowseResult(DNSServiceRef Reference, const TorcBonjourService &Service)
Handle newly discovered service.
static QMap< QByteArray, QByteArray > TxtRecordToMap(const QByteArray &TxtRecord)
Extracts a QMap from a properly formatted Bonjour Txt record.
static void TearDown(void)
Destroys the global TorcBonjour singleton.
quint32 Register(quint16 Port, const QByteArray &Type, const QByteArray &Name, const QMap< QByteArray, QByteArray > &TxtRecords, quint32 Reference=0)
void Resolve(DNSServiceRef Reference, DNSServiceErrorType ErrorType, const char *Fullname, const char *HostTarget, uint16_t Port, uint16_t TxtLen, const unsigned char *TxtRecord)
Handle name resolution respones from Bonjour.
#define TORC_BONJOUR_HOST
Wrapper around a DNS service reference, either advertised or discovered.
static TorcBonjour * Instance(void)
Returns the global TorcBonjour singleton.
A general purpose event object.
A service being advertised by this application.
#define LOG(_MASK_, _LEVEL_, _STRING_)
void AddObserver(QObject *Observer)
brief Register the given object to receive events.
void Notify(const TorcEvent &Event)
Brief Send the given event to each registered listener/observer.
#define TORC_BONJOUR_TYPE
#define TORC_BONJOUR_PORT
void DNSSD_API BonjourBrowseCallback(DNSServiceRef Ref, DNSServiceFlags Flags, uint32_t InterfaceIndex, DNSServiceErrorType ErrorCode, const char *Name, const char *Type, const char *Domain, void *Object)
TorcBonjourService & operator=(const TorcBonjourService &Other)
Advertises and searches for services via Bonjour (aka Zero Configuration Networking/Avahi) ...
void RemoveObserver(QObject *Observer)
brief Deregister the given object.
An external service which we are actively trying to discover.
void DNSSD_API BonjourRegisterCallback(DNSServiceRef Ref, DNSServiceFlags Flags, DNSServiceErrorType Errorcode, const char *Name, const char *Type, const char *Domain, void *Object)
void DNSSD_API BonjourResolveCallback(DNSServiceRef Ref, DNSServiceFlags Flags, uint32_t InterfaceIndex, DNSServiceErrorType ErrorCode, const char *Fullname, const char *HostTarget, uint16_t Port, uint16_t TxtLen, const unsigned char *TxtRecord, void *Object)
void SetFileDescriptor(int FileDescriptor, TorcBonjour *Object)
Sets the file descriptor and creates a QSocketNotifier to listen for socket events.
ServiceType m_serviceType
static void Suspend(bool Suspend)
Suspends all Bonjour activities.