1 /*****************************************************************************
2  * microdns.c: mDNS services discovery module
3  *****************************************************************************
4  * Copyright © 2016 VLC authors, VideoLAN and VideoLabs
5  *
6  * Authors: Steve Lhomme <robux4@videolabs.io>
7  *          Thomas Guillem <thomas@gllm.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27 
28 #include <assert.h>
29 
30 #include <vlc_common.h>
31 #include <vlc_atomic.h>
32 #include <vlc_plugin.h>
33 #include <vlc_modules.h>
34 #include <vlc_services_discovery.h>
35 #include <vlc_renderer_discovery.h>
36 
37 #include <microdns/microdns.h>
38 
39 static int OpenSD( vlc_object_t * );
40 static void CloseSD( vlc_object_t * );
41 static int OpenRD( vlc_object_t * );
42 static void CloseRD( vlc_object_t * );
43 
44 VLC_SD_PROBE_HELPER( "microdns", N_("mDNS Network Discovery"), SD_CAT_LAN )
45 VLC_RD_PROBE_HELPER( "microdns_renderer", "mDNS renderer Discovery" )
46 
47 #define CFG_PREFIX "sd-microdns-"
48 
49 #define LISTEN_INTERVAL INT64_C(15000000) /* 15 seconds */
50 #define TIMEOUT (3 * LISTEN_INTERVAL + INT64_C(5000000)) /* 3 * interval + 5 seconds */
51 
52 /*
53  * Module descriptor
54  */
55 vlc_module_begin()
56     set_shortname( "mDNS" )
57     set_description( N_( "mDNS Network Discovery" ) )
58     set_category( CAT_PLAYLIST )
59     set_subcategory( SUBCAT_PLAYLIST_SD )
60     set_capability( "services_discovery", 0 )
61     set_callbacks( OpenSD, CloseSD )
62     add_shortcut( "mdns", "microdns" )
63     VLC_SD_PROBE_SUBMODULE
64     add_submodule() \
65         set_description( N_( "mDNS Renderer Discovery" ) )
66         set_category( CAT_SOUT )
67         set_subcategory( SUBCAT_SOUT_RENDERER )
68         set_capability( "renderer_discovery", 0 )
69         set_callbacks( OpenRD, CloseRD )
70         add_shortcut( "mdns_renderer", "microdns_renderer" )
71         VLC_RD_PROBE_SUBMODULE
72 vlc_module_end ()
73 
74 static const struct
75 {
76     const char *psz_protocol;
77     const char *psz_service_name;
78     bool        b_renderer;
79     int         i_renderer_flags;
80 } protocols[] = {
81     { "ftp", "_ftp._tcp.local", false, 0 },
82     { "smb", "_smb._tcp.local", false, 0 },
83     { "nfs", "_nfs._tcp.local", false, 0 },
84     { "sftp", "_sftp-ssh._tcp.local", false, 0 },
85     { "rtsp", "_rtsp._tcp.local", false, 0 },
86     { "chromecast", "_googlecast._tcp.local", true, VLC_RENDERER_CAN_AUDIO },
87 };
88 #define NB_PROTOCOLS (sizeof(protocols) / sizeof(*protocols))
89 
90 struct discovery_sys
91 {
92     vlc_thread_t        thread;
93     atomic_bool         stop;
94     struct mdns_ctx *   p_microdns;
95     const char *        ppsz_service_names[NB_PROTOCOLS];
96     unsigned int        i_nb_service_names;
97     vlc_array_t         items;
98 };
99 
100 struct services_discovery_sys_t
101 {
102     struct discovery_sys s;
103 };
104 
105 struct vlc_renderer_discovery_sys
106 {
107     struct discovery_sys s;
108 };
109 
110 struct item
111 {
112     char *              psz_uri;
113     input_item_t *      p_input_item;
114     vlc_renderer_item_t*p_renderer_item;
115     mtime_t             i_last_seen;
116 };
117 
118 struct srv
119 {
120     const char *psz_protocol;
121     char *      psz_device_name;
122     uint16_t    i_port;
123     int         i_renderer_flags;
124 };
125 
126 static const char *const ppsz_options[] = {
127     NULL
128 };
129 
130 static void
print_error(vlc_object_t * p_obj,const char * psz_what,int i_status)131 print_error( vlc_object_t *p_obj, const char *psz_what, int i_status )
132 {
133     char psz_err_str[128];
134 
135     if( mdns_strerror( i_status, psz_err_str, sizeof(psz_err_str) ) == 0)
136         msg_Err( p_obj, "mDNS %s error: %s", psz_what, psz_err_str);
137     else
138         msg_Err( p_obj, "mDNS %s error: unknown: %d", psz_what, i_status);
139 }
140 
141 
142 static int
strrcmp(const char * s1,const char * s2)143 strrcmp(const char *s1, const char *s2)
144 {
145     size_t m, n;
146     m = strlen(s1);
147     n = strlen(s2);
148     if (n > m)
149         return 1;
150     return strncmp(s1 + m - n, s2, n);
151 }
152 
153 static int
items_add_input(struct discovery_sys * p_sys,services_discovery_t * p_sd,char * psz_uri,const char * psz_name)154 items_add_input( struct discovery_sys *p_sys, services_discovery_t *p_sd,
155                  char *psz_uri, const char *psz_name )
156 {
157     struct item *p_item = malloc( sizeof(struct item) );
158     if( p_item == NULL )
159     {
160         free( psz_uri );
161         return VLC_ENOMEM;
162     }
163 
164     input_item_t *p_input_item =
165         input_item_NewDirectory( psz_uri, psz_name, ITEM_NET );
166     if( p_input_item == NULL )
167     {
168         free( psz_uri );
169         free( p_item );
170         return VLC_ENOMEM;
171     }
172 
173     p_item->psz_uri = psz_uri;
174     p_item->p_input_item = p_input_item;
175     p_item->p_renderer_item = NULL;
176     p_item->i_last_seen = mdate();
177     vlc_array_append_or_abort( &p_sys->items, p_item );
178     services_discovery_AddItem( p_sd, p_input_item );
179 
180     return VLC_SUCCESS;
181 }
182 
183 static int
items_add_renderer(struct discovery_sys * p_sys,vlc_renderer_discovery_t * p_rd,const char * psz_name,char * psz_uri,const char * psz_demux_filter,const char * psz_icon_uri,int i_flags)184 items_add_renderer( struct discovery_sys *p_sys, vlc_renderer_discovery_t *p_rd,
185                     const char *psz_name, char *psz_uri,
186                     const char *psz_demux_filter, const char *psz_icon_uri,
187                     int i_flags )
188 {
189     struct item *p_item = malloc( sizeof(struct item) );
190     if( p_item == NULL )
191         return VLC_ENOMEM;
192 
193     const char *psz_extra_uri = i_flags & VLC_RENDERER_CAN_VIDEO ? NULL : "no-video";
194 
195     vlc_renderer_item_t *p_renderer_item =
196         vlc_renderer_item_new( "chromecast", psz_name, psz_uri, psz_extra_uri,
197                                psz_demux_filter, psz_icon_uri, i_flags );
198     if( p_renderer_item == NULL )
199     {
200         free( psz_uri );
201         free( p_item );
202         return VLC_ENOMEM;
203     }
204 
205     p_item->psz_uri = psz_uri;
206     p_item->p_input_item = NULL;
207     p_item->p_renderer_item = p_renderer_item;
208     p_item->i_last_seen = mdate();
209     vlc_array_append_or_abort( &p_sys->items, p_item );
210     vlc_rd_add_item( p_rd, p_renderer_item );
211 
212     return VLC_SUCCESS;
213 }
214 
215 static void
items_release(struct discovery_sys * p_sys,struct item * p_item)216 items_release( struct discovery_sys *p_sys, struct item *p_item )
217 {
218     (void) p_sys;
219     if( p_item->p_input_item != NULL )
220     {
221         input_item_Release( p_item->p_input_item );
222     }
223     else
224     {
225         assert( p_item->p_renderer_item != NULL );
226         vlc_renderer_item_release( p_item->p_renderer_item );
227     }
228 
229     free( p_item->psz_uri );
230     free( p_item );
231 }
232 
233 static bool
items_exists(struct discovery_sys * p_sys,const char * psz_uri)234 items_exists( struct discovery_sys *p_sys, const char *psz_uri )
235 {
236     for( size_t i = 0; i < vlc_array_count( &p_sys->items ); ++i )
237     {
238         struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
239         if( strcmp( p_item->psz_uri, psz_uri ) == 0 )
240         {
241             p_item->i_last_seen = mdate();
242             return true;
243         }
244     }
245     return false;
246 }
247 
248 static void
items_timeout(struct discovery_sys * p_sys,services_discovery_t * p_sd,vlc_renderer_discovery_t * p_rd)249 items_timeout( struct discovery_sys *p_sys, services_discovery_t *p_sd,
250                vlc_renderer_discovery_t *p_rd )
251 {
252     assert( p_rd != NULL || p_sd != NULL );
253     mtime_t i_now = mdate();
254 
255     /* Remove items that are not seen since TIMEOUT */
256     for( size_t i = 0; i < vlc_array_count( &p_sys->items ); ++i )
257     {
258         struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
259         if( i_now - p_item->i_last_seen > TIMEOUT )
260         {
261             if( p_sd != NULL )
262                 services_discovery_RemoveItem( p_sd, p_item->p_input_item );
263             else
264                 vlc_rd_remove_item( p_rd, p_item->p_renderer_item );
265             items_release( p_sys, p_item );
266             vlc_array_remove( &p_sys->items, i-- );
267         }
268     }
269 }
270 
271 static void
items_clear(struct discovery_sys * p_sys)272 items_clear( struct discovery_sys *p_sys )
273 {
274     for( size_t i = 0; i < vlc_array_count( &p_sys->items ); ++i )
275     {
276         struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
277         items_release( p_sys, p_item );
278     }
279     vlc_array_clear( &p_sys->items );
280 }
281 
282 static int
parse_entries(const struct rr_entry * p_entries,bool b_renderer,struct srv ** pp_srvs,unsigned int * p_nb_srv,const char ** ppsz_ip,bool * p_ipv6)283 parse_entries( const struct rr_entry *p_entries, bool b_renderer,
284                struct srv **pp_srvs, unsigned int *p_nb_srv,
285                const char **ppsz_ip, bool *p_ipv6 )
286 {
287     /* Count the number of servers */
288     unsigned int i_nb_srv = 0;
289     for( const struct rr_entry *p_entry = p_entries;
290          p_entry != NULL; p_entry = p_entry->next )
291     {
292         if( p_entry->type == RR_SRV )
293             i_nb_srv++;
294     }
295     if( i_nb_srv == 0 )
296         return VLC_EGENERIC;
297 
298     struct srv *p_srvs = calloc(i_nb_srv, sizeof(struct srv));
299     if( p_srvs == NULL )
300         return VLC_EGENERIC;
301 
302     /* There is one ip for several srvs, fetch them */
303     const char *psz_ip = NULL;
304     struct srv *p_srv = NULL;
305     i_nb_srv = 0;
306     for( const struct rr_entry *p_entry = p_entries;
307          p_entry != NULL; p_entry = p_entry->next )
308     {
309         if( p_entry->type == RR_SRV )
310         {
311             for( unsigned i = 0; i < NB_PROTOCOLS; ++i )
312             {
313                 if( !strrcmp( p_entry->name, protocols[i].psz_service_name ) &&
314                     protocols[i].b_renderer == b_renderer )
315                 {
316                     p_srv = &p_srvs[i_nb_srv];
317 
318                     p_srv->psz_device_name =
319                         strndup( p_entry->name, strlen( p_entry->name )
320                                  - strlen( protocols[i].psz_service_name ) - 1);
321                     if( p_srv->psz_device_name == NULL )
322                         break;
323                     p_srv->psz_protocol = protocols[i].psz_protocol;
324                     p_srv->i_port = p_entry->data.SRV.port;
325                     p_srv->i_renderer_flags = protocols[i].i_renderer_flags;
326                     ++i_nb_srv;
327                     break;
328                 }
329             }
330         }
331         else if( p_entry->type == RR_A && psz_ip == NULL )
332             psz_ip = p_entry->data.A.addr_str;
333         else if( p_entry->type == RR_AAAA && psz_ip == NULL )
334         {
335             psz_ip = p_entry->data.AAAA.addr_str;
336             *p_ipv6 = true;
337         }
338         else if( p_entry->type == RR_TXT && p_srv != NULL )
339         {
340             for ( struct rr_data_txt *p_txt = p_entry->data.TXT;
341                   p_txt != NULL ; p_txt = p_txt->next )
342             {
343                 if( !strcmp( p_srv->psz_protocol, "chromecast" ) )
344                 {
345                     if ( !strncmp( "fn=", p_txt->txt, 3 ) )
346                     {
347                         free( p_srv->psz_device_name );
348                         p_srv->psz_device_name = strdup( p_txt->txt + 3 );
349                     }
350                     else if( !strncmp( "ca=", p_txt->txt, 3 ) )
351                     {
352                         int ca = atoi( p_txt->txt + 3);
353                         /*
354                          * For chromecast, the `ca=` is composed from (at least)
355                          * 0x01 to indicate video support
356                          * 0x04 to indivate audio support
357                          */
358                         if ( ( ca & 0x01 ) != 0 )
359                             p_srv->i_renderer_flags |= VLC_RENDERER_CAN_VIDEO;
360                         if ( ( ca & 0x04 ) != 0 )
361                             p_srv->i_renderer_flags |= VLC_RENDERER_CAN_AUDIO;
362                     }
363                 }
364             }
365         }
366     }
367     if( psz_ip == NULL || i_nb_srv == 0 )
368     {
369         for( unsigned int i = 0; i < i_nb_srv; ++i )
370             free( p_srvs[i].psz_device_name );
371         free( p_srvs );
372         return VLC_EGENERIC;
373     }
374 
375     *pp_srvs = p_srvs;
376     *p_nb_srv = i_nb_srv;
377     *ppsz_ip = psz_ip;
378     return VLC_SUCCESS;
379 }
380 
381 static char *
create_uri(const char * psz_protocol,const char * psz_ip,bool b_ipv6,uint16_t i_port)382 create_uri( const char *psz_protocol, const char *psz_ip, bool b_ipv6,
383             uint16_t i_port )
384 {
385     char *psz_uri;
386 
387     return asprintf( &psz_uri, "%s://%s%s%s:%u", psz_protocol,
388                      b_ipv6 ? "[" : "", psz_ip, b_ipv6 ? "]" : "",
389                      i_port ) < 0 ? NULL : psz_uri;
390 }
391 
392 static void
new_entries_sd_cb(void * p_this,int i_status,const struct rr_entry * p_entries)393 new_entries_sd_cb( void *p_this, int i_status, const struct rr_entry *p_entries )
394 {
395     services_discovery_t *p_sd = (services_discovery_t *)p_this;
396     struct discovery_sys *p_sys = &p_sd->p_sys->s;
397     if( i_status < 0 )
398     {
399         print_error( VLC_OBJECT( p_sd ), "entry callback", i_status );
400         return;
401     }
402 
403     struct srv *p_srvs;
404     unsigned i_nb_srv;
405     const char *psz_ip;
406     bool b_ipv6 = false;
407     if( parse_entries( p_entries, false, &p_srvs, &i_nb_srv,
408                        &psz_ip, &b_ipv6 ) != VLC_SUCCESS )
409         return;
410 
411     /* send new input items (if they don't already exist) */
412     for( unsigned int i = 0; i < i_nb_srv; ++i )
413     {
414         struct srv *p_srv = &p_srvs[i];
415         char *psz_uri = create_uri( p_srv->psz_protocol, psz_ip, b_ipv6,
416                                     p_srv->i_port );
417 
418         if( psz_uri == NULL )
419             break;
420 
421         if( items_exists( p_sys, psz_uri ) )
422         {
423             free( psz_uri );
424             continue;
425         }
426         items_add_input( p_sys, p_sd, psz_uri, p_srv->psz_device_name );
427     }
428 
429     for( unsigned int i = 0; i < i_nb_srv; ++i )
430         free( p_srvs[i].psz_device_name );
431     free( p_srvs );
432 }
433 
434 
435 static bool
stop_sd_cb(void * p_this)436 stop_sd_cb( void *p_this )
437 {
438     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
439     struct discovery_sys *p_sys = &p_sd->p_sys->s;
440 
441     if( atomic_load( &p_sys->stop ) )
442         return true;
443     else
444     {
445         items_timeout( p_sys, p_sd, NULL );
446         return false;
447     }
448 }
449 
450 static void *
RunSD(void * p_this)451 RunSD( void *p_this )
452 {
453     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
454     struct discovery_sys *p_sys = &p_sd->p_sys->s;
455 
456     int i_status = mdns_listen( p_sys->p_microdns,
457                                 p_sys->ppsz_service_names,
458                                 p_sys->i_nb_service_names,
459                                 RR_PTR, LISTEN_INTERVAL / INT64_C(1000000),
460                                 stop_sd_cb, new_entries_sd_cb, p_sd );
461 
462     if( i_status < 0 )
463         print_error( VLC_OBJECT( p_sd ), "listen", i_status );
464 
465     return NULL;
466 }
467 
468 static void
new_entries_rd_cb(void * p_this,int i_status,const struct rr_entry * p_entries)469 new_entries_rd_cb( void *p_this, int i_status, const struct rr_entry *p_entries )
470 {
471     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
472     struct discovery_sys *p_sys = &p_rd->p_sys->s;
473     if( i_status < 0 )
474     {
475         print_error( VLC_OBJECT( p_rd ), "entry callback", i_status );
476         return;
477     }
478 
479     struct srv *p_srvs;
480     unsigned i_nb_srv;
481     const char *psz_ip;
482     bool b_ipv6 = false;
483     if( parse_entries( p_entries, true, &p_srvs, &i_nb_srv,
484                        &psz_ip, &b_ipv6 ) != VLC_SUCCESS )
485         return;
486 
487     const char *psz_model = NULL;
488     const char *psz_icon = NULL;
489     for( const struct rr_entry *p_entry = p_entries;
490          p_entry != NULL && ( psz_model == NULL || psz_icon == NULL );
491          p_entry = p_entry->next )
492     {
493         if( p_entry->type == RR_TXT )
494         {
495             const struct rr_data_txt *p_txt = p_entry->data.TXT;
496             while( p_txt && ( psz_model == NULL || psz_icon == NULL ) )
497             {
498                 if( !strncmp("md=", p_txt->txt, 3) )
499                     psz_model = p_txt->txt + 3;
500                 else if( !strncmp("ic=", p_txt->txt, 3) )
501                     psz_icon = p_txt->txt + 3;
502                 p_txt = p_txt->next;
503             }
504         }
505     }
506 
507     /* send new input items (if they don't already exist) */
508     for( unsigned int i = 0; i < i_nb_srv; ++i )
509     {
510         struct srv *p_srv = &p_srvs[i];
511         char *psz_icon_uri = NULL;
512         char *psz_uri = create_uri( p_srv->psz_protocol, psz_ip, b_ipv6,
513                                     p_srv->i_port );
514         const char *psz_demux_filter = NULL;
515 
516         if( psz_uri == NULL )
517             break;
518 
519         if( items_exists( p_sys, psz_uri ) )
520         {
521             free( psz_uri );
522             continue;
523         }
524 
525         if( psz_icon != NULL
526          && asprintf( &psz_icon_uri, "http://%s:8008%s", psz_ip, psz_icon )
527                       == -1 )
528         {
529             free( psz_uri );
530             break;
531         }
532 
533         if( strcmp( p_srv->psz_protocol, "chromecast" ) == 0)
534             psz_demux_filter = "cc_demux";
535 
536         items_add_renderer( p_sys, p_rd, p_srv->psz_device_name, psz_uri,
537                             psz_demux_filter, psz_icon_uri,
538                             p_srv->i_renderer_flags );
539         free(psz_icon_uri);
540     }
541 
542     for( unsigned int i = 0; i < i_nb_srv; ++i )
543         free( p_srvs[i].psz_device_name );
544     free( p_srvs );
545 }
546 
547 static bool
stop_rd_cb(void * p_this)548 stop_rd_cb( void *p_this )
549 {
550     vlc_renderer_discovery_t *p_rd = p_this;
551     struct discovery_sys *p_sys = &p_rd->p_sys->s;
552 
553     if( atomic_load( &p_sys->stop ) )
554         return true;
555     else
556     {
557         items_timeout( p_sys, NULL, p_rd );
558         return false;
559     }
560 }
561 
562 static void *
RunRD(void * p_this)563 RunRD( void *p_this )
564 {
565     vlc_renderer_discovery_t *p_rd = p_this;
566     struct discovery_sys *p_sys = &p_rd->p_sys->s;
567 
568     int i_status = mdns_listen( p_sys->p_microdns,
569                                 p_sys->ppsz_service_names,
570                                 p_sys->i_nb_service_names,
571                                 RR_PTR, LISTEN_INTERVAL / INT64_C(1000000),
572                                 stop_rd_cb, new_entries_rd_cb, p_rd );
573 
574     if( i_status < 0 )
575         print_error( VLC_OBJECT( p_rd ), "listen", i_status );
576 
577     return NULL;
578 }
579 
580 static int
OpenCommon(vlc_object_t * p_obj,struct discovery_sys * p_sys,bool b_renderer)581 OpenCommon( vlc_object_t *p_obj, struct discovery_sys *p_sys, bool b_renderer )
582 {
583     int i_ret = VLC_EGENERIC;
584     atomic_init( &p_sys->stop, false );
585     vlc_array_init( &p_sys->items );
586 
587     /* Listen to protocols that are handled by VLC */
588     for( unsigned int i = 0; i < NB_PROTOCOLS; ++i )
589     {
590         if( protocols[i].b_renderer == b_renderer )
591             p_sys->ppsz_service_names[p_sys->i_nb_service_names++] =
592                 protocols[i].psz_service_name;
593     }
594 
595     if( p_sys->i_nb_service_names == 0 )
596     {
597         msg_Err( p_obj, "no services found" );
598         goto error;
599     }
600     for( unsigned int i = 0; i < p_sys->i_nb_service_names; ++i )
601         msg_Dbg( p_obj, "mDNS: listening to %s %s", p_sys->ppsz_service_names[i],
602                  b_renderer ? "renderer" : "service" );
603 
604     int i_status;
605     if( ( i_status = mdns_init( &p_sys->p_microdns, MDNS_ADDR_IPV4,
606                                 MDNS_PORT ) ) < 0 )
607     {
608         print_error( p_obj, "init", i_status );
609         goto error;
610     }
611 
612     if( vlc_clone( &p_sys->thread, b_renderer ? RunRD : RunSD, p_obj,
613                    VLC_THREAD_PRIORITY_LOW) )
614     {
615         msg_Err( p_obj, "Can't run the lookup thread" );
616         goto error;
617     }
618 
619     return VLC_SUCCESS;
620 error:
621     if( p_sys->p_microdns != NULL )
622         mdns_destroy( p_sys->p_microdns );
623     free( p_sys );
624     return i_ret;
625 }
626 
627 static void
CleanCommon(struct discovery_sys * p_sys)628 CleanCommon( struct discovery_sys *p_sys )
629 {
630     atomic_store( &p_sys->stop, true );
631     vlc_join( p_sys->thread, NULL );
632 
633     items_clear( p_sys );
634     mdns_destroy( p_sys->p_microdns );
635 }
636 
637 static int
OpenSD(vlc_object_t * p_obj)638 OpenSD( vlc_object_t *p_obj )
639 {
640     services_discovery_t *p_sd = (services_discovery_t *)p_obj;
641 
642     p_sd->p_sys = calloc( 1, sizeof(services_discovery_sys_t) );
643     if( !p_sd->p_sys )
644         return VLC_ENOMEM;
645 
646     p_sd->description = _("mDNS Network Discovery");
647     config_ChainParse( p_sd, CFG_PREFIX, ppsz_options, p_sd->p_cfg );
648 
649     return OpenCommon( p_obj, &p_sd->p_sys->s, false );
650 }
651 
652 static void
CloseSD(vlc_object_t * p_this)653 CloseSD( vlc_object_t *p_this )
654 {
655     services_discovery_t *p_sd = (services_discovery_t *) p_this;
656 
657     CleanCommon( &p_sd->p_sys->s );
658     free( p_sd->p_sys );
659 }
660 
661 static int
OpenRD(vlc_object_t * p_obj)662 OpenRD( vlc_object_t *p_obj )
663 {
664     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_obj;
665 
666     p_rd->p_sys = calloc( 1, sizeof(vlc_renderer_discovery_sys) );
667     if( !p_rd->p_sys )
668         return VLC_ENOMEM;
669 
670     config_ChainParse( p_rd, CFG_PREFIX, ppsz_options, p_rd->p_cfg );
671 
672     return OpenCommon( p_obj, &p_rd->p_sys->s, true );
673 }
674 
675 static void
CloseRD(vlc_object_t * p_this)676 CloseRD( vlc_object_t *p_this )
677 {
678     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *) p_this;
679 
680     CleanCommon( &p_rd->p_sys->s );
681     free( p_rd->p_sys );
682 }
683