Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 131 additions & 83 deletions src/murmur/Zeroconf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,33 @@

#include "Zeroconf.h"

#define GET_SYMBOL(symbol) (symbol = reinterpret_cast< decltype(symbol) >(GetProcAddress(handle, #symbol)))
#define GET_SYMBOL(symbol) (symbol = reinterpret_cast< decltype(symbol) >(GetProcAddress(m_module, #symbol)))

Zeroconf::Zeroconf() : m_ok(false) {
#ifdef Q_OS_WIN
auto handle = GetModuleHandle(L"dnsapi.dll");
if (handle) {
GET_SYMBOL(DnsServiceConstructInstance);
GET_SYMBOL(DnsServiceFreeInstance);
GET_SYMBOL(DnsServiceRegister);
GET_SYMBOL(DnsServiceDeRegister);
GET_SYMBOL(DnsServiceRegisterCancel);

if (DnsServiceConstructInstance && DnsServiceFreeInstance && DnsServiceRegister && DnsServiceDeRegister
&& DnsServiceRegisterCancel) {
m_ok = true;
return;
}
Zeroconf::Zeroconf() : m_ok(false), m_reg(std::make_shared< Reg >()) {
if (m_reg->isOk()) {
m_ok = true;
return;
}

qWarning("Zeroconf: Native mDNS/DNS-SD API not available, falling back to third-party API");

handle = LoadLibrary(L"dnssd.dll");
if (!handle) {
HMODULE module = LoadLibrary(L"dnssd.dll");
if (!module) {
qWarning("Zeroconf: Failed to load dnssd.dll, assuming third-party API is not available");
return;
}
FreeLibrary(handle);

FreeLibrary(module);
#else
Zeroconf::Zeroconf() : m_ok(false) {
#endif
resetHelper();

m_ok = true;
}

Zeroconf::~Zeroconf() {
if (!m_helper) {
unregisterService();
}
}

void Zeroconf::resetHelper() {
Expand All @@ -50,17 +40,102 @@ void Zeroconf::resetHelper() {
}

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_WIN
return m_reg->request(record, port);
#else
return false;
#endif
}

bool Zeroconf::unregisterService() {
if (m_helper) {
resetHelper();
return true;
}
#ifdef Q_OS_WIN
return m_reg->cancel();
#else
return false;
#endif
}

void Zeroconf::helperError(const DNSServiceErrorType error) {
qWarning("Zeroconf: Third-party API reports error %d, service registration probably failed", error);
}

#ifdef Q_OS_WIN
Zeroconf::Reg::Reg() : m_instance(nullptr, InstanceDeleter{ DnsServiceFreeInstance }) {
m_module = GetModuleHandle(L"dnsapi.dll");
if (!m_module) {
return;
}

GET_SYMBOL(DnsServiceConstructInstance);
GET_SYMBOL(DnsServiceFreeInstance);
GET_SYMBOL(DnsServiceRegister);
GET_SYMBOL(DnsServiceDeRegister);
GET_SYMBOL(DnsServiceRegisterCancel);

if (!DnsServiceConstructInstance || !DnsServiceFreeInstance || !DnsServiceRegister || !DnsServiceDeRegister
|| !DnsServiceRegisterCancel) {
FreeModule(m_module);
m_module = nullptr;
}
}

Zeroconf::Reg::~Reg() {
if (!isOk()) {
return;
}

cancel();
FreeLibrary(m_module);
}

bool Zeroconf::Reg::cancel() {
if (!isOk()) {
return false;
}

if (m_cancel) {
const auto ret = DnsServiceRegisterCancel(&m_cancel.value());
if (ret != ERROR_SUCCESS && ret != ERROR_CANCELLED) {
qWarning("Zeroconf: DnsServiceRegisterCancel() failed with error %u!", ret);

m_cancel.reset();
m_instance.reset();

return false;
}
} else if (m_instance) {
DNS_SERVICE_REGISTER_REQUEST req{};
req.Version = DNS_QUERY_REQUEST_VERSION1;
req.pServiceInstance = m_instance.get();
req.pRegisterCompletionCallback = callback;
req.pQueryContext = new CallbackCtx(weak_from_this());

const auto ret = DnsServiceDeRegister(&req, nullptr);
if (ret != DNS_REQUEST_PENDING) {
qWarning("Zeroconf: DnsServiceDeRegister() failed with error %u!", ret);

return false;
}
}

return true;
}

bool Zeroconf::Reg::request(const BonjourRecord &record, const uint16_t port) {
if (!isOk()) {
return false;
}

DWORD size = 0;
GetComputerNameEx(ComputerNameDnsHostname, nullptr, &size);
std::vector< wchar_t > hostname(size);
Expand All @@ -84,76 +159,49 @@ bool Zeroconf::registerService(const BonjourRecord &record, const uint16_t port)
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;
}
DNS_SERVICE_REGISTER_REQUEST req{};
req.Version = DNS_QUERY_REQUEST_VERSION1;
req.pServiceInstance = instance;
req.pRegisterCompletionCallback = callback;
req.pQueryContext = new CallbackCtx(weak_from_this());

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;
}
m_cancel = DNS_SERVICE_CANCEL{};
m_instance.reset(instance);

if (m_helper) {
resetHelper();
return true;
}
#ifdef Q_OS_WIN
if (m_cancel) {
const auto ret = DnsServiceRegisterCancel(m_cancel.get());
if (ret == ERROR_SUCCESS || ret == ERROR_CANCELLED) {
return true;
}
const auto ret = DnsServiceRegister(&req, &m_cancel.value());
if (ret != DNS_REQUEST_PENDING) {
qWarning("Zeroconf: DnsServiceRegister() failed with error %u!", ret);

m_instance.reset();
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);
return false;
}
#endif
return false;
}

void Zeroconf::helperError(const DNSServiceErrorType error) {
qWarning("Zeroconf: Third-party API reports error %d, service registration probably failed", error);
return true;
}
#ifdef Q_OS_WIN
void WINAPI Zeroconf::callbackRegisterComplete(const DWORD status, void *context, DNS_SERVICE_INSTANCE *instance) {
auto zeroconf = static_cast< Zeroconf * >(context);

if (instance) {
zeroconf->DnsServiceFreeInstance(instance);
}
void WINAPI Zeroconf::Reg::callback(const DWORD status, void *userdata, DNS_SERVICE_INSTANCE *instance) {
auto ctx = std::unique_ptr< CallbackCtx >(static_cast< CallbackCtx * >(userdata));
if (auto self = ctx->regWeak.lock()) {
self->m_instance.reset(instance);

if (status == ERROR_CANCELLED) {
return;
}
if (!self->m_cancel) {
// No cancel handle, which means this is a de-registration.
return;
}

zeroconf->m_cancel.reset();
self->m_cancel.reset();

if (status != ERROR_SUCCESS) {
qWarning("Zeroconf: DnsServiceRegister() reports status code %u, service registration probably failed", status);
switch (status) {
case ERROR_SUCCESS:
case ERROR_CANCELLED:
break;
default:
qWarning("Zeroconf: DnsServiceRegister() reports status code %u, service registration "
"probably failed",
status);
}
}
}
#endif
Expand Down
85 changes: 62 additions & 23 deletions src/murmur/Zeroconf.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,14 @@
#include <memory>

#ifdef Q_OS_WIN
# include <optional>

# 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_WIN
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);
#ifdef Q_OS_WIN
PDNS_SERVICE_INSTANCE(WINAPI *DnsServiceConstructInstance)
(PCWSTR pServiceName, PCWSTR pHostName, PIP4_ADDRESS pIp4, PIP6_ADDRESS pIp6, WORD wPort, WORD wPriority,
WORD wWeight, DWORD dwPropertiesCount, PCWSTR *keys, PCWSTR *values);
VOID(WINAPI *DnsServiceFreeInstance)(PDNS_SERVICE_INSTANCE pInstance);
DWORD(WINAPI *DnsServiceRegister)(PDNS_SERVICE_REGISTER_REQUEST pRequest, PDNS_SERVICE_CANCEL pCancel);
DWORD(WINAPI *DnsServiceDeRegister)(PDNS_SERVICE_REGISTER_REQUEST pRequest, PDNS_SERVICE_CANCEL pCancel);
DWORD(WINAPI *DnsServiceRegisterCancel)(PDNS_SERVICE_CANCEL pCancelHandle);
#endif
public:
inline bool isOk() const { return m_ok; }
constexpr bool isOk() const { return m_ok; }

void resetHelper();

Expand All @@ -47,6 +27,65 @@ class Zeroconf : public QObject {

Zeroconf();
~Zeroconf();

protected:
bool m_ok;
#ifdef Q_OS_WIN
class Reg : public std::enable_shared_from_this< Reg > {
public:
constexpr auto isOk() const { return m_module != nullptr; }

bool cancel();
bool request(const BonjourRecord &record, const uint16_t port);

Reg();
~Reg();

protected:
struct CallbackCtx {
std::weak_ptr< Reg > regWeak;
};

struct InstanceDeleter {
using FreeFn = VOID(WINAPI *)(PDNS_SERVICE_INSTANCE);

FreeFn &m_freeFn;

explicit InstanceDeleter(FreeFn &freeFn) noexcept : m_freeFn(freeFn) {}

void operator()(PDNS_SERVICE_INSTANCE instance) const noexcept {
if (instance) {
m_freeFn(instance);
}
}
};

std::optional< DNS_SERVICE_CANCEL > m_cancel;
std::unique_ptr< DNS_SERVICE_INSTANCE, InstanceDeleter > m_instance;

static void WINAPI callback(const DWORD status, void *context, DNS_SERVICE_INSTANCE *instance);

PDNS_SERVICE_INSTANCE(WINAPI *DnsServiceConstructInstance)
(PCWSTR pServiceName, PCWSTR pHostName, PIP4_ADDRESS pIp4, PIP6_ADDRESS pIp6, WORD wPort, WORD wPriority,
WORD wWeight, DWORD dwPropertiesCount, PCWSTR *keys, PCWSTR *values);
VOID(WINAPI *DnsServiceFreeInstance)(PDNS_SERVICE_INSTANCE pInstance);
DWORD(WINAPI *DnsServiceRegister)(PDNS_SERVICE_REGISTER_REQUEST pRequest, PDNS_SERVICE_CANCEL pCancel);
DWORD(WINAPI *DnsServiceDeRegister)(PDNS_SERVICE_REGISTER_REQUEST pRequest, PDNS_SERVICE_CANCEL pCancel);
DWORD(WINAPI *DnsServiceRegisterCancel)(PDNS_SERVICE_CANCEL pCancelHandle);

private:
HMODULE m_module;
};

std::shared_ptr< Reg > m_reg;
#endif
std::unique_ptr< BonjourServiceRegister > m_helper;

void helperError(const DNSServiceErrorType error);

private:
Q_OBJECT
Q_DISABLE_COPY(Zeroconf)
};

#endif
Loading