ZeroconfAvahi.cxx 7.27 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20
#include "config.h"
21
#include "ZeroconfAvahi.hxx"
22
#include "AvahiPoll.hxx"
23
#include "ZeroconfInternal.hxx"
Max Kellermann's avatar
Max Kellermann committed
24
#include "Listen.hxx"
25
#include "system/FatalError.hxx"
26 27
#include "util/Domain.hxx"
#include "Log.hxx"
28 29 30 31 32 33 34 35 36

#include <avahi-client/client.h>
#include <avahi-client/publish.h>

#include <avahi-common/alternative.h>
#include <avahi-common/domain.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h>

37 38
#include <dbus/dbus.h>

39
static constexpr Domain avahi_domain("avahi");
40 41

static char *avahiName;
42
static bool avahi_running;
43
static MyAvahiPoll *avahi_poll;
44 45 46 47 48 49 50 51
static AvahiClient *avahiClient;
static AvahiEntryGroup *avahiGroup;

static void avahiRegisterService(AvahiClient * c);

/* Callback when the EntryGroup changes state */
static void avahiGroupCallback(AvahiEntryGroup * g,
			       AvahiEntryGroupState state,
52
			       gcc_unused void *userdata)
53 54 55 56
{
	char *n;
	assert(g);

57 58
	FormatDebug(avahi_domain,
		    "Service group changed to state %d", state);
59 60 61 62

	switch (state) {
	case AVAHI_ENTRY_GROUP_ESTABLISHED:
		/* The entry group has been established successfully */
63 64 65
		FormatDefault(avahi_domain,
			      "Service '%s' successfully established.",
			      avahiName);
66 67 68 69 70 71 72 73
		break;

	case AVAHI_ENTRY_GROUP_COLLISION:
		/* A service name collision happened. Let's pick a new name */
		n = avahi_alternative_service_name(avahiName);
		avahi_free(avahiName);
		avahiName = n;

74 75 76
		FormatDefault(avahi_domain,
			      "Service name collision, renaming service to '%s'",
			      avahiName);
77 78 79 80 81 82

		/* And recreate the services */
		avahiRegisterService(avahi_entry_group_get_client(g));
		break;

	case AVAHI_ENTRY_GROUP_FAILURE:
83 84 85 86
		FormatError(avahi_domain,
			    "Entry group failure: %s",
			    avahi_strerror(avahi_client_errno
					   (avahi_entry_group_get_client(g))));
87
		/* Some kind of failure happened while we were registering our services */
88
		avahi_running = false;
89 90 91
		break;

	case AVAHI_ENTRY_GROUP_UNCOMMITED:
92
		LogDebug(avahi_domain, "Service group is UNCOMMITED");
93 94
		break;
	case AVAHI_ENTRY_GROUP_REGISTERING:
95
		LogDebug(avahi_domain, "Service group is REGISTERING");
96 97 98 99 100 101
	}
}

/* Registers a new service with avahi */
static void avahiRegisterService(AvahiClient * c)
{
102 103 104
	FormatDebug(avahi_domain, "Registering service %s/%s",
		    SERVICE_TYPE, avahiName);

105 106 107 108 109 110
	int ret;
	assert(c);

	/* If this is the first time we're called,
	 * let's create a new entry group */
	if (!avahiGroup) {
111
		avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, nullptr);
112
		if (!avahiGroup) {
113 114 115
			FormatError(avahi_domain,
				    "Failed to create avahi EntryGroup: %s",
				    avahi_strerror(avahi_client_errno(c)));
116 117 118 119 120 121 122 123 124 125
			goto fail;
		}
	}

	/* Add the service */
	/* TODO: This currently binds to ALL interfaces.
	 *       We could maybe add a service per actual bound interface,
	 *       if that's better. */
	ret = avahi_entry_group_add_service(avahiGroup,
					    AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
126
					    AvahiPublishFlags(0),
127 128
					    avahiName, SERVICE_TYPE, nullptr,
					    nullptr, listen_port, nullptr);
129
	if (ret < 0) {
130 131
		FormatError(avahi_domain, "Failed to add service %s: %s",
			    SERVICE_TYPE, avahi_strerror(ret));
132 133 134 135 136 137
		goto fail;
	}

	/* Tell the server to register the service group */
	ret = avahi_entry_group_commit(avahiGroup);
	if (ret < 0) {
138 139
		FormatError(avahi_domain, "Failed to commit service group: %s",
			    avahi_strerror(ret));
140 141 142 143 144
		goto fail;
	}
	return;

