2 This file is part of PulseAudio.
4 Copyright 2004-2006 Lennart Poettering
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2.1 of the
9 License, or (at your option) any later version.
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
31 #include <avahi-client/client.h>
32 #include <avahi-client/lookup.h>
33 #include <avahi-common/alternative.h>
34 #include <avahi-common/error.h>
35 #include <avahi-common/domain.h>
36 #include <avahi-common/malloc.h>
38 #include <pulse/xmalloc.h>
40 #include <pulsecore/core-util.h>
41 #include <pulsecore/log.h>
42 #include <pulsecore/hashmap.h>
43 #include <pulsecore/modargs.h>
44 #include <pulsecore/namereg.h>
45 #include <pulsecore/avahi-wrap.h>
47 #include "module-zeroconf-discover-symdef.h"
49 PA_MODULE_AUTHOR("Lennart Poettering");
50 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery");
51 PA_MODULE_VERSION(PACKAGE_VERSION
);
52 PA_MODULE_LOAD_ONCE(true);
54 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
55 #define SERVICE_TYPE_SOURCE "_non-monitor._sub._pulse-source._tcp"
57 static const char* const valid_modargs
[] = {
62 AvahiIfIndex interface
;
63 AvahiProtocol protocol
;
64 char *name
, *type
, *domain
;
65 uint32_t module_index
;
71 AvahiPoll
*avahi_poll
;
73 AvahiServiceBrowser
*source_browser
, *sink_browser
;
78 static unsigned tunnel_hash(const void *p
) {
79 const struct tunnel
*t
= p
;
82 (unsigned) t
->interface
+
83 (unsigned) t
->protocol
+
84 pa_idxset_string_hash_func(t
->name
) +
85 pa_idxset_string_hash_func(t
->type
) +
86 pa_idxset_string_hash_func(t
->domain
);
89 static int tunnel_compare(const void *a
, const void *b
) {
90 const struct tunnel
*ta
= a
, *tb
= b
;
93 if (ta
->interface
!= tb
->interface
)
95 if (ta
->protocol
!= tb
->protocol
)
97 if ((r
= strcmp(ta
->name
, tb
->name
)))
99 if ((r
= strcmp(ta
->type
, tb
->type
)))
101 if ((r
= strcmp(ta
->domain
, tb
->domain
)))
107 static struct tunnel
*tunnel_new(
108 AvahiIfIndex interface
, AvahiProtocol protocol
,
109 const char *name
, const char *type
, const char *domain
) {
112 t
= pa_xnew(struct tunnel
, 1);
113 t
->interface
= interface
;
114 t
->protocol
= protocol
;
115 t
->name
= pa_xstrdup(name
);
116 t
->type
= pa_xstrdup(type
);
117 t
->domain
= pa_xstrdup(domain
);
118 t
->module_index
= PA_IDXSET_INVALID
;
122 static void tunnel_free(struct tunnel
*t
) {
130 static void resolver_cb(
131 AvahiServiceResolver
*r
,
132 AvahiIfIndex interface
, AvahiProtocol protocol
,
133 AvahiResolverEvent event
,
134 const char *name
, const char *type
, const char *domain
,
135 const char *host_name
, const AvahiAddress
*a
, uint16_t port
,
136 AvahiStringList
*txt
,
137 AvahiLookupResultFlags flags
,
140 struct userdata
*u
= userdata
;
145 tnl
= tunnel_new(interface
, protocol
, name
, type
, domain
);
147 if (event
!= AVAHI_RESOLVER_FOUND
)
148 pa_log("Resolving of '%s' failed: %s", name
, avahi_strerror(avahi_client_errno(u
->client
)));
150 char *device
= NULL
, *dname
, *module_name
, *args
;
152 char *if_suffix
= NULL
;
153 char at
[AVAHI_ADDRESS_STR_MAX
], cmt
[PA_CHANNEL_MAP_SNPRINT_MAX
];
157 bool channel_map_set
= false;
160 ss
= u
->core
->default_sample_spec
;
161 cm
= u
->core
->default_channel_map
;
163 for (l
= txt
; l
; l
= l
->next
) {
165 pa_assert_se(avahi_string_list_get_pair(l
, &key
, &value
, NULL
) == 0);
167 if (pa_streq(key
, "device")) {
171 } else if (pa_streq(key
, "rate"))
172 ss
.rate
= (uint32_t) atoi(value
);
173 else if (pa_streq(key
, "channels"))
174 ss
.channels
= (uint8_t) atoi(value
);
175 else if (pa_streq(key
, "format"))
176 ss
.format
= pa_parse_sample_format(value
);
177 else if (pa_streq(key
, "channel_map")) {
178 pa_channel_map_parse(&cm
, value
);
179 channel_map_set
= true;
186 if (!channel_map_set
&& cm
.channels
!= ss
.channels
)
187 pa_channel_map_init_extend(&cm
, ss
.channels
, PA_CHANNEL_MAP_DEFAULT
);
189 if (!pa_sample_spec_valid(&ss
)) {
190 pa_log("Service '%s' contains an invalid sample specification.", name
);
195 if (!pa_channel_map_valid(&cm
) || cm
.channels
!= ss
.channels
) {
196 pa_log("Service '%s' contains an invalid channel map.", name
);
202 dname
= pa_sprintf_malloc("tunnel.%s.%s", host_name
, device
);
204 dname
= pa_sprintf_malloc("tunnel.%s", host_name
);
206 if (!pa_namereg_is_valid_name(dname
)) {
207 pa_log("Cannot construct valid device name from credentials of service '%s'.", dname
);
213 t
= strstr(type
, "sink") ? "sink" : "source";
214 if (a
->proto
== AVAHI_PROTO_INET6
&&
215 a
->data
.ipv6
.address
[0] == 0xfe &&
216 (a
->data
.ipv6
.address
[1] & 0xc0) == 0x80)
217 if_suffix
= pa_sprintf_malloc("%%%d", interface
);
219 module_name
= pa_sprintf_malloc("module-tunnel-%s", t
);
220 args
= pa_sprintf_malloc("server=[%s%s]:%u "
227 avahi_address_snprint(at
, sizeof(at
), a
),
228 if_suffix
? if_suffix
: "", port
,
230 pa_sample_format_to_string(ss
.format
),
234 pa_channel_map_snprint(cmt
, sizeof(cmt
), &cm
));
236 pa_log_debug("Loading %s with arguments '%s'", module_name
, args
);
238 if ((m
= pa_module_load(u
->core
, module_name
, args
))) {
239 tnl
->module_index
= m
->index
;
240 pa_hashmap_put(u
->tunnels
, tnl
, tnl
);
244 pa_xfree(module_name
);
253 avahi_service_resolver_free(r
);
259 static void browser_cb(
260 AvahiServiceBrowser
*b
,
261 AvahiIfIndex interface
, AvahiProtocol protocol
,
262 AvahiBrowserEvent event
,
263 const char *name
, const char *type
, const char *domain
,
264 AvahiLookupResultFlags flags
,
267 struct userdata
*u
= userdata
;
272 if (flags
& AVAHI_LOOKUP_RESULT_LOCAL
)
275 t
= tunnel_new(interface
, protocol
, name
, type
, domain
);
277 if (event
== AVAHI_BROWSER_NEW
) {
279 if (!pa_hashmap_get(u
->tunnels
, t
))
280 if (!(avahi_service_resolver_new(u
->client
, interface
, protocol
, name
, type
, domain
, AVAHI_PROTO_UNSPEC
, 0, resolver_cb
, u
)))
281 pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u
->client
)));
283 /* We ignore the returned resolver object here, since the we don't
284 * need to attach any special data to it, and we can still destroy
285 * it from the callback */
287 } else if (event
== AVAHI_BROWSER_REMOVE
) {
290 if ((t2
= pa_hashmap_get(u
->tunnels
, t
))) {
291 pa_module_unload_request_by_index(u
->core
, t2
->module_index
, true);
292 pa_hashmap_remove(u
->tunnels
, t2
);
300 static void client_callback(AvahiClient
*c
, AvahiClientState state
, void *userdata
) {
301 struct userdata
*u
= userdata
;
309 case AVAHI_CLIENT_S_REGISTERING
:
310 case AVAHI_CLIENT_S_RUNNING
:
311 case AVAHI_CLIENT_S_COLLISION
:
313 if (!u
->sink_browser
) {
315 if (!(u
->sink_browser
= avahi_service_browser_new(
317 AVAHI_IF_UNSPEC
, AVAHI_PROTO_UNSPEC
,
323 pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c
)));
324 pa_module_unload_request(u
->module
, true);
328 if (!u
->source_browser
) {
330 if (!(u
->source_browser
= avahi_service_browser_new(
332 AVAHI_IF_UNSPEC
, AVAHI_PROTO_UNSPEC
,
338 pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c
)));
339 pa_module_unload_request(u
->module
, true);
345 case AVAHI_CLIENT_FAILURE
:
346 if (avahi_client_errno(c
) == AVAHI_ERR_DISCONNECTED
) {
349 pa_log_debug("Avahi daemon disconnected.");
351 if (!(u
->client
= avahi_client_new(u
->avahi_poll
, AVAHI_CLIENT_NO_FAIL
, client_callback
, u
, &error
))) {
352 pa_log("avahi_client_new() failed: %s", avahi_strerror(error
));
353 pa_module_unload_request(u
->module
, true);
359 case AVAHI_CLIENT_CONNECTING
:
361 if (u
->sink_browser
) {
362 avahi_service_browser_free(u
->sink_browser
);
363 u
->sink_browser
= NULL
;
366 if (u
->source_browser
) {
367 avahi_service_browser_free(u
->source_browser
);
368 u
->source_browser
= NULL
;
377 int pa__init(pa_module
*m
) {
380 pa_modargs
*ma
= NULL
;
383 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
384 pa_log("Failed to parse module arguments.");
388 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
391 u
->sink_browser
= u
->source_browser
= NULL
;
393 u
->tunnels
= pa_hashmap_new(tunnel_hash
, tunnel_compare
);
395 u
->avahi_poll
= pa_avahi_poll_new(m
->core
->mainloop
);
397 if (!(u
->client
= avahi_client_new(u
->avahi_poll
, AVAHI_CLIENT_NO_FAIL
, client_callback
, u
, &error
))) {
398 pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error
));
415 void pa__done(pa_module
*m
) {
419 if (!(u
= m
->userdata
))
423 avahi_client_free(u
->client
);
426 pa_avahi_poll_free(u
->avahi_poll
);
431 while ((t
= pa_hashmap_steal_first(u
->tunnels
))) {
432 pa_module_unload_request_by_index(u
->core
, t
->module_index
, true);
436 pa_hashmap_free(u
->tunnels
);