Files
QtZeroConf/bonjour.cpp
T
Jonathan Bagg 4a2e197517 Bonjour - destroy resolvers by resolvers list, not service list
If stopBrowser is called after a resolve starts, but before the resolve
finished, the service is never added to the services list.  This leaves the
resolver active and it fires when resolving completes after stopBrowser()
has destroyed a bunch of objects.  Fix is to destroy resolves according to
resolvers list and destroy services according to services list.
2025-09-15 16:01:36 -04:00

384 lines
11 KiB
C++

/**************************************************************************************************
---------------------------------------------------------------------------------------------------
Copyright (C) 2015 Jonathan Bagg
This file is part of QtZeroConf.
QtZeroConf is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QtZeroConf 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with QtZeroConf. If not, see <http://www.gnu.org/licenses/>.
---------------------------------------------------------------------------------------------------
Project name : QtZeroConf
File name : bonjour.cpp
Created : 20 July 2015
Author(s) : Jonathan Bagg
---------------------------------------------------------------------------------------------------
Wrapper for Apple's Bonjour library for use on Windows, MACs and iOS
---------------------------------------------------------------------------------------------------
**************************************************************************************************/
#include "qzeroconf.h"
#include "bonjour_p.h"
void Resolver::resolverReady()
{
DNSServiceErrorType err = DNSServiceProcessResult(DNSresolverRef);
if (err != kDNSServiceErr_NoError)
cleanUp();
}
void Resolver::addressReady()
{
DNSServiceErrorType err = DNSServiceProcessResult(DNSaddressRef);
if (err != kDNSServiceErr_NoError)
cleanUp();
}
void Resolver::cleanUp()
{
DNSServiceRefDeallocate(DNSresolverRef);
DNSServiceRefDeallocate(DNSaddressRef);
// the QSocketNotifiers resolverNotifier and addressNotifier get deleted when the QSharedPointer gets deleted along with the Resolver. No need to clear them here.
QString key = zcs->name() + QString::number(zcs->interfaceIndex());
ref->resolvers.remove(key);
delete this;
}
QZeroConfPrivate::QZeroConfPrivate(QZeroConf *parent)
{
pub = parent;
}
void QZeroConfPrivate::bsRead()
{
DNSServiceErrorType err = DNSServiceProcessResult(dnssRef);
if (err != kDNSServiceErr_NoError) {
cleanUp(dnssRef);
emit pub->error(QZeroConf::serviceRegistrationFailed);
}
}
void QZeroConfPrivate::browserRead()
{
DNSServiceErrorType err = DNSServiceProcessResult(browser);
if (err != kDNSServiceErr_NoError) {
cleanUp(browser);
emit pub->error(QZeroConf::browserFailed);
}
}
void QZeroConfPrivate::resolve(QZeroConfService zcs)
{
DNSServiceErrorType err;
Resolver *resolver = new Resolver;
QString key = zcs->name() + QString::number(zcs->interfaceIndex());
resolvers.insert(key, resolver);
resolver->ref = this;
resolver->zcs = zcs;
err = DNSServiceResolve(&resolver->DNSresolverRef, kDNSServiceFlagsTimeout, zcs->interfaceIndex(), zcs->name().toUtf8(), zcs->type().toUtf8(), zcs->domain().toUtf8(), static_cast<DNSServiceResolveReply>(resolverCallback), resolver);
if (err == kDNSServiceErr_NoError) {
int sockfd = DNSServiceRefSockFD(resolver->DNSresolverRef);
if (sockfd == -1) {
resolver->cleanUp();
}
else {
resolver->resolverNotifier = QSharedPointer<QSocketNotifier>::create(sockfd, QSocketNotifier::Read);
connect(resolver->resolverNotifier.data(), &QSocketNotifier::activated, resolver, &Resolver::resolverReady);
}
}
else {
resolver->cleanUp();
}
}
void DNSSD_API QZeroConfPrivate::registerCallback(DNSServiceRef, DNSServiceFlags, DNSServiceErrorType errorCode, const char *, const char *, const char *, void *userdata)
{
QZeroConfPrivate *ref = static_cast<QZeroConfPrivate *>(userdata);
if (errorCode == kDNSServiceErr_NoError) {
emit ref->pub->servicePublished();
}
else {
ref->cleanUp(ref->dnssRef);
emit ref->pub->error(QZeroConf::serviceRegistrationFailed);
}
}
void DNSSD_API QZeroConfPrivate::browseCallback(DNSServiceRef, DNSServiceFlags flags,
quint32 interfaceIndex, DNSServiceErrorType err, const char *name,
const char *type, const char *domain, void *userdata)
{
QString key;
QZeroConfService zcs;
QZeroConfPrivate *ref = static_cast<QZeroConfPrivate *>(userdata);
//qDebug() << name;
if (err == kDNSServiceErr_NoError) {
key = name + QString::number(interfaceIndex);
if (flags & kDNSServiceFlagsAdd) {
if (!ref->pub->services.contains(key)) {
zcs = QZeroConfService::create();
zcs->m_name = name;
zcs->m_type = type;
zcs->m_domain = domain;
zcs->m_interfaceIndex = interfaceIndex;
ref->resolve(zcs);
}
}
else if (ref->pub->services.contains(key)) {
zcs = ref->pub->services[key];
ref->pub->services.remove(key);
if (ref->resolvers.contains(key))
ref->resolvers[key]->cleanUp();
emit ref->pub->serviceRemoved(zcs);
}
}
else {
ref->cleanUp(ref->browser);
emit ref->pub->error(QZeroConf::browserFailed);
}
}
void DNSSD_API QZeroConfPrivate::resolverCallback(DNSServiceRef, DNSServiceFlags,
quint32 interfaceIndex, DNSServiceErrorType err, const char *,
const char *hostName, quint16 port, quint16 txtLen,
const unsigned char *txtRecord, void *userdata)
{
Resolver *resolver = static_cast<Resolver *>(userdata);
if (err != kDNSServiceErr_NoError) {
resolver->cleanUp();
return;
}
uchar recLen;
while (txtLen > 0) // add txt records
{
recLen = txtRecord[0];
txtRecord++;
QByteArray avahiText(reinterpret_cast<const char *>(txtRecord), recLen);
const int pos = avahiText.indexOf('=');
if (pos < 0)
resolver->zcs->m_txt[avahiText] = "";
else
resolver->zcs->m_txt[avahiText.left(pos)] = avahiText.mid(pos + 1, -1);
txtLen-= recLen + 1;
txtRecord+= recLen;
}
resolver->zcs->m_host = hostName;
resolver->zcs->m_port = qFromBigEndian<quint16>(port);
if (resolver->DNSaddressRef) {
DNSServiceRefDeallocate(resolver->DNSaddressRef);
resolver->DNSaddressRef = nullptr;
}
err = DNSServiceGetAddrInfo(&resolver->DNSaddressRef, kDNSServiceFlagsForceMulticast, interfaceIndex, resolver->ref->protocol, hostName, static_cast<DNSServiceGetAddrInfoReply>(addressReply), resolver);
if (err == kDNSServiceErr_NoError) {
int sockfd = DNSServiceRefSockFD(resolver->DNSaddressRef);
if (sockfd == -1) {
resolver->cleanUp();
}
else {
// Fix "multiple socket notifiers for same socket" warning
resolver->addressNotifier.clear();
resolver->addressNotifier = QSharedPointer<QSocketNotifier>::create(sockfd, QSocketNotifier::Read);
connect(resolver->addressNotifier.data(), &QSocketNotifier::activated, resolver, &Resolver::addressReady);
}
}
else {
resolver->cleanUp();
}
}
void DNSSD_API QZeroConfPrivate::addressReply(DNSServiceRef sdRef,
DNSServiceFlags flags, quint32 interfaceIndex,
DNSServiceErrorType err, const char *hostName,
const struct sockaddr* address, quint32 ttl, void *userdata)
{
Q_UNUSED(interfaceIndex)
Q_UNUSED(sdRef)
Q_UNUSED(ttl)
Q_UNUSED(hostName)
Resolver *resolver = static_cast<Resolver *>(userdata);
if (err == kDNSServiceErr_NoError) {
if ((flags & kDNSServiceFlagsAdd) != 0) {
QHostAddress hAddress(address);
resolver->zcs->setIp(hAddress);
QString key = resolver->zcs->name() + QString::number(interfaceIndex);
if (!resolver->ref->pub->services.contains(key)) {
resolver->ref->pub->services.insert(key, resolver->zcs);
emit resolver->ref->pub->serviceAdded(resolver->zcs);
}
else
emit resolver->ref->pub->serviceUpdated(resolver->zcs);
}
}
else
resolver->cleanUp();
}
void QZeroConfPrivate::cleanUp(DNSServiceRef toClean)
{
if (!toClean)
return;
else if (toClean == browser) {
browser = nullptr;
browserNotifier.clear();
for (auto resolver : resolvers)
resolver->cleanUp();
resolvers.clear();
for (auto service : pub->services)
emit pub->serviceRemoved(service);
pub->services.clear();
}
else if (toClean == dnssRef) {
dnssRef = nullptr;
serviceNotifier.clear();
}
DNSServiceRefDeallocate(toClean);
}
QZeroConf::QZeroConf(QObject *parent) : QObject (parent)
{
pri = new QZeroConfPrivate(this);
qRegisterMetaType<QZeroConfService>("QZeroConfService");
}
QZeroConf::~QZeroConf()
{
pri->cleanUp(pri->dnssRef);
pri->cleanUp(pri->browser);
delete pri;
}
void QZeroConf::startServicePublish(const char *name, const char *type, const char *domain, quint16 port, quint32 interface)
{
DNSServiceErrorType err;
if (pri->dnssRef) {
emit error(QZeroConf::serviceRegistrationFailed);
return;
}
err = DNSServiceRegister(&pri->dnssRef, 0, interface,
name,
type,
domain,
nullptr,
qFromBigEndian<quint16>(port),
static_cast<uint16_t>(pri->txt.size()), pri->txt.data(),
static_cast<DNSServiceRegisterReply>(QZeroConfPrivate::registerCallback), pri);
if (err == kDNSServiceErr_NoError) {
int sockfd = DNSServiceRefSockFD(pri->dnssRef);
if (sockfd == -1) {
pri->cleanUp(pri->dnssRef);
emit error(QZeroConf::serviceRegistrationFailed);
}
else {
pri->serviceNotifier = QSharedPointer<QSocketNotifier>::create(sockfd, QSocketNotifier::Read, this);
connect(pri->serviceNotifier.data(), &QSocketNotifier::activated, pri, &QZeroConfPrivate::bsRead);
}
}
else {
pri->cleanUp(pri->dnssRef);
emit error(QZeroConf::serviceRegistrationFailed);
}
}
void QZeroConf::stopServicePublish(void)
{
pri->cleanUp(pri->dnssRef);
}
bool QZeroConf::publishExists(void)
{
if (pri->dnssRef)
return true;
else
return false;
}
void QZeroConf::addServiceTxtRecord(QString nameOnly)
{
pri->txt.append(static_cast<char>(nameOnly.size()));
pri->txt.append(nameOnly.toUtf8());
}
void QZeroConf::addServiceTxtRecord(QString name, QString value)
{
name.append("=");
name.append(value);
addServiceTxtRecord(name);
}
void QZeroConf::clearServiceTxtRecords()
{
pri->txt.clear();
}
void QZeroConf::startBrowser(QString type, QAbstractSocket::NetworkLayerProtocol protocol)
{
DNSServiceErrorType err;
if (pri->browser) {
emit error(QZeroConf::browserFailed);
return;
}
switch (protocol) {
case QAbstractSocket::IPv4Protocol: pri->protocol = kDNSServiceProtocol_IPv4; break;
case QAbstractSocket::IPv6Protocol: pri->protocol = kDNSServiceProtocol_IPv6; break;
default:
qDebug("QZeroConf::startBrowser() - unsupported protocol, using IPv4");
pri->protocol = kDNSServiceProtocol_IPv4;
break;
}
err = DNSServiceBrowse(&pri->browser, 0, 0, type.toUtf8(), nullptr, static_cast<DNSServiceBrowseReply>(QZeroConfPrivate::browseCallback), pri);
if (err == kDNSServiceErr_NoError) {
int sockfd = DNSServiceRefSockFD(pri->browser);
if (sockfd == -1) {
pri->cleanUp(pri->browser);
emit error(QZeroConf::browserFailed);
}
else {
pri->browserNotifier = QSharedPointer<QSocketNotifier>::create(sockfd, QSocketNotifier::Read, this);
connect(pri->browserNotifier.data(), &QSocketNotifier::activated, pri, &QZeroConfPrivate::browserRead);
}
}
else {
pri->cleanUp(pri->browser);
emit error(QZeroConf::browserFailed);
}
}
void QZeroConf::stopBrowser(void)
{
pri->cleanUp(pri->browser);
}
bool QZeroConf::browserExists(void)
{
if (pri->browser)
return true;
else
return false;
}