fail:
145
	avahi_running = false;
146 147 148 149
}

/* Callback when avahi changes state */
static void avahiClientCallback(AvahiClient * c, AvahiClientState state,
150
				gcc_unused void *userdata)
151 152 153 154 155
{
	int reason;
	assert(c);

	/* Called whenever the client or server state changes */
156
	FormatDebug(avahi_domain, "Client changed to state %d", state);
157 158 159

	switch (state) {
	case AVAHI_CLIENT_S_RUNNING:
160
		LogDebug(avahi_domain, "Client is RUNNING");
161 162 163 164 165 166 167 168 169 170

		/* The server has startup successfully and registered its host
		 * name on the network, so it's time to create our services */
		if (!avahiGroup)
			avahiRegisterService(c);
		break;

	case AVAHI_CLIENT_FAILURE:
		reason = avahi_client_errno(c);
		if (reason == AVAHI_ERR_DISCONNECTED) {
171 172
			LogDefault(avahi_domain,
				   "Client Disconnected, will reconnect shortly");
173 174
			if (avahiGroup) {
				avahi_entry_group_free(avahiGroup);
175
				avahiGroup = nullptr;
176 177 178 179
			}
			if (avahiClient)
				avahi_client_free(avahiClient);
			avahiClient =
180
			    avahi_client_new(avahi_poll,
181
					     AVAHI_CLIENT_NO_FAIL,
182
					     avahiClientCallback, nullptr,
183 184
					     &reason);
			if (!avahiClient) {
185 186 187
				FormatWarning(avahi_domain,
					      "Could not reconnect: %s",
					      avahi_strerror(reason));
188
				avahi_running = false;
189 190
			}
		} else {
191 192 193
			FormatWarning(avahi_domain,
				      "Client failure: %s (terminal)",
				      avahi_strerror(reason));
194
			avahi_running = false;
195 196 197 198
		}
		break;

	case AVAHI_CLIENT_S_COLLISION:
199 200
		LogDebug(avahi_domain, "Client is COLLISION");

201 202 203 204
		/* 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 (avahiGroup) {
205
			LogDebug(avahi_domain, "Resetting group");
206 207 208
			avahi_entry_group_reset(avahiGroup);
		}

209 210
		break;

211
	case AVAHI_CLIENT_S_REGISTERING:
212 213
		LogDebug(avahi_domain, "Client is REGISTERING");

214 215 216 217 218 219
		/* 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 (avahiGroup) {
220
			LogDebug(avahi_domain, "Resetting group");
221 222 223 224 225 226
			avahi_entry_group_reset(avahiGroup);
		}

		break;

	case AVAHI_CLIENT_CONNECTING:
227 228
		LogDebug(avahi_domain, "Client is CONNECTING");
		break;
229 230 231
	}
}

232
void
233
AvahiInit(EventLoop &loop, const char *serviceName)
234
{
235
	LogDebug(avahi_domain, "Initializing interface");
236 237

	if (!avahi_is_valid_service_name(serviceName))
238
		FormatFatalError("Invalid zeroconf_name \"%s\"", serviceName);
239 240 241

	avahiName = avahi_strdup(serviceName);

242
	avahi_running = true;
243

244
	avahi_poll = new MyAvahiPoll(loop);
245

246
	int error;
247
	avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL,
248
				       avahiClientCallback, nullptr, &error);
249 250

	if (!avahiClient) {
251 252
		FormatError(avahi_domain, "Failed to create client: %s",
			    avahi_strerror(error));
253
		AvahiDeinit();
254 255 256
	}
}

257 258
void
AvahiDeinit(void)
259
{
260
	LogDebug(avahi_domain, "Shutting down interface");
261 262 263

	if (avahiGroup) {
		avahi_entry_group_free(avahiGroup);
264
		avahiGroup = nullptr;
265 266 267 268
	}

	if (avahiClient) {
		avahi_client_free(avahiClient);
269
		avahiClient = nullptr;
270 271
	}

272 273
	delete avahi_poll;
	avahi_poll = nullptr;
274

275
	avahi_free(avahiName);
276
	avahiName = nullptr;
277 278

	dbus_shutdown();
279
}