[wpiutil] Add mDNS resolver and announcer (#3733)

This commit is contained in:
Thad House
2021-11-25 22:08:26 -08:00
committed by GitHub
parent 4b1defc8d8
commit 82066946e5
28 changed files with 2562 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "AvahiClient.h"
#include <wpi/mutex.h>
#include <thread>
#include "dlfcn.h"
using namespace wpi;
#define AvahiFunctionLoad(snake_name) \
do { \
snake_name = \
reinterpret_cast<snake_name##_func>(dlsym(lib, "avahi_" #snake_name)); \
if (!snake_name) { \
return; \
} \
} while (false)
AvahiFunctionTable::AvahiFunctionTable() {
void* lib = dlopen("libavahi-common.so.3", RTLD_LAZY);
valid = false;
if (lib == nullptr) {
return;
}
AvahiFunctionLoad(threaded_poll_new);
AvahiFunctionLoad(threaded_poll_free);
AvahiFunctionLoad(threaded_poll_get);
AvahiFunctionLoad(threaded_poll_start);
AvahiFunctionLoad(threaded_poll_stop);
AvahiFunctionLoad(threaded_poll_lock);
AvahiFunctionLoad(threaded_poll_unlock);
AvahiFunctionLoad(string_list_new_from_array);
AvahiFunctionLoad(string_list_free);
AvahiFunctionLoad(unescape_label);
lib = dlopen("libavahi-client.so.3", RTLD_LAZY);
if (lib == nullptr) {
return;
}
AvahiFunctionLoad(client_new);
AvahiFunctionLoad(client_free);
AvahiFunctionLoad(service_browser_new);
AvahiFunctionLoad(service_browser_get_client);
AvahiFunctionLoad(service_browser_free);
AvahiFunctionLoad(service_resolver_new);
AvahiFunctionLoad(service_resolver_free);
AvahiFunctionLoad(entry_group_new);
AvahiFunctionLoad(entry_group_free);
AvahiFunctionLoad(entry_group_add_service_strlst);
AvahiFunctionLoad(entry_group_reset);
AvahiFunctionLoad(entry_group_is_empty);
AvahiFunctionLoad(entry_group_commit);
valid = true;
}
AvahiFunctionTable& AvahiFunctionTable::Get() {
static AvahiFunctionTable table;
return table;
}
static wpi::mutex ThreadLoopLock;
static std::weak_ptr<AvahiThread> ThreadLoop;
std::shared_ptr<AvahiThread> AvahiThread::Get() {
std::scoped_lock lock{ThreadLoopLock};
auto locked = ThreadLoop.lock();
if (!locked) {
locked = std::make_unique<AvahiThread>(private_init{});
ThreadLoop = locked;
}
return locked;
}
AvahiThread::AvahiThread(const private_init&) {
if (!table.IsValid()) {
return;
}
threadedPoll = table.threaded_poll_new();
table.threaded_poll_start(threadedPoll);
}
AvahiThread::~AvahiThread() noexcept {
if (!table.IsValid()) {
return;
}
if (threadedPoll) {
table.threaded_poll_stop(threadedPoll);
table.threaded_poll_free(threadedPoll);
}
}
void AvahiThread::lock() {
table.threaded_poll_lock(threadedPoll);
}
void AvahiThread::unlock() {
table.threaded_poll_unlock(threadedPoll);
}
const AvahiPoll* AvahiThread::GetPoll() const {
return table.threaded_poll_get(threadedPoll);
}

View File

@@ -0,0 +1,249 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <memory>
/***
This file is part of avahi.
avahi 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 2.1 of the
License, or (at your option) any later version.
avahi 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 avahi; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
typedef struct AvahiPoll AvahiPoll;
typedef enum {
AVAHI_SERVER_INVALID,
AVAHI_SERVER_REGISTERING,
AVAHI_SERVER_RUNNING,
AVAHI_SERVER_COLLISION,
AVAHI_SERVER_FAILURE
} AvahiServerState;
typedef struct AvahiClient AvahiClient;
typedef enum {
AVAHI_CLIENT_S_REGISTERING = AVAHI_SERVER_REGISTERING,
AVAHI_CLIENT_S_RUNNING = AVAHI_SERVER_RUNNING,
AVAHI_CLIENT_S_COLLISION = AVAHI_SERVER_COLLISION,
AVAHI_CLIENT_FAILURE = 100,
AVAHI_CLIENT_CONNECTING = 101
} AvahiClientState;
typedef enum {
AVAHI_CLIENT_IGNORE_USER_CONFIG = 1,
AVAHI_CLIENT_NO_FAIL = 2
} AvahiClientFlags;
typedef void (*AvahiClientCallback)(AvahiClient* s, AvahiClientState state,
void* userdata);
typedef struct AvahiServiceBrowser AvahiServiceBrowser;
typedef int AvahiProtocol;
typedef int AvahiIfIndex;
typedef enum {
AVAHI_BROWSER_NEW,
AVAHI_BROWSER_REMOVE,
AVAHI_BROWSER_CACHE_EXHAUSTED,
AVAHI_BROWSER_ALL_FOR_NOW,
AVAHI_BROWSER_FAILURE
} AvahiBrowserEvent;
typedef enum {
AVAHI_LOOKUP_RESULT_CACHED = 1,
AVAHI_LOOKUP_RESULT_WIDE_AREA = 2,
AVAHI_LOOKUP_RESULT_MULTICAST = 4,
AVAHI_LOOKUP_RESULT_LOCAL = 8,
AVAHI_LOOKUP_RESULT_OUR_OWN = 16,
AVAHI_LOOKUP_RESULT_STATIC = 32
} AvahiLookupResultFlags;
typedef void (*AvahiServiceBrowserCallback)(
AvahiServiceBrowser* b, AvahiIfIndex interface, AvahiProtocol protocol,
AvahiBrowserEvent event, const char* name, const char* type,
const char* domain, AvahiLookupResultFlags flags, void* userdata);
typedef enum {
AVAHI_LOOKUP_USE_WIDE_AREA = 1,
AVAHI_LOOKUP_USE_MULTICAST = 2,
AVAHI_LOOKUP_NO_TXT = 4,
AVAHI_LOOKUP_NO_ADDRESS = 8
} AvahiLookupFlags;
typedef struct AvahiServiceResolver AvahiServiceResolver;
typedef enum {
AVAHI_RESOLVER_FOUND,
AVAHI_RESOLVER_FAILURE
} AvahiResolverEvent;
typedef struct AvahiIPv4Address {
uint32_t address;
} AvahiIPv4Address;
typedef struct AvahiIPv6Address {
uint8_t address[16];
} AvahiIPv6Address;
typedef struct AvahiAddress {
AvahiProtocol proto;
union {
AvahiIPv6Address ipv6;
AvahiIPv4Address ipv4;
uint8_t data[1];
} data;
} AvahiAddress;
typedef struct AvahiStringList {
struct AvahiStringList* next;
size_t size;
uint8_t text[1];
} AvahiStringList;
typedef void (*AvahiServiceResolverCallback)(
AvahiServiceResolver* r, AvahiIfIndex interface, AvahiProtocol protocol,
AvahiResolverEvent event, const char* name, const char* type,
const char* domain, const char* host_name, const AvahiAddress* a,
uint16_t port, AvahiStringList* txt, AvahiLookupResultFlags flags,
void* userdata);
typedef struct AvahiThreadedPoll AvahiThreadedPoll;
typedef struct AvahiEntryGroup AvahiEntryGroup;
typedef enum {
AVAHI_ENTRY_GROUP_UNCOMMITED,
AVAHI_ENTRY_GROUP_REGISTERING,
AVAHI_ENTRY_GROUP_ESTABLISHED,
AVAHI_ENTRY_GROUP_COLLISION,
AVAHI_ENTRY_GROUP_FAILURE
} AvahiEntryGroupState;
typedef void (*AvahiEntryGroupCallback)(AvahiEntryGroup* g,
AvahiEntryGroupState state,
void* userdata);
typedef enum {
AVAHI_PUBLISH_UNIQUE = 1,
AVAHI_PUBLISH_NO_PROBE = 2,
AVAHI_PUBLISH_NO_ANNOUNCE = 4,
AVAHI_PUBLISH_ALLOW_MULTIPLE = 8,
AVAHI_PUBLISH_NO_REVERSE = 16,
AVAHI_PUBLISH_NO_COOKIE = 32,
AVAHI_PUBLISH_UPDATE = 64,
AVAHI_PUBLISH_USE_WIDE_AREA = 128,
AVAHI_PUBLISH_USE_MULTICAST = 256
} AvahiPublishFlags;
enum { AVAHI_IF_UNSPEC = -1 };
enum { AVAHI_PROTO_INET = 0, AVAHI_PROTO_INET6 = 1, AVAHI_PROTO_UNSPEC = -1 };
namespace wpi {
class AvahiFunctionTable {
public:
#define AvahiFunction(CapName, RetType, Parameters) \
using CapName##_func = RetType(*) Parameters; \
CapName##_func CapName = nullptr
AvahiFunction(threaded_poll_new, AvahiThreadedPoll*, (void));
AvahiFunction(threaded_poll_free, void, (AvahiThreadedPoll*));
AvahiFunction(threaded_poll_get, const AvahiPoll*, (AvahiThreadedPoll*));
AvahiFunction(threaded_poll_start, int, (AvahiThreadedPoll*));
AvahiFunction(threaded_poll_stop, int, (AvahiThreadedPoll*));
AvahiFunction(threaded_poll_lock, int, (AvahiThreadedPoll*));
AvahiFunction(threaded_poll_unlock, int, (AvahiThreadedPoll*));
AvahiFunction(client_new, AvahiClient*,
(const AvahiPoll* poll_api, AvahiClientFlags flags,
AvahiClientCallback callback, void* userdata, int* error));
AvahiFunction(client_free, void, (AvahiClient*));
AvahiFunction(service_browser_new, AvahiServiceBrowser*,
(AvahiClient * client, AvahiIfIndex interface,
AvahiProtocol protocol, const char* type, const char* domain,
AvahiLookupFlags flags, AvahiServiceBrowserCallback callback,
void* userdata));
AvahiFunction(service_browser_free, int, (AvahiServiceBrowser*));
AvahiFunction(service_resolver_new, AvahiServiceResolver*,
(AvahiClient * client, AvahiIfIndex interface,
AvahiProtocol protocol, const char* name, const char* type,
const char* domain, AvahiProtocol aprotocol,
AvahiLookupFlags flags, AvahiServiceResolverCallback callback,
void* userdata));
AvahiFunction(service_resolver_free, int, (AvahiServiceResolver*));
AvahiFunction(entry_group_new, AvahiEntryGroup*,
(AvahiClient*, AvahiEntryGroupCallback, void*));
AvahiFunction(entry_group_free, int, (AvahiEntryGroup*));
AvahiFunction(entry_group_add_service_strlst, int,
(AvahiEntryGroup * group, AvahiIfIndex interface,
AvahiProtocol protocol, AvahiPublishFlags flags,
const char* name, const char* type, const char* domain,
const char* host, uint16_t port, AvahiStringList*));
AvahiFunction(entry_group_reset, int, (AvahiEntryGroup*));
AvahiFunction(entry_group_is_empty, int, (AvahiEntryGroup*));
AvahiFunction(entry_group_commit, int, (AvahiEntryGroup*));
AvahiFunction(string_list_new_from_array, AvahiStringList*,
(const char** array, int len));
AvahiFunction(string_list_free, void, (AvahiStringList*));
AvahiFunction(service_browser_get_client, AvahiClient*,
(AvahiServiceBrowser*));
AvahiFunction(unescape_label, char*, (const char**, char*, size_t));
bool IsValid() const { return valid; }
static AvahiFunctionTable& Get();
private:
AvahiFunctionTable();
bool valid;
};
class AvahiThread {
private:
struct private_init {};
public:
explicit AvahiThread(const private_init&);
~AvahiThread() noexcept;
void lock();
const AvahiPoll* GetPoll() const;
void unlock();
static std::shared_ptr<AvahiThread> Get();
private:
AvahiThreadedPoll* threadedPoll;
AvahiFunctionTable& table = AvahiFunctionTable::Get();
};
} // namespace wpi

View File

@@ -0,0 +1,154 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/MulticastServiceAnnouncer.h"
#include <vector>
#include "AvahiClient.h"
#include "fmt/format.h"
#include "wpi/mutex.h"
using namespace wpi;
struct MulticastServiceAnnouncer::Impl {
AvahiFunctionTable& table = AvahiFunctionTable::Get();
std::shared_ptr<AvahiThread> thread = AvahiThread::Get();
AvahiClient* client;
AvahiEntryGroup* group = nullptr;
std::string serviceName;
std::string serviceType;
int port;
AvahiStringList* stringList = nullptr;
~Impl() noexcept {
if (stringList != nullptr && table.IsValid()) {
table.string_list_free(stringList);
}
}
};
static void EntryGroupCallback(AvahiEntryGroup*, AvahiEntryGroupState, void*) {}
static void RegisterService(AvahiClient* client,
MulticastServiceAnnouncer::Impl* impl) {
if (impl->group == nullptr) {
impl->group =
impl->table.entry_group_new(client, EntryGroupCallback, nullptr);
}
if (impl->table.entry_group_is_empty(impl->group)) {
impl->table.entry_group_add_service_strlst(
impl->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
AVAHI_PUBLISH_USE_MULTICAST, impl->serviceName.c_str(),
impl->serviceType.c_str(), "local", nullptr, impl->port,
impl->stringList);
impl->table.entry_group_commit(impl->group);
}
}
static void ClientCallback(AvahiClient* client, AvahiClientState state,
void* userdata) {
MulticastServiceAnnouncer::Impl* impl =
reinterpret_cast<MulticastServiceAnnouncer::Impl*>(userdata);
if (state == AVAHI_CLIENT_S_RUNNING) {
RegisterService(client, impl);
} else if (state == AVAHI_CLIENT_S_COLLISION ||
state == AVAHI_CLIENT_S_REGISTERING) {
if (impl->group) {
impl->table.entry_group_reset(impl->group);
}
}
}
MulticastServiceAnnouncer::MulticastServiceAnnouncer(
std::string_view serviceName, std::string_view serviceType, int port,
wpi::span<const std::pair<std::string, std::string>> txt) {
pImpl = std::make_unique<Impl>();
if (!pImpl->table.IsValid()) {
return;
}
pImpl->serviceName = serviceName;
pImpl->serviceType = serviceType;
pImpl->port = port;
std::vector<std::string> txts;
for (auto&& i : txt) {
txts.push_back(fmt::format("{}={}", i.first, i.second));
}
std::vector<const char*> txtArr;
for (auto&& i : txts) {
txtArr.push_back(i.c_str());
}
pImpl->stringList =
pImpl->table.string_list_new_from_array(txtArr.data(), txtArr.size());
}
MulticastServiceAnnouncer::MulticastServiceAnnouncer(
std::string_view serviceName, std::string_view serviceType, int port,
wpi::span<const std::pair<std::string_view, std::string_view>> txt) {
pImpl = std::make_unique<Impl>();
if (!pImpl->table.IsValid()) {
return;
}
pImpl->serviceName = serviceName;
pImpl->serviceType = serviceType;
pImpl->port = port;
std::vector<std::string> txts;
for (auto&& i : txt) {
txts.push_back(fmt::format("{}={}", i.first, i.second));
}
std::vector<const char*> txtArr;
for (auto&& i : txts) {
txtArr.push_back(i.c_str());
}
pImpl->stringList =
pImpl->table.string_list_new_from_array(txtArr.data(), txtArr.size());
}
MulticastServiceAnnouncer::~MulticastServiceAnnouncer() noexcept {
Stop();
}
bool MulticastServiceAnnouncer::HasImplementation() const {
return pImpl->table.IsValid();
}
void MulticastServiceAnnouncer::Start() {
if (!pImpl->table.IsValid()) {
return;
}
std::scoped_lock lock{*pImpl->thread};
if (pImpl->client) {
return;
}
pImpl->client =
pImpl->table.client_new(pImpl->thread->GetPoll(), AVAHI_CLIENT_NO_FAIL,
ClientCallback, pImpl.get(), nullptr);
}
void MulticastServiceAnnouncer::Stop() {
if (!pImpl->table.IsValid()) {
return;
}
std::scoped_lock lock{*pImpl->thread};
if (pImpl->client) {
if (pImpl->group) {
pImpl->table.entry_group_free(pImpl->group);
pImpl->group = nullptr;
}
pImpl->table.client_free(pImpl->client);
pImpl->client = nullptr;
}
}

View File

@@ -0,0 +1,148 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/MulticastServiceResolver.h"
#include "AvahiClient.h"
#include "wpi/SmallString.h"
#include "wpi/mutex.h"
using namespace wpi;
struct MulticastServiceResolver::Impl {
AvahiFunctionTable& table = AvahiFunctionTable::Get();
std::shared_ptr<AvahiThread> thread = AvahiThread::Get();
AvahiClient* client;
AvahiServiceBrowser* browser;
std::string serviceType;
MulticastServiceResolver* resolver;
void onFound(ServiceData&& data) {
resolver->eventQueue.push(std::move(data));
resolver->event.Set();
}
};
MulticastServiceResolver::MulticastServiceResolver(
std::string_view serviceType) {
pImpl = std::make_unique<Impl>();
pImpl->serviceType = serviceType;
pImpl->resolver = this;
}
MulticastServiceResolver::~MulticastServiceResolver() noexcept {
Stop();
}
bool MulticastServiceResolver::HasImplementation() const {
return pImpl->table.IsValid();
}
static void ResolveCallback(AvahiServiceResolver* r, AvahiIfIndex interface,
AvahiProtocol protocol, AvahiResolverEvent event,
const char* name, const char* type,
const char* domain, const char* host_name,
const AvahiAddress* address, uint16_t port,
AvahiStringList* txt, AvahiLookupResultFlags flags,
void* userdata) {
MulticastServiceResolver::Impl* impl =
reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
if (event == AVAHI_RESOLVER_FOUND) {
if (address->proto == AVAHI_PROTO_INET) {
AvahiStringList* strLst = txt;
MulticastServiceResolver::ServiceData data;
while (strLst != nullptr) {
std::string_view value{reinterpret_cast<const char*>(strLst->text),
strLst->size};
strLst = strLst->next;
size_t splitIndex = value.find('=');
if (splitIndex == value.npos) {
// Todo make this just do key
continue;
}
std::string_view key = value.substr(0, splitIndex);
value = value.substr(splitIndex + 1, value.size() - splitIndex - 1);
data.txt.emplace_back(std::pair<std::string, std::string>{key, value});
}
wpi::SmallString<256> outputHostName;
char label[256];
do {
impl->table.unescape_label(&host_name, label, sizeof(label));
if (label[0] == '\0') {
break;
}
outputHostName.append(label);
outputHostName.append(".");
} while (true);
data.ipv4Address = address->data.ipv4.address;
data.port = port;
data.serviceName = name;
data.hostName = outputHostName.string();
impl->onFound(std::move(data));
}
}
impl->table.service_resolver_free(r);
}
static void BrowseCallback(AvahiServiceBrowser* b, AvahiIfIndex interface,
AvahiProtocol protocol, AvahiBrowserEvent event,
const char* name, const char* type,
const char* domain, AvahiLookupResultFlags flags,
void* userdata) {
MulticastServiceResolver::Impl* impl =
reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
if (event == AVAHI_BROWSER_NEW) {
impl->table.service_resolver_new(
impl->table.service_browser_get_client(b), interface, protocol, name,
type, domain, AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_MULTICAST,
ResolveCallback, userdata);
}
}
static void ClientCallback(AvahiClient* client, AvahiClientState state,
void* userdata) {
MulticastServiceResolver::Impl* impl =
reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
if (state == AVAHI_CLIENT_S_RUNNING) {
impl->browser = impl->table.service_browser_new(
client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, impl->serviceType.c_str(),
"local", AvahiLookupFlags::AVAHI_LOOKUP_USE_MULTICAST, BrowseCallback,
userdata);
}
}
void MulticastServiceResolver::Start() {
if (!pImpl->table.IsValid()) {
return;
}
std::scoped_lock lock{*pImpl->thread};
if (pImpl->client) {
return;
}
pImpl->client =
pImpl->table.client_new(pImpl->thread->GetPoll(), AVAHI_CLIENT_NO_FAIL,
ClientCallback, pImpl.get(), nullptr);
}
void MulticastServiceResolver::Stop() {
if (!pImpl->table.IsValid()) {
return;
}
std::scoped_lock lock{*pImpl->thread};
if (pImpl->client) {
if (pImpl->browser) {
pImpl->table.service_browser_free(pImpl->browser);
pImpl->browser = nullptr;
}
pImpl->table.client_free(pImpl->client);
pImpl->client = nullptr;
}
}