ZeroconfAvahi.cxx 7.29 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 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 *avahi_name;
42
static MyAvahiPoll *avahi_poll;
43 44
static AvahiClient *avahi_client;
static AvahiEntryGroup *avahi_group;
45

46 47
static void
AvahiRegisterService(AvahiClient *c);
48

49 50 51 52 53 54 55
/**
 * Callback when the EntryGroup changes state.
 */
static void
AvahiGroupCallback(AvahiEntryGroup *g,
		   AvahiEntryGroupState state,
		   gcc_unused void *userdata)
56
{
57
	assert(g != nullptr);
58

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

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

	case AVAHI_ENTRY_GROUP_COLLISION:
		/* A service name collision happened. Let's pick a new name */
72 73 74 75 76
		{
			char *n = avahi_alternative_service_name(avahi_name);
			avahi_free(avahi_name);
			avahi_name = n;
		}
77

78 79
		FormatDefault(avahi_domain,
			      "Service name collision, renaming service to '%s'",
80
			      avahi_name);
81 82

		/* And recreate the services */
83
		AvahiRegisterService(avahi_entry_group_get_client(g));
84 85 86
		break;

	case AVAHI_ENTRY_GROUP_FAILURE:
87 88
		FormatError(avahi_domain,
			    "Entry group failure: %s",
89 90 91
			    avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
		/* Some kind of failure happened while we were
		   registering our services */
92 93 94
		break;

	case AVAHI_ENTRY_GROUP_UNCOMMITED:
95
		LogDebug(avahi_domain, "Service group is UNCOMMITED");
96
		break;
97

98
	case AVAHI_ENTRY_GROUP_REGISTERING:
99
		LogDebug(avahi_domain, "Service group is REGISTERING");
100 101 102
	}
}

103 104 105 106 107
/**
 * Registers a new service with avahi.
 */
static void
AvahiRegisterService(AvahiClient *c)
108
{
109
	assert(c != nullptr);
110

111 112
	FormatDebug(avahi_domain, "Registering service %s/%s",
		    SERVICE_TYPE, avahi_name);
113 114 115

	/* If this is the first time we're called,
	 * let's create a new entry group */
116 117 118
	if (!avahi_group) {
		avahi_group = avahi_entry_group_new(c, AvahiGroupCallback, nullptr);
		if (!avahi_group) {
119 120 121
			FormatError(avahi_domain,
				    "Failed to create avahi EntryGroup: %s",
				    avahi_strerror(avahi_client_errno(c)));
122
			return;
123 124 125 126 127 128 129
		}
	}

	/* Add the service */
	/* TODO: This currently binds to ALL interfaces.
	 *       We could maybe add a service per actual bound interface,
	 *       if that's better. */
130 131 132 133 134 135 136 137
	int result = avahi_entry_group_add_service(avahi_group,
						   AVAHI_IF_UNSPEC,
						   AVAHI_PROTO_UNSPEC,
						   AvahiPublishFlags(0),
						   avahi_name, SERVICE_TYPE,
						   nullptr, nullptr,
						   listen_port, nullptr);
	if (result < 0) {
138
		FormatError(avahi_domain, "Failed to add service %s: %s",
139
			    SERVICE_TYPE, avahi_strerror(result));
140
		return;
141 142 143
	}

	/* Tell the server to register the service group */
144 145
	result = avahi_entry_group_commit(avahi_group);
	if (result < 0) {
146
		FormatError(avahi_domain, "Failed to commit service group: %s",
147
			    avahi_strerror(result));
148
		return;
149 150 151 152
	}
}

/* Callback when avahi changes state */
153 154 155
static void
MyAvahiClientCallback(AvahiClient *c, AvahiClientState state,
		      gcc_unused void *userdata)
156
{
157
	assert(c != nullptr);
158 159

	/* Called whenever the client or server state changes */
160
	FormatDebug(avahi_domain, "Client changed to state %d", state);
161 162

	switch (state) {
163 164
		int reason;

165
	case AVAHI_CLIENT_S_RUNNING:
166
		LogDebug(avahi_domain, "Client is RUNNING");
167 168 169

		/* The server has startup successfully and registered its host
		 * name on the network, so it's time to create our services */
170 171
		if (avahi_group == nullptr)
			AvahiRegisterService(c);
172 173 174 175 176
		break;

	case AVAHI_CLIENT_FAILURE:
		reason = avahi_client_errno(c);
		if (reason == AVAHI_ERR_DISCONNECTED) {
177 178
			LogDefault(avahi_domain,
				   "Client Disconnected, will reconnect shortly");
179 180 181
			if (avahi_group != nullptr) {
				avahi_entry_group_free(avahi_group);
				avahi_group = nullptr;
182
			}
183 184 185 186

			if (avahi_client != nullptr)
				avahi_client_free(avahi_client);
			avahi_client =
187
			    avahi_client_new(avahi_poll,
188
					     AVAHI_CLIENT_NO_FAIL,
189
					     MyAvahiClientCallback, nullptr,
190
					     &reason);
191
			if (avahi_client == nullptr)
192 193 194
				FormatWarning(avahi_domain,
					      "Could not reconnect: %s",
					      avahi_strerror(reason));
195
		} else {
196 197 198
			FormatWarning(avahi_domain,
				      "Client failure: %s (terminal)",
				      avahi_strerror(reason));
199
		}
200

201 202 203
		break;

	case AVAHI_CLIENT_S_COLLISION:
204 205
		LogDebug(avahi_domain, "Client is COLLISION");

206 207 208 209
		/* 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) {
210
			LogDebug(avahi_domain, "Resetting group");
211
			avahi_entry_group_reset(avahi_group);
212 213
		}

214 215
		break;

216
	case AVAHI_CLIENT_S_REGISTERING:
217 218
		LogDebug(avahi_domain, "Client is REGISTERING");

219 220 221 222 223
		/* 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. */

224
		if (avahi_group != nullptr) {
225
			LogDebug(avahi_domain, "Resetting group");
226
			avahi_entry_group_reset(avahi_group);
227 228 229 230 231
		}

		break;

	case AVAHI_CLIENT_CONNECTING:
232 233
		LogDebug(avahi_domain, "Client is CONNECTING");
		break;
234 235 236
	}
}

237
void
238
AvahiInit(EventLoop &loop, const char *serviceName)
239
{
240
	LogDebug(avahi_domain, "Initializing interface");
241 242

	if (!avahi_is_valid_service_name(serviceName))
243
		FormatFatalError("Invalid zeroconf_name \"%s\"", serviceName);
244

245
	avahi_name = avahi_strdup(serviceName);
246

247
	avahi_poll = new MyAvahiPoll(loop);
248

249
	int error;
250 251 252 253
	avahi_client = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL,
					MyAvahiClientCallback, nullptr,
					&error);
	if (avahi_client == nullptr) {
254 255
		FormatError(avahi_domain, "Failed to create client: %s",
			    avahi_strerror(error));
256
		AvahiDeinit();
257 258 259
	}
}

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

265 266 267
	if (avahi_group != nullptr) {
		avahi_entry_group_free(avahi_group);
		avahi_group = nullptr;
268 269
	}

270 271 272
	if (avahi_client != nullptr) {
		avahi_client_free(avahi_client);
		avahi_client = nullptr;
273 274
	}

275 276
	delete avahi_poll;
	avahi_poll = nullptr;
277

278 279
	avahi_free(avahi_name);
	avahi_name = nullptr;
280 281

	dbus_shutdown();
282
}