[vlc-devel] [PATCH] Add support for AMT multicast tunneling over non-multicast enabled networks
Natalie Landsberg
natalie.landsberg97 at gmail.com
Wed Sep 11 17:36:28 CEST 2019
Updated based on VLC developer comments.
Changes:
1. Fixed error check for vlc_getaddrinfo() to be more clear.
Changed 'res' to 'response' and checked for != 0 instead. Looked at the documentation closer, my bad for missing it beforehand.
---
modules/access/Makefile.am | 6 +-
modules/access/amt.c | 1433 ++++++++++++++++++++++++++++++++++++
2 files changed, 1438 insertions(+), 1 deletion(-)
create mode 100644 modules/access/amt.c
diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am
index 1b85e8dd18..9b5202b5b6 100644
--- a/modules/access/Makefile.am
+++ b/modules/access/Makefile.am
@@ -122,7 +122,7 @@ liblinsys_sdi_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(accessdir)'
access_LTLIBRARIES += $(LTLIBlinsys_hdsdi) $(LTLIBlinsys_sdi)
EXTRA_LTLIBRARIES += liblinsys_hdsdi_plugin.la liblinsys_sdi_plugin.la
-libdecklink_plugin_la_SOURCES = access/decklink.cpp access/sdi.c access/sdi.h access/vlc_decklink.h
+libdecklink_plugin_la_SOURCES = access/decklink.cpp access/sdi.c access/sdi.h
libdecklink_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(CPPFLAGS_decklink)
libdecklink_plugin_la_LIBADD = $(LIBS_decklink) $(LIBDL) -lpthread
if HAVE_DECKLINK
@@ -379,6 +379,10 @@ libudp_plugin_la_SOURCES = access/udp.c
libudp_plugin_la_LIBADD = $(SOCKET_LIBS)
access_LTLIBRARIES += libudp_plugin.la
+libamt_plugin_la_SOURCES = access/amt.c access/amt.h
+libamt_plugin_la_LIBADD = $(SOCKET_LIBS)
+access_LTLIBRARIES += libamt_plugin.la
+
libunc_plugin_la_SOURCES = access/unc.c access/smb_common.h
libunc_plugin_la_LIBADD = -lmpr -lnetapi32
if HAVE_WIN32
diff --git a/modules/access/amt.c b/modules/access/amt.c
new file mode 100644
index 0000000000..e522ddcd55
--- /dev/null
+++ b/modules/access/amt.c
@@ -0,0 +1,1433 @@
+/**
+ * @file amt.c
+ * @brief Automatic Multicast Tunneling Protocol (AMT) file for VLC media player
+ * Allows multicast streaming when not in a multicast-enabled network
+ * Currently IPv4 is supported, but IPv6 is not yet.
+ *
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ * Copyright (c) Juniper Networks, Inc., 2018. All rights reserved.
+ *
+ * Authors: Christophe Massiot <massiot at via.ecp.fr> - original UDP code
+ * Tristan Leteurtre <tooney at via.ecp.fr> - original UDP code
+ * Laurent Aimar <fenrir at via.ecp.fr> - original UDP code
+ * Jean-Paul Saman <jpsaman #_at_# m2x dot nl> - original UDP code
+ * Remi Denis-Courmont - original UDP code
+ * Natalie Landsberg <natalie.landsberg97 at gmail.com> - AMT support
+ * Wayne Brassem <wbrassem at rogers.com> - Added FQDN support
+ *
+ * This code is licensed to you under the GNU Lesser General Public License
+ * version 2.1 or later. You may not use this code except in compliance with
+ * the GNU Lesser General Public License.
+ * This code is not an official Juniper product.
+ *
+ * This library 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 library 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ ****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+
+#include <vlc_common.h>
+#include <vlc_demux.h>
+#include <vlc_plugin.h>
+#include <vlc_access.h>
+#include <vlc_network.h>
+#include <vlc_block.h>
+#include <vlc_interrupt.h>
+#include <vlc_url.h>
+
+#ifdef HAVE_POLL
+ #include <poll.h>
+#endif
+#ifdef HAVE_SYS_UIO_H
+ #include <sys/uio.h>
+#endif
+
+#define BUFFER_TEXT N_("Receive buffer")
+#define BUFFER_LONGTEXT N_("AMT receive buffer size (bytes)" )
+#define TIMEOUT_TEXT N_("Native multicast timeout (sec)")
+#define AMT_TIMEOUT_TEXT N_("AMT timeout (sec)")
+#define AMT_RELAY_ADDRESS N_("AMT relay (IP address or FQDN)")
+#define AMT_RELAY_ADDR_LONG N_("AMT relay anycast address, or specify the relay you want by address or fully qualified domain name")
+#define AMT_DEFAULT_RELAY N_("amt-relay.m2icast.net")
+
+/*****************************************************************************
+ * Various Lengths of Msgs or Hdrs
+ *****************************************************************************/
+#define MAC_LEN 6 /* length of generated MAC in bytes */
+#define NONCE_LEN 4 /* length of nonce in bytes */
+
+#define MSG_TYPE_LEN 1 /* length of msg type */
+#define RELAY_QUERY_MSG_LEN 48 /* total length of relay query */
+#define RELAY_ADV_MSG_LEN 12 /* length of relay advertisement message */
+#define IGMP_QUERY_LEN 24 /* length of encapsulated IGMP query message */
+#define IGMP_REPORT_LEN 20
+#define AMT_HDR_LEN 2 /* length of AMT header on a packet */
+#define IP_HDR_LEN 20 /* length of standard IP header */
+#define IP_HDR_IGMP_LEN 24 /* length of IP header with an IGMP report */
+#define UDP_HDR_LEN 8 /* length of standard UDP header */
+#define AMT_REQUEST_MSG_LEN 9
+#define AMT_DISCO_MSG_LEN 8
+
+/*****************************************************************************
+ * Different AMT Message Types
+ *****************************************************************************/
+#define AMT_RELAY_DISCO 1 /* relay discovery */
+#define AMT_RELAY_ADV 2 /* relay advertisement */
+#define AMT_REQUEST 3 /* request */
+#define AMT_MEM_QUERY 4 /* membership query */
+#define AMT_MEM_UPD 5 /* membership update */
+#define AMT_MULT_DATA 6 /* multicast data */
+#define AMT_TEARDOWN 7 /* teardown (not currently supported) */
+
+/*****************************************************************************
+ * Different IGMP Message Types
+ *****************************************************************************/
+#define AMT_IGMPV3_MEMBERSHIP_QUERY_TYPEID 0x11
+#define AMT_IGMPV3_MEMBERSHIP_REPORT_TYPEID 0x22
+/* IGMPv2, interoperability */
+#define AMT_IGMPV1_MEMBERSHIP_REPORT_TYPEID 0x12
+#define AMT_IGMPV2_MEMBERSHIP_REPORT_TYPEID 0x16
+#define AMT_IGMPV2_MEMBERSHIP_LEAVE_TYPEID 0x17
+
+#define AMT_IGMP_INCLUDE 0x01
+#define AMT_IGMP_EXCLUDE 0x02
+#define AMT_IGMP_INCLUDE_CHANGE 0x03
+#define AMT_IGMP_EXCLUDE_CHANGE 0x04
+#define AMT_IGMP_ALLOW 0x05
+#define AMT_IGMP_BLOCK 0x06
+
+#define MCAST_ANYCAST "0.0.0.0"
+#define MCAST_ALLHOSTS "224.0.0.22"
+#define LOCAL_LOOPBACK "127.0.0.1"
+#define AMT_PORT 2268
+
+#define DEFAULT_MTU (1500u - (20 + 8))
+#define MAX_IPV4_UDP (65535u - (20 + 8))
+
+/* IPv4 Header Format */
+typedef struct _amt_ip {
+ uint8_t ver_ihl;
+ uint8_t tos;
+ uint16_t tot_len;
+ uint16_t id;
+ uint16_t frag_off;
+ uint8_t ttl;
+ uint8_t protocol;
+ uint16_t check;
+ uint32_t srcAddr;
+ uint32_t destAddr;
+} amt_ip_t;
+
+/* IPv4 Header Format with options field */
+typedef struct _amt_ip_alert {
+ uint8_t ver_ihl;
+ uint8_t tos;
+ uint16_t tot_len;
+ uint16_t id;
+ uint16_t frag_off;
+ uint8_t ttl;
+ uint8_t protocol;
+ uint16_t check;
+ uint32_t srcAddr;
+ uint32_t destAddr;
+ uint32_t options;
+} amt_ip_alert_t;
+
+/* IGMPv3 Group Record Format (RFC3376) */
+typedef struct _amt_igmpv3_groupRecord {
+ uint8_t type;
+ uint8_t auxDatalen;
+ uint16_t nSrc;
+ uint32_t ssm;
+ uint32_t srcIP[1];
+} amt_igmpv3_groupRecord_t;
+
+/* IGMPv3 Membership Report Format (RFC3376) */
+typedef struct _amt_igmpv3_membership_report {
+ uint8_t type;
+ uint8_t resv;
+ uint16_t checksum;
+ uint16_t resv2;
+ uint16_t nGroupRecord;
+ amt_igmpv3_groupRecord_t grp[1];
+} amt_igmpv3_membership_report_t;
+
+/* IGMPv3 Membership Query Format (RFC3376) */
+typedef struct _amt_igmpv3_membership_query {
+ uint8_t type;
+ uint8_t max_resp_code; /* in 100ms, Max Resp Time = (mant | 0x10) << (exp + 3) */
+ uint32_t checksum;
+ uint32_t ssmIP;
+ uint8_t s_qrv;
+ uint8_t qqic; /* in second, query Time = (mant | 0x10) << (exp + 3) */
+ uint16_t nSrc;
+ uint32_t srcIP[1];
+} amt_igmpv3_membership_query_t;
+
+/* ATM Membership Update Format (RFC7450) */
+typedef struct _amt_membership_update_msg {
+ amt_ip_alert_t ipHead;
+ amt_igmpv3_membership_report_t memReport;
+} amt_membership_update_msg_t;
+
+/* AMT Functions */
+static int amt_sockets_init( stream_t *p_access );
+static void amt_send_relay_discovery_msg( stream_t *p_access, char *relay_ip );
+static void amt_send_relay_request( stream_t *p_access, char *relay_ip );
+static int amt_joinSSM_group( stream_t *p_access );
+static int amt_joinASM_group( stream_t *p_access );
+static int amt_leaveASM_group( stream_t *p_access );
+static int amt_leaveSSM_group( stream_t *p_access );
+static bool amt_rcv_relay_adv( stream_t *p_access );
+static bool amt_rcv_relay_mem_query( stream_t *p_access );
+static void amt_send_mem_update( stream_t *p_access, char *relay_ip, bool leave );
+static bool open_amt_tunnel( stream_t *p_access );
+static void *amt_mem_upd( void *data );
+
+/* Struct to hold AMT state */
+typedef struct _access_sys_t
+{
+ char *mcastGroup;
+ char *mcastSrc;
+ char *relay;
+ char relayDisco[INET_ADDRSTRLEN];
+ block_t *overflow_block;
+
+ vlc_thread_t updateThread;
+ vlc_tick_t queryTime;
+
+ /* Mulicast group and source */
+ struct sockaddr_in mcastGroupAddr;
+ struct sockaddr_in mcastSrcAddr;
+
+ /* AMT relay imformation */
+ struct sockaddr_in relayDiscoAddr;
+ struct sockaddr_in relayAddr;
+ struct sockaddr_in stLocalAddr;
+ struct sockaddr_in stSvrAddr;
+
+ /* AMT Relay Membership Query data (RFC7450) */
+ struct relay_mem_query_msg_t {
+ uint32_t ulRcvedNonce;
+ uint8_t type;
+ uint8_t uchaMAC[MAC_LEN];
+ uint8_t uchaIGMP[IGMP_QUERY_LEN];
+ } relay_mem_query_msg;
+
+ /* AMT Relay Advertisement data (RFC7450) */
+ struct relay_adv_msg_t {
+ uint32_t ulRcvNonce;
+ uint32_t ipAddr;
+ uint8_t type;
+ } relay_adv_msg;
+
+ amt_ip_t relay_ip_hdr;
+ amt_igmpv3_membership_query_t relay_igmp_query;
+ ssize_t mtu;
+
+ uint32_t glob_ulNonce;
+ uint32_t ulRelayNonce;
+
+ int fd;
+ int sAMT;
+ int sQuery;
+ int timeout;
+ int amtTimeout;
+
+ bool tryAMT;
+ bool threadReady;
+} access_sys_t;
+
+/* Standard open/close functions */
+static int Open (vlc_object_t *);
+static void Close (vlc_object_t *);
+
+/* Utility functions */
+static unsigned short get_checksum( unsigned short *buffer, int nLen );
+static void make_report( amt_igmpv3_membership_report_t *mr );
+static void make_ip_header( amt_ip_alert_t *p_ipHead );
+
+vlc_module_begin ()
+ set_shortname( N_("AMT" ) )
+ set_description( N_("AMT input") )
+ set_category( CAT_INPUT )
+ set_subcategory( SUBCAT_INPUT_ACCESS )
+
+ add_integer( "amt-timeout", 5, AMT_TIMEOUT_TEXT, NULL, true )
+ add_integer( "amt-native-timeout", 3, TIMEOUT_TEXT, NULL, true )
+ add_string( "amt-relay", AMT_DEFAULT_RELAY, AMT_RELAY_ADDRESS, AMT_RELAY_ADDR_LONG, true )
+
+ set_capability( "access", 0 )
+ add_shortcut( "amt" )
+
+ set_callbacks( Open, Close )
+vlc_module_end ()
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+static block_t *BlockUDP( stream_t *, bool * );
+static int Control( stream_t *, int, va_list );
+
+/*****************************************************************************
+ * Open: Open a connection to the multicast feed
+ *****************************************************************************/
+static int Open( vlc_object_t *p_this )
+{
+ stream_t *p_access = (stream_t*) p_this;
+ access_sys_t *sys = NULL;
+ struct addrinfo hints, *serverinfo = NULL;
+ struct sockaddr_in *server_addr;
+ char *psz_name = NULL, *saveptr, *psz_strtok_r, ip_buffer[INET_ADDRSTRLEN];
+ int i_bind_port = 1234, i_server_port = 0, VLC_ret = VLC_SUCCESS, response;
+ vlc_url_t url;
+
+ if( p_access->b_preparsing )
+ return VLC_EGENERIC;
+
+ /* Allocate the structure for holding AMT info and zeroize it */
+ sys = vlc_obj_calloc( p_this, 1, sizeof( *sys ) );
+ if( unlikely( sys == NULL ) )
+ return VLC_ENOMEM;
+
+ /* The standard MPEG-2 transport is 188 bytes. 7 packets fit into a standard 1500 byte Ethernet frame */
+ sys->mtu = 7 * 188;
+
+ /* Protective packet overflow buffer designed to accommodate maximum IPv4 UDP payload minus the anticapated MTU */
+ sys->overflow_block = block_Alloc(MAX_IPV4_UDP - sys->mtu);
+ if( unlikely( sys->overflow_block == NULL ) )
+ return VLC_ENOMEM;
+
+ p_access->p_sys = sys;
+
+ /* Set up p_access */
+ ACCESS_SET_CALLBACKS( NULL, BlockUDP, Control, NULL );
+
+ if( !p_access->psz_location )
+ return VLC_EGENERIC;
+
+ psz_name = strdup( p_access->psz_location );
+ if ( unlikely( psz_name == NULL ) )
+ return VLC_ENOMEM;
+
+ /* Parse psz_name syntax :
+ * [serveraddr[:serverport]][@[bindaddr]:[bindport]] */
+ if( vlc_UrlParse( &url, p_access->psz_url ) != 0 )
+ {
+ msg_Err( p_access, "Invalid URL: %s", p_access->psz_url );
+ VLC_ret = VLC_EGENERIC;
+ goto cleanup;
+ }
+
+ /* Determining the multicast source and group depends on the URL provided */
+ /* */
+ /* The address(es) in the URL can be in the form of IP address or FQDN */
+ /* By calling vlc_getaaddrinfo() you get it in IP form either way */
+ /* */
+ /* Case 1: amt://<source-ip-address>@<multicast-group-ip-address> */
+ /* */
+ /* sys->mcastSrc = <source-ip-address> */
+ /* sys->mcastSrcAddr = inet_pton( sys->mcastSrc ) */
+ /* */
+ /* sys->mcastGroup = <multicast-group-ip-address> */
+ /* sys->mcastGroupAddr = inet_pton( sys->mcastGroup ) */
+ /* */
+ /* Case 2: amt://<multicast-group-ip-address> */
+ /* */
+ /* sys->mcastSrc = MCAST_ANYCAST = "0.0.0.0" */
+ /* sys->mcastSrcAddr = inet_pton( sys->mcastSrc ) = 0 */
+ /* */
+ /* sys->mcastGroup = <multicast-group-ip-address> */
+ /* sys->mcastGroupAddr = inet_pton( sys->mcastGroup ) */
+ /* */
+
+ /* If UDP port provided then assign port to stream */
+ if( url.i_port > 0 )
+ i_bind_port = url.i_port;
+
+ msg_Dbg( p_access, "Opening multicast: %s:%d local=%s:%d", url.psz_host, i_server_port, url.psz_path, i_bind_port );
+
+ /* Initialize hints prior to call to vlc_getaddrinfo with either IP address or FQDN */
+ memset( &hints, 0, sizeof( hints ));
+ hints.ai_family = AF_INET; /* Setting to AF_UNSPEC accepts both IPv4 and IPv6 */
+ hints.ai_socktype = SOCK_DGRAM;
+
+ /* Retrieve list of multicast addresses matching the multicast group identifier */
+ response = vlc_getaddrinfo( url.psz_host, AMT_PORT, &hints, &serverinfo );
+
+ /* If an error returned print reason and exit */
+ if( response != 0 )
+ {
+ msg_Err( p_access, "Could not find multicast group %s, reason: %s", url.psz_host, gai_strerror(response) );
+ VLC_ret = VLC_EGENERIC;
+ goto cleanup;
+ }
+
+ /* Convert binary socket address to string */
+ server_addr = (struct sockaddr_in *) serverinfo->ai_addr;
+ if( unlikely( inet_ntop(AF_INET, &(server_addr->sin_addr), ip_buffer, INET_ADDRSTRLEN) ) == NULL )
+ {
+ int errConv = errno;
+ msg_Err(p_access, "Could not convert binary socket address to string: %s", gai_strerror(errConv));
+ goto cleanup;
+ }
+
+ /* Store the binary socket representation of multicast group address */
+ sys->mcastGroupAddr = *server_addr;
+
+ /* Release the allocated memory */
+ freeaddrinfo( serverinfo );
+ serverinfo = NULL;
+
+ /* Store string representation */
+ sys->mcastGroup = strdup( ip_buffer );
+ if( unlikely( sys->mcastGroup == NULL ) )
+ {
+ VLC_ret = VLC_ENOMEM;
+ goto cleanup;
+ }
+
+ msg_Dbg( p_access, "Setting multicast group address to %s", sys->mcastGroup);
+
+ /* Extract the source from the URL, or the multicast group when no source is provided */
+ if ( !( psz_strtok_r = strtok_r( psz_name, "@", &saveptr ) ) )
+ {
+ msg_Err( p_access, "Could not parse location %s", psz_name);
+ VLC_ret = VLC_EGENERIC;
+ goto cleanup;
+ }
+
+ /* Store the string representation */
+ sys->mcastSrc = strdup( psz_strtok_r );
+ if( unlikely( sys->mcastSrc == NULL ) )
+ {
+ VLC_ret = VLC_ENOMEM;
+ goto cleanup;
+ }
+
+ /* If strings are equal then no multicast source has been specified, so try anycast */
+ if( strcmp( url.psz_host, sys->mcastSrc ) == 0 )
+ {
+ free( sys->mcastSrc );
+ sys->mcastSrc = strdup(MCAST_ANYCAST);
+ sys->mcastSrcAddr.sin_addr.s_addr = 0;
+ msg_Dbg( p_access, "No multicast source address specified, trying ASM...");
+ }
+
+ /* retrieve list of source addresses matching the multicast source identifier */
+ response = vlc_getaddrinfo( sys->mcastSrc, AMT_PORT, &hints, &serverinfo );
+
+ /* If an error returned print reason and exit */
+ if( response != 0 )
+ {
+ msg_Err( p_access, "Could not find multicast source %s, reason: %s", sys->mcastSrc, gai_strerror(response) );
+ VLC_ret = VLC_EGENERIC;
+ goto cleanup;
+ }
+
+ /* Convert binary socket address to string */
+ server_addr = (struct sockaddr_in *) serverinfo->ai_addr;
+ if( unlikely( inet_ntop(AF_INET, &(server_addr->sin_addr), ip_buffer, INET_ADDRSTRLEN) ) == NULL )
+ {
+ int errConv = errno;
+ msg_Err(p_access, "Could not binary socket address to string: %s", gai_strerror(errConv));
+ goto cleanup;
+ }
+
+ /* Store the binary socket representation of multicast source address */
+ sys->mcastSrcAddr = *server_addr;
+
+ /* free the original source address buffer (IP or FQDN) and assign it just the IP address */
+ free( sys->mcastSrc );
+
+ /* Store string representation */
+ sys->mcastSrc = strdup( ip_buffer );
+ if( unlikely( sys->mcastSrc == NULL ) )
+ {
+ VLC_ret = VLC_ENOMEM;
+ goto cleanup;
+ }
+
+ msg_Dbg( p_access, "Setting multicast source address to %s", sys->mcastSrc);
+
+ /* Pull the AMT relay address from the settings */
+ sys->relay = var_InheritString( p_access, "amt-relay" );
+ if( unlikely( sys->relay == NULL ) )
+ {
+ msg_Err( p_access, "No relay anycast or unicast address specified." );
+ VLC_ret = VLC_EGENERIC;
+ goto cleanup;
+ }
+
+ msg_Dbg( p_access, "Addresses: mcastGroup: %s mcastSrc: %s relay: %s", \
+ sys->mcastGroup, sys->mcastSrc, sys->relay);
+
+ /* Native multicast file descriptor */
+ sys->fd = net_OpenDgram( p_access, sys->mcastGroup, i_bind_port,
+ sys->mcastSrc, i_server_port, IPPROTO_UDP );
+ if( sys->fd == -1 )
+ {
+ msg_Err( p_access, "cannot open socket" );
+ VLC_ret = VLC_EGENERIC;
+ goto cleanup;
+ }
+
+ sys->timeout = var_InheritInteger( p_access, "amt-native-timeout");
+ if( sys->timeout > 0)
+ sys->timeout *= 1000;
+
+ sys->amtTimeout = var_InheritInteger( p_access, "amt-timeout" );
+ if( sys->amtTimeout > 0)
+ sys->amtTimeout *= 1000;
+
+ sys->tryAMT = false;
+ sys->threadReady = false;
+
+cleanup: /* fall through */
+
+ /* release the allocated memory */
+ free( psz_name );
+ vlc_UrlClean( &url );
+ if( serverinfo )
+ freeaddrinfo( serverinfo );
+
+ /* if an error occurred free the memory */
+ if ( VLC_ret != VLC_SUCCESS )
+ {
+ msg_Dbg( p_access, "Open(): Before free( sys-> sys->Addr ) #4" );
+ free( sys->mcastGroup );
+ free( sys->mcastSrc );
+ }
+
+ return VLC_ret;
+}
+
+/*****************************************************************************
+ * Close: Cancel thread and free data structures
+ *****************************************************************************/
+static void Close( vlc_object_t *p_this )
+{
+ stream_t *p_access = (stream_t*)p_this;
+ access_sys_t *sys = p_access->p_sys;
+
+ msg_Dbg( p_access, "Closing AMT plugin" );
+
+ /* If using AMT tunneling send leave message and free the relay addresses */
+ if ( sys->tryAMT )
+ {
+ /* Prepare socket options */
+ if( sys->mcastSrcAddr.sin_addr.s_addr )
+ amt_leaveSSM_group( p_access );
+ else
+ amt_leaveASM_group( p_access );
+
+ /* Send IGMP leave message */
+ amt_send_mem_update( p_access, sys->relayDisco, true );
+
+ free( sys->relay );
+ }
+
+ /* If overflow block allocated then free the memory */
+ if( sys->overflow_block )
+ block_Release( sys->overflow_block );
+
+ /* If membership thread spawned cancel it */
+ if( sys->threadReady )
+ {
+ msg_Dbg( p_access, "Canceling IGMP membership thread" );
+ sys->threadReady = false;
+ vlc_cancel( sys->updateThread );
+ vlc_join( sys->updateThread, NULL );
+ }
+
+ free( sys->mcastGroup );
+ free( sys->mcastSrc );
+
+ net_Close( sys->fd );
+ if( sys->sAMT != -1 )
+ net_Close( sys->sAMT );
+ if( sys->sQuery != -1 )
+ net_Close( sys->sQuery );
+
+ msg_Dbg( p_access, "Exiting AMT plugin" );
+}
+
+/*****************************************************************************
+ * Control: Define stream controls
+ *****************************************************************************/
+static int Control( stream_t *p_access, int i_query, va_list args )
+{
+ switch( i_query )
+ {
+ case STREAM_CAN_SEEK:
+ case STREAM_CAN_FASTSEEK:
+ case STREAM_CAN_PAUSE:
+ case STREAM_CAN_CONTROL_PACE:
+ *va_arg( args, bool * ) = false;
+ break;
+
+ case STREAM_GET_PTS_DELAY:
+ *va_arg( args, vlc_tick_t * ) =
+ VLC_TICK_FROM_MS(var_InheritInteger( p_access, "network-caching" ));
+ break;
+
+ default:
+ return VLC_EGENERIC;
+ }
+
+ return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * BlockUDP: Responsible for returning the multicast payload
+ * BlockUDP: Sections of code are based upon BlockUDP code in udp.c
+ *
+ * Default MTU based on number of MPEG-2 transports carried in a 1500 byte Ethernet frame
+ * however the code is able to receive maximal IPv4 UDP frames and then adjusts the MTU
+ *****************************************************************************/
+static block_t *BlockUDP(stream_t *p_access, bool *restrict eof)
+{
+ access_sys_t *sys = p_access->p_sys;
+ ssize_t len = 0, shift = 0, tunnel = IP_HDR_LEN + UDP_HDR_LEN + AMT_HDR_LEN;
+
+ /* Allocate anticipated MTU buffer for holding the UDP packet suitable for native or AMT tunneled multicast */
+ block_t *pkt = block_Alloc( sys->mtu + tunnel );
+ if ( unlikely( pkt == NULL ) )
+ return NULL;
+
+ /* Structure initialized to hold anticipated MTU buffer along with protective overflow buffer */
+ struct iovec iov[] = {{
+ .iov_base = pkt->p_buffer,
+ .iov_len = sys->mtu + tunnel,
+ },{
+ .iov_base = sys->overflow_block->p_buffer,
+ .iov_len = sys->overflow_block->i_buffer,
+ }};
+
+ /* References the two element array above to be passed into recvmsg */
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = 2,
+ };
+
+ struct pollfd ufd[1];
+
+ if( sys->tryAMT )
+ ufd[0].fd = sys->sAMT; /* AMT tunneling file descriptor */
+ else
+ ufd[0].fd = sys->fd; /* Native multicast file descriptor */
+ ufd[0].events = POLLIN;
+
+ switch (vlc_poll_i11e(ufd, 1, sys->timeout))
+ {
+ case 0:
+ if( !sys->tryAMT )
+ {
+ msg_Err(p_access, "Native multicast receive time-out");
+ if( !open_amt_tunnel( p_access ) )
+ goto error;
+ break;
+ }
+ else
+ {
+ *eof = true;
+ }
+ /* fall through */
+ case -1:
+ goto error;
+ }
+
+ /* If using AMT tunneling perform basic checks and point to beginning of the payload */
+ if( sys->tryAMT )
+ {
+ len = recvmsg( sys->sAMT, &msg, 0 );
+
+ /* Check for the integrity of the received AMT packet */
+ if( len < 0 || *(pkt->p_buffer) != AMT_MULT_DATA )
+ goto error;
+
+ /* Set the offet to the first byte of the payload */
+ shift += tunnel;
+
+ /* If the length received is less than the AMT tunnel header then it's truncated */
+ if( len < tunnel )
+ {
+ msg_Err(p_access, "%zd bytes packet truncated (MTU was %zd)", len, sys->mtu);
+ pkt->i_flags |= BLOCK_FLAG_CORRUPTED;
+ }
+
+ /* Otherwise subtract the length of the AMT encapsulation from the packet received */
+ else
+ {
+ len -= tunnel;
+ }
+ }
+
+ /* Otherwise pull native multicast */
+ else
+ {
+ len = recvmsg(sys->fd, &msg, 0);
+ }
+
+ /* If the payload length is greater than the MTU then the overflow buffer was utilized */
+ if ( unlikely( len > sys->mtu ) )
+ {
+ msg_Warn(p_access, "%zd bytes packet received (MTU was %zd), adjusting mtu", len, sys->mtu);
+
+ block_t *gather_block = sys->overflow_block;
+
+ /* Allocate a new overflow buffer based on the received payload length */
+ sys->overflow_block = block_Alloc(MAX_IPV4_UDP - len);
+
+ /* Set number of bytes consumed in the overflow block */
+ gather_block->i_buffer = len - sys->mtu;
+
+ /* Chain the anticipated packet and overflow buffers, copy into a single buffer and free the chain */
+ pkt->p_next = gather_block;
+ pkt = block_ChainGather( pkt );
+
+ /* Adjust the anticipated MTU to match the payload received */
+ sys->mtu = len;
+ }
+
+ /* Set the offset to payload start */
+ pkt->p_buffer += shift;
+ pkt->i_buffer -= shift;
+
+ return pkt;
+
+error:
+ block_Release( pkt );
+ return NULL;
+}
+
+/*****************************************************************************
+ * open_amt_tunnel: Create an AMT tunnel to the AMT relay
+ *****************************************************************************/
+static bool open_amt_tunnel( stream_t *p_access )
+{
+ struct addrinfo hints, *serverinfo, *server;
+ access_sys_t *sys = p_access->p_sys;
+
+ memset( &hints, 0, sizeof( hints ));
+ hints.ai_family = AF_INET; /* Setting to AF_UNSPEC accepts both IPv4 and IPv6 */
+ hints.ai_socktype = SOCK_DGRAM;
+
+ msg_Dbg( p_access, "Attempting AMT to %s...", sys->relay);
+ sys->tryAMT = true;
+
+ /* Retrieve list of addresses matching the AMT relay */
+ int response = vlc_getaddrinfo( sys->relay, AMT_PORT, &hints, &serverinfo );
+
+ /* If an error returned print reason and exit */
+ if( response != 0 )
+ {
+ msg_Err( p_access, "Could not find relay %s, reason: %s", sys->relay, gai_strerror(response) );
+ goto error;
+ }
+
+ /* Iterate through the list of sockets to find one that works */
+ for (server = serverinfo; server != NULL; server = server->ai_next)
+ {
+ struct sockaddr_in *server_addr = (struct sockaddr_in *) server->ai_addr;
+ char relay_ip[INET_ADDRSTRLEN];
+
+ /* Convert to binary representation */
+ if( unlikely( inet_ntop(AF_INET, &(server_addr->sin_addr), relay_ip, INET_ADDRSTRLEN) == NULL ) )
+ {
+ int errConv = errno;
+ msg_Err(p_access, "Could not convert relay ip to binary representation: %s", gai_strerror(errConv));
+ goto error;
+ }
+
+ /* Store string representation */
+ memcpy(sys->relayDisco, relay_ip, INET_ADDRSTRLEN);
+ if( unlikely( sys->relayDisco == NULL ) )
+ {
+ goto error;
+ }
+
+ msg_Dbg( p_access, "Trying AMT Server: %s", sys->relayDisco);
+
+ /* Store the binary representation */
+ sys->relayDiscoAddr.sin_addr = server_addr->sin_addr;
+
+ /* If can't open socket try any others in list */
+ if( amt_sockets_init( p_access ) != 0 )
+ msg_Err( p_access, "Error initializing socket to %s", relay_ip );
+
+ /* Otherwise negotiate with AMT relay and confirm you can pull a UDP packet */
+ else
+ {
+ amt_send_relay_discovery_msg( p_access, relay_ip );
+ msg_Dbg( p_access, "Sent relay AMT discovery message to %s", relay_ip );
+
+ if( !amt_rcv_relay_adv( p_access ) )
+ {
+ msg_Err( p_access, "Error receiving AMT relay advertisement msg from %s, skipping", relay_ip );
+ goto error;
+ }
+ msg_Dbg( p_access, "Received AMT relay advertisement from %s", relay_ip );
+
+ amt_send_relay_request( p_access, relay_ip );
+ msg_Dbg( p_access, "Sent AMT relay request message to %s", relay_ip );
+
+ if( !amt_rcv_relay_mem_query( p_access ) )
+ {
+ msg_Err( p_access, "Could not receive AMT relay membership query from %s, reason: %s", relay_ip, vlc_strerror(errno));
+ goto error;
+ }
+ msg_Dbg( p_access, "Received AMT relay membership query from %s", relay_ip );
+
+ /* If single source multicast send SSM join */
+ if( sys->mcastSrcAddr.sin_addr.s_addr )
+ {
+ if( amt_joinSSM_group( p_access ) != 0 )
+ {
+ msg_Err( p_access, "Error joining SSM %s", vlc_strerror(errno) );
+ goto error;
+ }
+ msg_Dbg( p_access, "Joined SSM src: %s group: %s", sys->mcastSrc, sys->mcastGroup );
+ }
+
+ /* If any source multicast send ASM join */
+ else {
+ if( amt_joinASM_group( p_access ) != 0 )
+ {
+ msg_Err( p_access, "Error joining ASM %s", vlc_strerror(errno) );
+ goto error;
+ }
+ msg_Dbg( p_access, "Joined ASM group: %s", sys->mcastGroup );
+ }
+
+ bool eof=false;
+ block_t *pkt;
+
+ /* Confirm that you can pull a UDP packet from the socket */
+ if ( !(pkt = BlockUDP( p_access, &eof )) )
+ msg_Err( p_access, "Unable to receive UDP packet from AMT relay %s for multicast group %s", relay_ip, sys->mcastGroup );
+ else
+ {
+ block_Release( pkt );
+ msg_Dbg( p_access, "Got UDP packet from multicast group %s via AMT relay %s, continuing...", sys->mcastGroup, relay_ip );
+ break; /* found an active server sending UDP packets, so exit loop */
+ }
+ }
+ }
+
+ /* if server is NULL then no AMT relay is responding */
+ if (server == NULL)
+ {
+ msg_Err( p_access, "No AMT servers responding" );
+ goto error;
+ }
+
+ sys->queryTime = vlc_tick_now() / CLOCK_FREQ;
+
+ /* release the allocated memory */
+ freeaddrinfo( serverinfo );
+ return true;
+
+error:
+ sys->threadReady = false;
+ if( serverinfo )
+ freeaddrinfo( serverinfo );
+ return false;
+}
+
+/**
+ * Calculate checksum
+ * */
+static unsigned short get_checksum( unsigned short *buffer, int nLen )
+{
+ int nleft = nLen;
+ int sum = 0;
+ unsigned short *w = buffer;
+ unsigned short answer = 0;
+
+ while (nleft > 1)
+ {
+ sum += *w++;
+ nleft -= 2;
+ }
+ if (nleft == 1)
+ {
+ *(unsigned char*)(&answer) = *(unsigned char*)w;
+ sum += answer;
+ }
+ sum = (sum >> 16) + (sum & 0xffff);
+ answer = ~sum;
+ return (answer);
+}
+
+/**
+ * Make IGMP Membership report
+ * */
+static void make_report( amt_igmpv3_membership_report_t *mr )
+{
+ mr->type = AMT_IGMPV3_MEMBERSHIP_REPORT_TYPEID;
+ mr->resv = 0;
+ mr->checksum = 0;
+ mr->resv2 = 0;
+ mr->nGroupRecord = htons(1);
+}
+
+/**
+ * Make IP header
+ * */
+static void make_ip_header( amt_ip_alert_t *p_ipHead )
+{
+ p_ipHead->ver_ihl = 0x46;
+ p_ipHead->tos = 0xc0;
+ p_ipHead->tot_len = htons( IP_HDR_IGMP_LEN + IGMP_REPORT_LEN );
+ p_ipHead->id = 0x00;
+ p_ipHead->frag_off = 0x0000;
+ p_ipHead->ttl = 0x01;
+ p_ipHead->protocol = 0x02;
+ p_ipHead->check = 0;
+ p_ipHead->srcAddr = INADDR_ANY;
+ p_ipHead->options = 0x0000;
+}
+
+/** Create relay discovery socket, query socket, UDP socket and
+ * fills in relay anycast address for discovery
+ * return 0 if successful, -1 if not
+ */
+static int amt_sockets_init( stream_t *p_access )
+{
+ struct sockaddr_in rcvAddr;
+ access_sys_t *sys = p_access->p_sys;
+ memset( &rcvAddr, 0, sizeof(struct sockaddr_in) );
+ int enable = 0, res = 0;
+
+ /* Relay anycast address for discovery */
+ sys->relayDiscoAddr.sin_family = AF_INET;
+ sys->relayDiscoAddr.sin_port = htons( AMT_PORT );
+
+ /* create UDP socket */
+ sys->sAMT = vlc_socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP, true );
+ if( sys->sAMT == -1 )
+ {
+ msg_Err( p_access, "Failed to create UDP socket" );
+ goto error;
+ }
+
+ res = setsockopt(sys->sAMT, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable));
+ if(res < 0)
+ {
+ msg_Err( p_access, "Couldn't set socket options for IPPROTO_IP, IP_PKTINFO\n %s", vlc_strerror(errno));
+ goto error;
+ }
+
+ res = setsockopt(sys->sAMT, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
+ if(res < 0)
+ {
+ msg_Err( p_access, "Couldn't make socket reusable");
+ goto error;
+ }
+
+ rcvAddr.sin_family = AF_INET;
+ rcvAddr.sin_port = htons( 0 );
+ rcvAddr.sin_addr.s_addr = INADDR_ANY;
+
+ if( bind(sys->sAMT, (struct sockaddr *)&rcvAddr, sizeof(rcvAddr) ) != 0 )
+ {
+ msg_Err( p_access, "Failed to bind UDP socket error: %s", vlc_strerror(errno) );
+ goto error;
+ }
+
+ sys->sQuery = vlc_socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP, true );
+ if( sys->sQuery == -1 )
+ {
+ msg_Err( p_access, "Failed to create query socket" );
+ goto error;
+ }
+
+ /* bind socket to local address */
+ sys->stLocalAddr.sin_family = AF_INET;
+ sys->stLocalAddr.sin_port = htons( 0 );
+ sys->stLocalAddr.sin_addr.s_addr = INADDR_ANY;
+
+ if( bind(sys->sQuery, (struct sockaddr *)&sys->stLocalAddr, sizeof(struct sockaddr) ) != 0 )
+ {
+ msg_Err( p_access, "Failed to bind query socket" );
+ goto error;
+ }
+
+ sys->stSvrAddr.sin_family = AF_INET;
+ sys->stSvrAddr.sin_port = htons( 9124 );
+ res = inet_pton( AF_INET, LOCAL_LOOPBACK, &sys->stSvrAddr.sin_addr );
+ if( res != 1 )
+ {
+ msg_Err( p_access, "Could not convert loopback address" );
+ goto error;
+ }
+
+ return 0;
+
+error:
+ if( sys->sAMT != -1 )
+ {
+ net_Close( sys->sAMT );
+ sys->sAMT = -1;
+ }
+
+ if( sys->sQuery != -1 )
+ {
+ net_Close( sys->sQuery );
+ sys->sQuery = -1;
+ }
+ return -1;
+}
+
+/**
+ * Send a relay discovery message, before 3-way handshake
+ * */
+static void amt_send_relay_discovery_msg( stream_t *p_access, char *relay_ip )
+{
+ char chaSendBuffer[AMT_DISCO_MSG_LEN];
+ unsigned int ulNonce;
+ int nRet;
+ access_sys_t *sys = p_access->p_sys;
+
+ /* initialize variables */
+ memset( chaSendBuffer, 0, sizeof(chaSendBuffer) );
+ ulNonce = 0;
+ nRet = 0;
+
+ /*
+ * create AMT discovery message format
+ * +---------------------------------------------------+
+ * | Msg Type(1Byte)| Reserved (3 byte)| nonce (4byte) |
+ * +---------------------------------------------------+
+ */
+
+ chaSendBuffer[0] = AMT_RELAY_DISCO;
+ chaSendBuffer[1] = 0;
+ chaSendBuffer[2] = 0;
+ chaSendBuffer[3] = 0;
+
+ /* create nonce and copy into send buffer */
+ srand( (unsigned int)time(NULL) );
+ ulNonce = htonl( rand() );
+ memcpy( &chaSendBuffer[4], &ulNonce, sizeof(ulNonce) );
+ sys->glob_ulNonce = ulNonce;
+
+ /* send it */
+ nRet = sendto( sys->sAMT, chaSendBuffer, sizeof(chaSendBuffer), 0,\
+ (struct sockaddr *)&sys->relayDiscoAddr, sizeof(struct sockaddr) );
+
+ if( nRet < 0)
+ msg_Err( p_access, "Sendto failed to %s with error %d.", relay_ip, errno);
+}
+
+/**
+ * Send relay request message, stage 2 of handshake
+ * */
+static void amt_send_relay_request( stream_t *p_access, char *relay_ip )
+{
+ char chaSendBuffer[AMT_REQUEST_MSG_LEN];
+ uint32_t ulNonce;
+ int nRet;
+ access_sys_t *sys = p_access->p_sys;
+
+ memset( chaSendBuffer, 0, sizeof(chaSendBuffer) );
+
+ ulNonce = 0;
+ nRet = 0;
+
+ /*
+ * create AMT request message format
+ * +-----------------------------------------------------------------+
+ * | Msg Type(1Byte)| Reserved(1byte)|P flag(1byte)|Reserved (2 byte)|
+ * +-----------------------------------------------------------------+
+ * | nonce (4byte) |
+ * +-----------------------------------------------------------------+
+ *
+ * The P flag is set to indicate which group membership protocol the
+ * gateway wishes the relay to use in the Membership Query response:
+
+ * Value Meaning
+
+ * 0 The relay MUST respond with a Membership Query message that
+ * contains an IPv4 packet carrying an IGMPv3 General Query
+ * message.
+ * 1 The relay MUST respond with a Membership Query message that
+ * contains an IPv6 packet carrying an MLDv2 General Query
+ * message.
+ *
+ */
+
+ chaSendBuffer[0] = AMT_REQUEST;
+ chaSendBuffer[1] = 0;
+ chaSendBuffer[2] = 0;
+ chaSendBuffer[3] = 0;
+
+ ulNonce = sys->glob_ulNonce;
+ memcpy( &chaSendBuffer[4], &ulNonce, sizeof(uint32_t) );
+
+ nRet = send( sys->sAMT, chaSendBuffer, sizeof(chaSendBuffer), 0 );
+
+ if( nRet < 0 )
+ msg_Err(p_access, "Error sending relay request to %s error: %s", relay_ip, vlc_strerror(errno) );
+}
+
+/*
+* create AMT request message format
+* +----------------------------------------------------------------------------------+
+* | Msg Type(1 byte)| Reserved (1 byte)| MAC (6 byte)| nonce (4 byte) | IGMP packet |
+* +----------------------------------------------------------------------------------+
+*/
+static void amt_send_mem_update( stream_t *p_access, char *relay_ip, bool leave)
+{
+ int sendBufSize = IP_HDR_IGMP_LEN + MAC_LEN + NONCE_LEN + AMT_HDR_LEN;
+ char pSendBuffer[ sendBufSize + IGMP_REPORT_LEN ];
+ uint32_t ulNonce = 0;
+ access_sys_t *sys = p_access->p_sys;
+
+ memset( pSendBuffer, 0, sizeof(pSendBuffer) );
+
+ pSendBuffer[0] = AMT_MEM_UPD;
+
+ /* copy relay MAC response */
+ memcpy( &pSendBuffer[2], sys->relay_mem_query_msg.uchaMAC, MAC_LEN );
+
+ /* copy nonce */
+ ulNonce = GetDWBE(sys->glob_ulNonce);
+ memcpy( &pSendBuffer[8], &ulNonce, NONCE_LEN );
+
+ /* make IP header for IGMP packet */
+ amt_ip_alert_t p_ipHead;
+ memset( &p_ipHead, 0, IP_HDR_IGMP_LEN );
+ make_ip_header( &p_ipHead );
+
+ struct sockaddr_in temp;
+ int res = inet_pton( AF_INET, MCAST_ALLHOSTS, &(temp.sin_addr) );
+ if( res != 1 )
+ {
+ msg_Err(p_access, "Could not convert all hosts multicast address: %s", gai_strerror(errno) );
+ return;
+ }
+ p_ipHead.destAddr = temp.sin_addr.s_addr;
+ p_ipHead.check = get_checksum( (unsigned short*)&p_ipHead, IP_HDR_IGMP_LEN );
+
+ amt_igmpv3_groupRecord_t groupRcd;
+ groupRcd.auxDatalen = 0;
+ groupRcd.ssm = sys->mcastGroupAddr.sin_addr.s_addr;
+
+ if( sys->mcastSrcAddr.sin_addr.s_addr )
+ {
+ groupRcd.type = leave ? AMT_IGMP_BLOCK:AMT_IGMP_INCLUDE;
+ groupRcd.nSrc = htons(1);
+ groupRcd.srcIP[0] = sys->mcastSrcAddr.sin_addr.s_addr;
+
+ } else {
+ groupRcd.type = leave ? AMT_IGMP_INCLUDE_CHANGE:AMT_IGMP_EXCLUDE_CHANGE;
+ groupRcd.nSrc = htons(0);
+ }
+
+ /* make IGMP membership report */
+ amt_igmpv3_membership_report_t p_igmpMemRep;
+ make_report( &p_igmpMemRep );
+
+ memcpy(&p_igmpMemRep.grp[0], &groupRcd, (int)sizeof(groupRcd) );
+ p_igmpMemRep.checksum = get_checksum( (unsigned short*)&p_igmpMemRep, IGMP_REPORT_LEN );
+
+ amt_membership_update_msg_t memUpdateMsg;
+ memset(&memUpdateMsg, 0, sizeof(memUpdateMsg));
+ memcpy(&memUpdateMsg.ipHead, &p_ipHead, sizeof(p_ipHead) );
+ memcpy(&memUpdateMsg.memReport, &p_igmpMemRep, sizeof(p_igmpMemRep) );
+
+ memcpy( &pSendBuffer[12], &memUpdateMsg, sizeof(memUpdateMsg) );
+
+ send( sys->sAMT, pSendBuffer, sizeof(pSendBuffer), 0 );
+
+ msg_Dbg( p_access, "AMT relay membership report sent to %s", relay_ip );
+}
+
+/**
+ * Receive relay advertisement message
+ *
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | V=0 |Type=2 | Reserved |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Discovery Nonce |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | |
+ * ~ Relay Address (IPv4 or IPv6) ~
+ * | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * */
+static bool amt_rcv_relay_adv( stream_t *p_access )
+{
+ char pkt[RELAY_ADV_MSG_LEN];
+ access_sys_t *sys = p_access->p_sys;
+
+ memset( pkt, 0, RELAY_ADV_MSG_LEN );
+
+ struct pollfd ufd[1];
+
+ ufd[0].fd = sys->sAMT;
+ ufd[0].events = POLLIN;
+
+ switch( vlc_poll_i11e(ufd, 1, sys->timeout) )
+ {
+ case 0:
+ msg_Err(p_access, "AMT relay advertisement receive time-out");
+ /* fall through */
+ case -1:
+ return false;
+ }
+
+ struct sockaddr temp;
+ uint32_t temp_size = sizeof( struct sockaddr );
+ ssize_t len = recvfrom( sys->sAMT, &pkt, RELAY_ADV_MSG_LEN, 0, &temp, &temp_size );
+
+ if (len < 0)
+ {
+ msg_Err(p_access, "Received message length less than zero");
+ return false;
+ }
+
+ memcpy( &sys->relay_adv_msg.type, &pkt[0], MSG_TYPE_LEN );
+ if( sys->relay_adv_msg.type != AMT_RELAY_ADV )
+ {
+ msg_Err( p_access, "Received message not an AMT relay advertisement, ignoring. ");
+ return false;
+ }
+
+ memcpy( &sys->relay_adv_msg.ulRcvNonce, &pkt[NONCE_LEN], NONCE_LEN );
+ if( sys->glob_ulNonce != sys->relay_adv_msg.ulRcvNonce )
+ {
+ msg_Err( p_access, "Discovery nonces differ! currNonce:%x rcvd%x", sys->glob_ulNonce, GetDWBE(sys->relay_adv_msg.ulRcvNonce) );
+ return false;
+ }
+
+ memcpy( &sys->relay_adv_msg.ipAddr, &pkt[8], 4 );
+
+ memset( &sys->relayAddr, 0, sizeof(sys->relayAddr) );
+ sys->relayAddr.sin_family = AF_INET;
+ sys->relayAddr.sin_addr.s_addr = sys->relay_adv_msg.ipAddr;
+ sys->relayAddr.sin_port = htons( AMT_PORT );
+
+ int nRet = connect( sys->sAMT, (struct sockaddr *)&sys->relayAddr, sizeof(sys->relayAddr) );
+ if( nRet < 0 )
+ {
+ msg_Err( p_access, "Error connecting AMT UDP socket: %s", vlc_strerror(errno) );
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Receive relay membership query message
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | V=0 |Type=4 | Reserved |L|G| Response MAC |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Request Nonce |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ | Encapsulated General Query Message |
+ ~ IPv4:IGMPv3(Membership Query) ~
+ | IPv6:MLDv2(Listener Query) |
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Gateway Port Number | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
+ | |
+ + +
+ | Gateway IP Address (IPv4 or IPv6) |
+ + +
+ | |
+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+static bool amt_rcv_relay_mem_query( stream_t *p_access )
+{
+ char pkt[RELAY_QUERY_MSG_LEN];
+ memset( pkt, 0, RELAY_QUERY_MSG_LEN );
+ struct pollfd ufd[1];
+ access_sys_t *sys = p_access->p_sys;
+
+ ufd[0].fd = sys->sAMT;
+ ufd[0].events = POLLIN;
+
+ switch( vlc_poll_i11e(ufd, 1, sys->timeout) )
+ {
+ case 0:
+ msg_Err(p_access, "AMT relay membership query receive time-out");
+ /* fall through */
+ case -1:
+ return false;
+ }
+
+ ssize_t len = recv( sys->sAMT, &pkt, RELAY_QUERY_MSG_LEN, 0 );
+
+ if (len < 0)
+ {
+ msg_Err(p_access, "length less than zero");
+ return false;
+ }
+
+ memcpy( &sys->relay_mem_query_msg.type, &pkt[0], MSG_TYPE_LEN );
+ /* pkt[1] is reserved */
+ memcpy( &sys->relay_mem_query_msg.uchaMAC[0], &pkt[AMT_HDR_LEN], MAC_LEN );
+ memcpy( &sys->relay_mem_query_msg.ulRcvedNonce, &pkt[AMT_HDR_LEN + MAC_LEN], NONCE_LEN );
+ if( sys->relay_mem_query_msg.ulRcvedNonce != sys->glob_ulNonce )
+ {
+ msg_Warn( p_access, "Nonces are different rcvd: %x glob: %x", sys->relay_mem_query_msg.ulRcvedNonce, sys->glob_ulNonce );
+ return false;
+ }
+
+ sys->glob_ulNonce = GetDWBE(&pkt[AMT_HDR_LEN + MAC_LEN]);
+
+ size_t shift = AMT_HDR_LEN + MAC_LEN + NONCE_LEN;
+ memcpy( &sys->relay_ip_hdr, &pkt[shift], IP_HDR_IGMP_LEN );
+
+ shift += IP_HDR_IGMP_LEN;
+ sys->relay_igmp_query.type = pkt[shift];
+ shift++; assert( shift < RELAY_QUERY_MSG_LEN);
+ sys->relay_igmp_query.max_resp_code = pkt[shift];
+ shift++; assert( shift < RELAY_QUERY_MSG_LEN);
+ memcpy( &sys->relay_igmp_query.checksum, &pkt[shift], 2 );
+ shift += 2; assert( shift < RELAY_QUERY_MSG_LEN);
+ memcpy( &sys->relay_igmp_query.ssmIP, &pkt[shift], 4 );
+ shift += 4; assert( shift < RELAY_QUERY_MSG_LEN);
+ sys->relay_igmp_query.s_qrv = pkt[shift];
+ shift++; assert( shift < RELAY_QUERY_MSG_LEN);
+ if( pkt[shift] == 0 )
+ sys->relay_igmp_query.qqic = 125;
+ else
+ sys->relay_igmp_query.qqic = pkt[shift];
+
+ shift++; assert( shift < RELAY_QUERY_MSG_LEN);
+ memcpy( &sys->relay_igmp_query.nSrc, &pkt[shift], 2 );
+
+ /* if a membership thread exists cancel it */
+ if( sys->threadReady )
+ {
+ msg_Dbg( p_access, "Canceling existing AMT relay membership update thread");
+
+ sys->threadReady = false;
+ vlc_cancel( sys->updateThread );
+ vlc_join( sys->updateThread, NULL );
+ }
+
+ msg_Dbg( p_access, "Spawning AMT relay membership update thread");
+
+ if( vlc_clone( &sys->updateThread, amt_mem_upd, p_access, VLC_THREAD_PRIORITY_LOW) )
+ {
+ msg_Err( p_access, "Could not create AMT relay membership update thread" );
+ return false;
+ }
+
+ /* set theadReady to true, thread will send periodic membership updates until false */
+ sys->threadReady = true;
+ return true;
+}
+
+/**
+ * Join SSM group based on input addresses, or use the defaults
+ * */
+static int amt_joinSSM_group( stream_t *p_access )
+{
+ struct ip_mreq_source imr;
+ access_sys_t *sys = p_access->p_sys;
+
+ imr.imr_multiaddr.s_addr = sys->mcastGroupAddr.sin_addr.s_addr;
+ imr.imr_sourceaddr.s_addr = sys->mcastSrcAddr.sin_addr.s_addr;
+ imr.imr_interface.s_addr = INADDR_ANY;
+
+ return setsockopt( sys->sAMT, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *)&imr, sizeof(imr) );
+}
+
+static int amt_joinASM_group( stream_t *p_access )
+{
+ struct ip_mreq imr;
+ access_sys_t *sys = p_access->p_sys;
+
+ imr.imr_multiaddr.s_addr = sys->mcastGroupAddr.sin_addr.s_addr;
+ imr.imr_interface.s_addr = INADDR_ANY;
+
+ return setsockopt( sys->sAMT, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&imr, sizeof(imr) );
+}
+
+/**
+ * Leave SSM group that was joined earlier.
+ * */
+static int amt_leaveSSM_group( stream_t *p_access )
+{
+ struct ip_mreq_source imr;
+ access_sys_t *sys = p_access->p_sys;
+
+ imr.imr_multiaddr.s_addr = sys->mcastGroupAddr.sin_addr.s_addr;
+ imr.imr_sourceaddr.s_addr = sys->mcastSrcAddr.sin_addr.s_addr;
+ imr.imr_interface.s_addr = INADDR_ANY;
+
+ return setsockopt( sys->sAMT, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, (char *)&imr, sizeof(imr) );
+}
+
+/**
+ * Leave ASM group that was joined earlier.
+ * */
+static int amt_leaveASM_group( stream_t *p_access )
+{
+ struct ip_mreq imr;
+ access_sys_t *sys = p_access->p_sys;
+
+ imr.imr_multiaddr.s_addr = sys->mcastGroupAddr.sin_addr.s_addr;
+ imr.imr_interface.s_addr = INADDR_ANY;
+
+ return setsockopt( sys->sAMT, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *)&imr, sizeof(imr) );
+}
+
+/* A thread is spawned since IGMP membership updates need to issued periodically in order to continue to receive multicast */
+/* Simple timers are not suitable for this requirement since the parent is otherwise occupied in receiving UDP packet flow */
+static void *amt_mem_upd( void *data )
+{
+ stream_t *p_access = (stream_t*) data;
+ access_sys_t *sys = p_access->p_sys;
+
+ msg_Dbg( p_access, "AMT relay membership update thread started" );
+
+ /* thread sends periodic AMT membership updates until Close() which terminates thread */
+ while ( TRUE )
+ {
+ amt_send_mem_update( p_access, sys->relayDisco, false );
+ vlc_tick_sleep( (vlc_tick_t)sys->relay_igmp_query.qqic * CLOCK_FREQ );
+ }
+
+ return NULL;
+}
--
2.20.1 (Apple Git-117)
More information about the vlc-devel
mailing list