[vlc-devel] [PATCH 5/7] Implement mDNS services advertisement submodule

Roland Bewick roland.bewick at gmail.com
Wed Jul 24 17:43:48 CEST 2019


This module will launch an mdns advertisement server and listen for discovery requests of the _vlc._tcp service type, making it possible to resolve the VLC web interface from the vlc.local domain
---
 modules/services_discovery/microdns.c | 310 +++++++++++++++++++++++++++++-----
 1 file changed, 268 insertions(+), 42 deletions(-)

diff --git a/modules/services_discovery/microdns.c b/modules/services_discovery/microdns.c
index e9ff0c6380..c460126b58 100644
--- a/modules/services_discovery/microdns.c
+++ b/modules/services_discovery/microdns.c
@@ -33,9 +33,22 @@
 #include <vlc_modules.h>
 #include <vlc_services_discovery.h>
 #include <vlc_renderer_discovery.h>
+#include <vlc_services_advertisement.h>
 
 #include <microdns/microdns.h>
 
+
+/*
+ * mDNS service announcement configuration
+ */
+
+/* This will point to the IP address and port of our running VLC interface */
+#define SA_DOMAIN "vlc.local"
+/* respond to PTR query of this type from mDNS clients */
+#define SA_SERVICE_TYPE_DOMAIN "_vlc._tcp.local"
+/* link service type to our domain name (vlc.local) and instance name (VLC) */
+#define SA_SERVICE_TYPE_LINK "vlc VLC._vlc._tcp.local"
+
 static int OpenSD( vlc_object_t * );
 static void CloseSD( vlc_object_t * );
 static int OpenRD( vlc_object_t * );
@@ -97,16 +110,28 @@ static const struct
 };
 #define NB_PROTOCOLS (sizeof(protocols) / sizeof(*protocols))
 
-struct discovery_sys
+struct microdns_sys_common
 {
     vlc_thread_t        thread;
     atomic_bool         stop;
     struct mdns_ctx *   p_microdns;
+};
+
+struct discovery_sys
+{
+    struct microdns_sys_common *p_sys_common;
     const char *        ppsz_service_names[NB_PROTOCOLS];
     unsigned int        i_nb_service_names;
     vlc_array_t         items;
 };
 
