[vlc-devel] [PATCH] modules: Created AMT module for multicast streaming.

Natalie Landsberg natalie.landsberg97 at gmail.com
Thu Jul 26 23:45:46 CEST 2018


AMT = Automatic Multicast Tunneling

This module aims to provide access to multicast streams from a
non-multicast enabled network. So, if you have a stream coming
from I2, you should be able to access it from outside of I2 using
this module.

Essentially it creates a tunnel for you to have access to the stream.

It works similarly to udp native multicast streaming:
-provide server address
-provide multicast group address
-(optional) provide AMT relay address

There exists a relay which can be used to test the stream. Can be
provided upon request.
---
 .gitignore                 |   1 +
 .tmp                       |   2 +
 env.build.sh               |   1 +
 modules/access/Makefile.am |   8 +
 modules/access/amt.c       | 969 +++++++++++++++++++++++++++++++++++++++++++++
 modules/access/amt.h       | 217 ++++++++++
 6 files changed, 1198 insertions(+)
 create mode 100755 .tmp
 create mode 120000 env.build.sh
 create mode 100644 modules/access/amt.c
 create mode 100644 modules/access/amt.h

diff --git a/.gitignore b/.gitignore
index 0b0ee9cc20..655e25cad3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,3 +53,4 @@ build*
 contrib-*
 install-*
 
+.vscode*
diff --git a/.tmp b/.tmp
new file mode 100755
index 0000000000..dd3f43ce1f
--- /dev/null
+++ b/.tmp
@@ -0,0 +1,2 @@
+#! /bin/sh
+exec /vlc -I "" "$@"
diff --git a/env.build.sh b/env.build.sh
new file mode 120000
index 0000000000..1c78a0b501
--- /dev/null
+++ b/env.build.sh
@@ -0,0 +1 @@
+/Users/nlandsberg/Desktop/development/vlc-3.0/extras/package/macosx/env.build.sh
\ No newline at end of file
diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am
index c6d66ab066..8f6cf55dc7 100644
--- a/modules/access/Makefile.am
+++ b/modules/access/Makefile.am
@@ -350,6 +350,14 @@ libudp_plugin_la_SOURCES = access/udp.c
 libudp_plugin_la_LIBADD = $(SOCKET_LIBS) $(LIBPTHREAD)
 access_LTLIBRARIES += libudp_plugin.la
 
+libamt_plugin_la_SOURCES = \
+	access/amt.c \
+	access/amt.h
+libamt_plugin_la_LIBADD = $(SOCKET_LIBS) $(LIBPTHREAD)
+libamt_plugin_la_LDFLAGS = $(AM_LD_FLAGS) -rpath '$(accessdir)'
+libamt_plugin_la_CFLAGS = $(AM_CFLAGS)
+access_LTLIBRARIES += libamt_plugin.la
+
 libsftp_plugin_la_SOURCES = access/sftp.c
 libsftp_plugin_la_CFLAGS = $(AM_CFLAGS) $(SFTP_CFLAGS)
 libsftp_plugin_la_LIBADD = $(SFTP_LIBS)
