1 /*****************************************************************************
2 * microdns.c: mDNS services discovery module
3 *****************************************************************************
4 * Copyright © 2016 VLC authors, VideoLAN and VideoLabs
6 * Authors: Steve Lhomme <robux4@videolabs.io>
7 * Thomas Guillem <thomas@gllm.fr>
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.
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.
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 *****************************************************************************/
28 #include <stdatomic.h>
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_modules.h>
34 #include <vlc_services_discovery.h>
35 #include <vlc_renderer_discovery.h>
37 #include <microdns/microdns.h>
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
* );
44 VLC_SD_PROBE_HELPER( "microdns", N_("mDNS Network Discovery"), SD_CAT_LAN
)
45 VLC_RD_PROBE_HELPER( "microdns_renderer", "mDNS renderer Discovery" )
47 #define CFG_PREFIX "sd-microdns-"
49 #define LISTEN_INTERVAL VLC_TICK_FROM_SEC(15) /* 15 seconds */
50 #define TIMEOUT (3 * LISTEN_INTERVAL + VLC_TICK_FROM_SEC(5)) /* 3 * interval + 5 seconds */
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
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
76 const char *psz_protocol
;
77 const char *psz_service_name
;
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
},
88 #define NB_PROTOCOLS (sizeof(protocols) / sizeof(*protocols))
94 struct mdns_ctx
* p_microdns
;
95 const char * ppsz_service_names
[NB_PROTOCOLS
];
96 unsigned int i_nb_service_names
;
102 struct discovery_sys s
;
103 } services_discovery_sys_t
;
105 struct vlc_renderer_discovery_sys
107 struct discovery_sys s
;
113 input_item_t
* p_input_item
;
114 vlc_renderer_item_t
*p_renderer_item
;
115 vlc_tick_t i_last_seen
;
120 const char *psz_protocol
;
121 char * psz_device_name
;
123 int i_renderer_flags
;
126 static const char *const ppsz_options
[] = {
131 print_error( vlc_object_t
*p_obj
, const char *psz_what
, int i_status
)
133 char psz_err_str
[128];
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
);
138 msg_Err( p_obj
, "mDNS %s error: unknown: %d", psz_what
, i_status
);
143 strrcmp(const char *s1
, const char *s2
)
150 return strncmp(s1
+ m
- n
, s2
, n
);
154 items_add_input( struct discovery_sys
*p_sys
, services_discovery_t
*p_sd
,
155 char *psz_uri
, const char *psz_name
)
157 struct item
*p_item
= malloc( sizeof(struct item
) );
164 input_item_t
*p_input_item
=
165 input_item_NewDirectory( psz_uri
, psz_name
, ITEM_NET
);
166 if( p_input_item
== NULL
)
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
= vlc_tick_now();
177 vlc_array_append_or_abort( &p_sys
->items
, p_item
);
178 services_discovery_AddItem( p_sd
, p_input_item
);
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
,
189 struct item
*p_item
= malloc( sizeof(struct item
) );
193 const char *psz_extra_uri
= i_flags
& VLC_RENDERER_CAN_VIDEO
? NULL
: "no-video";
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
)
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
= vlc_tick_now();
209 vlc_array_append_or_abort( &p_sys
->items
, p_item
);
210 vlc_rd_add_item( p_rd
, p_renderer_item
);
216 items_release( struct discovery_sys
*p_sys
, struct item
*p_item
)
219 if( p_item
->p_input_item
!= NULL
)
221 input_item_Release( p_item
->p_input_item
);
225 assert( p_item
->p_renderer_item
!= NULL
);
226 vlc_renderer_item_release( p_item
->p_renderer_item
);
229 free( p_item
->psz_uri
);
234 items_exists( struct discovery_sys
*p_sys
, const char *psz_uri
)
236 for( size_t i
= 0; i
< vlc_array_count( &p_sys
->items
); ++i
)
238 struct item
*p_item
= vlc_array_item_at_index( &p_sys
->items
, i
);
239 if( strcmp( p_item
->psz_uri
, psz_uri
) == 0 )
241 p_item
->i_last_seen
= vlc_tick_now();
249 items_timeout( struct discovery_sys
*p_sys
, services_discovery_t
*p_sd
,
250 vlc_renderer_discovery_t
*p_rd
)
252 assert( p_rd
!= NULL
|| p_sd
!= NULL
);
253 vlc_tick_t i_now
= vlc_tick_now();
255 /* Remove items that are not seen since TIMEOUT */
256 for( size_t i
= 0; i
< vlc_array_count( &p_sys
->items
); ++i
)
258 struct item
*p_item
= vlc_array_item_at_index( &p_sys
->items
, i
);
259 if( i_now
- p_item
->i_last_seen
> TIMEOUT
)
262 services_discovery_RemoveItem( p_sd
, p_item
->p_input_item
);
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
-- );
272 items_clear( struct discovery_sys
*p_sys
)
274 for( size_t i
= 0; i
< vlc_array_count( &p_sys
->items
); ++i
)
276 struct item
*p_item
= vlc_array_item_at_index( &p_sys
->items
, i
);
277 items_release( p_sys
, p_item
);
279 vlc_array_clear( &p_sys
->items
);
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
)
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
)
292 if( p_entry
->type
== RR_SRV
)
298 struct srv
*p_srvs
= calloc(i_nb_srv
, sizeof(struct srv
));
302 /* There is one ip for several srvs, fetch them */
303 const char *psz_ip
= NULL
;
304 struct srv
*p_srv
= NULL
;
306 for( const struct rr_entry
*p_entry
= p_entries
;
307 p_entry
!= NULL
; p_entry
= p_entry
->next
)
309 if( p_entry
->type
== RR_SRV
)
311 for( unsigned i
= 0; i
< NB_PROTOCOLS
; ++i
)
313 if( !strrcmp( p_entry
->name
, protocols
[i
].psz_service_name
) &&
314 protocols
[i
].b_renderer
== b_renderer
)
316 p_srv
= &p_srvs
[i_nb_srv
];
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
)
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
;
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
)
335 psz_ip
= p_entry
->data
.AAAA
.addr_str
;
338 else if( p_entry
->type
== RR_TXT
&& p_srv
!= NULL
)
340 for ( struct rr_data_txt
*p_txt
= p_entry
->data
.TXT
;
341 p_txt
!= NULL
; p_txt
= p_txt
->next
)
343 if( !strcmp( p_srv
->psz_protocol
, "chromecast" ) )
345 if ( !strncmp( "fn=", p_txt
->txt
, 3 ) )
347 free( p_srv
->psz_device_name
);
348 p_srv
->psz_device_name
= strdup( p_txt
->txt
+ 3 );
350 else if( !strncmp( "ca=", p_txt
->txt
, 3 ) )
352 int ca
= atoi( p_txt
->txt
+ 3);
354 * For chromecast, the `ca=` is composed from (at least)
355 * 0x01 to indicate video support
356 * 0x04 to indivate audio support
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
;
367 if( psz_ip
== NULL
|| i_nb_srv
== 0 )
374 *p_nb_srv
= i_nb_srv
;
380 create_uri( const char *psz_protocol
, const char *psz_ip
, bool b_ipv6
,
385 return asprintf( &psz_uri
, "%s://%s%s%s:%u", psz_protocol
,
386 b_ipv6
? "[" : "", psz_ip
, b_ipv6
? "]" : "",
387 i_port
) < 0 ? NULL
: psz_uri
;
391 new_entries_sd_cb( void *p_this
, int i_status
, const struct rr_entry
*p_entries
)
393 services_discovery_t
*p_sd
= (services_discovery_t
*)p_this
;
394 services_discovery_sys_t
*p_sdsys
= p_sd
->p_sys
;
395 struct discovery_sys
*p_sys
= &p_sdsys
->s
;
398 print_error( VLC_OBJECT( p_sd
), "entry callback", i_status
);
406 if( parse_entries( p_entries
, false, &p_srvs
, &i_nb_srv
,
407 &psz_ip
, &b_ipv6
) != VLC_SUCCESS
)
410 /* send new input items (if they don't already exist) */
411 for( unsigned int i
= 0; i
< i_nb_srv
; ++i
)
413 struct srv
*p_srv
= &p_srvs
[i
];
414 char *psz_uri
= create_uri( p_srv
->psz_protocol
, psz_ip
, b_ipv6
,
417 if( psz_uri
== NULL
)
420 if( items_exists( p_sys
, psz_uri
) )
425 items_add_input( p_sys
, p_sd
, psz_uri
, p_srv
->psz_device_name
);
428 for( unsigned int i
= 0; i
< i_nb_srv
; ++i
)
429 free( p_srvs
[i
].psz_device_name
);
435 stop_sd_cb( void *p_this
)
437 services_discovery_t
*p_sd
= ( services_discovery_t
* )p_this
;
438 services_discovery_sys_t
*p_sdsys
= p_sd
->p_sys
;
439 struct discovery_sys
*p_sys
= &p_sdsys
->s
;
441 if( atomic_load( &p_sys
->stop
) )
445 items_timeout( p_sys
, p_sd
, NULL
);
451 RunSD( void *p_this
)
453 services_discovery_t
*p_sd
= ( services_discovery_t
* )p_this
;
454 services_discovery_sys_t
*p_sdsys
= p_sd
->p_sys
;
455 struct discovery_sys
*p_sys
= &p_sdsys
->s
;
457 int i_status
= mdns_listen( p_sys
->p_microdns
,
458 p_sys
->ppsz_service_names
,
459 p_sys
->i_nb_service_names
,
460 RR_PTR
, SEC_FROM_VLC_TICK(LISTEN_INTERVAL
),
461 stop_sd_cb
, new_entries_sd_cb
, p_sd
);
464 print_error( VLC_OBJECT( p_sd
), "listen", i_status
);
470 new_entries_rd_cb( void *p_this
, int i_status
, const struct rr_entry
*p_entries
)
472 vlc_renderer_discovery_t
*p_rd
= (vlc_renderer_discovery_t
*)p_this
;
473 struct discovery_sys
*p_sys
= &p_rd
->p_sys
->s
;
476 print_error( VLC_OBJECT( p_rd
), "entry callback", i_status
);
484 if( parse_entries( p_entries
, true, &p_srvs
, &i_nb_srv
,
485 &psz_ip
, &b_ipv6
) != VLC_SUCCESS
)
488 const char *psz_model
= NULL
;
489 const char *psz_icon
= NULL
;
490 for( const struct rr_entry
*p_entry
= p_entries
;
491 p_entry
!= NULL
&& ( psz_model
== NULL
|| psz_icon
== NULL
);
492 p_entry
= p_entry
->next
)
494 if( p_entry
->type
== RR_TXT
)
496 const struct rr_data_txt
*p_txt
= p_entry
->data
.TXT
;
497 while( p_txt
&& ( psz_model
== NULL
|| psz_icon
== NULL
) )
499 if( !strncmp("md=", p_txt
->txt
, 3) )
500 psz_model
= p_txt
->txt
+ 3;
501 else if( !strncmp("ic=", p_txt
->txt
, 3) )
502 psz_icon
= p_txt
->txt
+ 3;
508 /* send new input items (if they don't already exist) */
509 for( unsigned int i
= 0; i
< i_nb_srv
; ++i
)
511 struct srv
*p_srv
= &p_srvs
[i
];
512 char *psz_icon_uri
= NULL
;
513 char *psz_uri
= create_uri( p_srv
->psz_protocol
, psz_ip
, b_ipv6
,
515 const char *psz_demux_filter
= NULL
;
517 if( psz_uri
== NULL
)
520 if( items_exists( p_sys
, psz_uri
) )
527 && asprintf( &psz_icon_uri
, "http://%s:8008%s", psz_ip
, psz_icon
)
534 if( strcmp( p_srv
->psz_protocol
, "chromecast" ) == 0)
535 psz_demux_filter
= "cc_demux";
537 items_add_renderer( p_sys
, p_rd
, p_srv
->psz_device_name
, psz_uri
,
538 psz_demux_filter
, psz_icon_uri
,
539 p_srv
->i_renderer_flags
);
543 for( unsigned int i
= 0; i
< i_nb_srv
; ++i
)
544 free( p_srvs
[i
].psz_device_name
);
549 stop_rd_cb( void *p_this
)
551 vlc_renderer_discovery_t
*p_rd
= p_this
;
552 struct discovery_sys
*p_sys
= &p_rd
->p_sys
->s
;
554 if( atomic_load( &p_sys
->stop
) )
558 items_timeout( p_sys
, NULL
, p_rd
);
564 RunRD( void *p_this
)
566 vlc_renderer_discovery_t
*p_rd
= p_this
;
567 struct discovery_sys
*p_sys
= &p_rd
->p_sys
->s
;
569 int i_status
= mdns_listen( p_sys
->p_microdns
,
570 p_sys
->ppsz_service_names
,
571 p_sys
->i_nb_service_names
,
572 RR_PTR
, SEC_FROM_VLC_TICK(LISTEN_INTERVAL
),
573 stop_rd_cb
, new_entries_rd_cb
, p_rd
);
576 print_error( VLC_OBJECT( p_rd
), "listen", i_status
);
582 OpenCommon( vlc_object_t
*p_obj
, struct discovery_sys
*p_sys
, bool b_renderer
)
584 int i_ret
= VLC_EGENERIC
;
585 atomic_init( &p_sys
->stop
, false );
586 vlc_array_init( &p_sys
->items
);
588 /* Listen to protocols that are handled by VLC */
589 for( unsigned int i
= 0; i
< NB_PROTOCOLS
; ++i
)
591 if( protocols
[i
].b_renderer
== b_renderer
)
592 p_sys
->ppsz_service_names
[p_sys
->i_nb_service_names
++] =
593 protocols
[i
].psz_service_name
;
596 if( p_sys
->i_nb_service_names
== 0 )
598 msg_Err( p_obj
, "no services found" );
601 for( unsigned int i
= 0; i
< p_sys
->i_nb_service_names
; ++i
)
602 msg_Dbg( p_obj
, "mDNS: listening to %s %s", p_sys
->ppsz_service_names
[i
],
603 b_renderer
? "renderer" : "service" );
606 if( ( i_status
= mdns_init( &p_sys
->p_microdns
, MDNS_ADDR_IPV4
,
609 print_error( p_obj
, "init", i_status
);
613 if( vlc_clone( &p_sys
->thread
, b_renderer
? RunRD
: RunSD
, p_obj
,
614 VLC_THREAD_PRIORITY_LOW
) )
616 msg_Err( p_obj
, "Can't run the lookup thread" );
622 if( p_sys
->p_microdns
!= NULL
)
623 mdns_destroy( p_sys
->p_microdns
);
629 CleanCommon( struct discovery_sys
*p_sys
)
631 atomic_store( &p_sys
->stop
, true );
632 vlc_join( p_sys
->thread
, NULL
);
634 items_clear( p_sys
);
635 mdns_destroy( p_sys
->p_microdns
);
639 OpenSD( vlc_object_t
*p_obj
)
641 services_discovery_t
*p_sd
= (services_discovery_t
*)p_obj
;
643 services_discovery_sys_t
*p_sys
= calloc( 1, sizeof(services_discovery_sys_t
) );
648 p_sd
->description
= _("mDNS Network Discovery");
649 config_ChainParse( p_sd
, CFG_PREFIX
, ppsz_options
, p_sd
->p_cfg
);
651 return OpenCommon( p_obj
, &p_sys
->s
, false );
655 CloseSD( vlc_object_t
*p_this
)
657 services_discovery_t
*p_sd
= (services_discovery_t
*) p_this
;
658 services_discovery_sys_t
*p_sys
= p_sd
->p_sys
;
660 CleanCommon( &p_sys
->s
);
665 OpenRD( vlc_object_t
*p_obj
)
667 vlc_renderer_discovery_t
*p_rd
= (vlc_renderer_discovery_t
*)p_obj
;
669 p_rd
->p_sys
= calloc( 1, sizeof(vlc_renderer_discovery_sys
) );
673 config_ChainParse( p_rd
, CFG_PREFIX
, ppsz_options
, p_rd
->p_cfg
);
675 return OpenCommon( p_obj
, &p_rd
->p_sys
->s
, true );
679 CloseRD( vlc_object_t
*p_this
)
681 vlc_renderer_discovery_t
*p_rd
= (vlc_renderer_discovery_t
*) p_this
;
683 CleanCommon( &p_rd
->p_sys
->s
);