+struct advertisement_sys
+{
+    struct microdns_sys_common *p_sys_common;
+    int i_http_port;
+    char *psz_http_host;
+};
+
 struct item
 {
     char *              psz_uri;
@@ -438,7 +463,7 @@ stop_sd_cb( void *p_this )
     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
     struct discovery_sys *p_sys = p_sd->p_sys;
 
-    if( atomic_load( &p_sys->stop ) )
+    if( atomic_load( &p_sys->p_sys_common->stop ) )
         return true;
     else
     {
@@ -453,7 +478,7 @@ RunSD( void *p_this )
     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
     struct discovery_sys *p_sys = p_sd->p_sys;
 
-    int i_status = mdns_listen( p_sys->p_microdns,
+    int i_status = mdns_listen( p_sys->p_sys_common->p_microdns,
                                 p_sys->ppsz_service_names,
                                 p_sys->i_nb_service_names,
                                 RR_PTR, SEC_FROM_VLC_TICK(LISTEN_INTERVAL),
@@ -550,7 +575,7 @@ stop_rd_cb( void *p_this )
     vlc_renderer_discovery_t *p_rd = p_this;
     struct discovery_sys *p_sys = p_rd->p_sys;
 
-    if( atomic_load( &p_sys->stop ) )
+    if( atomic_load( &p_sys->p_sys_common->stop ) )
         return true;
     else
     {
@@ -565,7 +590,7 @@ RunRD( void *p_this )
     vlc_renderer_discovery_t *p_rd = p_this;
     struct discovery_sys *p_sys = p_rd->p_sys;
 
-    int i_status = mdns_listen( p_sys->p_microdns,
+    int i_status = mdns_listen( p_sys->p_sys_common->p_microdns,
                                 p_sys->ppsz_service_names,
                                 p_sys->i_nb_service_names,
                                 RR_PTR, SEC_FROM_VLC_TICK(LISTEN_INTERVAL),
@@ -577,62 +602,205 @@ RunRD( void *p_this )
     return NULL;
 }
 
-static int
-OpenCommon( vlc_object_t *p_obj, struct discovery_sys *p_sys, bool b_renderer )
+static void
+request_sa_callback( void *cbarg, int r, const struct rr_entry *entry )
 {
-    int i_ret = VLC_EGENERIC;
-    atomic_init( &p_sys->stop, false );
-    vlc_array_init( &p_sys->items );
+    VLC_UNUSED( r );  /* result code from mdns_recv */
 
-    /* Listen to protocols that are handled by VLC */
-    for( unsigned int i = 0; i < NB_PROTOCOLS; ++i )
+    services_advertisement_t *p_sa = ( services_advertisement_t* )cbarg;
+    struct advertisement_sys *p_sys = p_sa->p_sys;
+    struct mdns_ctx *ctx = p_sys->p_sys_common->p_microdns;
+    struct mdns_hdr hdr = {0};  /* response header */
+
+    if ( entry->type != RR_PTR || entry->rr_class != RR_IN )
     {
-        if( protocols[i].b_renderer == b_renderer )
-            p_sys->ppsz_service_names[p_sys->i_nb_service_names++] =
-                protocols[i].psz_service_name;
+        msg_Dbg( VLC_OBJECT( p_sa ),
+                 "mDNS-SA: Skipped unsupported request type: %d class: %d\n",
+                 entry->type,
+                 entry->rr_class );
+        return;
     }
 
-    if( p_sys->i_nb_service_names == 0 )
+    /*
+     * When we receive a PTR record (question) from an mDNS client we need to
+     * answer with four record types that will allow our domain name (vlc.local)
+     * to resolve the IP address and port we are hosting the VLC interface on.
+     * 
+     * PTR: point service type (_vlc._tcp._local) to local domain
+     * TXT: provide additional information (HTTP server root directory etc.) 
+     * SRV: provide server information (port number etc.)
+     * A: link local domain to IP address
+     */
+
+    struct rr_entry answers[4] = {0};  /* PTR, TXT, SRV, A */
+
+    hdr.flags |= FLAG_QR;
+    hdr.flags |= FLAG_AA;
+
+    hdr.num_ans_rr = sizeof( answers ) / sizeof( answers[0] );
+
+    for (int i = 0; i < hdr.num_ans_rr; i++)
     {
-        msg_Err( p_obj, "no services found" );
-        goto error;
+            answers[i].rr_class = RR_IN;
+            answers[i].ttl      = 120;
+
+            if (i + 1 < hdr.num_ans_rr)
+                    answers[i].next = &answers[i + 1];
     }
-    for( unsigned int i = 0; i < p_sys->i_nb_service_names; ++i )
-        msg_Dbg( p_obj, "mDNS: listening to %s %s", p_sys->ppsz_service_names[i],
-                 b_renderer ? "renderer" : "service" );
+
+    /* RR_PTR */
+    char *ptr_name =               strdup( SA_SERVICE_TYPE_DOMAIN );
+    char *ptr_domain =             strdup( SA_SERVICE_TYPE_LINK );
+    answers[0].type =              RR_PTR;
+    answers[0].name =              ptr_name;
+    answers[0].data.PTR.domain =   ptr_domain;
+
+    /* RR_TXT (currently empty) */
+    char *txt_name =               strdup( SA_SERVICE_TYPE_LINK );
+    answers[1].type =              RR_TXT;
+    answers[1].name =              txt_name;
+    
+    /* RR_SRV */
+    char *srv_name =               strdup( SA_SERVICE_TYPE_LINK );
+    char *srv_target =             strdup( SA_DOMAIN );
+    answers[2].type =              RR_SRV;
+    answers[2].name =              srv_name;
+    answers[2].data.SRV.port =     p_sys->i_http_port;
+    answers[2].data.SRV.priority = 0;
+    answers[2].data.SRV.weight =   0;
+    answers[2].data.SRV.target =   srv_target;
+    
+    /* RR_A */
+    char *a_name = strdup( SA_DOMAIN );
+    answers[3].type = RR_A;
+    answers[3].name = a_name;
+    sprintf( answers[3].data.A.addr_str, "%s", p_sys->psz_http_host );
+    inet_pton( AF_INET, answers[3].data.A.addr_str, &answers[3].data.A.addr );
+
+    mdns_entries_send( ctx, &hdr, answers );
+
+    free( ptr_name );
+    free( ptr_domain );
+    free( txt_name );
+    free( srv_name );
+    free( srv_target );
+    free( a_name );
+}
+
+static bool
+stop_sa_cb( void *cbarg )
+{
+    services_advertisement_t *p_sa = ( services_advertisement_t* )cbarg;
+    struct advertisement_sys *p_sys = p_sa->p_sys;
+
+    return atomic_load( &p_sys->p_sys_common->stop );
+}
+
+static void *
+RunSA( void *p_this )
+{
+    services_advertisement_t *p_sa = ( services_advertisement_t* )p_this;
+    struct advertisement_sys *p_sys = p_sa->p_sys;
 
     int i_status;
-    if( ( i_status = mdns_init( &p_sys->p_microdns, MDNS_ADDR_IPV4,
+
+    i_status = mdns_announce( p_sys->p_sys_common->p_microdns,
+                              SA_SERVICE_TYPE_DOMAIN,
+                              RR_PTR,
+                              request_sa_callback,
+                              p_sa);
+
+    if( i_status < 0 )
+        print_error( VLC_OBJECT( p_sa ), "announce", i_status );
+    
+    i_status = mdns_serve(p_sys->p_sys_common->p_microdns, stop_sa_cb, p_sa);
+
+    return NULL;
+}
+
+static void
+CleanCommon( struct microdns_sys_common *p_sys_common )
+{
+    if ( p_sys_common->p_microdns )
+        mdns_destroy( p_sys_common->p_microdns );
+    free( p_sys_common );
+}
+
+static void
+CleanDiscoveryCommon( struct discovery_sys *p_sys )
+{
+    items_clear( p_sys );
+    CleanCommon( p_sys->p_sys_common );
+    free( p_sys );
+}
+
+static void
+CleanAdvertisementCommon( struct advertisement_sys *p_sys )
+{
+    free(p_sys->psz_http_host);
+    CleanCommon( p_sys->p_sys_common );
+    free( p_sys );
+}
+
+static void CloseCommon( struct microdns_sys_common *p_sys_common )
+{
+    atomic_store( &p_sys_common->stop, true );
+    vlc_join( p_sys_common->thread, NULL );
+}
+
+static int
+OpenCommon( vlc_object_t *p_obj, struct microdns_sys_common *p_sys_common,
+            void *(*entry)(void *) )
+{
+    int i_ret = VLC_EGENERIC;
+    atomic_init( &p_sys_common->stop, false );
+
+    int i_status;
+    if( ( i_status = mdns_init( &p_sys_common->p_microdns, MDNS_ADDR_IPV4,
                                 MDNS_PORT ) ) < 0 )
     {
         print_error( p_obj, "init", i_status );
         goto error;
     }
 
-    if( vlc_clone( &p_sys->thread, b_renderer ? RunRD : RunSD, p_obj,
-                   VLC_THREAD_PRIORITY_LOW) )
+    if( vlc_clone( &p_sys_common->thread, entry, p_obj, VLC_THREAD_PRIORITY_LOW) )
     {
-        msg_Err( p_obj, "Can't run the lookup thread" );
+        msg_Err( p_obj, "Can't run the mdns thread");
         goto error;
     }
 
     return VLC_SUCCESS;
 error:
-    if( p_sys->p_microdns != NULL )
-        mdns_destroy( p_sys->p_microdns );
-    free( p_sys );
+    CleanCommon( p_sys_common );
     return i_ret;
 }
 
-static void
-CleanCommon( struct discovery_sys *p_sys )
+static int OpenDiscoveryCommon( vlc_object_t *p_obj, struct discovery_sys *p_sys,
+                               bool b_renderer, void *(*entry)(void *) )
 {
-    atomic_store( &p_sys->stop, true );
-    vlc_join( p_sys->thread, NULL );
+    int i_ret = VLC_EGENERIC;
 
-    items_clear( p_sys );
-    mdns_destroy( p_sys->p_microdns );
-    free( p_sys );
+    vlc_array_init( &p_sys->items );
+    /* Listen to protocols that are handled by VLC */
+    for( unsigned int i = 0; i < NB_PROTOCOLS; ++i )
+    {
+        if( protocols[i].b_renderer == b_renderer )
+            p_sys->ppsz_service_names[p_sys->i_nb_service_names++] =
+                protocols[i].psz_service_name;
+    }
+    if( p_sys->i_nb_service_names == 0 )
+    {
+        msg_Err( p_obj, "no services found" );
+        goto error;
+    }
+    for( unsigned int i = 0; i < p_sys->i_nb_service_names; ++i )
+        msg_Dbg( p_obj, "mDNS: listening to %s %s", p_sys->ppsz_service_names[i],
+                b_renderer ? "renderer" : "service" );
+    
+    return OpenCommon( p_obj, p_sys->p_sys_common, entry );
+error:
+    CleanCommon( p_sys->p_sys_common );
+    return i_ret;
 }
 
 static int
@@ -643,12 +811,19 @@ OpenSD( vlc_object_t *p_obj )
     struct discovery_sys *p_sys = calloc( 1, sizeof(struct discovery_sys) );
     if( !p_sys )
         return VLC_ENOMEM;
+    p_sys->p_sys_common = calloc( 1, sizeof(struct microdns_sys_common) );
+    if( !p_sys->p_sys_common )
+    {
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
+
     p_sd->p_sys = p_sys;
 
     p_sd->description = _("mDNS Network Discovery");
     config_ChainParse( p_sd, CFG_PREFIX, ppsz_options, p_sd->p_cfg );
 
-    return OpenCommon( p_obj, p_sys, false );
+    return OpenDiscoveryCommon( p_obj, p_sys, false, RunSD );
 }
 
 static void
@@ -657,7 +832,8 @@ CloseSD( vlc_object_t *p_this )
     services_discovery_t *p_sd = (services_discovery_t *) p_this;
     struct discovery_sys *p_sys = p_sd->p_sys;
 
-    CleanCommon( p_sys );
+    CloseCommon( p_sys->p_sys_common );
+    CleanDiscoveryCommon( p_sys );
 }
 
 static int
@@ -668,11 +844,18 @@ OpenRD( vlc_object_t *p_obj )
     struct discovery_sys *p_sys = calloc( 1, sizeof(struct discovery_sys) );
     if( !p_sys )
         return VLC_ENOMEM;
+    p_sys->p_sys_common = calloc( 1, sizeof(struct microdns_sys_common) );
+    if( !p_sys->p_sys_common )
+    {
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
+
     p_rd->p_sys = p_sys;
 
     config_ChainParse( p_rd, CFG_PREFIX, ppsz_options, p_rd->p_cfg );
 
-    return OpenCommon( p_obj, p_sys, true );
+    return OpenDiscoveryCommon( p_obj, p_sys, true, RunRD );
 }
 
 static void
@@ -681,18 +864,61 @@ CloseRD( vlc_object_t *p_this )
     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *) p_this;
     struct discovery_sys *p_sys = p_rd->p_sys;
 
-    CleanCommon( p_sys );
+    CloseCommon( p_sys->p_sys_common );
+    CleanDiscoveryCommon( p_sys );
+}
+
+static char * detect_ip_address()
+{
+    return strdup("127.0.0.1"); /* TODO: actually detect ip address */
 }
 
 static int
 OpenSA( vlc_object_t *p_obj )
 {
-    msg_Dbg(p_obj, "TODO: Open service advertisement module");
-    return VLC_SUCCESS;
+    services_advertisement_t *p_sa = (services_advertisement_t *)p_obj;
+
+    struct advertisement_sys *p_sys = calloc( 1, sizeof(struct advertisement_sys) );
+    if( !p_sys )
+        return VLC_ENOMEM;
+    p_sys->p_sys_common = calloc( 1, sizeof(struct microdns_sys_common) );
+    if( !p_sys->p_sys_common )
+    {
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
+
+    p_sys->i_http_port = var_InheritInteger( p_obj, "http-port" ); /* 8080 if unset */
+    p_sys->psz_http_host = var_InheritString( p_obj, "http-host" ); /* null if unset */
+
+    if ( p_sys->psz_http_host && strlen( p_sys->psz_http_host ) > INET_ADDRSTRLEN )
+    {
+        msg_Err( p_obj, "http-host is not a valid IPv4 address: %s", p_sys->psz_http_host );
+        free( p_sys->psz_http_host );
+        p_sys->psz_http_host = NULL;
+    }
+
+    if( !p_sys->psz_http_host )
+    {
+        msg_Dbg( p_obj, "http-host unset, detecting local IP address...");
+        p_sys->psz_http_host = detect_ip_address();
+        msg_Dbg( p_obj, "chose local IP address: %s", p_sys->psz_http_host);
+    }
+
+    p_sa->p_sys = p_sys;
+
+    p_sa->description = _("mDNS Network Advertisement");
+    config_ChainParse( p_sa, CFG_PREFIX, ppsz_options, p_sa->p_cfg );
+
+    return OpenCommon( p_obj, p_sys->p_sys_common, RunSA );
 }
 
 static void
 CloseSA( vlc_object_t *p_this )
 {
-    msg_Dbg(p_this, "TODO: Close service advertisement module");
+    services_advertisement_t *p_sa = (services_advertisement_t *) p_this;
+    struct advertisement_sys *p_sys = p_sa->p_sys;
+
+    CloseCommon( p_sys->p_sys_common );
+    CleanAdvertisementCommon( p_sys );
 }
\ No newline at end of file
-- 
2.11.0



More information about the vlc-devel mailing list