diff --git a/modules/access/amt.c b/modules/access/amt.c
new file mode 100644
index 0000000000..712b8d31da
--- /dev/null
+++ b/modules/access/amt.c
@@ -0,0 +1,969 @@
+/**
+ * @file amt.c
+ * @brief Automatic Multicast Tunneling Protocol (AMT) helper 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) Juniper Networks, Inc., 2018 - 2018. All rights reserved.
+ *
+ * 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.
+ * Authors: Natalie Landsberg <nlandsberg at juniper.net>
+ *
+ * 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>
+#ifdef _WIN32
+# include <winsock2.h>
+#else
+# include <arpa/inet.h>
+#endif
+#include <sys/select.h>
+#include <sys/socket.h>
+
+#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 "amt.h"
+
+#ifdef HAVE_POLL
+ #include <poll.h>
+#endif
+#ifdef HAVE_SYS_UIO_H
+ #include <sys/uio.h>
+#endif
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+#ifndef AMT_PORT
+    #define AMT_PORT 2268 /* IANA */
+#endif
+
+static int  Open( vlc_object_t * );
+static void Close( vlc_object_t * );
+
+#define BUFFER_TEXT N_("Receive buffer")
+#define BUFFER_LONGTEXT N_("AMT receive buffer size (bytes)" )
+#define TIMEOUT_TEXT N_("AMT Source timeout (sec)")
+#define AMT_TIMEOUT_TEXT N_("AMT timeout (sec)")
+
+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 )
+
+    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 the socket
+ *****************************************************************************/
+static int Open( vlc_object_t *p_this )
+{
+    stream_t     *p_access = (stream_t*)p_this;
+    access_sys_t *sys;
+
+    if( p_access->b_preparsing )
+        return VLC_EGENERIC;
+
+    sys = vlc_obj_malloc( p_this, sizeof( *sys ) );
+    if( unlikely( sys == NULL ) )
+        return VLC_ENOMEM;
+
+    p_access->p_sys = sys;
+
+    /* Set up p_access */
+    ACCESS_SET_CALLBACKS( NULL, BlockUDP, Control, NULL );
+
+    char *psz_name = strdup( p_access->psz_location );
+    char *psz_parser;
+    const char *psz_server_addr, *psz_bind_addr, *psz_relay_addr = "";
+    int  i_bind_port = 1234, i_server_port = 0;
+
+    if( unlikely(psz_name == NULL) )
+        return VLC_ENOMEM;
+
+    /* Parse psz_name syntax :
+     * [serveraddr[:serverport]][@[bindaddr]:[bindport]]:[amt=relayip] */
+
+    psz_parser = strchr( psz_name, '@' );
+    if( psz_parser != NULL )
+    {
+        /* Found bind address and/or bind port */
+        *psz_parser++ = '\0';
+        psz_bind_addr = psz_parser;
+
+        if( psz_bind_addr[0] == '[' )
+            /* skips bracket'd IPv6 address */
+            psz_parser = strchr( psz_parser, ']' );
+
+        if( psz_parser != NULL )
+        {
+            psz_parser = strchr( psz_parser, ':' );
+            if( psz_parser != NULL )
+            {
+                *psz_parser++ = '\0';
+                if( psz_parser[0] == 'a' && psz_parser[1] == 'm' && psz_parser[2] == 't')
+                {
+                    psz_parser = strchr( psz_parser, '=' );
+                    if( psz_parser != NULL )
+                    {
+                        *psz_parser++ = '\0';
+                        psz_relay_addr = psz_parser;
+                    }
+                } else
+                    i_bind_port = atoi( psz_parser );
+            }
+        }
+    }
+
+    psz_server_addr = psz_name;
+    psz_parser = ( psz_server_addr[0] == '[' )
+        ? strchr( psz_name, ']' ) /* skips bracket'd IPv6 address */
+        : psz_name;
+
+    if( psz_parser != NULL )
+    {
+        psz_parser = strchr( psz_parser, ':' );
+        if( psz_parser != NULL )
+        {
+            *psz_parser++ = '\0';
+            i_server_port = atoi( psz_parser );
+        }
+    }
+
+    msg_Dbg( p_access, "opening server=%s:%d local=%s:%d relay addr=%s",
+             psz_server_addr, i_server_port, psz_bind_addr, i_bind_port, psz_relay_addr );
+
+    if( strlen( psz_bind_addr ) > 0 )
+    {
+        sys->mcastGroup = malloc( strlen( psz_bind_addr ) );
+        memset( sys->mcastGroup, 0, strlen(psz_bind_addr) );
+        memcpy( sys->mcastGroup, psz_bind_addr, strlen( psz_bind_addr ) );
+
+        bool done = false;
+        while( !done ) {
+            if( !isdigit( (int)( sys->mcastGroup[ strlen( sys->mcastGroup ) - 1 ] ) ) ||
+                strlen( psz_bind_addr ) < strlen( sys->mcastGroup - 1) ) {
+                sys->mcastGroup[ strlen( sys->mcastGroup ) - 1] = '\0';
+            } else {
+                done = true;
+            }
+        }
+    }
+
+    if( strlen( psz_server_addr) > 0 )
+    {
+        sys->srcAddr = malloc( strlen( psz_server_addr ) );
+        memset(sys->srcAddr, 0, strlen(psz_server_addr) );
+        memcpy( sys->srcAddr, psz_server_addr, strlen( psz_server_addr ) );
+    }
+
+    if( strlen( psz_relay_addr ) > 0 )
+    {
+        sys->relayAddr = malloc( strlen( psz_relay_addr ) );
+        memset(sys->relayAddr, 0, strlen(psz_relay_addr) );
+        memcpy( sys->relayAddr, psz_relay_addr, strlen( psz_server_addr ) );
+    } else {
+        sys->relayAddr = malloc( sizeof( "198.38.23.145" ) );
+        memset(sys->relayAddr, 0, sizeof( "198.38.23.145" ) );
+        memcpy( sys->relayAddr, "198.38.23.145", sizeof( "198.38.23.145" ) );
+    }
+
+    msg_Dbg( p_access, "Addresses: mcastGroup: %s srcAddr: %s relayAddr: %s", \
+                sys->mcastGroup, sys->srcAddr, sys->relayAddr);
+
+    sys->fd = net_OpenDgram( p_access, psz_bind_addr, i_bind_port,
+                             psz_server_addr, i_server_port, IPPROTO_UDP );
+    free( psz_name );
+    if( sys->fd == -1 )
+    {
+        msg_Err( p_access, "cannot open socket" );
+        return VLC_EGENERIC;
+    }
+
+    sys->mtu = 1500;
+
+    sys->timeout = var_InheritInteger( p_access, "udp-timeout");
+    if( sys->timeout > 0)
+        sys->timeout *= 1000;
+
+    sys->amtTimeout = var_InheritInteger( p_access, "amt-timeout" );
+    if( sys->amtTimeout > 0)
+        sys->amtTimeout *= 1000;
+
+    sys->triedAMT = false;
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Close: free unused 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;
+
+    amt_leaveSSM_group( sys, p_access );
+    amt_send_mem_update( sys, p_access, true );
+
+    net_Close( sys->fd );
+    net_Close( sys->sAMT );
+    net_Close( sys->sQuery );
+
+    free( sys->mcastGroup );
+    free( sys->srcAddr );
+    free( sys->relayAddr);
+}
+
+/*****************************************************************************
+ * Control:
+ *****************************************************************************/
+static int Control( stream_t *p_access, int i_query, va_list args )
+{
+    bool    *pb_bool;
+    int64_t *pi_64;
+
+    switch( i_query )
+    {
+        case STREAM_CAN_SEEK:
+        case STREAM_CAN_FASTSEEK:
+        case STREAM_CAN_PAUSE:
+        case STREAM_CAN_CONTROL_PACE:
+            pb_bool = va_arg( args, bool * );
+            *pb_bool = false;
+            break;
+
+        case STREAM_GET_PTS_DELAY:
+            pi_64 = va_arg( args, int64_t * );
+            *pi_64 = INT64_C(1000)
+                   * var_InheritInteger(p_access, "network-caching");
+            break;
+
+        default:
+            return VLC_EGENERIC;
+    }
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * BlockUDP:
+ *****************************************************************************/
+static block_t *BlockUDP(stream_t *access, bool *restrict eof)
+{
+    access_sys_t *sys = access->p_sys;
+
+    block_t *pkt = block_Alloc(sys->mtu);
+    if (unlikely(pkt == NULL))
+    {   /* OOM - dequeue and discard one packet */
+        char dummy;
+        recv(sys->fd, &dummy, 1, 0);
+        return NULL;
+    }
+
+    struct iovec iov = {
+        .iov_base = pkt->p_buffer,
+        .iov_len  = sys->mtu,
+    };
+    struct msghdr msg = {
+        .msg_iov = &iov,
+        .msg_iovlen = 1,
+#ifdef __linux__
+        .msg_flags = MSG_TRUNC,
+#endif
+    };
+
+    struct pollfd ufd[1];
+
+    if( sys->triedAMT )
+        ufd[0].fd = sys->sAMT;
+    else
+        ufd[0].fd = sys->fd;
+    ufd[0].events = POLLIN;
+
+    switch (vlc_poll_i11e(ufd, 1, sys->timeout))
+    {
+        case 0:
+            msg_Err(access, "receive time-out");
+            if( !sys->triedAMT )
+            {
+                msg_Dbg( access, "Attempting AMT...");
+                sys->triedAMT = true;
+
+                int init = amt_sockets_init( sys, access );
+                if( init != 0 )
+                {
+                    msg_Err( access, "Error initializing sockets" );
+                    goto skip;
+                }
+
+                amt_send_relay_discovery_msg( sys, access );
+                msg_Dbg( access, "Sent AMT discovery message" );
+                if( amt_rcv_relay_adv( sys, access ) )
+                {
+                    msg_Dbg( access, "Received relay adv" );
+                    amt_send_relay_request( sys, access );
+                } else
+                {
+                    msg_Err( access, "Error receiving relay adv msg, skipping" );
+                    goto skip;
+                }
+
+                if( amt_rcv_relay_mem_query( sys, access ) )
+                {
+                    msg_Dbg( access, "Received mem query" );
+                    if( amt_joinSSM_group( sys, access ) != 0 )
+                    {
+                        msg_Err(access, "Error joining SSM %s", strerror(errno));
+                        goto skip;
+                    }
+                    msg_Dbg( access, "Joined SSM group" );
+                    amt_send_mem_update( sys, access, false );
+                }
+
+                sys->queryTime = time( NULL );
+            }
+            else
+            {
+                msg_Err(access, "AMT receive time-out");
+                *eof = true;
+            }
+            /* fall through */
+        case -1:
+            goto skip;
+     }
+
+    ssize_t len;
+    if( sys->triedAMT )
+    {
+        if( (time(NULL) - sys->queryTime) > sys->relay_igmp_query.qqic )
+        {
+            amt_send_mem_update( sys, access, false );
+            sys->queryTime = time(NULL);
+        }
+
+        char amtpkt[sys->mtu];
+        struct sockaddr tmp;
+        u32 tmp_size = sizeof( struct sockaddr );
+        len = recvfrom( sys->sAMT, &amtpkt, sizeof( amtpkt ), 0, &tmp, &tmp_size );
+
+        if( len < 0 )
+            goto skip;
+
+        if( amtpkt[0] != AMT_MULT_DATA )
+        {
+            msg_Dbg( access, "Not AMT multicast data, dropping.");
+            goto skip;
+        }
+
+        amt_multicast_data_t *temp = malloc( sizeof( amt_multicast_data_t ) );
+        memset(temp, 0, sizeof( amt_multicast_data_t ) );
+        temp->type = amtpkt[0];
+        temp->resv = amtpkt[1];
+        memcpy( (void *)&temp->ip, &amtpkt[AMT_HDR_LEN], IP_HDR_LEN );
+        memcpy( (void *)&temp->udp, &amtpkt[IP_HDR_LEN + AMT_HDR_LEN], UDP_HDR_LEN );
+
+        ssize_t shift = IP_HDR_LEN + UDP_HDR_LEN + AMT_HDR_LEN;
+        temp->buf = malloc( (len - shift) );
+        memcpy( temp->buf, &amtpkt[shift], (len - shift) );
+
+        if( len < shift )
+        {
+            msg_Err(access, "%zd bytes packet truncated (MTU was %zu)",
+                len, sys->mtu);
+            pkt->i_flags |= BLOCK_FLAG_CORRUPTED;
+            sys->mtu = len;
+        } else {
+            len -= shift;
+        }
+
+        memcpy( pkt->p_buffer, &amtpkt[shift], len );
+        free( temp->buf );
+        free( temp );
+
+    }
+    else
+        len = recvmsg(sys->fd, &msg, 0);
+
+    if (len < 0)
+    {
+skip:
+        block_Release(pkt);
+        return NULL;
+    }
+
+#ifdef MSG_TRUNC
+    if (msg.msg_flags & MSG_TRUNC)
+    {
+        msg_Err(access, "%zd bytes packet truncated (MTU was %zu)",
+                len, sys->mtu);
+        pkt->i_flags |= BLOCK_FLAG_CORRUPTED;
+        sys->mtu = len;
+    }
+    else
+#endif
+        pkt->i_buffer = len;
+
+    return pkt;
+}
+
+/**
+ * Calculate checksum
+ * */
+u16 getChecksum( u16 *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 Membershipt report
+ * */
+void makeReport( 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
+ * */
+void makeIPHeader( amt_ip_alert_t *p_ipHead )
+{
+    p_ipHead->ver_ihl = 0x46;
+    p_ipHead->tos = 0xc0;
+    p_ipHead->tot_len = htons( IP_HDR_IGMP_LEN + sizeof(amt_igmpv3_membership_report_t) );
+    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
+ */
+int amt_sockets_init( access_sys_t *sys, stream_t *p_access )
+{
+    struct sockaddr_in rcvAddr;
+    memset( &rcvAddr, 0, sizeof(struct sockaddr_in) );
+    int enable = 1;
+
+    /* Relay anycast address for discovery */
+    sys->relayAddrDisco.sin_family = AF_INET;
+    sys->relayAddrDisco.sin_port = htons( AMT_PORT );
+    int res = inet_pton( AF_INET, sys->relayAddr, &sys->relayAddrDisco.sin_addr );
+    if( res != 1 )
+        msg_Err( p_access, "Could not convert relay anycast" );
+
+    /* 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" );
+        return -1;
+    }
+
+    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", strerror(errno));
+        return -1;
+    }
+
+    res = setsockopt(sys->sAMT, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
+    if(res < 0)
+    {
+        msg_Err( p_access, "Couldn't make socket reusable");
+        return -1;
+    }
+
+    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", strerror(errno) );
+        return -1;
+    }
+
+    sys->sQuery = vlc_socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP, true );
+    if( sys->sQuery == -1 )
+    {
+        msg_Err( p_access, "Failed to create query socket" );
+        return -1;
+    }
+
+    /* 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" );
+        return -1;
+    }
+
+    sys->stSvrAddr.sin_family        = AF_INET;
+    sys->stSvrAddr.sin_port          = htons( 9124 );
+    res = inet_aton( "127.0.0.1", &sys->stSvrAddr.sin_addr );
+    if( res == 0 )
+    {
+        msg_Err( p_access, "Could not convert loopback address" );
+        return -1;
+    }
+
+    return 0;
+}
+
+/**
+ * Send a relay discovery message, before 3-way handshake
+ * */
+void amt_send_relay_discovery_msg( access_sys_t *sys, stream_t *p_access )
+{
+    char          chaSendBuffer[AMT_DISCO_MSG_LEN];
+	unsigned int  ulNonce;
+	int           nRet;
+
+    /* 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( (void*)&chaSendBuffer[4], (void*)&ulNonce, sizeof(ulNonce) );
+    sys->glob_ulNonce = ulNonce;
+
+    /* send it */
+    nRet = sendto( sys->sAMT, chaSendBuffer, sizeof(chaSendBuffer), 0,\
+            (struct sockaddr *)&sys->relayAddrDisco, sizeof(struct sockaddr) );
+
+    if( nRet < 0)
+    {
+        msg_Err( p_access, "Sendto failed with error %d.", errno);
+        return;
+    }
+
+}
+
+/**
+ * Send relay request message, stage 2 of handshake
+ * */
+void amt_send_relay_request( access_sys_t *sys, stream_t *p_access )
+{
+    char chaSendBuffer[AMT_REQUEST_MSG_LEN];
+    unsigned int ulNonce;
+    int nRet;
+    int nRetry;
+
+    memset( chaSendBuffer, 0, sizeof(chaSendBuffer) );
+
+    ulNonce = 0;
+    nRet = 0;
+    nRetry = 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( (void*)&chaSendBuffer[4], (void*)&ulNonce, sizeof(unsigned int) );
+
+    nRet = send( sys->sAMT, chaSendBuffer, sizeof(chaSendBuffer), 0 );
+
+    if( nRet < 0 )
+    {
+        msg_Err(p_access, "Error sending relay request error: %s", strerror(errno) );
+        return;
+    }
+}
+
+/*
+* create AMT request message format
+* +----------------------------------------------------------------------------------+
+* | Msg Type(1 byte)| Reserved (1 byte)| MAC (6 byte)| nonce (4 byte) | IGMP packet  |
+* +----------------------------------------------------------------------------------+
+*/
+ void amt_send_mem_update( access_sys_t *sys, stream_t *p_access, bool leave)
+{
+    int sendBufSize = IP_HDR_IGMP_LEN + MAC_LEN + NONCE_LEN + AMT_HDR_LEN;
+    char pSendBuffer[ sendBufSize + (int)sizeof(amt_igmpv3_membership_report_t)];
+    unsigned int ulNonce = 0;
+    int nRet = 0;
+    memset( &pSendBuffer, 0, sizeof(pSendBuffer) );
+
+    pSendBuffer[0] = AMT_MEM_UPD;
+
+    /* copy relay MAC response */
+    memcpy( (void*)&pSendBuffer[2], (void*)sys->relay_mem_query_msg.uchaMAC, MAC_LEN );
+
+    /* copy nonce */
+    ulNonce = ntohl(sys->glob_ulNonce);
+    memcpy( (void*)&pSendBuffer[8], (void*)&ulNonce, NONCE_LEN );
+
+    /* make IP header for IGMP packet */
+    amt_ip_alert_t p_ipHead;
+    memset( &p_ipHead, 0, IP_HDR_IGMP_LEN );
+    makeIPHeader( &p_ipHead );
+
+    struct sockaddr_in temp;
+    if( inet_pton( AF_INET, MCAST_ALLHOSTS, &(temp.sin_addr) ) != 1 )
+    {
+        msg_Err( p_access, "Error converting %s", MCAST_ALLHOSTS );
+    }
+    p_ipHead.destAddr = temp.sin_addr.s_addr;
+    p_ipHead.check = getChecksum( (u16*)&p_ipHead, (int)sizeof(amt_ip_alert_t) );
+
+    amt_igmpv3_groupRecord_t groupRcd;
+    groupRcd.type = leave ? AMT_IGMP_BLOCK:AMT_IGMP_ALLOW;
+    groupRcd.auxDatalen = 0;
+    groupRcd.nSrc = htons(1);
+
+    if( inet_pton( AF_INET, sys->mcastGroup, &(temp.sin_addr) ) != 1 )
+    {
+        msg_Err( p_access, "Error converting multicast group address" );
+    }
+    groupRcd.ssm = temp.sin_addr.s_addr;
+
+    if( inet_pton( AF_INET, sys->srcAddr, &(temp.sin_addr) ) != 1 )
+    {
+        msg_Err( p_access, "Error converting src address" );
+    }
+    groupRcd.srcIP[0] = temp.sin_addr.s_addr;
+
+    /* make IGMP membership report */
+    amt_igmpv3_membership_report_t p_igmpMemRep;
+    makeReport( &p_igmpMemRep );
+
+    memcpy((void *)&p_igmpMemRep.grp[0], (void *)&groupRcd, (int)sizeof(groupRcd) );
+    p_igmpMemRep.checksum = getChecksum( (u16*)&p_igmpMemRep, (int)sizeof(amt_igmpv3_membership_report_t) );
+
+    amt_membership_update_msg_t memUpdateMsg;
+    memset((void*)&memUpdateMsg, 0, sizeof(memUpdateMsg));
+    memcpy((void*)&memUpdateMsg.ipHead, (void*)&p_ipHead, sizeof(p_ipHead) );
+    memcpy((void*)&memUpdateMsg.memReport, (void*)&p_igmpMemRep, sizeof(p_igmpMemRep) );
+
+    memcpy( (void*)&pSendBuffer[12], (void*)&memUpdateMsg, sizeof(memUpdateMsg) );
+
+    nRet = send( sys->sAMT, pSendBuffer, sizeof(pSendBuffer), 0 );
+
+    if( nRet < 0 )
+    {
+        msg_Err(p_access, "Error sending membership update error: %s", strerror(errno) );
+        return;
+    }
+}
+
+/**
+ * Receive relay advertisement message
+ *
+ *
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |  V=0  |Type=2 |                   Reserved                    |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |                        Discovery Nonce                        |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |                                                               |
+ *  ~                  Relay Address (IPv4 or IPv6)                 ~
+ *  |                                                               |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * */
+bool amt_rcv_relay_adv( access_sys_t *sys, stream_t *p_access )
+{
+    char pkt[RELAY_ADV_MSG_LEN];
+    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, "relay adv receive time-out");
+            /* fall through */
+        case -1:
+            goto skip;
+    }
+
+    struct sockaddr temp;
+    u32 temp_size = sizeof( struct sockaddr );
+    ssize_t len = recvfrom( sys->sAMT, &pkt, RELAY_ADV_MSG_LEN, 0, &temp, &temp_size );
+
+    if (len < 0)
+    {
+        skip:
+        msg_Err(p_access, "length less than zero");
+        return false;
+    }
+
+    memcpy( (void*)&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 relay advertisement, ignoring. ");
+        return false;
+    }
+
+    memcpy( (void*)&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, ntohl(sys->relay_adv_msg.ulRcvNonce) );
+        return false;
+    }
+
+    memcpy( (void*)&sys->relay_adv_msg.ipAddr, &pkt[8], 4 );
+
+    memset( &sys->relayAddress, 0, sizeof(sys->relayAddress) );
+    sys->relayAddress.sin_family       = AF_INET;
+    sys->relayAddress.sin_addr.s_addr  = sys->relay_adv_msg.ipAddr;
+    sys->relayAddress.sin_port         = htons( AMT_PORT );
+
+    int nRet = connect( sys->sAMT, (struct sockaddr *)&sys->relayAddress, sizeof(sys->relayAddress) );
+    if( nRet < 0 )
+    {
+        msg_Err( p_access, "Error connecting AMT UDP socket: %s", 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)              |
+   +                                                               +
+   |                                                               |
+   +                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                               |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+bool amt_rcv_relay_mem_query( access_sys_t *sys, stream_t *p_access )
+{
+    char pkt[RELAY_QUERY_MSG_LEN];
+    memset( &pkt, 0, RELAY_QUERY_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, "mem query receive time-out");
+            /* fall through */
+        case -1:
+            goto skip;
+    }
+
+    struct sockaddr tmp;
+    u32 tmp_size = sizeof( struct sockaddr );
+    ssize_t len = recvfrom( sys->sAMT, &pkt, RELAY_QUERY_MSG_LEN, 0, &tmp, &tmp_size );
+
+    if (len < 0)
+    {
+        skip:
+        msg_Err(p_access, "length less than zero");
+        return false;
+    }
+
+    memcpy( (void*)&sys->relay_mem_query_msg.type, &pkt[0], MSG_TYPE_LEN );
+    /* pkt[1] is reserved  */
+    memcpy( (void*)&sys->relay_mem_query_msg.uchaMAC[0], &pkt[AMT_HDR_LEN], MAC_LEN );
+    memcpy( (void*)&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 = ntohl(sys->relay_mem_query_msg.ulRcvedNonce);
+
+    int shift = AMT_HDR_LEN + MAC_LEN + NONCE_LEN;
+    memcpy( (void*)&sys->relay_ip_hdr, (void*)&pkt[shift], IP_HDR_IGMP_LEN );
+
+    shift += IP_HDR_IGMP_LEN;
+    sys->relay_igmp_query.type = pkt[shift];
+    shift++;
+    sys->relay_igmp_query.max_resp_code = pkt[shift];
+    shift++;
+    memcpy( (void*)&sys->relay_igmp_query.checksum, &pkt[shift], 2 );
+    shift += 2;
+    memcpy( (void*)&sys->relay_igmp_query.ssmIP, &pkt[shift], 4 );
+    shift += 4;
+    sys->relay_igmp_query.s_qrv = pkt[shift];
+    shift++;
+    if( (int)pkt[shift] == 0 )
+        sys->relay_igmp_query.qqic = 125;
+    else if( (int)pkt[shift] < 128 )
+        sys->relay_igmp_query.qqic = pkt[shift];
+    else {
+        int qqic;
+        qqic = ((pkt[shift]&0x0f) + 0x10) << (((pkt[shift] >> 4)&0x07) + 3);
+        sys->relay_igmp_query.qqic = qqic;
+    }
+
+    shift++;
+    memcpy( (void*)&sys->relay_igmp_query.nSrc, &pkt[shift], 2 );
+    shift++;
+    memcpy( (void*)&sys->relay_igmp_query.srcIP, &pkt[shift], 4 );
+
+    return true;
+}
+
+/**
+ * Join SSM group based on input addresses, or use the defaults
+ * */
+int amt_joinSSM_group( access_sys_t *sys, stream_t *p_access )
+{
+    struct ip_mreq_source imr;
+    struct sockaddr_in temp;
+    if( inet_pton( AF_INET, sys->mcastGroup, &(temp.sin_addr.s_addr) ) != 1)
+    {
+        msg_Err( p_access, "Error converting mcast group" );
+    }
+    imr.imr_multiaddr.s_addr = temp.sin_addr.s_addr;
+
+    inet_pton( AF_INET, sys->srcAddr, &(temp.sin_addr.s_addr) );
+    imr.imr_sourceaddr.s_addr = temp.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) );
+}
+
+/**
+ * Leave SSM group that was joined earlier.
+ * */
+int amt_leaveSSM_group( access_sys_t *sys, stream_t *p_access )
+{
+    struct ip_mreq_source imr;
+    struct sockaddr_in temp;
+    if( inet_pton( AF_INET, sys->mcastGroup, &(temp.sin_addr.s_addr) ) != 1)
+    {
+        msg_Err( p_access, "Error converting mcast group" );
+    }
+    imr.imr_multiaddr.s_addr = temp.sin_addr.s_addr;
+
+    inet_pton( AF_INET, sys->srcAddr, &(temp.sin_addr.s_addr) );
+    imr.imr_sourceaddr.s_addr = temp.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) );
+}
diff --git a/modules/access/amt.h b/modules/access/amt.h
new file mode 100644
index 0000000000..924112f78b
--- /dev/null
+++ b/modules/access/amt.h
@@ -0,0 +1,217 @@
+/*****************************************************************************
+ * @file amt.h
+ * @brief AMT access module structs
+ *
+ * Some of this code was pulled from or based off Cisco's open-source
+ * SSMAMTtools repo.
+ * Copyright (c) Juniper Networks, Inc., 2018 - 2018. All rights reserved.
+ *
+ * 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.
+ *
+ * Authors: Natalie Landsberg <nlandsberg at juniper.net>
+ *
+ * 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
+ ****************************************************************************/
+
+/*****************************************************************************
+ * 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 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_ALLOW 0x05
+#define AMT_IGMP_BLOCK 0x06
+
+#define MCAST_ALLHOSTS "224.0.0.1"
+
+typedef struct access_sys_t access_sys_t;
+
+typedef char 		    s8;
+typedef unsigned char 	u8;
+typedef short 		    s16;
+typedef unsigned short 	u16;
+typedef int 		    s32;
+typedef unsigned int 	u32;
+
+typedef struct _amt_ip {
+    u8  ver_ihl;
+    u8	tos;
+    u16 tot_len;
+    u16 id;
+    u16 frag_off;
+    u8  ttl;
+    u8  protocol;
+    u16 check;
+    u32 srcAddr;
+    u32 destAddr;
+} amt_ip_t;
+
+typedef struct _amt_ip_alert {
+    u8  ver_ihl;
+    u8	tos;
+    u16 tot_len;
+    u16 id;
+    u16 frag_off;
+    u8  ttl;
+    u8  protocol;
+    u16 check;
+    u32 srcAddr;
+    u32 destAddr;
+    u32 options;
+} amt_ip_alert_t;
+
+typedef struct _amt_igmpv3_groupRecord {
+    u8 	type;
+    u8 	auxDatalen;
+    u16 nSrc;
+    u32 ssm;
+    u32 srcIP[1];
+} amt_igmpv3_groupRecord_t;
+
+typedef struct _amt_igmpv3_membership_report {
+    u8  type;
+    u8  resv;
+    u16 checksum;
+    u16 resv2;
+    u16 nGroupRecord;
+    amt_igmpv3_groupRecord_t grp[1];
+} amt_igmpv3_membership_report_t;
+
+typedef struct _amt_igmpv3_membership_query {
+    u8  type;
+    u8  max_resp_code;  /* in 100ms, Max Resp Time = (mant | 0x10) << (exp + 3) */
+    u16 checksum;
+    u32 ssmIP;
+    u8  s_qrv;
+    u8  qqic;           /* in second, query Time = (mant | 0x10) << (exp + 3) */
+    u16 nSrc;
+    u32 srcIP[1];
+} amt_igmpv3_membership_query_t;
+
+typedef struct _amt_membership_update_msg {
+    amt_ip_alert_t ipHead;
+    amt_igmpv3_membership_report_t memReport;
+} amt_membership_update_msg_t;
+
+typedef struct _amt_udpHdr {
+    u16 srcPort;
+    u16 dstPort;
+    u16 len;
+    u16 check;
+} amt_udpHdr_t;
+
+typedef struct _amt_multicast_data {
+    u8       		type;
+    u8  		    resv;
+    amt_ip_t 		ip;
+    amt_udpHdr_t    udp;
+    u8  		    *buf;
+} amt_multicast_data_t;
+
+typedef struct _argsStruct {
+    access_sys_t *sys;
+    stream_t *p_access;
+} argsStruct;
+
+/* AMT Functions */
+int amt_sockets_init( access_sys_t *sys, stream_t *p_access );
+void amt_send_relay_discovery_msg( access_sys_t *sys, stream_t *p_access );
+void amt_send_relay_request( access_sys_t *sys, stream_t *p_access );
+int amt_joinSSM_group( access_sys_t *sys, stream_t *p_access );
+int amt_leaveSSM_group( access_sys_t *sys, stream_t *p_access );
+bool amt_rcv_relay_adv( access_sys_t *sys, stream_t *p_access );
+bool amt_rcv_relay_mem_query( access_sys_t *sys, stream_t *p_access );
+void amt_send_mem_update( access_sys_t *sys, stream_t *p_access, bool leave );
+
+/* Utility functions */
+u16 getChecksum( u16 *buffer, int nLen );
+void makeReport( amt_igmpv3_membership_report_t *mr );
+void makeIPHeader( amt_ip_alert_t *p_ipHead );
+
+struct access_sys_t
+{
+    int fd;
+    int sAMT;
+    int sQuery;
+    int timeout;
+    int amtTimeout;
+    size_t mtu;
+    bool triedAMT;
+
+    time_t queryTime;
+
+    char *mcastGroup;
+    char *srcAddr;
+    char *relayAddr;
+
+    struct sockaddr_in relayAddrDisco;
+    struct sockaddr_in relayAddress;
+    struct sockaddr_in stLocalAddr;
+    struct sockaddr_in stSvrAddr;
+
+    u32 glob_ulNonce;
+    u32 ulRelayNonce;
+
+    struct relay_mem_query_msg_t {
+        u8 type;
+        u32 ulRcvedNonce;
+        u8 uchaMAC[MAC_LEN];
+        u8 uchaIGMP[IGMP_QUERY_LEN];
+    } relay_mem_query_msg;
+
+    struct relay_adv_msg_t {
+        u8 type;
+        u32 ulRcvNonce;
+        u32 ipAddr;
+    } relay_adv_msg;
+
+    amt_ip_t relay_ip_hdr;
+    amt_igmpv3_membership_query_t relay_igmp_query;
+};
-- 
2.14.3 (Apple Git-98)




More information about the vlc-devel mailing list