services_discovery: add a service discovery for mDNS
authorThomas Guillem <thomas@gllm.fr>
Wed, 23 Dec 2015 12:49:05 +0000 (13:49 +0100)
committerThomas Guillem <thomas@gllm.fr>
Fri, 5 Feb 2016 18:16:02 +0000 (19:16 +0100)
NEWS
configure.ac
modules/MODULES_LIST
modules/services_discovery/Makefile.am
modules/services_discovery/microdns.c [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index ca861f5..ee0e99e 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -140,6 +140,7 @@ Muxers:
 
 Service Discovery:
  * New NetBios service discovery using libdsm
+ * New mDNS services discovery using libmicrodns
  * Rewrite of the UPnP service discovery
 
 Mac OS X Interface
index 39347c2..20c581c 100644 (file)
@@ -4028,6 +4028,12 @@ dnl UPnP Plugin (Intel SDK)
 dnl
 PKG_ENABLE_MODULES_VLC([UPNP], [upnp], [libupnp], [Intel UPNP SDK],[auto])
 
+dnl
+dnl mDNS using libmicrodns
+dnl
+PKG_ENABLE_MODULES_VLC([MICRODNS], [], [microdns], [mDNS services discovery], [auto])
+
+
 EXTEND_HELP_STRING([Misc options:])
 
 dnl
index 095297d..53a5a9b 100644 (file)
@@ -224,6 +224,7 @@ $Id$
  * mediacodec: Android Jelly Bean MediaCodec decoder module
  * mediadirs: Picture/Music/Video user directories as service discoveries
  * mft: Media Foundation Transform audio/video decoder
+ * microdns: mDNS services discovery
  * minimal_macosx: a minimal Mac OS X GUI, using the FrameWork
  * mirror: mirror video filter
  * mjpeg: a demuxer for multipart and concatenated JPEG data
index 11fca94..6e69217 100644 (file)
@@ -68,3 +68,11 @@ libos2drive_plugin_la_SOURCES = services_discovery/os2drive.c
 if HAVE_OS2
 sd_LTLIBRARIES += libos2drive_plugin.la
 endif
+
+libmicrodns_plugin_la_SOURCES = services_discovery/microdns.c
+libmicrodns_plugin_la_CFLAGS = $(AM_CFLAGS) $(MICRODNS_CFLAGS)
+libmicrodns_plugin_la_LIBADD = $(SOCKET_LIBS) $(MICRODNS_LIBS)
+libmicrodns_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)'
+sd_LTLIBRARIES += $(LTLIBmicrodns)
+EXTRA_LTLIBRARIES += libmicrodns_plugin.la
+
diff --git a/modules/services_discovery/microdns.c b/modules/services_discovery/microdns.c
new file mode 100644 (file)
index 0000000..a6f761c
--- /dev/null
@@ -0,0 +1,445 @@
+/*****************************************************************************
+ * microdns.c: mDNS services discovery module
+ *****************************************************************************
+ * Copyright © 2016 VLC authors, VideoLAN and VideoLabs
+ *
+ * Authors: Steve Lhomme <robux4@videolabs.io>
+ *          Thomas Guillem <thomas@gllm.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <assert.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_modules.h>
+#include <vlc_services_discovery.h>
+
+#include <microdns/microdns.h>
+
+static int Open( vlc_object_t * );
+static void Close( vlc_object_t * );
+
+VLC_SD_PROBE_HELPER( "microdns", "mDNS Network Discovery", SD_CAT_LAN )
+
+#define NAMES_TEXT N_( "Service Names" )
+#define NAMES_LONGTEXT N_( "List of names to look for, separated with ','" )
+
+#define CFG_PREFIX "sd-microdns-"
+
+#define LISTEN_INTERVAL INT64_C(20000000) /* 20 seconds */
+#define TIMEOUT (LISTEN_INTERVAL + INT64_C(5000000)) /* interval + 5 seconds */
+
+/*
+ * Module descriptor
+ */
+vlc_module_begin()
+    set_shortname( "mDNS" )
+    set_description( N_( "mDNS Network Discovery" ) )
+    set_category( CAT_PLAYLIST )
+    set_subcategory( SUBCAT_PLAYLIST_SD )
+    set_capability( "services_discovery", 0 )
+    set_callbacks( Open, Close )
+    add_shortcut( "microdns" )
+    add_string( CFG_PREFIX "names", NULL, NAMES_TEXT, NAMES_LONGTEXT, false )
+    VLC_SD_PROBE_SUBMODULE
+vlc_module_end ()
+
+struct services_discovery_sys_t
+{
+    vlc_thread_t        thread;
+    struct mdns_ctx *   p_microdns;
+    char *              psz_service_names_opt;
+    const char **       ppsz_service_names;
+    unsigned int        i_nb_service_names;
+    vlc_array_t         items;
+};
+
+struct item
+{
+    char *          psz_uri;
+    input_item_t *  p_input_item;
+    mtime_t         i_last_seen;
+};
+
+struct srv
+{
+    const char *psz_protocol;
+    char *      psz_device_name;
+    uint16_t    i_port;
+};
+
+static const struct
+{
+    const char *psz_protocol;
+    const char *psz_service_name;
+    uint16_t    i_default_port;
+} protocols[] = {
+    { "ftp", "_ftp._tcp.local", 21 },
+    { "smb", "_smb._tcp.local", 445 },
+    { "nfs", "_nfs._tcp.local", 2049 },
+    { "sftp", "_sftp-ssh._tcp.local", 22 },
+};
+#define NB_PROTOCOLS (sizeof(protocols) / sizeof(*protocols))
+
+static const char *const ppsz_options[] = {
+    "names",
+    NULL
+};
+
+static void
+print_error( services_discovery_t *p_sd, const char *psz_what, int i_status )
+{
+    char psz_err_str[128];
+
+    if( mdns_strerror( i_status, psz_err_str, sizeof(psz_err_str) ) == 0)
+        msg_Err( p_sd, "mDNS %s error: %s", psz_what, psz_err_str);
+    else
+        msg_Err( p_sd, "mDNS %s error: unknown: %d", psz_what, i_status);
+}
+
+
+static int
+strrcmp(const char *s1, const char *s2)
+{
+    size_t m, n;
+    m = strlen(s1);
+    n = strlen(s2);
+    if (n > m)
+        return 1;
+    return strncmp(s1 + m - n, s2, n);
+}
+
+static int
+items_add( services_discovery_t *p_sd, char *psz_uri,
+           input_item_t *p_input_item )
+{
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
+
+    struct item *p_item = malloc( sizeof(struct item) );
+    if( p_item == NULL )
+        return VLC_ENOMEM;
+    p_item->psz_uri = psz_uri;
+    p_item->p_input_item = p_input_item;
+    p_item->i_last_seen = mdate();
+    vlc_array_append( &p_sys->items, p_item );
+    services_discovery_AddItem( p_sd, p_input_item, NULL );
+    return VLC_SUCCESS;
+}
+
+static void
+items_release( struct item *p_item )
+{
+    input_item_Release( p_item->p_input_item );
+    free( p_item->psz_uri );
+    free( p_item );
+}
+
+static bool
+items_exists( services_discovery_t *p_sd, const char *psz_uri )
+{
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
+
+    for( int i = 0; i < vlc_array_count( &p_sys->items ); ++i )
+    {
+        struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
+        if( strcmp( psz_uri, p_item->psz_uri ) == 0 )
+        {
+            p_item->i_last_seen = mdate();
+            return true;
+        }
+    }
+    return false;
+}
+
+static void
+items_timeout( services_discovery_t *p_sd )
+{
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
+    mtime_t i_now = mdate();
+
+    /* Remove items that are not seen since TIMEOUT */
+    for( int i = 0; i < vlc_array_count( &p_sys->items ); ++i )
+    {
+        struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
+        if( i_now - p_item->i_last_seen > TIMEOUT )
+        {
+            services_discovery_RemoveItem( p_sd, p_item->p_input_item );
+            items_release( p_item );
+            vlc_array_remove( &p_sys->items, i-- );
+        }
+    }
+}
+
+static void
+items_clear( services_discovery_t *p_sd )
+{
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
+
+    for( int i = 0; i < vlc_array_count( &p_sys->items ); ++i )
+    {
+        struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
+        items_release( p_item );
+    }
+    vlc_array_clear( &p_sys->items );
+}
+
+static void
+new_entries_cb( void *p_this, int i_status,
+                    const struct rr_entry *p_entries )
+{
+    services_discovery_t *p_sd = p_this;
+
+    if( i_status < 0 )
+    {
+        print_error( p_sd, "entry callback", i_status );
+        return;
+    }
+
+    /* Count the number of servers */
+    unsigned int i_nb_srv = 0, i_srv_idx = 0;
+    for( const struct rr_entry *p_entry = p_entries;
+         p_entry != NULL; p_entry = p_entry->next )
+    {
+        if( p_entry->type == RR_SRV )
+            i_nb_srv++;
+    }
+    if( i_nb_srv == 0 )
+        return;
+
+    struct srv *p_srvs = calloc(i_nb_srv, sizeof(struct srv));
+    if( p_srvs == NULL )
+        return;
+
+    /* There is one ip for several srvs, fetch them */
+    const char *psz_ip = NULL;
+    for( const struct rr_entry *p_entry = p_entries;
+         p_entry != NULL; p_entry = p_entry->next )
+    {
+        if( p_entry->type == RR_SRV )
+        {
+            struct srv *p_srv = &p_srvs[i_srv_idx];
+
+            for( unsigned i = 0; i < NB_PROTOCOLS; ++i )
+            {
+                if( !strrcmp( p_entry->name, protocols[i].psz_service_name )  )
+                {
+                    p_srv->psz_device_name =
+                        strndup( p_entry->name, strlen( p_entry->name )
+                                 - strlen( protocols[i].psz_service_name ) - 1);
+                    if( p_srv->psz_device_name == NULL )
+                        break;
+                    p_srv->psz_protocol = protocols[i].psz_protocol;
+                    if( protocols[i].i_default_port != p_entry->data.SRV.port )
+                        p_srv->i_port = p_entry->data.SRV.port;
+                    ++i_srv_idx;
+                    break;
+                }
+            }
+        }
+        else if( p_entry->type == RR_A && psz_ip == NULL )
+            psz_ip = p_entry->data.A.addr_str;
+        /* TODO support ipv6
+        else if( p_entry->type == RR_AAAA )
+            psz_ip = p_entry->data.AAAA.addr_str;
+        */
+    }
+    if( psz_ip == NULL || i_srv_idx == 0 )
+    {
+        free( p_srvs );
+        return;
+    }
+
+    /* send new input items (if they don't already exist) */
+    for( i_srv_idx = 0; i_srv_idx < i_nb_srv; ++i_srv_idx )
+    {
+        struct srv *p_srv = &p_srvs[i_srv_idx];
+        char psz_port[7]; /* ":65536\0" */
+        if( p_srv->i_port != 0 )
+            sprintf( psz_port, ":%u", p_srv->i_port );
+
+        char *psz_uri;
+        if( asprintf( &psz_uri, "%s://%s%s", p_srv->psz_protocol, psz_ip,
+                      p_srv->i_port != 0 ? psz_port : "" ) < 0 )
+            continue;
+
+        if( items_exists( p_sd, psz_uri ) )
+        {
+            free( psz_uri );
+            continue;
+        }
+        input_item_t *p_input_item =
+            input_item_NewWithTypeExt( psz_uri, p_srv->psz_device_name,
+                                       0, NULL, 0, -1, ITEM_TYPE_NODE, true );
+
+        if( p_input_item != NULL
+         && items_add( p_sd, psz_uri, p_input_item ) != VLC_SUCCESS )
+        {
+            if( p_input_item != NULL )
+                input_item_Release( p_input_item );
+            free( psz_uri );
+        }
+    }
+
+    for( i_srv_idx = 0; i_srv_idx < i_nb_srv; ++i_srv_idx )
+        free( p_srvs[i_srv_idx].psz_device_name );
+    free( p_srvs );
+}
+
+static bool
+stop_cb( void *p_this )
+{
+    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
+    vlc_testcancel();
+    items_timeout( p_sd );
+    return false;
+}
+
+static void *
+Run( void *p_this )
+{
+    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
+
+    int i_status = mdns_listen( p_sys->p_microdns,
+                                p_sys->ppsz_service_names,
+                                p_sys->i_nb_service_names,
+                                RR_PTR, LISTEN_INTERVAL / INT64_C(1000000),
+                                stop_cb, new_entries_cb, p_sd );
+
+    if( i_status < 0 )
+        print_error( p_sd, "listen", i_status );
+
+    return NULL;
+}
+
+static int
+Open( vlc_object_t *p_obj )
+{
+    services_discovery_t *p_sd = (services_discovery_t *)p_obj;
+    services_discovery_sys_t *p_sys = NULL;
+    int i_ret = VLC_ENOMEM;
+
+    p_sd->p_sys = p_sys = calloc( 1, sizeof(services_discovery_sys_t) );
+    if( !p_sys )
+        return VLC_ENOMEM;
+
+    vlc_array_init( &p_sys->items );
+    config_ChainParse( p_sd, CFG_PREFIX, ppsz_options, p_sd->p_cfg );
+
+    p_sys->psz_service_names_opt =
+        var_GetNonEmptyString( p_sd, CFG_PREFIX "names" );
+    if( p_sys->psz_service_names_opt )
+    {
+        /* Listen to protocols from names option */
+        unsigned int i_count = 0;
+        size_t i_size;
+        char *psz = p_sys->psz_service_names_opt;
+        const char *psz_end = psz + strlen(psz);
+        while( psz < psz_end && ( i_size = strcspn(psz, ",") ) > 0 )
+        {
+            i_count++;
+            psz += i_size + 1;
+        }
+        assert( i_count > 0 );
+
+        p_sys->ppsz_service_names = calloc( i_count, sizeof(char*) );
+        if( !p_sys->ppsz_service_names )
+            goto error;
+
+        psz = p_sys->psz_service_names_opt;
+        for( unsigned int i = 0; i < i_count; ++i )
+        {
+            p_sys->ppsz_service_names[i] = psz;
+
+            i_size = strcspn( psz, "," );
+            assert( i_size > 0 );
+
+            psz[i_size] = '\0';
+            psz += i_size + 1;
+        }
+        p_sys->i_nb_service_names = i_count;
+    }
+    else
+    {
+        /* Listen to protocols that are handled by VLC */
+        const unsigned i_count = NB_PROTOCOLS;
+        p_sys->ppsz_service_names = calloc( i_count, sizeof(char*) );
+        if( !p_sys->ppsz_service_names )
+            goto error;
+
+        for( unsigned int i = 0; i < i_count; ++i )
+        {
+            /* Listen to a protocol only if a module can handle it */
+            if( module_exists( protocols[i].psz_protocol ) )
+                p_sys->ppsz_service_names[p_sys->i_nb_service_names++] =
+                    protocols[i].psz_service_name;
+        }
+    }
+
+    i_ret = VLC_EGENERIC;
+    if( p_sys->i_nb_service_names == 0 )
+    {
+        msg_Err( p_sd, "no services found" );
+        goto error;
+    }
+    for( unsigned int i = 0; i < p_sys->i_nb_service_names; ++i )
+        msg_Dbg( p_sd, "mDNS: listening to %s", p_sys->ppsz_service_names[i] );
+
+    int i_status;
+    if( ( i_status = mdns_init( &p_sys->p_microdns, MDNS_ADDR_IPV4,
+                                MDNS_PORT ) ) < 0 )
+    {
+        print_error( p_sd, "init", i_status );
+        goto error;
+    }
+
+    if( vlc_clone( &p_sys->thread, Run, p_sd, VLC_THREAD_PRIORITY_LOW) )
+    {
+        msg_Err( p_sd, "Can't run the lookup thread" );
+        goto error;
+    }
+
+    return VLC_SUCCESS;
+error:
+    if( p_sys->p_microdns != NULL )
+        mdns_destroy( p_sys->p_microdns );
+    free( p_sys->psz_service_names_opt );
+    free( p_sys->ppsz_service_names );
+    free( p_sys );
+    return i_ret;
+}
+
+static void
+Close( vlc_object_t *p_this )
+{
+    services_discovery_t *p_sd = (services_discovery_t *) p_this;
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
+
+    vlc_cancel( p_sys->thread );
+    vlc_join( p_sys->thread, NULL );
+
+    items_clear( p_sd );
+    mdns_destroy( p_sys->p_microdns );
+
+    free( p_sys->psz_service_names_opt );
+    free( p_sys->ppsz_service_names );
+    free( p_sys );
+}