Files
Apollo/src/platform/macos/publish.cpp
Cathy J. Fitzpatrick 3cc12dfbe7 macOS: New implementation of service publication (#2786)
The current implementation of service publication on macOS uses
`avahi-client`, but the majority of macOS machines do not have Avahi
installed because macOS provides a native alternative (`mDNSresponder`),
meaning that there is no reason to install Avahi.

The current implementation also attempts to load the Avahi client
libraries using `dlopen(3)`, which has a variety of restrictions on
macOS, such as only being willing to load from certain directories.
Depending on where the Avahi binaries are installed, they might not
be loadable through the current invocation of `dlopen(3)`.

Instead of using an Avahi client on macOS, it makes more sense to use
the native macOS API for publishing services via `mDNSresponder`. This
commit supplies such an implementation that uses the macOS native API.
It also has the advantage of being much simpler than the previous
implementation. Furthermore, this new implementation works on all
macOS machines, because it relies only on native APIs, rather than on
third-party software that is not commonly installed on macOS.
2024-07-07 15:19:51 +00:00

124 lines
4.6 KiB
C++

/**
* @file src/platform/macos/publish.cpp
* @brief Definitions for publishing services on macOS.
*/
#include <dns_sd.h>
#include <thread>
#include "src/logging.h"
#include "src/network.h"
#include "src/nvhttp.h"
#include "src/platform/common.h"
using namespace std::literals;
namespace platf::publish {
namespace {
/** @brief Custom deleter intended to be used for `std::unique_ptr<DNSServiceRef>`. */
struct ServiceRefDeleter {
typedef DNSServiceRef pointer; ///< Type of object to be deleted.
void
operator()(pointer serviceRef) {
DNSServiceRefDeallocate(serviceRef);
BOOST_LOG(info) << "Deregistered DNS service."sv;
}
};
/** @brief This class encapsulates the polling and deinitialization of our connection with
* the mDNS service. Implements the `::platf::deinit_t` interface.
*/
class deinit_t: public ::platf::deinit_t, std::unique_ptr<DNSServiceRef, ServiceRefDeleter> {
public:
/** @brief Construct deinit_t object.
*
* Create a thread that will use `select(2)` to wait for a response from the mDNS service.
* The thread will give up if an error is received or if `_stopRequested` becomes true.
*
* @param serviceRef An initialized reference to the mDNS service.
*/
deinit_t(DNSServiceRef serviceRef):
unique_ptr(serviceRef) {
_thread = std::thread { [serviceRef, &_stopRequested = std::as_const(_stopRequested)]() {
const auto socket = DNSServiceRefSockFD(serviceRef);
while (!_stopRequested) {
auto fdset = fd_set {};
FD_ZERO(&fdset);
FD_SET(socket, &fdset);
auto timeout = timeval { .tv_sec = 3, .tv_usec = 0 }; // 3 second timeout
const auto ready = select(socket + 1, &fdset, nullptr, nullptr, &timeout);
if (ready == -1) {
BOOST_LOG(error) << "Failed to obtain response from DNS service."sv;
break;
}
else if (ready != 0) {
DNSServiceProcessResult(serviceRef);
break;
}
}
} };
}
/** @brief Ensure that we gracefully finish polling the mDNS service before freeing our
* connection to it.
*/
~deinit_t() override {
_stopRequested = true;
_thread.join();
}
deinit_t(const deinit_t &) = delete;
deinit_t &
operator=(const deinit_t &) = delete;
private:
std::thread _thread; ///< Thread for polling the mDNS service for a response.
std::atomic<bool> _stopRequested = false; ///< Whether to stop polling the mDNS service.
};
/** @brief Callback that will be invoked when the mDNS service finishes registering our service.
* @param errorCode Describes whether the registration was successful.
*/
void
registrationCallback(DNSServiceRef /*serviceRef*/, DNSServiceFlags /*flags*/,
DNSServiceErrorType errorCode, const char * /*name*/,
const char * /*regtype*/, const char * /*domain*/, void * /*context*/) {
if (errorCode != kDNSServiceErr_NoError) {
BOOST_LOG(error) << "Failed to register DNS service: Error "sv << errorCode;
return;
}
BOOST_LOG(info) << "Successfully registered DNS service."sv;
}
} // anonymous namespace
/**
* @brief Main entry point for publication of our service on macOS.
*
* This function initiates a connection to the macOS mDNS service and requests to register
* our Sunshine service. Registration will occur asynchronously (unless it fails immediately,
* which is probably only possible if the host machine is misconfigured).
*
* @return Either `nullptr` (if the registration fails immediately) or a `uniqur_ptr<deinit_t>`,
* which will manage polling for a response from the mDNS service, and then, when
* deconstructed, will deregister the service.
*/
[[nodiscard]] std::unique_ptr<::platf::deinit_t>
start() {
auto serviceRef = DNSServiceRef {};
const auto status = DNSServiceRegister(
&serviceRef,
0, // flags
0, // interfaceIndex
SERVICE_NAME, SERVICE_TYPE,
nullptr, // domain
nullptr, // host
htons(net::map_port(nvhttp::PORT_HTTP)),
0, // txtLen
nullptr, // txtRecord
registrationCallback,
nullptr // context
);
if (status != kDNSServiceErr_NoError) {
BOOST_LOG(error) << "Failed immediately to register DNS service: Error "sv << status;
return nullptr;
}
return std::make_unique<deinit_t>(serviceRef);
}
} // namespace platf::publish