preparser: use new input event handling
[vlc.git] / modules / services_discovery / microdns.c
blob135b627eba45f47603a11d46dea6779e59e33243
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 *****************************************************************************/
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
28 #include <stdatomic.h>
29 #include <assert.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 */
53 * Module descriptor
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 ()
74 static const struct
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 },
88 #define NB_PROTOCOLS (sizeof(protocols) / sizeof(*protocols))
90 struct discovery_sys
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;
100 typedef struct
102 struct discovery_sys s;
103 } services_discovery_sys_t;
105 struct vlc_renderer_discovery_sys
107 struct discovery_sys s;
110 struct item
112 char * psz_uri;
113 input_item_t * p_input_item;
114 vlc_renderer_item_t*p_renderer_item;
115 vlc_tick_t i_last_seen;
118 struct srv
120 const char *psz_protocol;
121 char * psz_device_name;
122 uint16_t i_port;
123 int i_renderer_flags;
126 static const char *const ppsz_options[] = {
127 NULL
130 static void
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);
137 else
138 msg_Err( p_obj, "mDNS %s error: unknown: %d", psz_what, i_status);
142 static int
143 strrcmp(const char *s1, const char *s2)
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);
153 static int
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) );
158 if( p_item == NULL )
160 free( psz_uri );
161 return VLC_ENOMEM;
164 input_item_t *p_input_item =
165 input_item_NewDirectory( psz_uri, psz_name, ITEM_NET );
166 if( p_input_item == NULL )
168 free( psz_uri );
169 free( p_item );
170 return VLC_ENOMEM;
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 );
180 return VLC_SUCCESS;
183 static int
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 )
189 struct item *p_item = malloc( sizeof(struct item) );
190 if( p_item == NULL )
191 return VLC_ENOMEM;
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 )
200 free( psz_uri );
201 free( p_item );
202 return VLC_ENOMEM;
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 );
212 return VLC_SUCCESS;
215 static void
216 items_release( struct discovery_sys *p_sys, struct item *p_item )
218 (void) p_sys;
219 if( p_item->p_input_item != NULL )
221 input_item_Release( p_item->p_input_item );
223 else
225 assert( p_item->p_renderer_item != NULL );
226 vlc_renderer_item_release( p_item->p_renderer_item );
229 free( p_item->psz_uri );
230 free( p_item );
233 static bool
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();
242 return true;
245 return false;
248 static void
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 )
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-- );
271 static void
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 );
282 static int
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 )
293 i_nb_srv++;
295 if( i_nb_srv == 0 )
296 return VLC_EGENERIC;
298 struct srv *p_srvs = calloc(i_nb_srv, sizeof(struct srv));
299 if( p_srvs == NULL )
300 return VLC_EGENERIC;
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 )
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 )
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;
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;
336 *p_ipv6 = true;
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 )
369 free( p_srvs );
370 return VLC_EGENERIC;
373 *pp_srvs = p_srvs;
374 *p_nb_srv = i_nb_srv;
375 *ppsz_ip = psz_ip;
376 return VLC_SUCCESS;
379 static char *
380 create_uri( const char *psz_protocol, const char *psz_ip, bool b_ipv6,
381 uint16_t i_port )
383 char *psz_uri;
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;
390 static void
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;
396 if( i_status < 0 )
398 print_error( VLC_OBJECT( p_sd ), "entry callback", i_status );
399 return;
402 struct srv *p_srvs;
403 unsigned i_nb_srv;
404 const char *psz_ip;
405 bool b_ipv6 = false;
406 if( parse_entries( p_entries, false, &p_srvs, &i_nb_srv,
407 &psz_ip, &b_ipv6 ) != VLC_SUCCESS )
408 return;
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,
415 p_srv->i_port );
417 if( psz_uri == NULL )
418 break;
420 if( items_exists( p_sys, psz_uri ) )
422 free( psz_uri );
423 continue;
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 );
430 free( p_srvs );
434 static bool
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 ) )
442 return true;
443 else
445 items_timeout( p_sys, p_sd, NULL );
446 return false;
450 static void *
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 );
463 if( i_status < 0 )
464 print_error( VLC_OBJECT( p_sd ), "listen", i_status );
466 return NULL;
469 static void
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;
474 if( i_status < 0 )
476 print_error( VLC_OBJECT( p_rd ), "entry callback", i_status );
477 return;
480 struct srv *p_srvs;
481 unsigned i_nb_srv;
482 const char *psz_ip;
483 bool b_ipv6 = false;
484 if( parse_entries( p_entries, true, &p_srvs, &i_nb_srv,
485 &psz_ip, &b_ipv6 ) != VLC_SUCCESS )
486 return;
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;
503 p_txt = p_txt->next;
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,
514 p_srv->i_port );
515 const char *psz_demux_filter = NULL;
517 if( psz_uri == NULL )
518 break;
520 if( items_exists( p_sys, psz_uri ) )
522 free( psz_uri );
523 continue;
526 if( psz_icon != NULL
527 && asprintf( &psz_icon_uri, "http://%s:8008%s", psz_ip, psz_icon )
528 == -1 )
530 free( psz_uri );
531 break;
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 );
540 free(psz_icon_uri);
543 for( unsigned int i = 0; i < i_nb_srv; ++i )
544 free( p_srvs[i].psz_device_name );
545 free( p_srvs );
548 static bool
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 ) )
555 return true;
556 else
558 items_timeout( p_sys, NULL, p_rd );
559 return false;
563 static void *
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 );
575 if( i_status < 0 )
576 print_error( VLC_OBJECT( p_rd ), "listen", i_status );
578 return NULL;
581 static int
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" );
599 goto error;
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" );
605 int i_status;
606 if( ( i_status = mdns_init( &p_sys->p_microdns, MDNS_ADDR_IPV4,
607 MDNS_PORT ) ) < 0 )
609 print_error( p_obj, "init", i_status );
610 goto error;
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" );
617 goto error;
620 return VLC_SUCCESS;
621 error:
622 if( p_sys->p_microdns != NULL )
623 mdns_destroy( p_sys->p_microdns );
624 free( p_sys );
625 return i_ret;
628 static void
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 );
638 static int
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) );
644 if( !p_sys )
645 return VLC_ENOMEM;
646 p_sd->p_sys = p_sys;
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 );
654 static void
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 );
661 free( p_sys );
664 static int
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) );
670 if( !p_rd->p_sys )
671 return VLC_ENOMEM;
673 config_ChainParse( p_rd, CFG_PREFIX, ppsz_options, p_rd->p_cfg );
675 return OpenCommon( p_obj, &p_rd->p_sys->s, true );
678 static void
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 );
684 free( p_rd->p_sys );