DCP: fixed include-guards
[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
36 #include <microdns/microdns.h>
37
38 static int Open( vlc_object_t * );
39 static void Close( vlc_object_t * );
40
41 VLC_SD_PROBE_HELPER( "microdns", "mDNS Network Discovery", SD_CAT_LAN )
42
43 #define NAMES_TEXT N_( "Service Names" )
44 #define NAMES_LONGTEXT N_( "List of names to look for, separated with ','" )
45
46 #define CFG_PREFIX "sd-microdns-"
47
48 #define LISTEN_INTERVAL INT64_C(20000000) /* 20 seconds */
49 #define TIMEOUT (LISTEN_INTERVAL + INT64_C(5000000)) /* interval + 5 seconds */
50
51 /*
52  * Module descriptor
53  */
54 vlc_module_begin()
55     set_shortname( "mDNS" )
56     set_description( N_( "mDNS Network Discovery" ) )
57     set_category( CAT_PLAYLIST )
58     set_subcategory( SUBCAT_PLAYLIST_SD )
59     set_capability( "services_discovery", 0 )
60     set_callbacks( Open, Close )
61     add_shortcut( "mdns", "microdns" )
62     add_string( CFG_PREFIX "names", NULL, NAMES_TEXT, NAMES_LONGTEXT, false )
63     VLC_SD_PROBE_SUBMODULE
64 vlc_module_end ()
65
66 struct services_discovery_sys_t
67 {
68     vlc_thread_t        thread;
69     atomic_bool         stop;
70     struct mdns_ctx *   p_microdns;
71     char *              psz_service_names_opt;
72     const char **       ppsz_service_names;
73     unsigned int        i_nb_service_names;
74     vlc_array_t         items;
75 };
76
77 struct item
78 {
79     char *          psz_uri;
80     input_item_t *  p_input_item;
81     mtime_t         i_last_seen;
82 };
83
84 struct srv
85 {
86     const char *psz_protocol;
87     char *      psz_device_name;
88     uint16_t    i_port;
89 };
90
91 static const struct
92 {
93     const char *psz_protocol;
94     const char *psz_service_name;
95     uint16_t    i_default_port;
96 } protocols[] = {
97     { "ftp", "_ftp._tcp.local", 21 },
98     { "smb", "_smb._tcp.local", 445 },
99     { "nfs", "_nfs._tcp.local", 2049 },
100     { "sftp", "_sftp-ssh._tcp.local", 22 },
101 };
102 #define NB_PROTOCOLS (sizeof(protocols) / sizeof(*protocols))
103
104 static const char *const ppsz_options[] = {
105     "names",
106     NULL
107 };
108
109 static void
110 print_error( services_discovery_t *p_sd, const char *psz_what, int i_status )
111 {
112     char psz_err_str[128];
113
114     if( mdns_strerror( i_status, psz_err_str, sizeof(psz_err_str) ) == 0)
115         msg_Err( p_sd, "mDNS %s error: %s", psz_what, psz_err_str);
116     else
117         msg_Err( p_sd, "mDNS %s error: unknown: %d", psz_what, i_status);
118 }
119
120
121 static int
122 strrcmp(const char *s1, const char *s2)
123 {
124     size_t m, n;
125     m = strlen(s1);
126     n = strlen(s2);
127     if (n > m)
128         return 1;
129     return strncmp(s1 + m - n, s2, n);
130 }
131
132 static int
133 items_add( services_discovery_t *p_sd, char *psz_uri,
134            input_item_t *p_input_item )
135 {
136     services_discovery_sys_t *p_sys = p_sd->p_sys;
137
138     struct item *p_item = malloc( sizeof(struct item) );
139     if( p_item == NULL )
140         return VLC_ENOMEM;
141     p_item->psz_uri = psz_uri;
142     p_item->p_input_item = p_input_item;
143     p_item->i_last_seen = mdate();
144     vlc_array_append( &p_sys->items, p_item );
145     services_discovery_AddItem( p_sd, p_input_item, NULL );
146     return VLC_SUCCESS;
147 }
148
149 static void
150 items_release( struct item *p_item )
151 {
152     input_item_Release( p_item->p_input_item );
153     free( p_item->psz_uri );
154     free( p_item );
155 }
156
157 static bool
158 items_exists( services_discovery_t *p_sd, const char *psz_uri )
159 {
160     services_discovery_sys_t *p_sys = p_sd->p_sys;
161
162     for( int i = 0; i < vlc_array_count( &p_sys->items ); ++i )
163     {
164         struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
165         if( strcmp( psz_uri, p_item->psz_uri ) == 0 )
166         {
167             p_item->i_last_seen = mdate();
168             return true;
169         }
170     }
171     return false;
172 }
173
174 static void
175 items_timeout( services_discovery_t *p_sd )
176 {
177     services_discovery_sys_t *p_sys = p_sd->p_sys;
178     mtime_t i_now = mdate();
179
180     /* Remove items that are not seen since TIMEOUT */
181     for( int i = 0; i < vlc_array_count( &p_sys->items ); ++i )
182     {
183         struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
184         if( i_now - p_item->i_last_seen > TIMEOUT )
185         {
186             services_discovery_RemoveItem( p_sd, p_item->p_input_item );
187             items_release( p_item );
188             vlc_array_remove( &p_sys->items, i-- );
189         }
190     }
191 }
192
193 static void
194 items_clear( services_discovery_t *p_sd )
195 {
196     services_discovery_sys_t *p_sys = p_sd->p_sys;
197
198     for( int i = 0; i < vlc_array_count( &p_sys->items ); ++i )
199     {
200         struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
201         items_release( p_item );
202     }
203     vlc_array_clear( &p_sys->items );
204 }
205
206 static void
207 new_entries_cb( void *p_this, int i_status,
208                     const struct rr_entry *p_entries )
209 {
210     services_discovery_t *p_sd = p_this;
211
212     if( i_status < 0 )
213     {
214         print_error( p_sd, "entry callback", i_status );
215         return;
216     }
217
218     /* Count the number of servers */
219     unsigned int i_nb_srv = 0, i_srv_idx = 0;
220     for( const struct rr_entry *p_entry = p_entries;
221          p_entry != NULL; p_entry = p_entry->next )
222     {
223         if( p_entry->type == RR_SRV )
224             i_nb_srv++;
225     }
226     if( i_nb_srv == 0 )
227         return;
228
229     struct srv *p_srvs = calloc(i_nb_srv, sizeof(struct srv));
230     if( p_srvs == NULL )
231         return;
232
233     /* There is one ip for several srvs, fetch them */
234     const char *psz_ip = NULL;
235     for( const struct rr_entry *p_entry = p_entries;
236          p_entry != NULL; p_entry = p_entry->next )
237     {
238         if( p_entry->type == RR_SRV )
239         {
240             struct srv *p_srv = &p_srvs[i_srv_idx];
241
242             for( unsigned i = 0; i < NB_PROTOCOLS; ++i )
243             {
244                 if( !strrcmp( p_entry->name, protocols[i].psz_service_name )  )
245                 {
246                     p_srv->psz_device_name =
247                         strndup( p_entry->name, strlen( p_entry->name )
248                                  - strlen( protocols[i].psz_service_name ) - 1);
249                     if( p_srv->psz_device_name == NULL )
250                         break;
251                     p_srv->psz_protocol = protocols[i].psz_protocol;
252                     if( protocols[i].i_default_port != p_entry->data.SRV.port )
253                         p_srv->i_port = p_entry->data.SRV.port;
254                     ++i_srv_idx;
255                     break;
256                 }
257             }
258         }
259         else if( p_entry->type == RR_A && psz_ip == NULL )
260             psz_ip = p_entry->data.A.addr_str;
261         /* TODO support ipv6
262         else if( p_entry->type == RR_AAAA )
263             psz_ip = p_entry->data.AAAA.addr_str;
264         */
265     }
266     if( psz_ip == NULL || i_srv_idx == 0 )
267     {
268         free( p_srvs );
269         return;
270     }
271
272     /* send new input items (if they don't already exist) */
273     for( i_srv_idx = 0; i_srv_idx < i_nb_srv; ++i_srv_idx )
274     {
275         struct srv *p_srv = &p_srvs[i_srv_idx];
276         char psz_port[7]; /* ":65536\0" */
277         if( p_srv->i_port != 0 )
278             sprintf( psz_port, ":%u", p_srv->i_port );
279
280         char *psz_uri;
281         if( asprintf( &psz_uri, "%s://%s%s", p_srv->psz_protocol, psz_ip,
282                       p_srv->i_port != 0 ? psz_port : "" ) < 0 )
283             continue;
284
285         if( items_exists( p_sd, psz_uri ) )
286         {
287             free( psz_uri );
288             continue;
289         }
290         input_item_t *p_input_item =
291             input_item_NewWithTypeExt( psz_uri, p_srv->psz_device_name,
292                                        0, NULL, 0, -1, ITEM_TYPE_NODE, true );
293
294         if( p_input_item != NULL
295          && items_add( p_sd, psz_uri, p_input_item ) != VLC_SUCCESS )
296         {
297             if( p_input_item != NULL )
298                 input_item_Release( p_input_item );
299             free( psz_uri );
300         }
301     }
302
303     for( i_srv_idx = 0; i_srv_idx < i_nb_srv; ++i_srv_idx )
304         free( p_srvs[i_srv_idx].psz_device_name );
305     free( p_srvs );
306 }
307
308 static bool
309 stop_cb( void *p_this )
310 {
311     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
312     services_discovery_sys_t *p_sys = p_sd->p_sys;
313
314     if( atomic_load( &p_sys->stop ) )
315         return true;
316     else
317     {
318         items_timeout( p_sd );
319         return false;
320     }
321 }
322
323 static void *
324 Run( void *p_this )
325 {
326     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
327     services_discovery_sys_t *p_sys = p_sd->p_sys;
328
329     int i_status = mdns_listen( p_sys->p_microdns,
330                                 p_sys->ppsz_service_names,
331                                 p_sys->i_nb_service_names,
332                                 RR_PTR, LISTEN_INTERVAL / INT64_C(1000000),
333                                 stop_cb, new_entries_cb, p_sd );
334
335     if( i_status < 0 )
336         print_error( p_sd, "listen", i_status );
337
338     return NULL;
339 }
340
341 static int
342 Open( vlc_object_t *p_obj )
343 {
344     services_discovery_t *p_sd = (services_discovery_t *)p_obj;
345     services_discovery_sys_t *p_sys = NULL;
346     int i_ret = VLC_ENOMEM;
347
348     p_sd->p_sys = p_sys = calloc( 1, sizeof(services_discovery_sys_t) );
349     if( !p_sys )
350         return VLC_ENOMEM;
351
352     atomic_init( &p_sys->stop, false );
353     vlc_array_init( &p_sys->items );
354     config_ChainParse( p_sd, CFG_PREFIX, ppsz_options, p_sd->p_cfg );
355
356     p_sys->psz_service_names_opt =
357         var_GetNonEmptyString( p_sd, CFG_PREFIX "names" );
358     if( p_sys->psz_service_names_opt )
359     {
360         /* Listen to protocols from names option */
361         unsigned int i_count = 0;
362         size_t i_size;
363         char *psz = p_sys->psz_service_names_opt;
364         const char *psz_end = psz + strlen(psz);
365         while( psz < psz_end && ( i_size = strcspn(psz, ",") ) > 0 )
366         {
367             i_count++;
368             psz += i_size + 1;
369         }
370         assert( i_count > 0 );
371
372         p_sys->ppsz_service_names = calloc( i_count, sizeof(char*) );
373         if( !p_sys->ppsz_service_names )
374             goto error;
375
376         psz = p_sys->psz_service_names_opt;
377         for( unsigned int i = 0; i < i_count; ++i )
378         {
379             p_sys->ppsz_service_names[i] = psz;
380
381             i_size = strcspn( psz, "," );
382             assert( i_size > 0 );
383
384             psz[i_size] = '\0';
385             psz += i_size + 1;
386         }
387         p_sys->i_nb_service_names = i_count;
388     }
389     else
390     {
391         /* Listen to protocols that are handled by VLC */
392         const unsigned i_count = NB_PROTOCOLS;
393         p_sys->ppsz_service_names = calloc( i_count, sizeof(char*) );
394         if( !p_sys->ppsz_service_names )
395             goto error;
396
397         for( unsigned int i = 0; i < i_count; ++i )
398         {
399             /* Listen to a protocol only if a module can handle it */
400             if( module_exists( protocols[i].psz_protocol ) )
401                 p_sys->ppsz_service_names[p_sys->i_nb_service_names++] =
402                     protocols[i].psz_service_name;
403         }
404     }
405
406     i_ret = VLC_EGENERIC;
407     if( p_sys->i_nb_service_names == 0 )
408     {
409         msg_Err( p_sd, "no services found" );
410         goto error;
411     }
412     for( unsigned int i = 0; i < p_sys->i_nb_service_names; ++i )
413         msg_Dbg( p_sd, "mDNS: listening to %s", p_sys->ppsz_service_names[i] );
414
415     int i_status;
416     if( ( i_status = mdns_init( &p_sys->p_microdns, MDNS_ADDR_IPV4,
417                                 MDNS_PORT ) ) < 0 )
418     {
419         print_error( p_sd, "init", i_status );
420         goto error;
421     }
422
423     if( vlc_clone( &p_sys->thread, Run, p_sd, VLC_THREAD_PRIORITY_LOW) )
424     {
425         msg_Err( p_sd, "Can't run the lookup thread" );
426         goto error;
427     }
428
429     return VLC_SUCCESS;
430 error:
431     if( p_sys->p_microdns != NULL )
432         mdns_destroy( p_sys->p_microdns );
433     free( p_sys->psz_service_names_opt );
434     free( p_sys->ppsz_service_names );
435     free( p_sys );
436     return i_ret;
437 }
438
439 static void
440 Close( vlc_object_t *p_this )
441 {
442     services_discovery_t *p_sd = (services_discovery_t *) p_this;
443     services_discovery_sys_t *p_sys = p_sd->p_sys;
444
445     atomic_store( &p_sys->stop, true );
446     vlc_join( p_sys->thread, NULL );
447
448     items_clear( p_sd );
449     mdns_destroy( p_sys->p_microdns );
450
451     free( p_sys->psz_service_names_opt );
452     free( p_sys->ppsz_service_names );
453     free( p_sys );
454 }