• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***
2   This file is part of PulseAudio.
3 
4   Copyright 2009 Daniel Mack
5   based on module-zeroconf-publish.c
6 
7   PulseAudio is free software; you can redistribute it and/or modify
8   it under the terms of the GNU Lesser General Public License as
9   published by the Free Software Foundation; either version 2.1 of the
10   License, or (at your option) any later version.
11 
12   PulseAudio is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   General Public License for more details.
16 
17   You should have received a copy of the GNU Lesser General Public
18   License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19 ***/
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <dns_sd.h>
30 
31 #include <CoreFoundation/CoreFoundation.h>
32 
33 #include <pulse/xmalloc.h>
34 #include <pulse/util.h>
35 
36 #include <pulsecore/parseaddr.h>
37 #include <pulsecore/sink.h>
38 #include <pulsecore/source.h>
39 #include <pulsecore/native-common.h>
40 #include <pulsecore/core-util.h>
41 #include <pulsecore/log.h>
42 #include <pulsecore/dynarray.h>
43 #include <pulsecore/modargs.h>
44 #include <pulsecore/protocol-native.h>
45 
46 PA_MODULE_AUTHOR("Daniel Mack");
47 PA_MODULE_DESCRIPTION("Mac OS X Bonjour Service Publisher");
48 PA_MODULE_VERSION(PACKAGE_VERSION);
49 PA_MODULE_LOAD_ONCE(true);
50 
51 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
52 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
53 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
54 
55 static const char* const valid_modargs[] = {
56     NULL
57 };
58 
59 enum service_subtype {
60     SUBTYPE_HARDWARE,
61     SUBTYPE_VIRTUAL,
62     SUBTYPE_MONITOR
63 };
64 
65 struct service {
66     struct userdata *userdata;
67     DNSServiceRef service;
68     DNSRecordRef rec, rec2;
69     char *service_name;
70     pa_object *device;
71     enum service_subtype subtype;
72 };
73 
74 struct userdata {
75     pa_core *core;
76     pa_module *module;
77 
78     pa_hashmap *services;
79     char *service_name;
80 
81     pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
82 
83     pa_native_protocol *native;
84     DNSServiceRef main_service;
85 };
86 
get_service_data(struct service * s,pa_sample_spec * ret_ss,pa_channel_map * ret_map,const char ** ret_name,pa_proplist ** ret_proplist,enum service_subtype * ret_subtype)87 static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, pa_proplist **ret_proplist, enum service_subtype *ret_subtype) {
88     pa_assert(s);
89     pa_assert(ret_ss);
90     pa_assert(ret_proplist);
91     pa_assert(ret_subtype);
92 
93     if (pa_sink_isinstance(s->device)) {
94         pa_sink *sink = PA_SINK(s->device);
95 
96         *ret_ss = sink->sample_spec;
97         *ret_map = sink->channel_map;
98         *ret_name = sink->name;
99         *ret_proplist = sink->proplist;
100         *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
101 
102     } else if (pa_source_isinstance(s->device)) {
103         pa_source *source = PA_SOURCE(s->device);
104 
105         *ret_ss = source->sample_spec;
106         *ret_map = source->channel_map;
107         *ret_name = source->name;
108         *ret_proplist = source->proplist;
109         *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
110 
111     } else
112         pa_assert_not_reached();
113 }
114 
txt_record_server_data(pa_core * c,TXTRecordRef * txt)115 static void txt_record_server_data(pa_core *c, TXTRecordRef *txt) {
116     char s[128];
117     char *t;
118 
119     pa_assert(c);
120 
121     TXTRecordSetValue(txt, "server-version", strlen(PACKAGE_NAME" "PACKAGE_VERSION), PACKAGE_NAME" "PACKAGE_VERSION);
122 
123     t = pa_get_user_name_malloc();
124     TXTRecordSetValue(txt, "user-name", strlen(t), t);
125     pa_xfree(t);
126 
127     t = pa_machine_id();
128     TXTRecordSetValue(txt, "machine-id", strlen(t), t);
129     pa_xfree(t);
130 
131     t = pa_uname_string();
132     TXTRecordSetValue(txt, "uname", strlen(t), t);
133     pa_xfree(t);
134 
135     t = pa_get_fqdn(s, sizeof(s));
136     TXTRecordSetValue(txt, "fqdn", strlen(t), t);
137 
138     snprintf(s, sizeof(s), "0x%08x", c->cookie);
139     TXTRecordSetValue(txt, "cookie", strlen(s), s);
140 }
141 
142 static void service_free(struct service *s);
143 
dns_service_register_reply(DNSServiceRef sdRef,DNSServiceFlags flags,DNSServiceErrorType errorCode,const char * name,const char * regtype,const char * domain,void * context)144 static void dns_service_register_reply(DNSServiceRef sdRef,
145                                        DNSServiceFlags flags,
146                                        DNSServiceErrorType errorCode,
147                                        const char *name,
148                                        const char *regtype,
149                                        const char *domain,
150                                        void *context) {
151     struct service *s = context;
152 
153     pa_assert(s);
154 
155     switch (errorCode) {
156     case kDNSServiceErr_NameConflict:
157         pa_log("DNS service reported kDNSServiceErr_NameConflict\n");
158         service_free(s);
159         break;
160 
161     case kDNSServiceErr_NoError:
162     default:
163         break;
164     }
165 }
166 
compute_port(struct userdata * u)167 static uint16_t compute_port(struct userdata *u) {
168     pa_strlist *i;
169 
170     pa_assert(u);
171 
172     for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) {
173         pa_parsed_address a;
174 
175         if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
176             (a.type == PA_PARSED_ADDRESS_TCP4 ||
177              a.type == PA_PARSED_ADDRESS_TCP6 ||
178              a.type == PA_PARSED_ADDRESS_TCP_AUTO) &&
179             a.port > 0) {
180 
181             pa_xfree(a.path_or_host);
182             return htons(a.port);
183         }
184 
185         pa_xfree(a.path_or_host);
186     }
187 
188     return htons(PA_NATIVE_DEFAULT_PORT);
189 }
190 
publish_service(struct service * s)191 static int publish_service(struct service *s) {
192     int r = -1;
193     TXTRecordRef txt;
194     DNSServiceErrorType err;
195     const char *name = NULL, *t;
196     pa_proplist *proplist = NULL;
197     pa_sample_spec ss;
198     pa_channel_map map;
199     char cm[PA_CHANNEL_MAP_SNPRINT_MAX], tmp[64];
200     enum service_subtype subtype;
201 
202     const char * const subtype_text[] = {
203         [SUBTYPE_HARDWARE] = "hardware",
204         [SUBTYPE_VIRTUAL] = "virtual",
205         [SUBTYPE_MONITOR] = "monitor"
206     };
207 
208     pa_assert(s);
209 
210     if (s->service) {
211         DNSServiceRefDeallocate(s->service);
212         s->service = NULL;
213     }
214 
215     TXTRecordCreate(&txt, 0, NULL);
216 
217     txt_record_server_data(s->userdata->core, &txt);
218 
219     get_service_data(s, &ss, &map, &name, &proplist, &subtype);
220     TXTRecordSetValue(&txt, "device", strlen(name), name);
221 
222     snprintf(tmp, sizeof(tmp), "%u", ss.rate);
223     TXTRecordSetValue(&txt, "rate", strlen(tmp), tmp);
224 
225     snprintf(tmp, sizeof(tmp), "%u", ss.channels);
226     TXTRecordSetValue(&txt, "channels", strlen(tmp), tmp);
227 
228     t = pa_sample_format_to_string(ss.format);
229     TXTRecordSetValue(&txt, "format", strlen(t), t);
230 
231     t = pa_channel_map_snprint(cm, sizeof(cm), &map);
232     TXTRecordSetValue(&txt, "channel_map", strlen(t), t);
233 
234     t = subtype_text[subtype];
235     TXTRecordSetValue(&txt, "subtype", strlen(t), t);
236 
237     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION)))
238         TXTRecordSetValue(&txt, "description", strlen(t), t);
239     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME)))
240         TXTRecordSetValue(&txt, "icon-name", strlen(t), t);
241     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME)))
242         TXTRecordSetValue(&txt, "vendor-name", strlen(t), t);
243     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
244         TXTRecordSetValue(&txt, "product-name", strlen(t), t);
245     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS)))
246         TXTRecordSetValue(&txt, "class", strlen(t), t);
247     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR)))
248         TXTRecordSetValue(&txt, "form-factor", strlen(t), t);
249 
250     err = DNSServiceRegister(&s->service,
251                              0,         /* flags */
252                              kDNSServiceInterfaceIndexAny,
253                              s->service_name,
254                              pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
255                              NULL,      /* domain */
256                              NULL,      /* host */
257                              compute_port(s->userdata),
258                              TXTRecordGetLength(&txt),
259                              TXTRecordGetBytesPtr(&txt),
260                              dns_service_register_reply, s);
261 
262     if (err != kDNSServiceErr_NoError) {
263         pa_log("DNSServiceRegister() returned err %d", err);
264         goto finish;
265     }
266 
267     pa_log_debug("Successfully registered Bonjour services for >%s<.", s->service_name);
268     return 0;
269 
270 finish:
271 
272     /* Remove this service */
273     if (r < 0)
274         service_free(s);
275 
276     TXTRecordDeallocate(&txt);
277 
278     return r;
279 }
280 
get_service(struct userdata * u,pa_object * device)281 static struct service *get_service(struct userdata *u, pa_object *device) {
282     struct service *s;
283     char *hn, *un;
284     const char *n;
285 
286     pa_assert(u);
287     pa_object_assert_ref(device);
288 
289     if ((s = pa_hashmap_get(u->services, device)))
290         return s;
291 
292     s = pa_xnew0(struct service, 1);
293     s->userdata = u;
294     s->device = device;
295 
296     if (pa_sink_isinstance(device)) {
297         if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
298             n = PA_SINK(device)->name;
299     } else {
300         if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
301             n = PA_SOURCE(device)->name;
302     }
303 
304     hn = pa_get_host_name_malloc();
305     un = pa_get_user_name_malloc();
306 
307     s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1);
308 
309     pa_xfree(un);
310     pa_xfree(hn);
311 
312     pa_hashmap_put(u->services, s->device, s);
313 
314     return s;
315 }
316 
service_free(struct service * s)317 static void service_free(struct service *s) {
318     pa_assert(s);
319 
320     pa_hashmap_remove(s->userdata->services, s->device);
321 
322     if (s->service)
323         DNSServiceRefDeallocate(s->service);
324 
325     pa_xfree(s->service_name);
326     pa_xfree(s);
327 }
328 
shall_ignore(pa_object * o)329 static bool shall_ignore(pa_object *o) {
330     pa_object_assert_ref(o);
331 
332     if (pa_sink_isinstance(o))
333         return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
334 
335     if (pa_source_isinstance(o))
336         return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
337 
338     pa_assert_not_reached();
339 }
340 
device_new_or_changed_cb(pa_core * c,pa_object * o,struct userdata * u)341 static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
342     pa_assert(c);
343     pa_object_assert_ref(o);
344 
345     if (!shall_ignore(o))
346         publish_service(get_service(u, o));
347 
348     return PA_HOOK_OK;
349 }
350 
device_unlink_cb(pa_core * c,pa_object * o,struct userdata * u)351 static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
352     struct service *s;
353 
354     pa_assert(c);
355     pa_object_assert_ref(o);
356 
357     if ((s = pa_hashmap_get(u->services, o)))
358         service_free(s);
359 
360     return PA_HOOK_OK;
361 }
362 
publish_main_service(struct userdata * u)363 static int publish_main_service(struct userdata *u) {
364     DNSServiceErrorType err;
365     TXTRecordRef txt;
366 
367     pa_assert(u);
368 
369     if (u->main_service) {
370         DNSServiceRefDeallocate(u->main_service);
371         u->main_service = NULL;
372     }
373 
374     TXTRecordCreate(&txt, 0, NULL);
375     txt_record_server_data(u->core, &txt);
376 
377     err = DNSServiceRegister(&u->main_service,
378                              0, /* flags */
379                              kDNSServiceInterfaceIndexAny,
380                              u->service_name,
381                              SERVICE_TYPE_SERVER,
382                              NULL, /* domain */
383                              NULL, /* host */
384                              compute_port(u),
385                              TXTRecordGetLength(&txt),
386                              TXTRecordGetBytesPtr(&txt),
387                              NULL, NULL);
388 
389     if (err != kDNSServiceErr_NoError) {
390         pa_log("%s(): DNSServiceRegister() returned err %d", __func__, err);
391         return err;
392     }
393 
394     TXTRecordDeallocate(&txt);
395 
396     return 0;
397 }
398 
publish_all_services(struct userdata * u)399 static int publish_all_services(struct userdata *u) {
400     pa_sink *sink;
401     pa_source *source;
402     uint32_t idx;
403 
404     pa_assert(u);
405 
406     pa_log_debug("Publishing services in Bonjour");
407 
408     for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
409         if (!shall_ignore(PA_OBJECT(sink)))
410             publish_service(get_service(u, PA_OBJECT(sink)));
411 
412     for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
413         if (!shall_ignore(PA_OBJECT(source)))
414             publish_service(get_service(u, PA_OBJECT(source)));
415 
416     return publish_main_service(u);
417 }
418 
unpublish_all_services(struct userdata * u)419 static void unpublish_all_services(struct userdata *u) {
420     void *state = NULL;
421     struct service *s;
422 
423     pa_assert(u);
424 
425     pa_log_debug("Unpublishing services in Bonjour");
426 
427     while ((s = pa_hashmap_iterate(u->services, &state, NULL)))
428         service_free(s);
429 
430     if (u->main_service)
431         DNSServiceRefDeallocate(u->main_service);
432 }
433 
pa__init(pa_module * m)434 int pa__init(pa_module*m) {
435 
436     struct userdata *u;
437     pa_modargs *ma = NULL;
438     char *hn, *un;
439 
440     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
441         pa_log("Failed to parse module arguments.");
442         goto fail;
443     }
444 
445     m->userdata = u = pa_xnew0(struct userdata, 1);
446     u->core = m->core;
447     u->module = m;
448     u->native = pa_native_protocol_get(u->core);
449 
450     u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
451 
452     u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
453     u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
454     u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
455     u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
456     u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
457     u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
458 
459     un = pa_get_user_name_malloc();
460     hn = pa_get_host_name_malloc();
461     u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), kDNSServiceMaxDomainName-1);
462     pa_xfree(un);
463     pa_xfree(hn);
464 
465     publish_all_services(u);
466     pa_modargs_free(ma);
467 
468     return 0;
469 
470 fail:
471     pa__done(m);
472 
473     if (ma)
474         pa_modargs_free(ma);
475 
476     return -1;
477 }
478 
pa__done(pa_module * m)479 void pa__done(pa_module*m) {
480     struct userdata*u;
481     pa_assert(m);
482 
483     if (!(u = m->userdata))
484         return;
485 
486     unpublish_all_services(u);
487 
488     if (u->services)
489         pa_hashmap_free(u->services);
490 
491     if (u->sink_new_slot)
492         pa_hook_slot_free(u->sink_new_slot);
493     if (u->source_new_slot)
494         pa_hook_slot_free(u->source_new_slot);
495     if (u->sink_changed_slot)
496         pa_hook_slot_free(u->sink_changed_slot);
497     if (u->source_changed_slot)
498         pa_hook_slot_free(u->source_changed_slot);
499     if (u->sink_unlink_slot)
500         pa_hook_slot_free(u->sink_unlink_slot);
501     if (u->source_unlink_slot)
502         pa_hook_slot_free(u->source_unlink_slot);
503 
504     if (u->native)
505         pa_native_protocol_unref(u->native);
506 
507     pa_xfree(u->service_name);
508     pa_xfree(u);
509 }
510