3 * @brief Automatic Multicast Tunneling Protocol (AMT) file for VLC media player
4 * Allows multicast streaming when not in a multicast-enabled network
5 * Currently IPv4 is supported, but IPv6 is not yet.
7 * Copyright (C) 2018 VLC authors and VideoLAN
8 * Copyright (c) Juniper Networks, Inc., 2018. All rights reserved.
10 * Authors: Christophe Massiot <massiot@via.ecp.fr> - original UDP code
11 * Tristan Leteurtre <tooney@via.ecp.fr> - original UDP code
12 * Laurent Aimar <fenrir@via.ecp.fr> - original UDP code
13 * Jean-Paul Saman <jpsaman #_at_# m2x dot nl> - original UDP code
14 * Remi Denis-Courmont - original UDP code
15 * Natalie Landsberg <natalie.landsberg97@gmail.com> - AMT support
16 * Wayne Brassem <wbrassem@rogers.com> - Added FQDN support
18 * This code is licensed to you under the GNU Lesser General Public License
19 * version 2.1 or later. You may not use this code except in compliance with
20 * the GNU Lesser General Public License.
21 * This code is not an official Juniper product.
23 * This library is free software; you can redistribute it and/or
24 * modify it under the terms of the GNU Lesser General Public License
25 * as published by the Free Software Foundation; either version 2.1
26 * of the License, or (at your option) any later version.
28 * This library is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU Lesser General Public License for more details.
33 * You should have received a copy of the GNU Lesser General Public
34 * License along with this library; if not, write to the Free Software
35 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
36 ****************************************************************************/
44 #ifdef HAVE_ARPA_INET_H
45 # include <arpa/inet.h>
48 #ifdef HAVE_SYS_SOCKET_H
49 # include <sys/socket.h>
52 #include <vlc_common.h>
53 #include <vlc_demux.h>
54 #include <vlc_plugin.h>
55 #include <vlc_access.h>
56 #include <vlc_network.h>
57 #include <vlc_block.h>
58 #include <vlc_interrupt.h>
68 #define BUFFER_TEXT N_("Receive buffer")
69 #define BUFFER_LONGTEXT N_("AMT receive buffer size (bytes)" )
70 #define TIMEOUT_TEXT N_("Native multicast timeout (sec)")
71 #define AMT_TIMEOUT_TEXT N_("AMT timeout (sec)")
72 #define AMT_RELAY_ADDRESS N_("AMT relay (IP address or FQDN)")
73 #define AMT_RELAY_ADDR_LONG N_("AMT relay anycast address, or specify the relay you want by address or fully qualified domain name")
74 #define AMT_DEFAULT_RELAY N_("amt-relay.m2icast.net")
76 /*****************************************************************************
77 * Various Lengths of Msgs or Hdrs
78 *****************************************************************************/
79 #define MAC_LEN 6 /* length of generated MAC in bytes */
80 #define NONCE_LEN 4 /* length of nonce in bytes */
82 #define MSG_TYPE_LEN 1 /* length of msg type */
83 #define RELAY_QUERY_MSG_LEN 48 /* total length of relay query */
84 #define RELAY_ADV_MSG_LEN 12 /* length of relay advertisement message */
85 #define IGMP_QUERY_LEN 24 /* length of encapsulated IGMP query message */
86 #define IGMP_REPORT_LEN 20
87 #define AMT_HDR_LEN 2 /* length of AMT header on a packet */
88 #define IP_HDR_LEN 20 /* length of standard IP header */
89 #define IP_HDR_IGMP_LEN 24 /* length of IP header with an IGMP report */
90 #define UDP_HDR_LEN 8 /* length of standard UDP header */
91 #define AMT_REQUEST_MSG_LEN 9
92 #define AMT_DISCO_MSG_LEN 8
94 /*****************************************************************************
95 * Different AMT Message Types
96 *****************************************************************************/
97 #define AMT_RELAY_DISCO 1 /* relay discovery */
98 #define AMT_RELAY_ADV 2 /* relay advertisement */
99 #define AMT_REQUEST 3 /* request */
100 #define AMT_MEM_QUERY 4 /* membership query */
101 #define AMT_MEM_UPD 5 /* membership update */
102 #define AMT_MULT_DATA 6 /* multicast data */
103 #define AMT_TEARDOWN 7 /* teardown (not currently supported) */
105 /*****************************************************************************
106 * Different IGMP Message Types
107 *****************************************************************************/
108 #define AMT_IGMPV3_MEMBERSHIP_QUERY_TYPEID 0x11
109 #define AMT_IGMPV3_MEMBERSHIP_REPORT_TYPEID 0x22
110 /* IGMPv2, interoperability */
111 #define AMT_IGMPV1_MEMBERSHIP_REPORT_TYPEID 0x12
112 #define AMT_IGMPV2_MEMBERSHIP_REPORT_TYPEID 0x16
113 #define AMT_IGMPV2_MEMBERSHIP_LEAVE_TYPEID 0x17
115 #define AMT_IGMP_INCLUDE 0x01
116 #define AMT_IGMP_EXCLUDE 0x02
117 #define AMT_IGMP_INCLUDE_CHANGE 0x03
118 #define AMT_IGMP_EXCLUDE_CHANGE 0x04
119 #define AMT_IGMP_ALLOW 0x05
120 #define AMT_IGMP_BLOCK 0x06
122 #define MCAST_ANYCAST "0.0.0.0"
123 #define MCAST_ALLHOSTS "224.0.0.22"
124 #define LOCAL_LOOPBACK "127.0.0.1"
125 #define AMT_PORT 2268
127 #define DEFAULT_MTU (1500u - (20 + 8))
129 /* IPv4 Header Format */
130 typedef struct _amt_ip
{
143 /* IPv4 Header Format with options field */
144 typedef struct _amt_ip_alert
{
158 /* IGMPv3 Group Record Format (RFC3376) */
159 typedef struct _amt_igmpv3_groupRecord
{
165 } amt_igmpv3_groupRecord_t
;
167 /* IGMPv3 Membership Report Format (RFC3376) */
168 typedef struct _amt_igmpv3_membership_report
{
173 uint16_t nGroupRecord
;
174 amt_igmpv3_groupRecord_t grp
[1];
175 } amt_igmpv3_membership_report_t
;
177 /* IGMPv3 Membership Query Format (RFC3376) */
178 typedef struct _amt_igmpv3_membership_query
{
180 uint8_t max_resp_code
; /* in 100ms, Max Resp Time = (mant | 0x10) << (exp + 3) */
184 uint8_t qqic
; /* in second, query Time = (mant | 0x10) << (exp + 3) */
187 } amt_igmpv3_membership_query_t
;
189 /* ATM Membership Update Format (RFC7450) */
190 typedef struct _amt_membership_update_msg
{
191 amt_ip_alert_t ipHead
;
192 amt_igmpv3_membership_report_t memReport
;
193 } amt_membership_update_msg_t
;
196 static int amt_sockets_init( stream_t
*p_access
);
197 static void amt_send_relay_discovery_msg( stream_t
*p_access
, char *relay_ip
);
198 static void amt_send_relay_request( stream_t
*p_access
, char *relay_ip
);
199 static int amt_joinSSM_group( stream_t
*p_access
);
200 static int amt_joinASM_group( stream_t
*p_access
);
201 static int amt_leaveASM_group( stream_t
*p_access
);
202 static int amt_leaveSSM_group( stream_t
*p_access
);
203 static bool amt_rcv_relay_adv( stream_t
*p_access
);
204 static bool amt_rcv_relay_mem_query( stream_t
*p_access
);
205 static void amt_send_mem_update( stream_t
*p_access
, char *relay_ip
, bool leave
);
206 static bool open_amt_tunnel( stream_t
*p_access
);
207 static void amt_update_timer_cb( void *data
);
209 /* Struct to hold AMT state */
210 typedef struct _access_sys_t
213 char relayDisco
[INET_ADDRSTRLEN
];
215 vlc_timer_t updateTimer
;
217 /* Mulicast group and source */
218 struct sockaddr_in mcastGroupAddr
;
219 struct sockaddr_in mcastSrcAddr
;
221 /* AMT relay imformation */
222 struct sockaddr_in relayDiscoAddr
;
224 /* AMT Relay Membership Query data (RFC7450) */
225 struct relay_mem_query_msg_t
{
226 uint32_t ulRcvedNonce
;
228 uint8_t uchaMAC
[MAC_LEN
];
229 uint8_t uchaIGMP
[IGMP_QUERY_LEN
];
230 } relay_mem_query_msg
;
232 amt_igmpv3_membership_query_t relay_igmp_query
;
235 uint32_t glob_ulNonce
;
245 /* Standard open/close functions */
246 static int Open (vlc_object_t
*);
247 static void Close (vlc_object_t
*);
249 /* Utility functions */
250 static unsigned short get_checksum( unsigned short *buffer
, int nLen
);
251 static void make_report( amt_igmpv3_membership_report_t
*mr
);
252 static void make_ip_header( amt_ip_alert_t
*p_ipHead
);
255 set_shortname( N_("AMT" ) )
256 set_description( N_("AMT input") )
257 set_category( CAT_INPUT
)
258 set_subcategory( SUBCAT_INPUT_ACCESS
)
260 add_integer( "amt-timeout", 5, AMT_TIMEOUT_TEXT
, NULL
, true )
261 add_integer( "amt-native-timeout", 5, TIMEOUT_TEXT
, NULL
, true )
262 add_string( "amt-relay", AMT_DEFAULT_RELAY
, AMT_RELAY_ADDRESS
, AMT_RELAY_ADDR_LONG
, true )
264 set_capability( "access", 0 )
265 add_shortcut( "amt" )
267 set_callbacks( Open
, Close
)
270 /*****************************************************************************
272 *****************************************************************************/
273 static block_t
*BlockAMT( stream_t
*, bool * );
274 static int Control( stream_t
*, int, va_list );
276 /*****************************************************************************
277 * Open: Open a connection to the multicast feed
278 *****************************************************************************/
279 static int Open( vlc_object_t
*p_this
)
281 stream_t
*p_access
= (stream_t
*) p_this
;
282 access_sys_t
*sys
= NULL
;
283 struct addrinfo hints
, *serverinfo
= NULL
;
284 struct sockaddr_in
*server_addr
;
285 char *psz_name
= NULL
, *saveptr
, *psz_strtok_r
;
286 char mcastSrc_buf
[INET_ADDRSTRLEN
], mcastGroup_buf
[INET_ADDRSTRLEN
];
287 const char *mcastSrc
, *mcastGroup
;
288 int i_bind_port
= 1234, i_server_port
= 0, VLC_ret
= VLC_SUCCESS
, response
;
289 vlc_url_t url
= { 0 };
291 if( p_access
->b_preparsing
)
294 /* Set up p_access */
295 ACCESS_SET_CALLBACKS( NULL
, BlockAMT
, Control
, NULL
);
297 if( !p_access
->psz_location
)
300 /* Allocate the structure for holding AMT info and zeroize it */
301 sys
= vlc_obj_calloc( p_this
, 1, sizeof( *sys
) );
302 if( unlikely( sys
== NULL
) )
305 /* The standard MPEG-2 transport is 188 bytes. 7 packets fit into a standard 1500 byte Ethernet frame */
308 p_access
->p_sys
= sys
;
310 sys
->fd
= sys
->sAMT
= sys
->sQuery
= -1;
312 psz_name
= strdup( p_access
->psz_location
);
313 if ( unlikely( psz_name
== NULL
) )
315 VLC_ret
= VLC_EGENERIC
;
319 /* Parse psz_name syntax :
320 * [serveraddr[:serverport]][@[bindaddr]:[bindport]] */
321 if( vlc_UrlParse( &url
, p_access
->psz_url
) != 0 )
323 msg_Err( p_access
, "Invalid URL: %s", p_access
->psz_url
);
324 VLC_ret
= VLC_EGENERIC
;
328 /* Determining the multicast source and group depends on the URL provided */
330 /* The address(es) in the URL can be in the form of IP address or FQDN */
331 /* By calling vlc_getaaddrinfo() you get it in IP form either way */
333 /* Case 1: amt://<source-ip-address>@<multicast-group-ip-address> */
335 /* mcastSrc = <source-ip-address> */
336 /* sys->mcastSrcAddr = inet_pton( sys->mcastSrc ) */
338 /* mcastGroup = <multicast-group-ip-address> */
339 /* sys->mcastGroupAddr = inet_pton( sys->mcastGroup ) */
341 /* Case 2: amt://<multicast-group-ip-address> */
343 /* mcastSrc = MCAST_ANYCAST = "0.0.0.0" */
344 /* sys->mcastSrcAddr = inet_pton( sys->mcastSrc ) = 0 */
346 /* mcastGroup = <multicast-group-ip-address> */
347 /* sys->mcastGroupAddr = inet_pton( sys->mcastGroup ) */
350 /* If UDP port provided then assign port to stream */
352 i_bind_port
= url
.i_port
;
354 msg_Dbg( p_access
, "Opening multicast: %s:%d local=%s:%d", url
.psz_host
, i_server_port
, url
.psz_path
, i_bind_port
);
356 /* Initialize hints prior to call to vlc_getaddrinfo with either IP address or FQDN */
357 memset( &hints
, 0, sizeof( hints
));
358 hints
.ai_family
= AF_INET
; /* Setting to AF_UNSPEC accepts both IPv4 and IPv6 */
359 hints
.ai_socktype
= SOCK_DGRAM
;
361 /* Retrieve list of multicast addresses matching the multicast group identifier */
362 response
= vlc_getaddrinfo( url
.psz_host
, AMT_PORT
, &hints
, &serverinfo
);
364 /* If an error returned print reason and exit */
367 msg_Err( p_access
, "Could not find multicast group %s, reason: %s", url
.psz_host
, gai_strerror(response
) );
368 VLC_ret
= VLC_EGENERIC
;
372 /* Convert binary socket address to string */
373 server_addr
= (struct sockaddr_in
*) serverinfo
->ai_addr
;
374 if( unlikely( inet_ntop(AF_INET
, &(server_addr
->sin_addr
), mcastGroup_buf
, INET_ADDRSTRLEN
) == NULL
) )
377 msg_Err(p_access
, "Could not convert binary socket address to string: %s", gai_strerror(errConv
));
380 mcastGroup
= mcastGroup_buf
;
382 /* Store the binary socket representation of multicast group address */
383 sys
->mcastGroupAddr
= *server_addr
;
385 /* Release the allocated memory */
386 freeaddrinfo( serverinfo
);
389 /* Store string representation */
391 msg_Dbg( p_access
, "Setting multicast group address to %s", mcastGroup
);
393 /* Extract the source from the URL, or the multicast group when no source is provided */
394 psz_strtok_r
= strtok_r( psz_name
, "@", &saveptr
);
397 msg_Err( p_access
, "Could not parse location %s", psz_name
);
398 VLC_ret
= VLC_EGENERIC
;
402 /* Store the string representation */
403 mcastSrc
= psz_strtok_r
;
405 /* If strings are equal then no multicast source has been specified, so try anycast */
406 if( strcmp( url
.psz_host
, mcastSrc
) == 0 )
408 mcastSrc
= MCAST_ANYCAST
;
409 sys
->mcastSrcAddr
.sin_addr
.s_addr
= 0;
410 msg_Dbg( p_access
, "No multicast source address specified, trying ASM...");
413 /* retrieve list of source addresses matching the multicast source identifier */
414 response
= vlc_getaddrinfo( mcastSrc
, AMT_PORT
, &hints
, &serverinfo
);
416 /* If an error returned print reason and exit */
419 msg_Err( p_access
, "Could not find multicast source %s, reason: %s", mcastSrc
, gai_strerror(response
) );
420 VLC_ret
= VLC_EGENERIC
;
424 /* Convert binary socket address to string */
425 server_addr
= (struct sockaddr_in
*) serverinfo
->ai_addr
;
426 if( unlikely( inet_ntop(AF_INET
, &(server_addr
->sin_addr
), mcastSrc_buf
, INET_ADDRSTRLEN
) == NULL
) )
429 msg_Err(p_access
, "Could not binary socket address to string: %s", gai_strerror(errConv
));
432 mcastSrc
= mcastSrc_buf
;
434 /* Store the binary socket representation of multicast source address */
435 sys
->mcastSrcAddr
= *server_addr
;
437 msg_Dbg( p_access
, "Setting multicast source address to %s", mcastSrc
);
439 /* Pull the AMT relay address from the settings */
440 sys
->relay
= var_InheritString( p_access
, "amt-relay" );
441 if( unlikely( sys
->relay
== NULL
) )
443 msg_Err( p_access
, "No relay anycast or unicast address specified." );
444 VLC_ret
= VLC_EGENERIC
;
448 msg_Dbg( p_access
, "Addresses: mcastGroup: %s mcastSrc: %s relay: %s", \
449 mcastGroup
, mcastSrc
, sys
->relay
);
451 /* Native multicast file descriptor */
452 sys
->fd
= net_OpenDgram( p_access
, mcastGroup
, i_bind_port
,
453 mcastSrc
, i_server_port
, IPPROTO_UDP
);
456 VLC_ret
= VLC_EGENERIC
;
460 int ret
= vlc_timer_create( &sys
->updateTimer
, amt_update_timer_cb
, p_access
);
463 VLC_ret
= VLC_EGENERIC
;
467 sys
->timeout
= var_InheritInteger( p_access
, "amt-native-timeout");
468 if( sys
->timeout
> 0)
469 sys
->timeout
*= 1000;
473 cleanup
: /* fall through */
476 vlc_UrlClean( &url
);
478 freeaddrinfo( serverinfo
);
480 if ( VLC_ret
!= VLC_SUCCESS
)
484 net_Close( sys
->fd
);
490 /*****************************************************************************
491 * Close: Cancel thread and free data structures
492 *****************************************************************************/
493 static void Close( vlc_object_t
*p_this
)
495 stream_t
*p_access
= (stream_t
*)p_this
;
496 access_sys_t
*sys
= p_access
->p_sys
;
498 vlc_timer_destroy( sys
->updateTimer
);
500 /* If using AMT tunneling send leave message and free the relay addresses */
503 /* Prepare socket options */
504 if( sys
->mcastSrcAddr
.sin_addr
.s_addr
)
505 amt_leaveSSM_group( p_access
);
507 amt_leaveASM_group( p_access
);
509 /* Send IGMP leave message */
510 amt_send_mem_update( p_access
, sys
->relayDisco
, true );
514 net_Close( sys
->fd
);
515 if( sys
->sAMT
!= -1 )
516 net_Close( sys
->sAMT
);
517 if( sys
->sQuery
!= -1 )
518 net_Close( sys
->sQuery
);
521 /*****************************************************************************
522 * Control: Define stream controls
523 *****************************************************************************/
524 static int Control( stream_t
*p_access
, int i_query
, va_list args
)
528 case STREAM_CAN_SEEK
:
529 case STREAM_CAN_FASTSEEK
:
530 case STREAM_CAN_PAUSE
:
531 case STREAM_CAN_CONTROL_PACE
:
532 *va_arg( args
, bool * ) = false;
535 case STREAM_GET_PTS_DELAY
:
536 *va_arg( args
, vlc_tick_t
* ) =
537 VLC_TICK_FROM_MS(var_InheritInteger( p_access
, "network-caching" ));
547 /*****************************************************************************
548 * ReadAMT: Responsible for returning the multicast payload
550 * Default MTU based on number of MPEG-2 transports carried in a 1500 byte Ethernet frame
551 * however the code is able to receive maximal IPv4 UDP frames and then adjusts the MTU
552 *****************************************************************************/
553 static block_t
*BlockAMT(stream_t
*p_access
, bool *restrict eof
)
555 access_sys_t
*sys
= p_access
->p_sys
;
556 ssize_t len
= 0, shift
= 0, tunnel
= IP_HDR_LEN
+ UDP_HDR_LEN
+ AMT_HDR_LEN
;
558 /* Allocate anticipated MTU buffer for holding the UDP packet suitable for native or AMT tunneled multicast */
559 block_t
*pkt
= block_Alloc( sys
->mtu
+ tunnel
);
560 if ( unlikely( pkt
== NULL
) )
563 struct pollfd ufd
[1];
566 ufd
[0].fd
= sys
->sAMT
; /* AMT tunneling file descriptor */
568 ufd
[0].fd
= sys
->fd
; /* Native multicast file descriptor */
569 ufd
[0].events
= POLLIN
;
571 switch (vlc_poll_i11e(ufd
, 1, sys
->timeout
))
576 msg_Err(p_access
, "Native multicast receive time-out");
577 if( !open_amt_tunnel( p_access
) )
590 /* If using AMT tunneling perform basic checks and point to beginning of the payload */
593 /* AMT is a wrapper for UDP streams, so recv is used. */
594 len
= recv( sys
->sAMT
, pkt
->p_buffer
, sys
->mtu
+ tunnel
, 0 );
596 /* Check for the integrity of the received AMT packet */
597 if( len
< 0 || *(pkt
->p_buffer
) != AMT_MULT_DATA
)
600 /* Set the offet to the first byte of the payload */
603 /* If the length received is less than the AMT tunnel header then it's truncated */
606 msg_Err(p_access
, "%zd bytes packet truncated (MTU was %zd)", len
, sys
->mtu
);
607 pkt
->i_flags
|= BLOCK_FLAG_CORRUPTED
;
610 /* Otherwise subtract the length of the AMT encapsulation from the packet received */
616 /* Otherwise pull native multicast */
619 struct sockaddr temp
;
620 socklen_t temp_size
= sizeof( struct sockaddr
);
621 len
= recvfrom( sys
->sAMT
, (char *)pkt
->p_buffer
, sys
->mtu
+ tunnel
, 0, (struct sockaddr
*)&temp
, &temp_size
);
624 /* Set the offset to payload start */
625 pkt
->p_buffer
+= shift
;
626 pkt
->i_buffer
-= shift
;
631 block_Release( pkt
);
635 /*****************************************************************************
636 * open_amt_tunnel: Create an AMT tunnel to the AMT relay
637 *****************************************************************************/
638 static bool open_amt_tunnel( stream_t
*p_access
)
640 struct addrinfo hints
, *serverinfo
, *server
;
641 access_sys_t
*sys
= p_access
->p_sys
;
643 memset( &hints
, 0, sizeof( hints
));
644 hints
.ai_family
= AF_INET
; /* Setting to AF_UNSPEC accepts both IPv4 and IPv6 */
645 hints
.ai_socktype
= SOCK_DGRAM
;
647 msg_Dbg( p_access
, "Attempting AMT to %s...", sys
->relay
);
650 /* Retrieve list of addresses matching the AMT relay */
651 int response
= vlc_getaddrinfo( sys
->relay
, AMT_PORT
, &hints
, &serverinfo
);
653 /* If an error returned print reason and exit */
656 msg_Err( p_access
, "Could not find relay %s, reason: %s", sys
->relay
, gai_strerror(response
) );
660 /* Iterate through the list of sockets to find one that works */
661 for (server
= serverinfo
; server
!= NULL
&& !vlc_killed(); server
= server
->ai_next
)
663 struct sockaddr_in
*server_addr
= (struct sockaddr_in
*) server
->ai_addr
;
664 char relay_ip
[INET_ADDRSTRLEN
];
666 /* Convert to binary representation */
667 if( unlikely( inet_ntop(AF_INET
, &(server_addr
->sin_addr
), relay_ip
, INET_ADDRSTRLEN
) == NULL
) )
670 msg_Err(p_access
, "Could not convert relay ip to binary representation: %s", gai_strerror(errConv
));
674 /* Store string representation */
675 memcpy(sys
->relayDisco
, relay_ip
, INET_ADDRSTRLEN
);
676 if( unlikely( sys
->relayDisco
== NULL
) )
681 msg_Dbg( p_access
, "Trying AMT Server: %s", sys
->relayDisco
);
683 /* Store the binary representation */
684 sys
->relayDiscoAddr
.sin_addr
= server_addr
->sin_addr
;
686 if( amt_sockets_init( p_access
) != 0 )
687 continue; /* Try next server */
689 /* Negotiate with AMT relay and confirm you can pull a UDP packet */
690 amt_send_relay_discovery_msg( p_access
, relay_ip
);
691 msg_Dbg( p_access
, "Sent relay AMT discovery message to %s", relay_ip
);
693 if( !amt_rcv_relay_adv( p_access
) )
695 msg_Err( p_access
, "Error receiving AMT relay advertisement msg from %s, skipping", relay_ip
);
698 msg_Dbg( p_access
, "Received AMT relay advertisement from %s", relay_ip
);
700 amt_send_relay_request( p_access
, relay_ip
);
701 msg_Dbg( p_access
, "Sent AMT relay request message to %s", relay_ip
);
703 if( !amt_rcv_relay_mem_query( p_access
) )
705 msg_Err( p_access
, "Could not receive AMT relay membership query from %s, reason: %s", relay_ip
, vlc_strerror(errno
));
708 msg_Dbg( p_access
, "Received AMT relay membership query from %s", relay_ip
);
710 /* If single source multicast send SSM join */
711 if( sys
->mcastSrcAddr
.sin_addr
.s_addr
)
713 if( amt_joinSSM_group( p_access
) != 0 )
715 msg_Err( p_access
, "Error joining SSM %s", vlc_strerror(errno
) );
718 msg_Dbg( p_access
, "Joined SSM" );
721 /* If any source multicast send ASM join */
723 if( amt_joinASM_group( p_access
) != 0 )
725 msg_Err( p_access
, "Error joining ASM %s", vlc_strerror(errno
) );
728 msg_Dbg( p_access
, "Joined ASM group" );
731 /* If started, the timer must be stopped before trying the next server
732 * in order to avoid data-race with sys->sAMT. */
733 vlc_timer_disarm( sys
->updateTimer
);
735 amt_send_mem_update( p_access
, sys
->relayDisco
, false );
739 /* Confirm that you can pull a UDP packet from the socket */
740 if ( !(pkt
= BlockAMT( p_access
, &eof
)) )
742 msg_Err( p_access
, "Unable to receive UDP packet from AMT relay %s for multicast group", relay_ip
);
747 block_Release( pkt
);
748 msg_Dbg( p_access
, "Got UDP packet from multicast group via AMT relay %s, continuing...", relay_ip
);
749 break; /* found an active server sending UDP packets, so exit loop */
753 /* if server is NULL then no AMT relay is responding */
756 msg_Err( p_access
, "No AMT servers responding" );
760 /* release the allocated memory */
761 freeaddrinfo( serverinfo
);
765 vlc_timer_disarm( sys
->updateTimer
);
767 freeaddrinfo( serverinfo
);
774 static unsigned short get_checksum( unsigned short *buffer
, int nLen
)
778 unsigned short *w
= buffer
;
779 unsigned short answer
= 0;
788 *(unsigned char*)(&answer
) = *(unsigned char*)w
;
791 sum
= (sum
>> 16) + (sum
& 0xffff);
797 * Make IGMP Membership report
799 static void make_report( amt_igmpv3_membership_report_t
*mr
)
801 mr
->type
= AMT_IGMPV3_MEMBERSHIP_REPORT_TYPEID
;
805 mr
->nGroupRecord
= htons(1);
811 static void make_ip_header( amt_ip_alert_t
*p_ipHead
)
813 p_ipHead
->ver_ihl
= 0x46;
814 p_ipHead
->tos
= 0xc0;
815 p_ipHead
->tot_len
= htons( IP_HDR_IGMP_LEN
+ IGMP_REPORT_LEN
);
817 p_ipHead
->frag_off
= 0x0000;
818 p_ipHead
->ttl
= 0x01;
819 p_ipHead
->protocol
= 0x02;
821 p_ipHead
->srcAddr
= INADDR_ANY
;
822 p_ipHead
->options
= 0x0000;
825 /** Create relay discovery socket, query socket, UDP socket and
826 * fills in relay anycast address for discovery
827 * return 0 if successful, -1 if not
829 static int amt_sockets_init( stream_t
*p_access
)
831 struct sockaddr_in rcvAddr
;
832 access_sys_t
*sys
= p_access
->p_sys
;
833 memset( &rcvAddr
, 0, sizeof(struct sockaddr_in
) );
834 int enable
= 0, res
= 0;
836 /* Relay anycast address for discovery */
837 sys
->relayDiscoAddr
.sin_family
= AF_INET
;
838 sys
->relayDiscoAddr
.sin_port
= htons( AMT_PORT
);
840 /* create UDP socket */
841 sys
->sAMT
= vlc_socket( AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
, true );
842 if( sys
->sAMT
== -1 )
844 msg_Err( p_access
, "Failed to create UDP socket" );
848 res
= setsockopt(sys
->sAMT
, SOL_SOCKET
, SO_REUSEADDR
, &enable
, sizeof(enable
));
851 msg_Err( p_access
, "Couldn't make socket reusable");
855 rcvAddr
.sin_family
= AF_INET
;
856 rcvAddr
.sin_port
= htons( 0 );
857 rcvAddr
.sin_addr
.s_addr
= INADDR_ANY
;
859 if( bind(sys
->sAMT
, (struct sockaddr
*)&rcvAddr
, sizeof(rcvAddr
) ) != 0 )
861 msg_Err( p_access
, "Failed to bind UDP socket error: %s", vlc_strerror(errno
) );
865 sys
->sQuery
= vlc_socket( AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
, true );
866 if( sys
->sQuery
== -1 )
868 msg_Err( p_access
, "Failed to create query socket" );
872 /* bind socket to local address */
873 struct sockaddr_in stLocalAddr
=
875 .sin_family
= AF_INET
,
876 .sin_port
= htons( 0 ),
877 .sin_addr
.s_addr
= INADDR_ANY
,
880 if( bind(sys
->sQuery
, (struct sockaddr
*)&stLocalAddr
, sizeof(struct sockaddr
) ) != 0 )
882 msg_Err( p_access
, "Failed to bind query socket" );
886 struct sockaddr_in stSvrAddr
=
888 .sin_family
= AF_INET
,
889 .sin_port
= htons( 9124 ),
892 res
= inet_pton( AF_INET
, LOCAL_LOOPBACK
, &stSvrAddr
.sin_addr
);
895 msg_Err( p_access
, "Could not convert loopback address" );
898 /* TODO: stSvrAddr is unused ? */
903 if( sys
->sAMT
!= -1 )
905 net_Close( sys
->sAMT
);
909 if( sys
->sQuery
!= -1 )
911 net_Close( sys
->sQuery
);
918 * Send a relay discovery message, before 3-way handshake
920 static void amt_send_relay_discovery_msg( stream_t
*p_access
, char *relay_ip
)
922 char chaSendBuffer
[AMT_DISCO_MSG_LEN
];
923 unsigned int ulNonce
;
925 access_sys_t
*sys
= p_access
->p_sys
;
927 /* initialize variables */
928 memset( chaSendBuffer
, 0, sizeof(chaSendBuffer
) );
933 * create AMT discovery message format
934 * +---------------------------------------------------+
935 * | Msg Type(1Byte)| Reserved (3 byte)| nonce (4byte) |
936 * +---------------------------------------------------+
939 chaSendBuffer
[0] = AMT_RELAY_DISCO
;
940 chaSendBuffer
[1] = 0;
941 chaSendBuffer
[2] = 0;
942 chaSendBuffer
[3] = 0;
944 /* create nonce and copy into send buffer */
945 srand( (unsigned int)time(NULL
) );
946 ulNonce
= htonl( rand() );
947 memcpy( &chaSendBuffer
[4], &ulNonce
, sizeof(ulNonce
) );
948 sys
->glob_ulNonce
= ulNonce
;
951 nRet
= sendto( sys
->sAMT
, chaSendBuffer
, sizeof(chaSendBuffer
), 0,\
952 (struct sockaddr
*)&sys
->relayDiscoAddr
, sizeof(struct sockaddr
) );
955 msg_Err( p_access
, "Sendto failed to %s with error %d.", relay_ip
, errno
);
959 * Send relay request message, stage 2 of handshake
961 static void amt_send_relay_request( stream_t
*p_access
, char *relay_ip
)
963 char chaSendBuffer
[AMT_REQUEST_MSG_LEN
];
966 access_sys_t
*sys
= p_access
->p_sys
;
968 memset( chaSendBuffer
, 0, sizeof(chaSendBuffer
) );
974 * create AMT request message format
975 * +-----------------------------------------------------------------+
976 * | Msg Type(1Byte)| Reserved(1byte)|P flag(1byte)|Reserved (2 byte)|
977 * +-----------------------------------------------------------------+
979 * +-----------------------------------------------------------------+
981 * The P flag is set to indicate which group membership protocol the
982 * gateway wishes the relay to use in the Membership Query response:
986 * 0 The relay MUST respond with a Membership Query message that
987 * contains an IPv4 packet carrying an IGMPv3 General Query
989 * 1 The relay MUST respond with a Membership Query message that
990 * contains an IPv6 packet carrying an MLDv2 General Query
995 chaSendBuffer
[0] = AMT_REQUEST
;
996 chaSendBuffer
[1] = 0;
997 chaSendBuffer
[2] = 0;
998 chaSendBuffer
[3] = 0;
1000 ulNonce
= sys
->glob_ulNonce
;
1001 memcpy( &chaSendBuffer
[4], &ulNonce
, sizeof(uint32_t) );
1003 nRet
= send( sys
->sAMT
, chaSendBuffer
, sizeof(chaSendBuffer
), 0 );
1006 msg_Err(p_access
, "Error sending relay request to %s error: %s", relay_ip
, vlc_strerror(errno
) );
1010 * create AMT request message format
1011 * +----------------------------------------------------------------------------------+
1012 * | Msg Type(1 byte)| Reserved (1 byte)| MAC (6 byte)| nonce (4 byte) | IGMP packet |
1013 * +----------------------------------------------------------------------------------+
1015 static void amt_send_mem_update( stream_t
*p_access
, char *relay_ip
, bool leave
)
1017 int sendBufSize
= IP_HDR_IGMP_LEN
+ MAC_LEN
+ NONCE_LEN
+ AMT_HDR_LEN
;
1018 char pSendBuffer
[ sendBufSize
+ IGMP_REPORT_LEN
];
1019 uint32_t ulNonce
= 0;
1020 access_sys_t
*sys
= p_access
->p_sys
;
1022 memset( pSendBuffer
, 0, sizeof(pSendBuffer
) );
1024 pSendBuffer
[0] = AMT_MEM_UPD
;
1026 /* copy relay MAC response */
1027 memcpy( &pSendBuffer
[2], sys
->relay_mem_query_msg
.uchaMAC
, MAC_LEN
);
1030 ulNonce
= sys
->glob_ulNonce
;
1031 memcpy( &pSendBuffer
[8], &ulNonce
, NONCE_LEN
);
1033 /* make IP header for IGMP packet */
1034 amt_ip_alert_t p_ipHead
;
1035 memset( &p_ipHead
, 0, IP_HDR_IGMP_LEN
);
1036 make_ip_header( &p_ipHead
);
1038 struct sockaddr_in temp
;
1039 int res
= inet_pton( AF_INET
, MCAST_ALLHOSTS
, &(temp
.sin_addr
) );
1042 msg_Err(p_access
, "Could not convert all hosts multicast address: %s", gai_strerror(errno
) );
1045 p_ipHead
.destAddr
= temp
.sin_addr
.s_addr
;
1046 p_ipHead
.check
= get_checksum( (unsigned short*)&p_ipHead
, IP_HDR_IGMP_LEN
);
1048 amt_igmpv3_groupRecord_t groupRcd
;
1049 groupRcd
.auxDatalen
= 0;
1050 groupRcd
.ssm
= sys
->mcastGroupAddr
.sin_addr
.s_addr
;
1052 if( sys
->mcastSrcAddr
.sin_addr
.s_addr
)
1054 groupRcd
.type
= leave
? AMT_IGMP_BLOCK
:AMT_IGMP_INCLUDE
;
1055 groupRcd
.nSrc
= htons(1);
1056 groupRcd
.srcIP
[0] = sys
->mcastSrcAddr
.sin_addr
.s_addr
;
1059 groupRcd
.type
= leave
? AMT_IGMP_INCLUDE_CHANGE
:AMT_IGMP_EXCLUDE_CHANGE
;
1060 groupRcd
.nSrc
= htons(0);
1063 /* make IGMP membership report */
1064 amt_igmpv3_membership_report_t p_igmpMemRep
;
1065 make_report( &p_igmpMemRep
);
1067 memcpy(&p_igmpMemRep
.grp
[0], &groupRcd
, (int)sizeof(groupRcd
) );
1068 p_igmpMemRep
.checksum
= get_checksum( (unsigned short*)&p_igmpMemRep
, IGMP_REPORT_LEN
);
1070 amt_membership_update_msg_t memUpdateMsg
;
1071 memset(&memUpdateMsg
, 0, sizeof(memUpdateMsg
));
1072 memcpy(&memUpdateMsg
.ipHead
, &p_ipHead
, sizeof(p_ipHead
) );
1073 memcpy(&memUpdateMsg
.memReport
, &p_igmpMemRep
, sizeof(p_igmpMemRep
) );
1075 memcpy( &pSendBuffer
[12], &memUpdateMsg
, sizeof(memUpdateMsg
) );
1077 send( sys
->sAMT
, pSendBuffer
, sizeof(pSendBuffer
), 0 );
1079 msg_Dbg( p_access
, "AMT relay membership report sent to %s", relay_ip
);
1083 * Receive relay advertisement message
1086 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1087 * | V=0 |Type=2 | Reserved |
1088 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1089 * | Discovery Nonce |
1090 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1092 * ~ Relay Address (IPv4 or IPv6) ~
1094 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1096 static bool amt_rcv_relay_adv( stream_t
*p_access
)
1098 char pkt
[RELAY_ADV_MSG_LEN
];
1099 access_sys_t
*sys
= p_access
->p_sys
;
1101 memset( pkt
, 0, RELAY_ADV_MSG_LEN
);
1103 struct pollfd ufd
[1];
1105 ufd
[0].fd
= sys
->sAMT
;
1106 ufd
[0].events
= POLLIN
;
1108 switch( vlc_poll_i11e(ufd
, 1, sys
->timeout
) )
1111 msg_Err(p_access
, "AMT relay advertisement receive time-out");
1117 struct sockaddr temp
;
1118 socklen_t temp_size
= sizeof( struct sockaddr
);
1119 ssize_t len
= recvfrom( sys
->sAMT
, pkt
, RELAY_ADV_MSG_LEN
, 0, &temp
, &temp_size
);
1123 msg_Err(p_access
, "Received message length less than zero");
1127 /* AMT Relay Advertisement data (RFC7450) */
1129 uint32_t ulRcvNonce
;
1134 memcpy( &relay_adv_msg
.type
, &pkt
[0], MSG_TYPE_LEN
);
1135 if( relay_adv_msg
.type
!= AMT_RELAY_ADV
)
1137 msg_Err( p_access
, "Received message not an AMT relay advertisement, ignoring. ");
1141 memcpy( &relay_adv_msg
.ulRcvNonce
, &pkt
[NONCE_LEN
], NONCE_LEN
);
1142 if( sys
->glob_ulNonce
!= relay_adv_msg
.ulRcvNonce
)
1144 msg_Err( p_access
, "Discovery nonces differ! currNonce:%x rcvd%x", sys
->glob_ulNonce
, (uint32_t) ntohl(relay_adv_msg
.ulRcvNonce
) );
1148 memcpy( &relay_adv_msg
.ipAddr
, &pkt
[8], 4 );
1150 struct sockaddr_in relayAddr
=
1152 .sin_family
= AF_INET
,
1153 .sin_addr
.s_addr
= relay_adv_msg
.ipAddr
,
1154 .sin_port
= htons( AMT_PORT
),
1157 int nRet
= connect( sys
->sAMT
, (struct sockaddr
*)&relayAddr
, sizeof(relayAddr
) );
1160 msg_Err( p_access
, "Error connecting AMT UDP socket: %s", vlc_strerror(errno
) );
1168 * Receive relay membership query message
1169 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1170 | V=0 |Type=4 | Reserved |L|G| Response MAC |
1171 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
1173 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1175 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1177 | Encapsulated General Query Message |
1178 ~ IPv4:IGMPv3(Membership Query) ~
1179 | IPv6:MLDv2(Listener Query) |
1181 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1182 | Gateway Port Number | |
1183 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
1186 | Gateway IP Address (IPv4 or IPv6) |
1189 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1191 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1193 static bool amt_rcv_relay_mem_query( stream_t
*p_access
)
1195 char pkt
[RELAY_QUERY_MSG_LEN
];
1196 memset( pkt
, 0, RELAY_QUERY_MSG_LEN
);
1197 struct pollfd ufd
[1];
1198 access_sys_t
*sys
= p_access
->p_sys
;
1200 ufd
[0].fd
= sys
->sAMT
;
1201 ufd
[0].events
= POLLIN
;
1203 switch( vlc_poll_i11e(ufd
, 1, sys
->timeout
) )
1206 msg_Err(p_access
, "AMT relay membership query receive time-out");
1212 ssize_t len
= recv( sys
->sAMT
, pkt
, RELAY_QUERY_MSG_LEN
, 0 );
1214 if (len
< 0 || len
!= RELAY_QUERY_MSG_LEN
)
1216 msg_Err(p_access
, "length less than zero");
1220 memcpy( &sys
->relay_mem_query_msg
.type
, &pkt
[0], MSG_TYPE_LEN
);
1221 /* pkt[1] is reserved */
1222 memcpy( &sys
->relay_mem_query_msg
.uchaMAC
[0], &pkt
[AMT_HDR_LEN
], MAC_LEN
);
1223 memcpy( &sys
->relay_mem_query_msg
.ulRcvedNonce
, &pkt
[AMT_HDR_LEN
+ MAC_LEN
], NONCE_LEN
);
1224 if( sys
->relay_mem_query_msg
.ulRcvedNonce
!= sys
->glob_ulNonce
)
1226 msg_Warn( p_access
, "Nonces are different rcvd: %x glob: %x", sys
->relay_mem_query_msg
.ulRcvedNonce
, sys
->glob_ulNonce
);
1230 size_t shift
= AMT_HDR_LEN
+ MAC_LEN
+ NONCE_LEN
+ IP_HDR_IGMP_LEN
;
1231 sys
->relay_igmp_query
.type
= pkt
[shift
];
1232 shift
++; assert( shift
< RELAY_QUERY_MSG_LEN
);
1233 sys
->relay_igmp_query
.max_resp_code
= pkt
[shift
];
1234 shift
++; assert( shift
< RELAY_QUERY_MSG_LEN
);
1235 memcpy( &sys
->relay_igmp_query
.checksum
, &pkt
[shift
], 2 );
1236 shift
+= 2; assert( shift
< RELAY_QUERY_MSG_LEN
);
1237 memcpy( &sys
->relay_igmp_query
.ssmIP
, &pkt
[shift
], 4 );
1238 shift
+= 4; assert( shift
< RELAY_QUERY_MSG_LEN
);
1239 sys
->relay_igmp_query
.s_qrv
= pkt
[shift
];
1240 shift
++; assert( shift
< RELAY_QUERY_MSG_LEN
);
1241 if( pkt
[shift
] == 0 )
1242 sys
->relay_igmp_query
.qqic
= 125;
1244 sys
->relay_igmp_query
.qqic
= pkt
[shift
];
1246 shift
++; assert( shift
< RELAY_QUERY_MSG_LEN
);
1247 memcpy( &sys
->relay_igmp_query
.nSrc
, &pkt
[shift
], 2 );
1249 /* Arms the timer for a single shot: cf. amt_update_timer_cb comment */
1250 vlc_timer_schedule( sys
->updateTimer
, false,
1251 VLC_TICK_FROM_SEC( sys
->relay_igmp_query
.qqic
), 0 );
1257 * Join SSM group based on input addresses, or use the defaults
1259 static int amt_joinSSM_group( stream_t
*p_access
)
1261 #ifdef IP_ADD_SOURCE_MEMBERSHIP
1262 struct ip_mreq_source imr
;
1263 access_sys_t
*sys
= p_access
->p_sys
;
1265 imr
.imr_multiaddr
.s_addr
= sys
->mcastGroupAddr
.sin_addr
.s_addr
;
1266 imr
.imr_sourceaddr
.s_addr
= sys
->mcastSrcAddr
.sin_addr
.s_addr
;
1267 imr
.imr_interface
.s_addr
= INADDR_ANY
;
1269 return setsockopt( sys
->sAMT
, IPPROTO_IP
, IP_ADD_SOURCE_MEMBERSHIP
, (char *)&imr
, sizeof(imr
) );
1276 static int amt_joinASM_group( stream_t
*p_access
)
1279 access_sys_t
*sys
= p_access
->p_sys
;
1281 imr
.imr_multiaddr
.s_addr
= sys
->mcastGroupAddr
.sin_addr
.s_addr
;
1282 imr
.imr_interface
.s_addr
= INADDR_ANY
;
1284 return setsockopt( sys
->sAMT
, IPPROTO_IP
, IP_ADD_MEMBERSHIP
, (char *)&imr
, sizeof(imr
) );
1288 * Leave SSM group that was joined earlier.
1290 static int amt_leaveSSM_group( stream_t
*p_access
)
1292 #ifdef IP_DROP_SOURCE_MEMBERSHIP
1293 struct ip_mreq_source imr
;
1294 access_sys_t
*sys
= p_access
->p_sys
;
1296 imr
.imr_multiaddr
.s_addr
= sys
->mcastGroupAddr
.sin_addr
.s_addr
;
1297 imr
.imr_sourceaddr
.s_addr
= sys
->mcastSrcAddr
.sin_addr
.s_addr
;
1298 imr
.imr_interface
.s_addr
= INADDR_ANY
;
1300 return setsockopt( sys
->sAMT
, IPPROTO_IP
, IP_DROP_SOURCE_MEMBERSHIP
, (char *)&imr
, sizeof(imr
) );
1308 * Leave ASM group that was joined earlier.
1310 static int amt_leaveASM_group( stream_t
*p_access
)
1313 access_sys_t
*sys
= p_access
->p_sys
;
1315 imr
.imr_multiaddr
.s_addr
= sys
->mcastGroupAddr
.sin_addr
.s_addr
;
1316 imr
.imr_interface
.s_addr
= INADDR_ANY
;
1318 return setsockopt( sys
->sAMT
, IPPROTO_IP
, IP_DROP_MEMBERSHIP
, (char *)&imr
, sizeof(imr
) );
1321 /* A timer is spawned since IGMP membership updates need to issued periodically
1322 * in order to continue to receive multicast. */
1323 static void amt_update_timer_cb( void *data
)
1325 stream_t
*p_access
= (stream_t
*) data
;
1326 access_sys_t
*sys
= p_access
->p_sys
;
1328 amt_send_mem_update( p_access
, sys
->relayDisco
, false );
1330 /* Arms the timer again for a single shot from this callback. That way, the
1331 * time spent in amt_send_mem_update() is taken into consideration. */
1332 vlc_timer_schedule( sys
->updateTimer
, false,
1333 VLC_TICK_FROM_SEC( sys
->relay_igmp_query
.qqic
), 0 );