Merge PR #4494: FEAT(client, server): use native mDNS/DNS-SD API on Windows, if available

This commit is contained in:
Davide Beatrici 2020-09-23 01:04:07 +02:00 committed by GitHub
commit 69dfdd2135
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 730 additions and 240 deletions

View File

@ -39,7 +39,7 @@ if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
PATH=$PATH:/usr/lib/mxe/usr/bin
${MUMBLE_HOST}.static-cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -Dtests=ON -Dversion=$VER -Dstatic=ON -Dsymbols=ON -Dasio=ON \
-Dbonjour=OFF -Dice=OFF -Doverlay=OFF -Donline-tests=ON ..
-Dzeroconf=OFF -Dice=OFF -Doverlay=OFF -Donline-tests=ON ..
cmake --build .
# TODO: investigate why tests fail.
#ctest

View File

@ -10,7 +10,7 @@ option(server "Build the server (Murmur)" ON)
option(qssldiffiehellmanparameters "Build support for custom Diffie-Hellman parameters." ON)
option(bonjour "Build support for Bonjour." ON)
option(zeroconf "Build support for zeroconf (mDNS/DNS-SD)." ON)
option(dbus "Build support for DBus." ON)

View File

@ -1,21 +0,0 @@
// Copyright 2005-2020 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "BonjourClient.h"
BonjourClient::BonjourClient() {
#ifdef Q_OS_WIN
HMODULE hLib = LoadLibrary(L"DNSSD.DLL");
if (!hLib) {
qWarning("Bonjour: Failed to load dnssd.dll");
return;
}
FreeLibrary(hLib);
#endif
bsbBrowser.reset(new BonjourServiceBrowser(this));
bsbBrowser->browseForServiceType(QLatin1String("_mumble._tcp"));
bsrResolver.reset(new BonjourServiceResolver(this));
return;
}

View File

@ -1,23 +0,0 @@
// Copyright 2005-2020 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#ifndef MUMBLE_MUMBLE_BONJOURCLIENT_H_
#define MUMBLE_MUMBLE_BONJOURCLIENT_H_
#include "BonjourServiceBrowser.h"
#include "BonjourServiceResolver.h"
class BonjourClient : public QObject {
private:
Q_OBJECT
Q_DISABLE_COPY(BonjourClient)
public:
BonjourClient();
QScopedPointer< BonjourServiceBrowser > bsbBrowser;
QScopedPointer< BonjourServiceResolver > bsrResolver;
};
#endif

View File

@ -814,7 +814,7 @@ if(g15)
endif()
endif()
if(bonjour)
if(zeroconf)
if(NOT APPLE)
find_pkg(avahi-compat-libdns_sd QUIET)
if(avahi-compat-libdns_sd_FOUND)
@ -831,9 +831,10 @@ if(bonjour)
target_sources(mumble
PRIVATE
"BonjourClient.cpp"
"BonjourClient.h"
"Zeroconf.cpp"
"Zeroconf.h"
# Unlike what the name implies, this 3rdparty helper is not actually related to Bonjour.
# It just uses the API provided by mDNSResponder, making it compatible with Avahi too.
"${3RDPARTY_DIR}/qqbonjour/BonjourRecord.h"
"${3RDPARTY_DIR}/qqbonjour/BonjourServiceBrowser.cpp"
"${3RDPARTY_DIR}/qqbonjour/BonjourServiceBrowser.h"
@ -841,7 +842,7 @@ if(bonjour)
"${3RDPARTY_DIR}/qqbonjour/BonjourServiceResolver.h"
)
target_compile_definitions(mumble PRIVATE "USE_BONJOUR")
target_compile_definitions(mumble PRIVATE "USE_ZEROCONF")
target_include_directories(mumble PRIVATE "${3RDPARTY_DIR}/qqbonjour")
endif()

View File

