upnp: remove m_server_list_lock, p_server_list is already protected by s_lock
[vlc.git] / modules / services_discovery / microdns.c
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", "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(20000000) /* 20 seconds */
50 #define TIMEOUT (LISTEN_INTERVAL + INT64_C(5000000)) /* 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 * 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
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
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
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( &p_sys->items, p_item );
178     services_discovery_AddItem( p_sd, p_input_item, NULL );
179
180     return VLC_SUCCESS;
181 }
182
183 static int
184 items_add_renderer( struct discovery_sys *p_sys, vlc_renderer_discovery *p_rd,
185                     const char *psz_name, char *psz_uri,
186                     const char *psz_icon_uri, int i_flags )
187 {
188     struct item *p_item = malloc( sizeof(struct item) );
189     if( p_item == NULL )
190         return VLC_ENOMEM;
191
192     const char *psz_extra_uri = i_flags & VLC_RENDERER_CAN_VIDEO ? NULL : "video=0";
193
194     vlc_renderer_item *p_renderer_item =
195         vlc_renderer_item_new( psz_name, psz_uri, psz_extra_uri, psz_icon_uri, i_flags );
196     if( p_renderer_item == NULL )
197     {
198         free( psz_uri );
199         free( p_item );
200         return VLC_ENOMEM;
201     }
202
203     p_item->psz_uri = psz_uri;
204     p_item->p_input_item = NULL;
205     p_item->p_renderer_item = p_renderer_item;
206     p_item->i_last_seen = mdate();
207     vlc_array_append( &p_sys->items, p_item );
208     vlc_rd_add_item( p_rd, p_renderer_item );
209
210     return VLC_SUCCESS;
211 }
212
213 static void
214 items_release( struct discovery_sys *p_sys, struct item *p_item )
215 {
216     (void) p_sys;
217     if( p_item->p_input_item != NULL )
218     {
219         input_item_Release( p_item->p_input_item );
220     }
221     else
222     {
223         assert( p_item->p_renderer_item != NULL );
224         vlc_renderer_item_release( p_item->p_renderer_item );
225     }
226
227     free( p_item->psz_uri );
228     free( p_item );
229 }
230
231 static bool
232 items_exists( struct discovery_sys *p_sys, const char *psz_uri )
233 {
234     for( int i = 0; i < vlc_array_count( &p_sys->items ); ++i )
235     {
236         struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
237         if( strcmp( p_item->psz_uri, psz_uri ) == 0 )
238         {
239             p_item->i_last_seen = mdate();
240             return true;
241         }
242     }
243     return false;
244 }
245
246 static void
247 items_timeout( struct discovery_sys *p_sys, services_discovery_t *p_sd,
248                vlc_renderer_discovery *p_rd )
249 {
250     mtime_t i_now = mdate();
251
252     /* Remove items that are not seen since TIMEOUT */
253     for( int i = 0; i < vlc_array_count( &p_sys->items ); ++i )
254     {
255         struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
256         if( i_now - p_item->i_last_seen > TIMEOUT )
257         {
258             items_release( p_sys, p_item );
259             if( p_sd != NULL )
260                 services_discovery_RemoveItem( p_sd, p_item->p_input_item );
261             else
262             {
263                 assert( p_rd != NULL );
264                 vlc_rd_remove_item( p_rd, p_item->p_renderer_item );
265             }
266             vlc_array_remove( &p_sys->items, i-- );
267         }
268     }
269 }
270
271 static void
272 items_clear( struct discovery_sys *p_sys )
273 {
274     for( int 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
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     i_nb_srv = 0;
305     for( const struct rr_entry *p_entry = p_entries;
306          p_entry != NULL; p_entry = p_entry->next )
307     {
308         if( p_entry->type == RR_SRV )
309         {
310             for( unsigned i = 0; i < NB_PROTOCOLS; ++i )
311             {
312                 if( !strrcmp( p_entry->name, protocols[i].psz_service_name ) &&
313                     protocols[i].b_renderer == b_renderer )
314                 {
315                     struct srv *p_srv = &p_srvs[i_nb_srv];
316
317                     p_srv->psz_device_name =
318                         strndup( p_entry->name, strlen( p_entry->name )
319                                  - strlen( protocols[i].psz_service_name ) - 1);
320                     if( p_srv->psz_device_name == NULL )
321                         break;
322                     p_srv->psz_protocol = protocols[i].psz_protocol;
323                     p_srv->i_port = p_entry->data.SRV.port;
324                     p_srv->i_renderer_flags = protocols[i].i_renderer_flags;
325                     ++i_nb_srv;
326                     break;
327                 }
328             }
329         }
330         else if( p_entry->type == RR_A && psz_ip == NULL )
331             psz_ip = p_entry->data.A.addr_str;
332         else if( p_entry->type == RR_AAAA && psz_ip == NULL )
333         {
334             psz_ip = p_entry->data.AAAA.addr_str;
335             *p_ipv6 = true;
336         }
337     }
338     if( psz_ip == NULL || i_nb_srv == 0 )
339     {
340         free( p_srvs );
341         return VLC_EGENERIC;
342     }
343
344     *pp_srvs = p_srvs;
345     *p_nb_srv = i_nb_srv;
346     *ppsz_ip = psz_ip;
347     return VLC_SUCCESS;
348 }
349
350 static char *
351 create_uri( const char *psz_protocol, const char *psz_ip, bool b_ipv6,
352             uint16_t i_port )
353 {
354     char *psz_uri;
355
356     return asprintf( &psz_uri, "%s://%s%s%s:%u", psz_protocol,
357                      b_ipv6 ? "[" : "", psz_ip, b_ipv6 ? "]" : "",
358                      i_port ) < 0 ? NULL : psz_uri;
359 }
360
361 static void
362 new_entries_sd_cb( void *p_this, int i_status, const struct rr_entry *p_entries )
363 {
364     services_discovery_t *p_sd = (services_discovery_t *)p_this;
365     struct discovery_sys *p_sys = &p_sd->p_sys->s;
366     if( i_status < 0 )
367     {
368         print_error( VLC_OBJECT( p_sd ), "entry callback", i_status );
369         return;
370     }
371
372     struct srv *p_srvs;
373     unsigned i_nb_srv;
374     const char *psz_ip;
375     bool b_ipv6;
376     if( parse_entries( p_entries, false, &p_srvs, &i_nb_srv,
377                        &psz_ip, &b_ipv6 ) != VLC_SUCCESS )
378         return;
379
380     /* send new input items (if they don't already exist) */
381     for( unsigned int i = 0; i < i_nb_srv; ++i )
382     {
383         struct srv *p_srv = &p_srvs[i];
384         char *psz_uri = create_uri( p_srv->psz_protocol, psz_ip, b_ipv6,
385                                     p_srv->i_port );
386
387         if( psz_uri == NULL )
388             break;
389
390         if( items_exists( p_sys, psz_uri ) )
391         {
392             free( psz_uri );
393             continue;
394         }
395         items_add_input( p_sys, p_sd, psz_uri, p_srv->psz_device_name );
396     }
397
398     for( unsigned int i = 0; i < i_nb_srv; ++i )
399         free( p_srvs[i].psz_device_name );
400     free( p_srvs );
401 }
402
403
404 static bool
405 stop_sd_cb( void *p_this )
406 {
407     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
408     struct discovery_sys *p_sys = &p_sd->p_sys->s;
409
410     if( atomic_load( &p_sys->stop ) )
411         return true;
412     else
413     {
414         items_timeout( p_sys, p_sd, NULL );
415         return false;
416     }
417 }
418
419 static void *
420 RunSD( void *p_this )
421 {
422     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
423     struct discovery_sys *p_sys = &p_sd->p_sys->s;
424
425     int i_status = mdns_listen( p_sys->p_microdns,
426                                 p_sys->ppsz_service_names,
427                                 p_sys->i_nb_service_names,
428                                 RR_PTR, LISTEN_INTERVAL / INT64_C(1000000),
429                                 stop_sd_cb, new_entries_sd_cb, p_sd );
430
431     if( i_status < 0 )
432         print_error( VLC_OBJECT( p_sd ), "listen", i_status );
433
434     return NULL;
435 }
436
437 static void
438 new_entries_rd_cb( void *p_this, int i_status, const struct rr_entry *p_entries )
439 {
440     vlc_renderer_discovery *p_rd = (vlc_renderer_discovery *)p_this;
441     struct discovery_sys *p_sys = &p_rd->p_sys->s;
442     if( i_status < 0 )
443     {
444         print_error( VLC_OBJECT( p_rd ), "entry callback", i_status );
445         return;
446     }
447
448     struct srv *p_srvs;
449     unsigned i_nb_srv;
450     const char *psz_ip;
451     bool b_ipv6;
452     if( parse_entries( p_entries, true, &p_srvs, &i_nb_srv,
453                        &psz_ip, &b_ipv6 ) != VLC_SUCCESS )
454         return;
455
456     const char *psz_model = NULL;
457     const char *psz_icon = NULL;
458     for( const struct rr_entry *p_entry = p_entries;
459          p_entry != NULL && ( psz_model == NULL || psz_icon == NULL );
460          p_entry = p_entry->next )
461     {
462         if( p_entry->type == RR_TXT )
463         {
464             const struct rr_data_txt *p_txt = p_entry->data.TXT;
465             while( p_txt && ( psz_model == NULL || psz_icon == NULL ) )
466             {
467                 if( p_txt->txt )
468                 {
469                     if( !strncmp("md=", p_txt->txt, 3) )
470                         psz_model = p_txt->txt + 3;
471                     else if( !strncmp("ic=", p_txt->txt, 3) )
472                         psz_icon = p_txt->txt + 3;
473                 }
474                 p_txt = p_txt->next;
475             }
476         }
477     }
478
479     /* send new input items (if they don't already exist) */
480     for( unsigned int i = 0; i < i_nb_srv; ++i )
481     {
482         struct srv *p_srv = &p_srvs[i];
483         char *psz_icon_uri = NULL;
484         char *psz_uri = create_uri( p_srv->psz_protocol, psz_ip, b_ipv6,
485                                     p_srv->i_port );
486
487         if( psz_uri == NULL )
488             break;
489
490         if( items_exists( p_sys, psz_uri ) )
491         {
492             free( psz_uri );
493             continue;
494         }
495
496         if( psz_icon != NULL
497          && asprintf( &psz_icon_uri, "http://%s:8008%s", psz_ip, psz_icon )
498                       == -1 )
499         {
500             free( psz_uri );
501             break;
502         }
503
504         if( strcmp( p_srv->psz_protocol, "chromecast" ) == 0
505          && ( psz_model == NULL
506            || strcasecmp( psz_model, "Chromecast Audio" ) != 0 ) )
507             p_srv->i_renderer_flags |= VLC_RENDERER_CAN_VIDEO;
508
509         items_add_renderer( p_sys, p_rd, p_srv->psz_device_name, psz_uri,
510                             psz_icon_uri, p_srv->i_renderer_flags );
511         free(psz_icon_uri);
512     }
513
514     for( unsigned int i = 0; i < i_nb_srv; ++i )
515         free( p_srvs[i].psz_device_name );
516     free( p_srvs );
517 }
518
519 static bool
520 stop_rd_cb( void *p_this )
521 {
522     vlc_renderer_discovery *p_rd = ( vlc_renderer_discovery* )p_this;
523     struct discovery_sys *p_sys = &p_rd->p_sys->s;
524
525     if( atomic_load( &p_sys->stop ) )
526         return true;
527     else
528     {
529         items_timeout( p_sys, NULL, p_rd );
530         return false;
531     }
532 }
533
534 static void *
535 RunRD( void *p_this )
536 {
537     vlc_renderer_discovery *p_rd = ( vlc_renderer_discovery* )p_this;
538     struct discovery_sys *p_sys = &p_rd->p_sys->s;
539
540     int i_status = mdns_listen( p_sys->p_microdns,
541                                 p_sys->ppsz_service_names,
542                                 p_sys->i_nb_service_names,
543                                 RR_PTR, LISTEN_INTERVAL / INT64_C(1000000),
544                                 stop_rd_cb, new_entries_rd_cb, p_rd );
545
546     if( i_status < 0 )
547         print_error( VLC_OBJECT( p_rd ), "listen", i_status );
548
549     return NULL;
550 }
551
552 static int
553 OpenCommon( vlc_object_t *p_obj, struct discovery_sys *p_sys, bool b_renderer )
554 {
555     int i_ret = VLC_EGENERIC;
556     atomic_init( &p_sys->stop, false );
557     vlc_array_init( &p_sys->items );
558
559     /* Listen to protocols that are handled by VLC */
560     for( unsigned int i = 0; i < NB_PROTOCOLS; ++i )
561     {
562         if( protocols[i].b_renderer == b_renderer )
563             p_sys->ppsz_service_names[p_sys->i_nb_service_names++] =
564                 protocols[i].psz_service_name;
565     }
566
567     if( p_sys->i_nb_service_names == 0 )
568     {
569         msg_Err( p_obj, "no services found" );
570         goto error;
571     }
572     for( unsigned int i = 0; i < p_sys->i_nb_service_names; ++i )
573         msg_Dbg( p_obj, "mDNS: listening to %s %s", p_sys->ppsz_service_names[i],
574                  b_renderer ? "renderer" : "service" );
575
576     int i_status;
577     if( ( i_status = mdns_init( &p_sys->p_microdns, MDNS_ADDR_IPV4,
578                                 MDNS_PORT ) ) < 0 )
579     {
580         print_error( p_obj, "init", i_status );
581         goto error;
582     }
583
584     if( vlc_clone( &p_sys->thread, b_renderer ? RunRD : RunSD, p_obj,
585                    VLC_THREAD_PRIORITY_LOW) )
586     {
587         msg_Err( p_obj, "Can't run the lookup thread" );
588         goto error;
589     }
590
591     return VLC_SUCCESS;
592 error:
593     if( p_sys->p_microdns != NULL )
594         mdns_destroy( p_sys->p_microdns );
595     free( p_sys );
596     return i_ret;
597 }
598
599 static void
600 CleanCommon( struct discovery_sys *p_sys )
601 {
602     atomic_store( &p_sys->stop, true );
603     vlc_join( p_sys->thread, NULL );
604
605     items_clear( p_sys );
606     mdns_destroy( p_sys->p_microdns );
607 }
608
609 static int
610 OpenSD( vlc_object_t *p_obj )
611 {
612     services_discovery_t *p_sd = (services_discovery_t *)p_obj;
613
614     p_sd->p_sys = calloc( 1, sizeof(services_discovery_sys_t) );
615     if( !p_sd->p_sys )
616         return VLC_ENOMEM;
617
618     config_ChainParse( p_sd, CFG_PREFIX, ppsz_options, p_sd->p_cfg );
619
620     return OpenCommon( p_obj, &p_sd->p_sys->s, false );
621 }
622
623 static void
624 CloseSD( vlc_object_t *p_this )
625 {
626     services_discovery_t *p_sd = (services_discovery_t *) p_this;
627
628     CleanCommon( &p_sd->p_sys->s );
629     free( p_sd->p_sys );
630 }
631
632 static int
633 OpenRD( vlc_object_t *p_obj )
634 {
635     vlc_renderer_discovery *p_rd = (vlc_renderer_discovery *)p_obj;
636
637     p_rd->p_sys = calloc( 1, sizeof(vlc_renderer_discovery_sys) );
638     if( !p_rd->p_sys )
639         return VLC_ENOMEM;
640
641     config_ChainParse( p_rd, CFG_PREFIX, ppsz_options, p_rd->p_cfg );
642
643     return OpenCommon( p_obj, &p_rd->p_sys->s, true );
644 }
645
646 static void
647 CloseRD( vlc_object_t *p_this )
648 {
649     vlc_renderer_discovery *p_rd = (vlc_renderer_discovery *) p_this;
650
651     CleanCommon( &p_rd->p_sys->s );
652     free( p_rd->p_sys );
653 }