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 INT64_C(15000000) /* 15 seconds */
50 #define TIMEOUT (3 * LISTEN_INTERVAL + INT64_C(5000000)) /* 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
;
100 struct services_discovery_sys_t
102 struct discovery_sys s
;
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
;
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
= mdate();
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
= mdate();
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
= mdate();
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 mtime_t i_now
= mdate();
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 struct discovery_sys
*p_sys
= &p_sd
->p_sys
->s
;
397 print_error( VLC_OBJECT( p_sd
), "entry callback", i_status
);
405 if( parse_entries( p_entries
, false, &p_srvs
, &i_nb_srv
,
406 &psz_ip
, &b_ipv6
) != VLC_SUCCESS
)
409 /* send new input items (if they don't already exist) */
410 for( unsigned int i
= 0; i
< i_nb_srv
; ++i
)
412 struct srv
*p_srv
= &p_srvs
[i
];
413 char *psz_uri
= create_uri( p_srv
->psz_protocol
, psz_ip
, b_ipv6
,
416 if( psz_uri
== NULL
)
419 if( items_exists( p_sys
, psz_uri
) )
424 items_add_input( p_sys
, p_sd
, psz_uri
, p_srv
->psz_device_name
);
427 for( unsigned int i
= 0; i
< i_nb_srv
; ++i
)
428 free( p_srvs
[i
].psz_device_name
);
434 stop_sd_cb( void *p_this
)
436 services_discovery_t
*p_sd
= ( services_discovery_t
* )p_this
;
437 struct discovery_sys
*p_sys
= &p_sd
->p_sys
->s
;
439 if( atomic_load( &p_sys
->stop
) )
443 items_timeout( p_sys
, p_sd
, NULL
);
449 RunSD( void *p_this
)
451 services_discovery_t
*p_sd
= ( services_discovery_t
* )p_this
;
452 struct discovery_sys
*p_sys
= &p_sd
->p_sys
->s
;
454 int i_status
= mdns_listen( p_sys
->p_microdns
,
455 p_sys
->ppsz_service_names
,
456 p_sys
->i_nb_service_names
,
457 RR_PTR
, LISTEN_INTERVAL
/ INT64_C(1000000),
458 stop_sd_cb
, new_entries_sd_cb
, p_sd
);
461 print_error( VLC_OBJECT( p_sd
), "listen", i_status
);
467 new_entries_rd_cb( void *p_this
, int i_status
, const struct rr_entry
*p_entries
)
469 vlc_renderer_discovery_t
*p_rd
= (vlc_renderer_discovery_t
*)p_this
;
470 struct discovery_sys
*p_sys
= &p_rd
->p_sys
->s
;
473 print_error( VLC_OBJECT( p_rd
), "entry callback", i_status
);
481 if( parse_entries( p_entries
, true, &p_srvs
, &i_nb_srv
,
482 &psz_ip
, &b_ipv6
) != VLC_SUCCESS
)
485 const char *psz_model
= NULL
;
486 const char *psz_icon
= NULL
;
487 for( const struct rr_entry
*p_entry
= p_entries
;
488 p_entry
!= NULL
&& ( psz_model
== NULL
|| psz_icon
== NULL
);
489 p_entry
= p_entry
->next
)
491 if( p_entry
->type
== RR_TXT
)
493 const struct rr_data_txt
*p_txt
= p_entry
->data
.TXT
;
494 while( p_txt
&& ( psz_model
== NULL
|| psz_icon
== NULL
) )
496 if( !strncmp("md=", p_txt
->txt
, 3) )
497 psz_model
= p_txt
->txt
+ 3;
498 else if( !strncmp("ic=", p_txt
->txt
, 3) )
499 psz_icon
= p_txt
->txt
+ 3;
505 /* send new input items (if they don't already exist) */
506 for( unsigned int i
= 0; i
< i_nb_srv
; ++i
)
508 struct srv
*p_srv
= &p_srvs
[i
];
509 char *psz_icon_uri
= NULL
;
510 char *psz_uri
= create_uri( p_srv
->psz_protocol
, psz_ip
, b_ipv6
,
512 const char *psz_demux_filter
= NULL
;
514 if( psz_uri
== NULL
)
517 if( items_exists( p_sys
, psz_uri
) )
524 && asprintf( &psz_icon_uri
, "http://%s:8008%s", psz_ip
, psz_icon
)
531 if( strcmp( p_srv
->psz_protocol
, "chromecast" ) == 0)
532 psz_demux_filter
= "cc_demux";
534 items_add_renderer( p_sys
, p_rd
, p_srv
->psz_device_name
, psz_uri
,
535 psz_demux_filter
, psz_icon_uri
,
536 p_srv
->i_renderer_flags
);
540 for( unsigned int i
= 0; i
< i_nb_srv
; ++i
)
541 free( p_srvs
[i
].psz_device_name
);
546 stop_rd_cb( void *p_this
)
548 vlc_renderer_discovery_t
*p_rd
= p_this
;
549 struct discovery_sys
*p_sys
= &p_rd
->p_sys
->s
;
551 if( atomic_load( &p_sys
->stop
) )
555 items_timeout( p_sys
, NULL
, p_rd
);
561 RunRD( void *p_this
)
563 vlc_renderer_discovery_t
*p_rd
= p_this
;
564 struct discovery_sys
*p_sys
= &p_rd
->p_sys
->s
;
566 int i_status
= mdns_listen( p_sys
->p_microdns
,
567 p_sys
->ppsz_service_names
,
568 p_sys
->i_nb_service_names
,
569 RR_PTR
, LISTEN_INTERVAL
/ INT64_C(1000000),
570 stop_rd_cb
, new_entries_rd_cb
, p_rd
);
573 print_error( VLC_OBJECT( p_rd
), "listen", i_status
);
579 OpenCommon( vlc_object_t
*p_obj
, struct discovery_sys
*p_sys
, bool b_renderer
)
581 int i_ret
= VLC_EGENERIC
;
582 atomic_init( &p_sys
->stop
, false );
583 vlc_array_init( &p_sys
->items
);
585 /* Listen to protocols that are handled by VLC */
586 for( unsigned int i
= 0; i
< NB_PROTOCOLS
; ++i
)
588 if( protocols
[i
].b_renderer
== b_renderer
)
589 p_sys
->ppsz_service_names
[p_sys
->i_nb_service_names
++] =
590 protocols
[i
].psz_service_name
;
593 if( p_sys
->i_nb_service_names
== 0 )
595 msg_Err( p_obj
, "no services found" );
598 for( unsigned int i
= 0; i
< p_sys
->i_nb_service_names
; ++i
)
599 msg_Dbg( p_obj
, "mDNS: listening to %s %s", p_sys
->ppsz_service_names
[i
],
600 b_renderer
? "renderer" : "service" );
603 if( ( i_status
= mdns_init( &p_sys
->p_microdns
, MDNS_ADDR_IPV4
,
606 print_error( p_obj
, "init", i_status
);
610 if( vlc_clone( &p_sys
->thread
, b_renderer
? RunRD
: RunSD
, p_obj
,
611 VLC_THREAD_PRIORITY_LOW
) )
613 msg_Err( p_obj
, "Can't run the lookup thread" );
619 if( p_sys
->p_microdns
!= NULL
)
620 mdns_destroy( p_sys
->p_microdns
);
626 CleanCommon( struct discovery_sys
*p_sys
)
628 atomic_store( &p_sys
->stop
, true );
629 vlc_join( p_sys
->thread
, NULL
);
631 items_clear( p_sys
);
632 mdns_destroy( p_sys
->p_microdns
);
636 OpenSD( vlc_object_t
*p_obj
)
638 services_discovery_t
*p_sd
= (services_discovery_t
*)p_obj
;
640 p_sd
->p_sys
= calloc( 1, sizeof(services_discovery_sys_t
) );
644 p_sd
->description
= _("mDNS Network Discovery");
645 config_ChainParse( p_sd
, CFG_PREFIX
, ppsz_options
, p_sd
->p_cfg
);
647 return OpenCommon( p_obj
, &p_sd
->p_sys
->s
, false );
651 CloseSD( vlc_object_t
*p_this
)
653 services_discovery_t
*p_sd
= (services_discovery_t
*) p_this
;
655 CleanCommon( &p_sd
->p_sys
->s
);
660 OpenRD( vlc_object_t
*p_obj
)
662 vlc_renderer_discovery_t
*p_rd
= (vlc_renderer_discovery_t
*)p_obj
;
664 p_rd
->p_sys
= calloc( 1, sizeof(vlc_renderer_discovery_sys
) );
668 config_ChainParse( p_rd
, CFG_PREFIX
, ppsz_options
, p_rd
->p_cfg
);
670 return OpenCommon( p_obj
, &p_rd
->p_sys
->s
, true );
674 CloseRD( vlc_object_t
*p_this
)
676 vlc_renderer_discovery_t
*p_rd
= (vlc_renderer_discovery_t
*) p_this
;
678 CleanCommon( &p_rd
->p_sys
->s
);