@ -5,10 +5,8 @@
#include "ConnectDialog.h"
#ifdef USE_BONJOUR
# include "BonjourClient.h"
# include "BonjourServiceBrowser.h"
# include "BonjourServiceResolver.h"
#ifdef USE_ZEROCONF
# include "Zeroconf.h"
#endif
#include "Channel.h"
@ -102,7 +100,7 @@ ServerView::ServerView(QWidget *p) : QTreeWidget(p) {
siFavorite->setExpanded(true);
siFavorite->setHidden(true);
#ifdef USE_BONJOUR
#ifdef USE_ZEROCONF
siLAN = new ServerItem(tr("LAN"), ServerItem::LANType);
addTopLevelItem(siLAN);
siLAN->setExpanded(true);
@ -220,10 +218,10 @@ ServerItem::ServerItem(const FavoriteServer &fs) : QTreeWidgetItem(QTreeWidgetIt
qsUrl = fs.qsUrl;
bCA = false;
#ifdef USE_BONJOUR
#ifdef USE_ZEROCONF
if (fs.qsHostname.startsWith(QLatin1Char('@'))) {
qsBonjourHost = fs.qsHostname.mid(1);
brRecord = BonjourRecord(qsBonjourHost, QLatin1String("_mumble._tcp."), QLatin1String("local."));
zeroconfHost = fs.qsHostname.mid(1);
zeroconfRecord = BonjourRecord(zeroconfHost, QLatin1String("_mumble._tcp."), QLatin1String("local."));
} else {
qsHostname = fs.qsHostname;
}
@ -261,10 +259,10 @@ ServerItem::ServerItem(const QString &name, const QString &host, unsigned short
qsPassword = password;
bCA = false;
#ifdef USE_BONJOUR
#ifdef USE_ZEROCONF
if (host.startsWith(QLatin1Char('@'))) {
qsBonjourHost = host.mid(1);
brRecord = BonjourRecord(qsBonjourHost, QLatin1String("_mumble._tcp."), QLatin1String("local."));
zeroconfHost = host.mid(1);
zeroconfRecord = BonjourRecord(zeroconfHost, QLatin1String("_mumble._tcp."), QLatin1String("local."));
} else {
qsHostname = host;
}
@ -274,16 +272,16 @@ ServerItem::ServerItem(const QString &name, const QString &host, unsigned short
init();
}
#ifdef USE_BONJOUR
#ifdef USE_ZEROCONF
ServerItem::ServerItem(const BonjourRecord &br) : QTreeWidgetItem(QTreeWidgetItem::UserType) {
siParent = nullptr;
bParent = false;
itType = LANType;
qsName = br.serviceName;
qsBonjourHost = qsName;
brRecord = br;
usPort = 0;
bCA = false;
siParent = nullptr;
bParent = false;
itType = LANType;
qsName = br.serviceName;
zeroconfHost = qsName;
zeroconfRecord = br;
usPort = 0;
bCA = false;
init();
}
@ -314,9 +312,9 @@ ServerItem::ServerItem(const ServerItem *si) {
qsCountryCode = si->qsCountryCode;
qsContinentCode = si->qsContinentCode;
qsUrl = si->qsUrl;
#ifdef USE_BONJOUR
qsBonjourHost = si->qsBonjourHost;
brRecord = si->brRecord;
#ifdef USE_ZEROCONF
zeroconfHost = si->zeroconfHost;
zeroconfRecord = si->zeroconfRecord;
#endif
qlAddresses = si->qlAddresses;
bCA = si->bCA;
@ -490,10 +488,10 @@ QVariant ServerItem::data(int column, int role) const {
.arg(ConnectDialog::tr("Servername"), qsName.toHtmlEscaped())
+ QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>")
.arg(ConnectDialog::tr("Hostname"), qsHostname.toHtmlEscaped());
#ifdef USE_BONJOUR
if (!qsBonjourHost.isEmpty())
#ifdef USE_ZEROCONF
if (!zeroconfHost.isEmpty())
qs += QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>")
.arg(ConnectDialog::tr("Bonjour name"), qsBonjourHost.toHtmlEscaped());
.arg(ConnectDialog::tr("Bonjour name"), zeroconfHost.toHtmlEscaped());
#endif
qs += QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>")
.arg(ConnectDialog::tr("Port"))
@ -592,9 +590,9 @@ void ServerItem::setDatas(double elapsed, quint32 users, quint32 maxusers) {
FavoriteServer ServerItem::toFavoriteServer() const {
FavoriteServer fs;
fs.qsName = qsName;
#ifdef USE_BONJOUR
if (!qsBonjourHost.isEmpty())
fs.qsHostname = QLatin1Char('@') + qsBonjourHost;
#ifdef USE_ZEROCONF
if (!zeroconfHost.isEmpty())
fs.qsHostname = QLatin1Char('@') + zeroconfHost;
else
fs.qsHostname = qsHostname;
#else
@ -944,7 +942,7 @@ ConnectDialog::ConnectDialog(QWidget *p, bool autoconnect) : QDialog(p), bAutoCo
bAllowPing = g.s.ptProxyType == Settings::NoProxy;
bAllowHostLookup = g.s.ptProxyType == Settings::NoProxy;
bAllowBonjour = g.s.ptProxyType == Settings::NoProxy;
bAllowZeroconf = g.s.ptProxyType == Settings::NoProxy;
bAllowFilters = g.s.ptProxyType == Settings::NoProxy;
if (tPublicServers.elapsed() >= 60 * 24 * 1000000ULL) {
@ -1033,22 +1031,16 @@ ConnectDialog::ConnectDialog(QWidget *p, bool autoconnect) : QDialog(p), bAutoCo
startDns(si);
qtwServers->siFavorite->addServerItem(si);
}
#ifdef USE_ZEROCONF
if (bAllowZeroconf && g.zeroconf && g.zeroconf->isOk()) {
connect(g.zeroconf, &Zeroconf::recordsChanged, this, &ConnectDialog::onUpdateLanList);
connect(g.zeroconf, &Zeroconf::recordResolved, this, &ConnectDialog::onResolved);
connect(g.zeroconf, &Zeroconf::resolveError, this, &ConnectDialog::onLanResolveError);
onUpdateLanList(g.zeroconf->currentRecords());
#ifdef USE_BONJOUR
// Make sure the we got the objects we need, then wire them up
if (bAllowBonjour && g.bc->bsbBrowser && g.bc->bsrResolver) {
connect(g.bc->bsbBrowser.data(), SIGNAL(error(DNSServiceErrorType)), this,
SLOT(onLanBrowseError(DNSServiceErrorType)));
connect(g.bc->bsbBrowser.data(), SIGNAL(currentBonjourRecordsChanged(const QList< BonjourRecord > &)), this,
SLOT(onUpdateLanList(const QList< BonjourRecord > &)));
connect(g.bc->bsrResolver.data(), SIGNAL(error(BonjourRecord, DNSServiceErrorType)), this,
SLOT(onLanResolveError(BonjourRecord, DNSServiceErrorType)));
connect(g.bc->bsrResolver.data(), SIGNAL(bonjourRecordResolved(BonjourRecord, QString, int)), this,
SLOT(onResolved(BonjourRecord, QString, int)));
onUpdateLanList(g.bc->bsbBrowser->currentRecords());
g.zeroconf->startBrowser(QLatin1String("_mumble._tcp"));
}
#endif
qtPingTick = new QTimer(this);
connect(qtPingTick, SIGNAL(timeout()), this, SLOT(timeTick()));
@ -1082,6 +1074,12 @@ ConnectDialog::ConnectDialog(QWidget *p, bool autoconnect) : QDialog(p), bAutoCo
}
ConnectDialog::~ConnectDialog() {
#ifdef USE_ZEROCONF
if (bAllowZeroconf && g.zeroconf && g.zeroconf->isOk()) {
g.zeroconf->stopBrowser();
g.zeroconf->cleanupResolvers();
}
#endif
ServerItem::qmIcons.clear();
QList< FavoriteServer > ql;
@ -1175,9 +1173,9 @@ void ConnectDialog::on_qaFavoriteEdit_triggered() {
return;
QString host;
#ifdef USE_BONJOUR
if (!si->qsBonjourHost.isEmpty())
host = QLatin1Char('@') + si->qsBonjourHost;
#ifdef USE_ZEROCONF
if (!si->zeroconfHost.isEmpty())
host = QLatin1Char('@') + si->zeroconfHost;
else
host = si->qsHostname;
#else
@ -1196,16 +1194,16 @@ void ConnectDialog::on_qaFavoriteEdit_triggered() {
si->reset();
si->usPort = cde->usPort;
#ifdef USE_BONJOUR
#ifdef USE_ZEROCONF
if (cde->qsHostname.startsWith(QLatin1Char('@'))) {
si->qsHostname = QString();
si->qsBonjourHost = cde->qsHostname.mid(1);
si->brRecord =
BonjourRecord(si->qsBonjourHost, QLatin1String("_mumble._tcp."), QLatin1String("local."));
si->qsHostname = QString();
si->zeroconfHost = cde->qsHostname.mid(1);
si->zeroconfRecord =
BonjourRecord(si->zeroconfHost, QLatin1String("_mumble._tcp."), QLatin1String("local."));
} else {
si->qsHostname = cde->qsHostname;
si->qsBonjourHost = QString();
si->brRecord = BonjourRecord();
si->qsHostname = cde->qsHostname;
si->zeroconfHost = QString();
si->zeroconfRecord = BonjourRecord();
}
#else
si->qsHostname = cde->qsHostname;
@ -1355,11 +1353,11 @@ void ConnectDialog::initList() {
WebFetch::fetch(QLatin1String("publist"), url, this, SLOT(fetched(QByteArray, QUrl, QMap< QString, QString >)));
}
#ifdef USE_BONJOUR
void ConnectDialog::onResolved(BonjourRecord record, QString host, int port) {
#ifdef USE_ZEROCONF
void ConnectDialog::onResolved(const BonjourRecord record, const QString host, const uint16_t port) {
qlBonjourActive.removeAll(record);
foreach (ServerItem *si, qlItems) {
if (si->brRecord == record) {
if (si->zeroconfRecord == record) {
unsigned short usport = static_cast< unsigned short >(port);
if ((host != si->qsHostname) || (usport != si->usPort)) {
stopDns(si);
@ -1384,7 +1382,7 @@ void ConnectDialog::onUpdateLanList(const QList< BonjourRecord > &list) {
foreach (const BonjourRecord &record, list) {
bool found = false;
foreach (ServerItem *si, old) {
if (si->brRecord == record) {
if (si->zeroconfRecord == record) {
items.insert(si);
found = true;
break;
@ -1393,7 +1391,7 @@ void ConnectDialog::onUpdateLanList(const QList< BonjourRecord > &list) {
if (!found) {
ServerItem *si = new ServerItem(record);
qlItems << si;
g.bc->bsrResolver->resolveBonjourRecord(record);
g.zeroconf->startResolver(record);
startDns(si);
qtwServers->siLAN->addServerItem(si);
}
@ -1406,13 +1404,8 @@ void ConnectDialog::onUpdateLanList(const QList< BonjourRecord > &list) {
}
}
void ConnectDialog::onLanBrowseError(DNSServiceErrorType err) {
qWarning() << "Bonjour reported browser error " << err;
}
void ConnectDialog::onLanResolveError(BonjourRecord br, DNSServiceErrorType err) {
qlBonjourActive.removeAll(br);
qWarning() << "Bonjour reported resolver error " << err;
void ConnectDialog::onLanResolveError(const BonjourRecord record) {
qlBonjourActive.removeAll(record);
}
#endif
@ -1627,17 +1620,15 @@ void ConnectDialog::startDns(ServerItem *si) {
foreach (const ServerAddress &addr, si->qlAddresses) { qhPings[addr].insert(si); }
return;
}
#ifdef USE_BONJOUR
if (bAllowBonjour && si->qsHostname.isEmpty() && !si->brRecord.serviceName.isEmpty()) {
if (!qlBonjourActive.contains(si->brRecord)) {
g.bc->bsrResolver->resolveBonjourRecord(si->brRecord);
qlBonjourActive.append(si->brRecord);
#ifdef USE_ZEROCONF
if (bAllowZeroconf && si->qsHostname.isEmpty() && !si->zeroconfRecord.serviceName.isEmpty()) {
if (!qlBonjourActive.contains(si->zeroconfRecord)) {
g.zeroconf->startResolver(si->zeroconfRecord);
qlBonjourActive.append(si->zeroconfRecord);
}
return;
}
#endif
if (!qhDNSWait.contains(unresolved)) {
if (si->itType == ServerItem::PublicType)
qlDNSLookup.append(unresolved);

View File

@ -20,7 +20,7 @@
#include <QtNetwork/QHostInfo>
#ifdef USE_BONJOUR
#ifdef USE_ZEROCONF
# include "BonjourRecord.h"
# include <dns_sd.h>
#endif
@ -136,9 +136,9 @@ public:
QString qsContinentCode;
QString qsUrl;
#ifdef USE_BONJOUR
QString qsBonjourHost;
BonjourRecord brRecord;
#ifdef USE_ZEROCONF
QString zeroconfHost;
BonjourRecord zeroconfRecord;
#endif
/// Contains the resolved addresses for
/// this ServerItem.
@ -150,7 +150,7 @@ public:
ServerItem(const PublicInfo &pi);
ServerItem(const QString &name, const QString &host, unsigned short port, const QString &uname,
const QString &password = QString());
#ifdef USE_BONJOUR
#ifdef USE_ZEROCONF
ServerItem(const BonjourRecord &br);
#endif
ServerItem(const QString &name, ItemType itype);
@ -279,9 +279,9 @@ protected:
/// bAllowHostLookup determines whether ConnectDialog can
/// resolve hosts via DNS, Bonjour, and so on.
bool bAllowHostLookup;
/// bAllowBonjour determines whether ConfigDialog can use
/// Bonjour to find nearby servers on the local network.
bool bAllowBonjour;
/// bAllowZeroconf determines whether ConfigDialog can use
/// zeroconf to find nearby servers on the local network.
bool bAllowZeroconf;
/// bAllowFilters determines whether filters are available
/// in the ConfigDialog. If this option is diabled, the
/// 'Show All' filter is forced, and no other filter can
@ -353,15 +353,14 @@ public:
ConnectDialog(QWidget *parent, bool autoconnect);
~ConnectDialog();
#ifdef USE_BONJOUR
#ifdef USE_ZEROCONF
protected:
QList< BonjourRecord > qlBonjourActive;
public slots:
void onUpdateLanList(const QList< BonjourRecord > &);
void onLanBrowseError(DNSServiceErrorType);
void onResolved(BonjourRecord, QString, int);
void onLanResolveError(BonjourRecord, DNSServiceErrorType);
void onResolved(const BonjourRecord, const QString, const uint16_t);
void onLanResolveError(const BonjourRecord);
#endif
private slots:
void on_qleSearchServername_textChanged(const QString &searchServername);

View File

@ -105,9 +105,9 @@ Global::Global(const QString &qsConfigPath) {
qs = nullptr;
bc = nullptr;
lcd = nullptr;
l = nullptr;
zeroconf = nullptr;
lcd = nullptr;
l = nullptr;
#ifdef USE_OVERLAY
ocIntercept = nullptr;

View File

@ -26,7 +26,7 @@ class Plugins;
class QSettings;
class Overlay;
class LCD;
class BonjourClient;
class Zeroconf;
class OverlayClient;
class CELTCodec;
class OpusCodec;
@ -57,7 +57,7 @@ public:
Overlay *o;
#endif
LCD *lcd;
BonjourClient *bc;
Zeroconf *zeroconf;
QNetworkAccessManager *nam;
QSharedPointer< LogEmitter > le;
DeveloperConsole *c;

302
src/mumble/Zeroconf.cpp Normal file
View File

@ -0,0 +1,302 @@
// Copyright 2005-2020 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "Zeroconf.h"
#define SYMBOL_EXISTS(symbol) (GetProcAddress(handle, symbol))
Zeroconf::Zeroconf() : m_ok(false) {
qRegisterMetaType< uint16_t >("uint16_t");
qRegisterMetaType< BonjourRecord >("BonjourRecord");
qRegisterMetaType< QList< BonjourRecord > >("QList<BonjourRecord>");
#ifdef Q_OS_WIN64
static bool winDnsUnavailable = false;
if (!winDnsUnavailable) {
auto handle = GetModuleHandle(L"dnsapi.dll");
if (handle) {
if (SYMBOL_EXISTS("DnsServiceBrowse") && SYMBOL_EXISTS("DnsServiceBrowseCancel")
&& SYMBOL_EXISTS("DnsServiceResolve") && SYMBOL_EXISTS("DnsServiceResolveCancel")
&& SYMBOL_EXISTS("DnsServiceFreeInstance")) {
m_ok = true;
return;
}
}
winDnsUnavailable = true;
qWarning("Zeroconf: Native mDNS/DNS-SD API not available, falling back to third-party API");
}
static bool dnssdLoadFailed = false;
if (!dnssdLoadFailed) {
auto handle = LoadLibrary(L"dnssd.DLL");
if (!handle) {
dnssdLoadFailed = true;
qWarning("Zeroconf: Failed to load dnssd.dll, assuming third-party API is not available");
return;
}
FreeLibrary(handle);
}
#endif
resetHelperBrowser();
resetHelperResolver();
m_ok = true;
}
Zeroconf::~Zeroconf() {
if (!m_helperBrowser) {
stopBrowser();
}
if (!m_helperResolver) {
cleanupResolvers();
}
}
void Zeroconf::resetHelperBrowser() {
m_helperBrowser.reset(new BonjourServiceBrowser(this));
connect(m_helperBrowser.get(), &BonjourServiceBrowser::currentBonjourRecordsChanged, this,
&Zeroconf::helperBrowserRecordsChanged);
connect(m_helperBrowser.get(), &BonjourServiceBrowser::error, this, &Zeroconf::helperBrowserError);
}
void Zeroconf::resetHelperResolver() {
m_helperResolver.reset(new BonjourServiceResolver(this));
connect(m_helperResolver.get(), &BonjourServiceResolver::bonjourRecordResolved, this,
&Zeroconf::helperResolverRecordResolved);
connect(m_helperResolver.get(), &BonjourServiceResolver::error, this, &Zeroconf::helperResolverError);
}
bool Zeroconf::startBrowser(const QString &serviceType) {
if (!m_ok) {
return false;
}
stopBrowser();
if (m_helperBrowser) {
m_helperBrowser->browseForServiceType(serviceType);
return true;
}
#ifdef Q_OS_WIN64
const QString queryName = serviceType + QLatin1String(".local");
DNS_SERVICE_BROWSE_REQUEST request{};
request.Version = DNS_QUERY_REQUEST_VERSION1;
request.QueryName = reinterpret_cast< LPCWSTR >(queryName.utf16());
request.pBrowseCallback = callbackBrowseComplete;
request.pQueryContext = this;
m_cancelBrowser.reset(new DNS_SERVICE_CANCEL{});
const auto ret = DnsServiceBrowse(&request, m_cancelBrowser.get());
if (ret == DNS_REQUEST_PENDING) {
return true;
}
m_cancelBrowser.reset();
qWarning("Zeroconf: DnsServiceBrowse() failed with error %u!", ret);
#endif
return false;
}
bool Zeroconf::stopBrowser() {
if (!m_ok) {
return false;
}
if (m_helperBrowser) {
resetHelperBrowser();
return true;
}
#ifdef Q_OS_WIN64
if (m_cancelBrowser) {
const auto ret = DnsServiceBrowseCancel(m_cancelBrowser.get());
if (ret == ERROR_SUCCESS || ret == ERROR_CANCELLED) {
m_cancelBrowser.reset();
return true;
}
qWarning("Zeroconf: DnsServiceBrowseCancel() failed with error %u!", ret);
return false;
}
#endif
return true;
}
bool Zeroconf::startResolver(const BonjourRecord &record) {
if (!m_ok) {
return false;
}
if (m_helperResolver) {
m_helperResolver->resolveBonjourRecord(record);
return true;
}
#ifdef Q_OS_WIN64
stopResolver(record);
auto qualifiedHostname = record.serviceName.toStdWString() + L"." + record.registeredType.toStdWString()
+ record.replyDomain.toStdWString();
m_resolvers.append(Resolver(this, record));
auto context = &m_resolvers.last();
DNS_SERVICE_RESOLVE_REQUEST request{};
request.Version = DNS_QUERY_REQUEST_VERSION1;
request.QueryName = &qualifiedHostname[0];
request.pResolveCompletionCallback = &Zeroconf::callbackResolveComplete;
request.pQueryContext = context;
const auto ret = DnsServiceResolve(&request, &context->m_cancel);
if (ret == DNS_REQUEST_PENDING) {
return true;
}
m_resolvers.removeLast();
qWarning("Zeroconf: DnsServiceResolve() failed with error %u!", ret);
#endif
return false;
}
#ifdef Q_OS_WIN64
bool Zeroconf::stopResolver(const BonjourRecord &record) {
if (!m_ok) {
return false;
}
Resolver tmp(this, record);
if (!m_resolvers.contains(tmp)) {
return true;
}
auto resolver = m_resolvers[m_resolvers.indexOf(tmp)];
return stopResolver(resolver);
}
bool Zeroconf::stopResolver(Resolver &resolver) {
if (!m_ok) {
return false;
}
const auto ret = DnsServiceResolveCancel(&resolver.m_cancel);
if (ret == ERROR_SUCCESS || ret == ERROR_CANCELLED) {
return true;
}
qWarning("Zeroconf: DnsServiceResolveCancel() failed with error %u!", ret);
return false;
}
#endif
bool Zeroconf::cleanupResolvers() {
if (!m_ok) {
return false;
}
if (m_helperResolver) {
resetHelperResolver();
return true;
}
auto result = true;
#ifdef Q_OS_WIN64
for (auto i = 0; i < m_resolvers.size(); ++i) {
if (!stopResolver(m_resolvers[i])) {
result = false;
}
}
m_resolvers.clear();
#endif
return result;
}
void Zeroconf::helperBrowserRecordsChanged(const QList< BonjourRecord > &records) {
emit recordsChanged(records);
}
void Zeroconf::helperResolverRecordResolved(const BonjourRecord record, const QString hostname, const int port) {
emit recordResolved(record, hostname, port);
}
void Zeroconf::helperBrowserError(const DNSServiceErrorType error) const {
qWarning("Zeroconf: Third-party browser API reports error %d", error);
}
void Zeroconf::helperResolverError(const BonjourRecord record, const DNSServiceErrorType error) {
qWarning("Zeroconf: Third-party resolver API reports error %d", error);
emit resolveError(record);
}
#ifdef Q_OS_WIN64
void WINAPI Zeroconf::callbackBrowseComplete(const DWORD status, void *context, DNS_RECORD *records) {
auto zeroconf = static_cast< Zeroconf * >(context);
zeroconf->m_cancelBrowser.reset();
if (status != ERROR_SUCCESS) {
if (records) {
DnsRecordListFree(records, DnsFreeRecordList);
}
qWarning("Zeroconf: DnsServiceBrowse() reports status code %u, ignoring results", status);
return;
}
if (!records) {
return;
}
bool changed = false;
for (auto cur = records; cur; cur = cur->pNext) {
if (cur->wType != DNS_TYPE_PTR) {
continue;
}
// Example: "_mumble._tcp.local".
const auto domain = QString::fromWCharArray(cur->pName);
// Example: "Test._mumble._tcp.local".
const auto hostname = QString::fromWCharArray(cur->Data.PTR.pNameHost);
BonjourRecord record;
record.serviceName = hostname.left(hostname.lastIndexOf(domain));
// Trim trailing ".".
record.serviceName.resize(record.serviceName.size() - 1);
record.replyDomain = domain.mid(domain.lastIndexOf('.'));
record.registeredType = domain.left(domain.lastIndexOf(record.replyDomain));
if (!zeroconf->m_records.contains(record)) {
zeroconf->m_records.append(record);
changed = true;
}
}
DnsRecordListFree(records, DnsFreeRecordList);
if (changed) {
emit zeroconf->recordsChanged(zeroconf->m_records);
}
}
void WINAPI Zeroconf::callbackResolveComplete(const DWORD status, void *context, DNS_SERVICE_INSTANCE *instance) {
if (status != ERROR_SUCCESS) {
if (instance) {
DnsServiceFreeInstance(instance);
}
qWarning("Zeroconf: DnsServiceResolve() reports status code %u, ignoring result", status);
return;
}
if (!instance) {
return;
}
auto resolverContext = static_cast< Resolver * >(context);
emit resolverContext->m_zeroconf->recordResolved(resolverContext->m_record,
QString::fromWCharArray(instance->pszHostName), instance->wPort);
DnsServiceFreeInstance(instance);
}
#endif

78
src/mumble/Zeroconf.h Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2005-2020 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#ifndef MUMBLE_MUMBLE_ZEROCONF_H_
#define MUMBLE_MUMBLE_ZEROCONF_H_
#include "BonjourServiceBrowser.h"
#include "BonjourServiceResolver.h"
#include <memory>
#ifdef Q_OS_WIN64
# include <windns.h>
#endif
class Zeroconf : public QObject {
private:
Q_OBJECT
Q_DISABLE_COPY(Zeroconf)
protected:
#ifdef Q_OS_WIN64
struct Resolver {
Zeroconf *m_zeroconf;
BonjourRecord m_record;
DNS_SERVICE_CANCEL m_cancel;
bool operator==(const Resolver &other) const { return m_record == other.m_record; }
Resolver(Zeroconf *zeroconf, const BonjourRecord &record) : m_zeroconf(zeroconf), m_record(record){};
};
#endif
bool m_ok;
QList< BonjourRecord > m_records;
std::unique_ptr< BonjourServiceBrowser > m_helperBrowser;
std::unique_ptr< BonjourServiceResolver > m_helperResolver;
#ifdef Q_OS_WIN64
QList< Resolver > m_resolvers;
std::unique_ptr< DNS_SERVICE_CANCEL > m_cancelBrowser;
bool stopResolver(Resolver &resolver);
static void WINAPI callbackBrowseComplete(const DWORD status, void *context, DNS_RECORD *records);
static void WINAPI callbackResolveComplete(const DWORD status, void *context, DNS_SERVICE_INSTANCE *instance);
#endif
void resetHelperBrowser();
void resetHelperResolver();
void helperBrowserRecordsChanged(const QList< BonjourRecord > &records);
void helperResolverRecordResolved(const BonjourRecord record, const QString hostname, const int port);
void helperBrowserError(const DNSServiceErrorType error) const;
void helperResolverError(const BonjourRecord record, const DNSServiceErrorType error);
public:
inline bool isOk() const { return m_ok; }
inline QList< BonjourRecord > currentRecords() const {
return m_helperBrowser ? m_helperBrowser->currentRecords() : m_records;
}
bool startBrowser(const QString &serviceType);
bool stopBrowser();
bool startResolver(const BonjourRecord &record);
#ifdef Q_OS_WIN64
bool stopResolver(const BonjourRecord &record);
#endif
bool cleanupResolvers();
Zeroconf();
~Zeroconf();
signals:
void recordsChanged(const QList< BonjourRecord > &records);
void recordResolved(const BonjourRecord record, const QString hostname, const uint16_t port);
void resolveError(const BonjourRecord record);
};
#endif

View File

@ -18,8 +18,8 @@
#include "MainWindow.h"
#include "Plugins.h"
#include "ServerHandler.h"
#ifdef USE_BONJOUR
# include "BonjourClient.h"
#ifdef USE_ZEROCONF
# include "Zeroconf.h"
#endif
#ifdef USE_DBUS
# include "DBus.h"
@ -559,9 +559,9 @@ int main(int argc, char **argv) {
// Initialize database
g.db = new Database(QLatin1String("main"));
#ifdef USE_BONJOUR
// Initialize bonjour
g.bc = new BonjourClient();
#ifdef USE_ZEROCONF
// Initialize zeroconf
g.zeroconf = new Zeroconf();
#endif
#ifdef USE_OVERLAY
@ -759,8 +759,8 @@ int main(int argc, char **argv) {
delete g.p;
delete g.l;
#ifdef USE_BONJOUR
delete g.bc;
#ifdef USE_ZEROCONF
delete g.zeroconf;
#endif
#ifdef USE_OVERLAY

View File

@ -98,7 +98,7 @@
# include <mmintrin.h>
# endif
# ifdef USE_BONJOUR
# ifdef USE_ZEROCONF
# include <dns_sd.h>
# endif

View File

@ -1,31 +0,0 @@
// Copyright 2005-2020 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "BonjourServer.h"
#include "BonjourServiceRegister.h"
BonjourServer::BonjourServer() {
bsrRegister = nullptr;
#ifdef Q_OS_WIN
static bool bDelayLoadFailed = false;
if (bDelayLoadFailed)
return;
HMODULE hLib = LoadLibrary(L"DNSSD.DLL");
if (!hLib) {
bDelayLoadFailed = true;
qWarning("Bonjour: Failed to load dnssd.dll");
return;
}
FreeLibrary(hLib);
#endif
bsrRegister = new BonjourServiceRegister(this);
}
BonjourServer::~BonjourServer() {
delete bsrRegister;
}

View File

@ -1,24 +0,0 @@
// Copyright 2005-2020 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#ifndef MUMBLE_MURMUR_BONJOURSERVER_H_
#define MUMBLE_MURMUR_BONJOURSERVER_H_
#include <QtCore/QObject>
class BonjourServiceRegister;
class BonjourServer : public QObject {
private:
Q_OBJECT
Q_DISABLE_COPY(BonjourServer)
public:
BonjourServer();
~BonjourServer();
BonjourServiceRegister *bsrRegister;
};
#endif

View File

@ -145,7 +145,7 @@ else()
set_target_properties(murmur PROPERTIES OUTPUT_NAME "murmurd")
endif()
if(bonjour)
if(zeroconf)
if(NOT APPLE)
find_pkg(avahi-compat-libdns_sd QUIET)
if(avahi-compat-libdns_sd_FOUND)
@ -160,15 +160,16 @@ if(bonjour)
endif()
endif()
target_compile_definitions(murmur PRIVATE "USE_BONJOUR")
target_compile_definitions(murmur PRIVATE "USE_ZEROCONF")
target_include_directories(murmur PRIVATE "${3RDPARTY_DIR}/qqbonjour")
target_sources(murmur
PRIVATE
"BonjourServer.cpp"
"BonjourServer.h"
"Zeroconf.cpp"
"Zeroconf.h"
# Unlike what the name implies, this 3rdparty helper is not actually related to Bonjour.
# It just uses the API provided by mDNSResponder, making it compatible with Avahi too.
"${3RDPARTY_DIR}/qqbonjour/BonjourRecord.h"
"${3RDPARTY_DIR}/qqbonjour/BonjourServiceRegister.cpp"
"${3RDPARTY_DIR}/qqbonjour/BonjourServiceRegister.h"

View File

@ -22,9 +22,8 @@
#include "User.h"
#include "Version.h"
#ifdef USE_BONJOUR
# include "BonjourServer.h"
# include "BonjourServiceRegister.h"
#ifdef USE_ZEROCONF
# include "Zeroconf.h"
#endif
#include "Utils.h"
@ -116,8 +115,8 @@ QSslSocket *SslServer::nextPendingSSLConnection() {
Server::Server(int snum, QObject *p) : QThread(p) {
bValid = true;
iServerNum = snum;
#ifdef USE_BONJOUR
bsRegistration = nullptr;
#ifdef USE_ZEROCONF
zeroconf = nullptr;
#endif
bUsingMetaCert = false;
@ -273,9 +272,9 @@ Server::Server(int snum, QObject *p) : QThread(p) {
uiVersionBlob = qToBigEndian(static_cast< quint32 >((major << 16) | (minor << 8) | patch));
if (bValid) {
#ifdef USE_BONJOUR
#ifdef USE_ZEROCONF
if (bBonjour)
initBonjour();
initZeroconf();
#endif
initRegister();
}
@ -327,8 +326,8 @@ void Server::stopThread() {
}
Server::~Server() {
#ifdef USE_BONJOUR
removeBonjour();
#ifdef USE_ZEROCONF
removeZeroconf();
#endif
stopThread();
@ -626,11 +625,12 @@ void Server::setLiveConf(const QString &key, const QString &value) {
bForceExternalAuth = !v.isNull() ? QVariant(v).toBool() : Meta::mp.bForceExternalAuth;
else if (key == "bonjour") {
bBonjour = !v.isNull() ? QVariant(v).toBool() : Meta::mp.bBonjour;
#ifdef USE_BONJOUR
if (bBonjour && !bsRegistration)
initBonjour();
else if (!bBonjour && bsRegistration)
removeBonjour();
#ifdef USE_ZEROCONF
if (bBonjour && !zeroconf) {
initZeroconf();
} else if (!bBonjour && zeroconf) {
removeZeroconf();
}
#endif
} else if (key == "allowping")
bAllowPing = !v.isNull() ? QVariant(v).toBool() : Meta::mp.bAllowPing;
@ -664,19 +664,30 @@ void Server::setLiveConf(const QString &key, const QString &value) {
}
}
#ifdef USE_BONJOUR
void Server::initBonjour() {
bsRegistration = new BonjourServer();
if (bsRegistration->bsrRegister) {
log("Announcing server via bonjour");
bsRegistration->bsrRegister->registerService(BonjourRecord(qsRegName, "_mumble._tcp", ""), usPort);
#ifdef USE_ZEROCONF
void Server::initZeroconf() {
zeroconf = new Zeroconf();
if (zeroconf->isOk()) {
log("Registering zeroconf service...");
zeroconf->registerService(BonjourRecord(qsRegName, "_mumble._tcp", ""), usPort);
return;
}
delete zeroconf;
zeroconf = nullptr;
}
void Server::removeBonjour() {
delete bsRegistration;
bsRegistration = nullptr;
log("Stopped announcing server via bonjour");
void Server::removeZeroconf() {
if (!zeroconf) {
return;
}
if (zeroconf->isOk()) {
log("Unregistering zeroconf service...");
}
delete zeroconf;
zeroconf = nullptr;
}
#endif

View File

@ -45,7 +45,7 @@
# include <winsock2.h>
#endif
class BonjourServer;
class Zeroconf;
class Channel;
class PacketDataStream;
class ServerUser;
@ -98,8 +98,8 @@ protected:
QNetworkAccessManager *qnamNetwork;
#ifdef USE_BONJOUR
BonjourServer *bsRegistration;
#ifdef USE_ZEROCONF
Zeroconf *zeroconf;
#endif
void startThread();
void stopThread();
@ -173,9 +173,9 @@ public:
bool bOpus;
void recheckCodecVersions(ServerUser *connectingUser = 0);
#ifdef USE_BONJOUR
void initBonjour();
void removeBonjour();
#ifdef USE_ZEROCONF
void initZeroconf();
void removeZeroconf();
#endif
// Registration, implementation in Register.cpp
QTimer qtTick;

162
src/murmur/Zeroconf.cpp Normal file
View File

@ -0,0 +1,162 @@
// Copyright 2005-2020 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "Zeroconf.h"
#define SYMBOL_EXISTS(symbol) (GetProcAddress(handle, symbol))
Zeroconf::Zeroconf() : m_ok(false) {
#ifdef Q_OS_WIN64
static bool winDnsUnavailable = false;
if (!winDnsUnavailable) {
auto handle = GetModuleHandle(L"dnsapi.dll");
if (handle) {
if (SYMBOL_EXISTS("DnsServiceConstructInstance") && SYMBOL_EXISTS("DnsServiceFreeInstance")
&& SYMBOL_EXISTS("DnsServiceRegister") && SYMBOL_EXISTS("DnsServiceDeRegister")
&& SYMBOL_EXISTS("DnsServiceRegisterCancel")) {
m_ok = true;
return;
}
}
winDnsUnavailable = true;
qWarning("Zeroconf: Native mDNS/DNS-SD API not available, falling back to third-party API");
}
static bool dnssdLoadFailed = false;
if (!dnssdLoadFailed) {
auto handle = LoadLibrary(L"dnssd.DLL");
if (!handle) {
dnssdLoadFailed = true;
qWarning("Zeroconf: Failed to load dnssd.dll, assuming third-party API is not available");
return;
}
FreeLibrary(handle);
}
#endif
resetHelper();
m_ok = true;
}
Zeroconf::~Zeroconf() {
if (!m_helper) {
unregisterService();
}
}
void Zeroconf::resetHelper() {
m_helper.reset(new BonjourServiceRegister(this));
connect(m_helper.get(), &BonjourServiceRegister::error, this, &Zeroconf::helperError);
}
bool Zeroconf::registerService(const BonjourRecord &record, const uint16_t port) {
if (!m_ok) {
return false;
}
unregisterService();
if (m_helper) {
m_helper->registerService(record, port);
return true;
}
#ifdef Q_OS_WIN64
DWORD size = 0;
GetComputerNameEx(ComputerNameDnsHostname, nullptr, &size);
std::vector< wchar_t > hostname(size);
if (!GetComputerNameEx(ComputerNameDnsHostname, &hostname[0], &size)) {
qWarning("Zeroconf: GetComputerNameEx() failed with error %u!", GetLastError());
return false;
}
const auto domain = record.replyDomain.isEmpty() ? L".local" : L"." + record.replyDomain.toStdWString();
const auto qualifiedHostname = &hostname[0] + domain;
auto service = record.serviceName.isEmpty() ? &hostname[0] : record.serviceName.toStdWString();
service += L".";
service += record.registeredType.toStdWString();
service += domain;
auto instance = DnsServiceConstructInstance(service.c_str(), qualifiedHostname.c_str(), nullptr, nullptr, port, 0,
0, 0, nullptr, nullptr);
if (!instance) {
qWarning("Zeroconf: DnsServiceConstructInstance() returned nullptr!");
return false;
}
m_request.reset(new DNS_SERVICE_REGISTER_REQUEST{});
m_request->Version = DNS_QUERY_REQUEST_VERSION1;
m_request->pServiceInstance = instance;
m_request->pRegisterCompletionCallback = callbackRegisterComplete;
m_request->pQueryContext = this;
m_cancel.reset(new DNS_SERVICE_CANCEL{});
const auto ret = DnsServiceRegister(m_request.get(), m_cancel.get());
DnsServiceFreeInstance(instance);
if (ret == DNS_REQUEST_PENDING) {
return true;
}
qWarning("Zeroconf: DnsServiceRegister() failed with error %u!", ret);
m_request.reset();
m_cancel.reset();
#endif
return false;
}
bool Zeroconf::unregisterService() {
if (!m_ok) {
return false;
}
if (m_helper) {
resetHelper();
return true;
}
#ifdef Q_OS_WIN64
if (m_cancel) {
const auto ret = DnsServiceRegisterCancel(m_cancel.get());
if (ret == ERROR_SUCCESS || ret == ERROR_CANCELLED) {
return true;
}
m_cancel.reset();
qWarning("Zeroconf: DnsServiceRegisterCancel() failed with error %u!", ret);
} else if (m_request) {
const auto ret = DnsServiceDeRegister(m_request.get(), nullptr);
if (ret == DNS_REQUEST_PENDING) {
return true;
}
qWarning("Zeroconf: DnsServiceDeRegister() failed with error %u!", ret);
}
#endif
return false;
}
void Zeroconf::helperError(const DNSServiceErrorType error) {
qWarning("Zeroconf: Third-party API reports error %d, service registration probably failed", error);
}
#ifdef Q_OS_WIN64
void WINAPI Zeroconf::callbackRegisterComplete(const DWORD status, void *context, DNS_SERVICE_INSTANCE *instance) {
if (instance) {
DnsServiceFreeInstance(instance);
}
if (status == ERROR_CANCELLED) {
return;
}
auto zeroconf = static_cast< Zeroconf * >(context);
zeroconf->m_cancel.reset();
if (status != ERROR_SUCCESS) {
qWarning("Zeroconf: DnsServiceRegister() reports status code %u, service registration probably failed", status);
}
}
#endif

44
src/murmur/Zeroconf.h Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2005-2020 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#ifndef MUMBLE_MURMUR_ZEROCONF_H_
#define MUMBLE_MURMUR_ZEROCONF_H_
#include "BonjourServiceRegister.h"
#include <memory>
#ifdef Q_OS_WIN64
# include <windns.h>
#endif
class Zeroconf : public QObject {
private:
Q_OBJECT
Q_DISABLE_COPY(Zeroconf)
protected:
bool m_ok;
std::unique_ptr< BonjourServiceRegister > m_helper;
#ifdef Q_OS_WIN64
std::unique_ptr< DNS_SERVICE_CANCEL > m_cancel;
std::unique_ptr< DNS_SERVICE_REGISTER_REQUEST > m_request;
static void WINAPI callbackRegisterComplete(const DWORD status, void *context, DNS_SERVICE_INSTANCE *instance);
#endif
void helperError(const DNSServiceErrorType error);
public:
inline bool isOk() const { return m_ok; }
void resetHelper();
bool registerService(const BonjourRecord &record, const uint16_t port);
bool unregisterService();
Zeroconf();
~Zeroconf();
};
#endif

View File

@ -83,7 +83,7 @@ void __cpuid(int a[4], int b);
# include <boost/shared_ptr.hpp>
# include <boost/weak_ptr.hpp>
# ifdef USE_BONJOUR
# ifdef USE_ZEROCONF
# include <dns_sd.h>
# endif