diff --git a/src/zeroconf/ZeroconfAvahi.cxx b/src/zeroconf/ZeroconfAvahi.cxx index fab7c611a..2d6047381 100644 --- a/src/zeroconf/ZeroconfAvahi.cxx +++ b/src/zeroconf/ZeroconfAvahi.cxx @@ -18,7 +18,9 @@ */ #include "ZeroconfAvahi.hxx" -#include "avahi/Poll.hxx" +#include "avahi/Client.hxx" +#include "avahi/ConnectionListener.hxx" +#include "avahi/ErrorHandler.hxx" #include "ZeroconfInternal.hxx" #include "Listen.hxx" #include "util/Domain.hxx" @@ -35,17 +37,34 @@ static constexpr Domain avahi_domain("avahi"); -class AvahiGlue final { +class AvahiGlue final : Avahi::ErrorHandler, Avahi::ConnectionListener { public: - Avahi::Poll poll; + Avahi::Client client; explicit AvahiGlue(EventLoop &event_loop) - :poll(event_loop) {} + :client(event_loop, *this) + { + client.AddListener(*this); + } + + ~AvahiGlue() noexcept { + client.RemoveListener(*this); + } + + /* virtual methods from class AvahiConnectionListener */ + void OnAvahiConnect(AvahiClient *client) noexcept override; + void OnAvahiDisconnect() noexcept override; + void OnAvahiChanged() noexcept override; + + /* virtual methods from class Avahi::ErrorHandler */ + bool OnAvahiError(std::exception_ptr e) noexcept override { + LogError(e); + return true; + } }; static char *avahi_name; static AvahiGlue *avahi_glue; -static AvahiClient *avahi_client; static AvahiEntryGroup *avahi_group; static void @@ -154,91 +173,32 @@ AvahiRegisterService(AvahiClient *c) } } -/* Callback when avahi changes state */ -static void -MyAvahiClientCallback(AvahiClient *c, AvahiClientState state, - [[maybe_unused]] void *userdata) +void +AvahiGlue::OnAvahiConnect(AvahiClient *c) noexcept { - assert(c != nullptr); + if (avahi_group == nullptr) + AvahiRegisterService(c); +} - /* Called whenever the client or server state changes */ - FormatDebug(avahi_domain, "Client changed to state %d", state); - - switch (state) { - int reason; - - case AVAHI_CLIENT_S_RUNNING: - LogDebug(avahi_domain, "Client is RUNNING"); - - /* The server has startup successfully and registered its host - * name on the network, so it's time to create our services */ - if (avahi_group == nullptr) - AvahiRegisterService(c); - break; - - case AVAHI_CLIENT_FAILURE: - reason = avahi_client_errno(c); - if (reason == AVAHI_ERR_DISCONNECTED) { - LogNotice(avahi_domain, - "Client Disconnected, will reconnect shortly"); - if (avahi_group != nullptr) { - avahi_entry_group_free(avahi_group); - avahi_group = nullptr; - } - - if (avahi_client != nullptr) - avahi_client_free(avahi_client); - avahi_client = - avahi_client_new(&avahi_glue->poll, - AVAHI_CLIENT_NO_FAIL, - MyAvahiClientCallback, nullptr, - &reason); - if (avahi_client == nullptr) - FormatWarning(avahi_domain, - "Could not reconnect: %s", - avahi_strerror(reason)); - } else { - FormatWarning(avahi_domain, - "Client failure: %s (terminal)", - avahi_strerror(reason)); - } - - break; - - case AVAHI_CLIENT_S_COLLISION: - LogDebug(avahi_domain, "Client is COLLISION"); - - /* Let's drop our registered services. When the server - is back in AVAHI_SERVER_RUNNING state we will - register them again with the new host name. */ - if (avahi_group != nullptr) { - LogDebug(avahi_domain, "Resetting group"); - avahi_entry_group_reset(avahi_group); - } - - break; - - case AVAHI_CLIENT_S_REGISTERING: - LogDebug(avahi_domain, "Client is REGISTERING"); - - /* The server records are now being established. This - * might be caused by a host name change. We need to wait - * for our own records to register until the host name is - * properly esatblished. */ - - if (avahi_group != nullptr) { - LogDebug(avahi_domain, "Resetting group"); - avahi_entry_group_reset(avahi_group); - } - - break; - - case AVAHI_CLIENT_CONNECTING: - LogDebug(avahi_domain, "Client is CONNECTING"); - break; +void +AvahiGlue::OnAvahiDisconnect() noexcept +{ + if (avahi_group != nullptr) { + avahi_entry_group_free(avahi_group); + avahi_group = nullptr; } } +void +AvahiGlue::OnAvahiChanged() noexcept +{ + if (avahi_group != nullptr) { + LogDebug(avahi_domain, "Resetting group"); + avahi_entry_group_reset(avahi_group); + } +} + + void AvahiInit(EventLoop &loop, const char *serviceName) { @@ -250,16 +210,6 @@ AvahiInit(EventLoop &loop, const char *serviceName) avahi_name = avahi_strdup(serviceName); avahi_glue = new AvahiGlue(loop); - - int error; - avahi_client = avahi_client_new(&avahi_glue->poll, AVAHI_CLIENT_NO_FAIL, - MyAvahiClientCallback, nullptr, - &error); - if (avahi_client == nullptr) { - FormatError(avahi_domain, "Failed to create client: %s", - avahi_strerror(error)); - AvahiDeinit(); - } } void @@ -272,11 +222,6 @@ AvahiDeinit() avahi_group = nullptr; } - if (avahi_client != nullptr) { - avahi_client_free(avahi_client); - avahi_client = nullptr; - } - delete avahi_glue; avahi_free(avahi_name); diff --git a/src/zeroconf/avahi/Client.cxx b/src/zeroconf/avahi/Client.cxx new file mode 100644 index 000000000..a1fa5c17e --- /dev/null +++ b/src/zeroconf/avahi/Client.cxx @@ -0,0 +1,139 @@ +/* + * Copyright 2007-2021 CM4all GmbH + * All rights reserved. + * + * author: Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Client.hxx" +#include "ConnectionListener.hxx" +#include "ErrorHandler.hxx" +#include "Error.hxx" + +#include + +#include + +namespace Avahi { + +Client::Client(EventLoop &event_loop, ErrorHandler &_error_handler) noexcept + :error_handler(_error_handler), + reconnect_timer(event_loop, BIND_THIS_METHOD(OnReconnectTimer)), + poll(event_loop) +{ + reconnect_timer.Schedule({}); +} + +Client::~Client() noexcept +{ + Close(); +} + +void +Client::Close() noexcept +{ + if (client != nullptr) { + for (auto *l : listeners) + l->OnAvahiDisconnect(); + + avahi_client_free(client); + client = nullptr; + } + + reconnect_timer.Cancel(); +} + +void +Client::ClientCallback(AvahiClient *c, AvahiClientState state) noexcept +{ + int error; + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + for (auto *l : listeners) + l->OnAvahiConnect(c); + + break; + + case AVAHI_CLIENT_FAILURE: + error = avahi_client_errno(c); + if (error == AVAHI_ERR_DISCONNECTED) { + Close(); + + reconnect_timer.Schedule(std::chrono::seconds(10)); + } else { + if (!error_handler.OnAvahiError(std::make_exception_ptr(MakeError(error, + "Avahi connection error")))) + return; + + reconnect_timer.Schedule(std::chrono::minutes(1)); + } + + for (auto *l : listeners) + l->OnAvahiDisconnect(); + + break; + + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + for (auto *l : listeners) + l->OnAvahiChanged(); + + break; + + case AVAHI_CLIENT_CONNECTING: + break; + } +} + +void +Client::ClientCallback(AvahiClient *c, AvahiClientState state, + void *userdata) noexcept +{ + auto &client = *(Client *)userdata; + client.ClientCallback(c, state); +} + +void +Client::OnReconnectTimer() noexcept +{ + int error; + client = avahi_client_new(&poll, AVAHI_CLIENT_NO_FAIL, + ClientCallback, this, + &error); + if (client == nullptr) { + if (!error_handler.OnAvahiError(std::make_exception_ptr(MakeError(error, + "Failed to create Avahi client")))) + return; + + reconnect_timer.Schedule(std::chrono::minutes(1)); + return; + } +} + +} // namespace Avahi diff --git a/src/zeroconf/avahi/Client.hxx b/src/zeroconf/avahi/Client.hxx new file mode 100644 index 000000000..f8be027ad --- /dev/null +++ b/src/zeroconf/avahi/Client.hxx @@ -0,0 +1,93 @@ +/* + * Copyright 2007-2021 CM4all GmbH + * All rights reserved. + * + * author: Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "Poll.hxx" +#include "event/CoarseTimerEvent.hxx" + +#include + +#include + +class EventLoop; + +namespace Avahi { + +class ErrorHandler; +class ConnectionListener; + +class Client final { + ErrorHandler &error_handler; + + CoarseTimerEvent reconnect_timer; + + Poll poll; + + AvahiClient *client = nullptr; + + std::forward_list listeners; + +public: + Client(EventLoop &event_loop, ErrorHandler &_error_handler) noexcept; + ~Client() noexcept; + + Client(const Client &) = delete; + Client &operator=(const Client &) = delete; + + EventLoop &GetEventLoop() const noexcept { + return poll.GetEventLoop(); + } + + void Close() noexcept; + + AvahiClient *GetClient() noexcept { + return client; + } + + void AddListener(ConnectionListener &listener) noexcept { + listeners.push_front(&listener); + } + + void RemoveListener(ConnectionListener &listener) noexcept { + listeners.remove(&listener); + } + +private: + void ClientCallback(AvahiClient *c, AvahiClientState state) noexcept; + static void ClientCallback(AvahiClient *c, AvahiClientState state, + void *userdata) noexcept; + + void OnReconnectTimer() noexcept; +}; + +} // namespace Avahi diff --git a/src/zeroconf/avahi/ConnectionListener.hxx b/src/zeroconf/avahi/ConnectionListener.hxx new file mode 100644 index 000000000..1b8a234a7 --- /dev/null +++ b/src/zeroconf/avahi/ConnectionListener.hxx @@ -0,0 +1,61 @@ +/* + * Copyright 2007-2021 CM4all GmbH + * All rights reserved. + * + * author: Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace Avahi { + +class ConnectionListener { +public: + /** + * The connection to the Avahi daemon has been established. + * + * Note that this may be called again after a collision + * (AVAHI_CLIENT_S_COLLISION) or a host name change + * (AVAHI_CLIENT_S_REGISTERING). + */ + virtual void OnAvahiConnect(AvahiClient *client) noexcept = 0; + virtual void OnAvahiDisconnect() noexcept = 0; + + /** + * Something about the Avahi connection has changed, e.g. a + * collision (AVAHI_CLIENT_S_COLLISION) or a host name change + * (AVAHI_CLIENT_S_REGISTERING). Services shall be + * unpublished now, and will be re-published in the following + * OnAvahiConnect() call. + */ + virtual void OnAvahiChanged() noexcept {} +}; + +} // namespace Avahi diff --git a/src/zeroconf/avahi/Error.cxx b/src/zeroconf/avahi/Error.cxx new file mode 100644 index 000000000..bc16a9efc --- /dev/null +++ b/src/zeroconf/avahi/Error.cxx @@ -0,0 +1,56 @@ +/* + * Copyright 2007-2021 CM4all GmbH + * All rights reserved. + * + * author: Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Error.hxx" + +#include +#include + +#include + +namespace Avahi { + +ErrorCategory error_category; + +std::string +ErrorCategory::message(int condition) const +{ + return avahi_strerror(condition); +} + +std::system_error +MakeError(AvahiClient &client, const char *msg) noexcept +{ + return MakeError(avahi_client_errno(&client), msg); +} + +} // namespace Avahi diff --git a/src/zeroconf/avahi/Error.hxx b/src/zeroconf/avahi/Error.hxx new file mode 100644 index 000000000..e3bbf37f9 --- /dev/null +++ b/src/zeroconf/avahi/Error.hxx @@ -0,0 +1,61 @@ +/* + * Copyright 2007-2021 CM4all GmbH + * All rights reserved. + * + * author: Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +struct AvahiClient; + +namespace Avahi { + +class ErrorCategory final : public std::error_category { +public: + const char *name() const noexcept override { + return "avahi-client"; + } + + std::string message(int condition) const override; +}; + +extern ErrorCategory error_category; + +inline std::system_error +MakeError(int error, const char *msg) noexcept +{ + return std::system_error(error, error_category, msg); +} + +std::system_error +MakeError(AvahiClient &client, const char *msg) noexcept; + +} // namespace Avahi diff --git a/src/zeroconf/avahi/ErrorHandler.hxx b/src/zeroconf/avahi/ErrorHandler.hxx new file mode 100644 index 000000000..fae70057d --- /dev/null +++ b/src/zeroconf/avahi/ErrorHandler.hxx @@ -0,0 +1,50 @@ +/* + * Copyright 2007-2021 CM4all GmbH + * All rights reserved. + * + * author: Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +struct AvahiClient; + +namespace Avahi { + +class ErrorHandler { +public: + /** + * @return true to keep retrying, false if the failed object + * has been disposed + */ + virtual bool OnAvahiError(std::exception_ptr e) noexcept = 0; +}; + +} // namespace Avahi diff --git a/src/zeroconf/avahi/meson.build b/src/zeroconf/avahi/meson.build index 76de2949a..0d47f5988 100644 --- a/src/zeroconf/avahi/meson.build +++ b/src/zeroconf/avahi/meson.build @@ -6,6 +6,8 @@ endif avahi = static_library( 'avahi', + 'Client.cxx', + 'Error.cxx', 'Poll.cxx', include_directories: inc, dependencies: [