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