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 <mk@cm4all.com>
+ *
+ * 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 <avahi-common/error.h>
+
+#include <cassert>
+
+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 <mk@cm4all.com>
+ *
+ * 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 <avahi-client/client.h>
+
+#include <forward_list>
+
+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<ConnectionListener *> 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 <mk@cm4all.com>
+ *
+ * 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 <avahi-client/client.h>
+
+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 <mk@cm4all.com>
+ *
+ * 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 <avahi-client/client.h>
+#include <avahi-common/error.h>
+
+#include <system_error>
+
+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 <mk@cm4all.com>
+ *
+ * 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 <system_error>
+
+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 <mk@cm4all.com>
+ *
+ * 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 <exception>
+
+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: [