contrib: cargo: use the 0.6.13 cargo-c version
[vlc.git] / modules / access / amt.c
blob52bf0271ef9c002e42e570711cfe2a5f49dec0bb
1 /**
2 * @file amt.c
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 ****************************************************************************/
37 #ifdef HAVE_CONFIG_H
38 # include "config.h"
39 #endif
41 #include <errno.h>
42 #include <ctype.h>
43 #include <assert.h>
44 #ifdef HAVE_ARPA_INET_H
45 # include <arpa/inet.h>
46 #endif
48 #ifdef HAVE_SYS_SOCKET_H
49 # include <sys/socket.h>
50 #endif
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>
59 #include <vlc_url.h>
61 #ifdef HAVE_POLL_H
62 #include <poll.h>
63 #endif
64 #ifdef HAVE_SYS_UIO_H
65 #include <sys/uio.h>
66 #endif
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 {
131 uint8_t ver_ihl;
132 uint8_t tos;
133 uint16_t tot_len;
134 uint16_t id;
135 uint16_t frag_off;
136 uint8_t ttl;
137 uint8_t protocol;
138 uint16_t check;
139 uint32_t srcAddr;
140 uint32_t destAddr;
141 } amt_ip_t;
143 /* IPv4 Header Format with options field */
144 typedef struct _amt_ip_alert {
145 uint8_t ver_ihl;
146 uint8_t tos;
147 uint16_t tot_len;
148 uint16_t id;
149 uint16_t frag_off;
150 uint8_t ttl;
151 uint8_t protocol;
152 uint16_t check;
153 uint32_t srcAddr;
154 uint32_t destAddr;
155 uint32_t options;
156 } amt_ip_alert_t;
158 /* IGMPv3 Group Record Format (RFC3376) */
159 typedef struct _amt_igmpv3_groupRecord {
160 uint8_t type;
161 uint8_t auxDatalen;
162 uint16_t nSrc;
163 uint32_t ssm;
164 uint32_t srcIP[1];
165 } amt_igmpv3_groupRecord_t;
167 /* IGMPv3 Membership Report Format (RFC3376) */
168 typedef struct _amt_igmpv3_membership_report {
169 uint8_t type;
170 uint8_t resv;
171 uint16_t checksum;
172 uint16_t resv2;
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 {
179 uint8_t type;
180 uint8_t max_resp_code; /* in 100ms, Max Resp Time = (mant | 0x10) << (exp + 3) */
181 uint32_t checksum;
182 uint32_t ssmIP;
183 uint8_t s_qrv;
184 uint8_t qqic; /* in second, query Time = (mant | 0x10) << (exp + 3) */
185 uint16_t nSrc;
186 uint32_t srcIP[1];
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;
195 /* AMT Functions */
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
212 char *relay;
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;
227 uint8_t type;
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;
233 size_t mtu;
235 uint32_t glob_ulNonce;
237 int fd;
238 int sAMT;
239 int sQuery;
240 int timeout;
242 bool tryAMT;
243 } access_sys_t;
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 );
254 vlc_module_begin ()
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 )
268 vlc_module_end ()
270 /*****************************************************************************
271 * Local prototypes
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 )
292 return VLC_EGENERIC;
294 /* Set up p_access */
295 ACCESS_SET_CALLBACKS( NULL, BlockAMT, Control, NULL );
297 if( !p_access->psz_location )
298 return VLC_EGENERIC;
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 ) )
303 return VLC_ENOMEM;
305 /* The standard MPEG-2 transport is 188 bytes. 7 packets fit into a standard 1500 byte Ethernet frame */
306 sys->mtu = 7 * 188;
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;
316 goto cleanup;
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;
325 goto cleanup;
328 /* Determining the multicast source and group depends on the URL provided */
329 /* */
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 */
332 /* */
333 /* Case 1: amt://<source-ip-address>@<multicast-group-ip-address> */
334 /* */
335 /* mcastSrc = <source-ip-address> */
336 /* sys->mcastSrcAddr = inet_pton( sys->mcastSrc ) */
337 /* */
338 /* mcastGroup = <multicast-group-ip-address> */
339 /* sys->mcastGroupAddr = inet_pton( sys->mcastGroup ) */
340 /* */
341 /* Case 2: amt://<multicast-group-ip-address> */
342 /* */
343 /* mcastSrc = MCAST_ANYCAST = "0.0.0.0" */
344 /* sys->mcastSrcAddr = inet_pton( sys->mcastSrc ) = 0 */
345 /* */
346 /* mcastGroup = <multicast-group-ip-address> */
347 /* sys->mcastGroupAddr = inet_pton( sys->mcastGroup ) */
348 /* */
350 /* If UDP port provided then assign port to stream */
351 if( url.i_port > 0 )
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 */
365 if( response != 0 )
367 msg_Err( p_access, "Could not find multicast group %s, reason: %s", url.psz_host, gai_strerror(response) );
368 VLC_ret = VLC_EGENERIC;
369 goto cleanup;
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 ) )
376 int errConv = errno;
377 msg_Err(p_access, "Could not convert binary socket address to string: %s", gai_strerror(errConv));
378 goto cleanup;
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 );
387 serverinfo = NULL;
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 );
395 if ( !psz_strtok_r )
397 msg_Err( p_access, "Could not parse location %s", psz_name);
398 VLC_ret = VLC_EGENERIC;
399 goto cleanup;
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 */
417 if( response != 0 )
419 msg_Err( p_access, "Could not find multicast source %s, reason: %s", mcastSrc, gai_strerror(response) );
420 VLC_ret = VLC_EGENERIC;
421 goto cleanup;
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 ) )
428 int errConv = errno;
429 msg_Err(p_access, "Could not binary socket address to string: %s", gai_strerror(errConv));
430 goto cleanup;
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;
445 goto cleanup;
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 );
454 if( sys->fd == -1 )
456 VLC_ret = VLC_EGENERIC;
457 goto cleanup;
460 int ret = vlc_timer_create( &sys->updateTimer, amt_update_timer_cb, p_access );
461 if( ret != 0 )
463 VLC_ret = VLC_EGENERIC;
464 goto cleanup;
467 sys->timeout = var_InheritInteger( p_access, "amt-native-timeout");
468 if( sys->timeout > 0)
469 sys->timeout *= 1000;
471 sys->tryAMT = false;
473 cleanup: /* fall through */
475 free( psz_name );
476 vlc_UrlClean( &url );
477 if( serverinfo )
478 freeaddrinfo( serverinfo );
480 if ( VLC_ret != VLC_SUCCESS )
482 free( sys->relay );
483 if( sys->fd != -1 )
484 net_Close( sys->fd );
487 return VLC_ret;
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 */
501 if ( sys->tryAMT )
503 /* Prepare socket options */
504 if( sys->mcastSrcAddr.sin_addr.s_addr )
505 amt_leaveSSM_group( p_access );
506 else
507 amt_leaveASM_group( p_access );
509 /* Send IGMP leave message */
510 amt_send_mem_update( p_access, sys->relayDisco, true );
512 free( sys->relay );
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 )
526 switch( i_query )
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;
533 break;
535 case STREAM_GET_PTS_DELAY:
536 *va_arg( args, vlc_tick_t * ) =
537 VLC_TICK_FROM_MS(var_InheritInteger( p_access, "network-caching" ));
538 break;
540 default:
541 return VLC_EGENERIC;
544 return VLC_SUCCESS;
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 ) )
561 return NULL;
563 struct pollfd ufd[1];
565 if( sys->tryAMT )
566 ufd[0].fd = sys->sAMT; /* AMT tunneling file descriptor */
567 else
568 ufd[0].fd = sys->fd; /* Native multicast file descriptor */
569 ufd[0].events = POLLIN;
571 switch (vlc_poll_i11e(ufd, 1, sys->timeout))
573 case 0:
574 if( !sys->tryAMT )
576 msg_Err(p_access, "Native multicast receive time-out");
577 if( !open_amt_tunnel( p_access ) )
578 goto error;
579 break;
581 else
583 *eof = true;
585 /* fall through */
586 case -1:
587 goto error;
590 /* If using AMT tunneling perform basic checks and point to beginning of the payload */
591 if( sys->tryAMT )
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 )
598 goto error;
600 /* Set the offet to the first byte of the payload */
601 shift += tunnel;
603 /* If the length received is less than the AMT tunnel header then it's truncated */
604 if( len < tunnel )
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 */
611 else
613 len -= tunnel;
616 /* Otherwise pull native multicast */
617 else
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;
628 return pkt;
630 error:
631 block_Release( pkt );
632 return NULL;
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);
648 sys->tryAMT = true;
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 */
654 if( response != 0 )
656 msg_Err( p_access, "Could not find relay %s, reason: %s", sys->relay, gai_strerror(response) );
657 goto error;
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 ) )
669 int errConv = errno;
670 msg_Err(p_access, "Could not convert relay ip to binary representation: %s", gai_strerror(errConv));
671 goto error;
674 /* Store string representation */
675 memcpy(sys->relayDisco, relay_ip, INET_ADDRSTRLEN);
676 if( unlikely( sys->relayDisco == NULL ) )
678 goto error;
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 );
696 goto error;
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));
706 goto error;
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) );
716 goto error;
718 msg_Dbg( p_access, "Joined SSM" );
721 /* If any source multicast send ASM join */
722 else {
723 if( amt_joinASM_group( p_access ) != 0 )
725 msg_Err( p_access, "Error joining ASM %s", vlc_strerror(errno) );
726 goto error;
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 );
736 bool eof=false;
737 block_t *pkt;
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 );
743 continue;
745 else
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 */
754 if (server == NULL)
756 msg_Err( p_access, "No AMT servers responding" );
757 goto error;
760 /* release the allocated memory */
761 freeaddrinfo( serverinfo );
762 return true;
764 error:
765 vlc_timer_disarm( sys->updateTimer );
766 if( serverinfo )
767 freeaddrinfo( serverinfo );
768 return false;
772 * Calculate checksum
773 * */
774 static unsigned short get_checksum( unsigned short *buffer, int nLen )
776 int nleft = nLen;
777 int sum = 0;
778 unsigned short *w = buffer;
779 unsigned short answer = 0;
781 while (nleft > 1)
783 sum += *w++;
784 nleft -= 2;
786 if (nleft == 1)
788 *(unsigned char*)(&answer) = *(unsigned char*)w;
789 sum += answer;
791 sum = (sum >> 16) + (sum & 0xffff);
792 answer = ~sum;
793 return (answer);
797 * Make IGMP Membership report
798 * */
799 static void make_report( amt_igmpv3_membership_report_t *mr )
801 mr->type = AMT_IGMPV3_MEMBERSHIP_REPORT_TYPEID;
802 mr->resv = 0;
803 mr->checksum = 0;
804 mr->resv2 = 0;
805 mr->nGroupRecord = htons(1);
809 * Make IP header
810 * */
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 );
816 p_ipHead->id = 0x00;
817 p_ipHead->frag_off = 0x0000;
818 p_ipHead->ttl = 0x01;
819 p_ipHead->protocol = 0x02;
820 p_ipHead->check = 0;
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" );
845 goto error;
848 res = setsockopt(sys->sAMT, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
849 if(res < 0)
851 msg_Err( p_access, "Couldn't make socket reusable");
852 goto error;
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) );
862 goto error;
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" );
869 goto error;
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" );
883 goto error;
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 );
893 if( res != 1 )
895 msg_Err( p_access, "Could not convert loopback address" );
896 goto error;
898 /* TODO: stSvrAddr is unused ? */
900 return 0;
902 error:
903 if( sys->sAMT != -1 )
905 net_Close( sys->sAMT );
906 sys->sAMT = -1;
909 if( sys->sQuery != -1 )
911 net_Close( sys->sQuery );
912 sys->sQuery = -1;
914 return -1;
918 * Send a relay discovery message, before 3-way handshake
919 * */
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;
924 int nRet;
925 access_sys_t *sys = p_access->p_sys;
927 /* initialize variables */
928 memset( chaSendBuffer, 0, sizeof(chaSendBuffer) );
929 ulNonce = 0;
930 nRet = 0;
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;
950 /* send it */
951 nRet = sendto( sys->sAMT, chaSendBuffer, sizeof(chaSendBuffer), 0,\
952 (struct sockaddr *)&sys->relayDiscoAddr, sizeof(struct sockaddr) );
954 if( nRet < 0)
955 msg_Err( p_access, "Sendto failed to %s with error %d.", relay_ip, errno);
959 * Send relay request message, stage 2 of handshake
960 * */
961 static void amt_send_relay_request( stream_t *p_access, char *relay_ip )
963 char chaSendBuffer[AMT_REQUEST_MSG_LEN];
964 uint32_t ulNonce;
965 int nRet;
966 access_sys_t *sys = p_access->p_sys;
968 memset( chaSendBuffer, 0, sizeof(chaSendBuffer) );
970 ulNonce = 0;
971 nRet = 0;
974 * create AMT request message format
975 * +-----------------------------------------------------------------+
976 * | Msg Type(1Byte)| Reserved(1byte)|P flag(1byte)|Reserved (2 byte)|
977 * +-----------------------------------------------------------------+
978 * | nonce (4byte) |
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:
984 * Value Meaning
986 * 0 The relay MUST respond with a Membership Query message that
987 * contains an IPv4 packet carrying an IGMPv3 General Query
988 * message.
989 * 1 The relay MUST respond with a Membership Query message that
990 * contains an IPv6 packet carrying an MLDv2 General Query
991 * message.
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 );
1005 if( nRet < 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 );
1029 /* copy nonce */
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) );
1040 if( res != 1 )
1042 msg_Err(p_access, "Could not convert all hosts multicast address: %s", gai_strerror(errno) );
1043 return;
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;
1058 } else {
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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1091 * | |
1092 * ~ Relay Address (IPv4 or IPv6) ~
1093 * | |
1094 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1095 * */
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) )
1110 case 0:
1111 msg_Err(p_access, "AMT relay advertisement receive time-out");
1112 /* fall through */
1113 case -1:
1114 return false;
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 );
1121 if (len < 0)
1123 msg_Err(p_access, "Received message length less than zero");
1124 return false;
1127 /* AMT Relay Advertisement data (RFC7450) */
1128 struct {
1129 uint32_t ulRcvNonce;
1130 uint32_t ipAddr;
1131 uint8_t type;
1132 } relay_adv_msg;
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. ");
1138 return false;
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) );
1145 return false;
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) );
1158 if( nRet < 0 )
1160 msg_Err( p_access, "Error connecting AMT UDP socket: %s", vlc_strerror(errno) );
1161 return false;
1164 return true;
1168 * Receive relay membership query message
1169 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1170 | V=0 |Type=4 | Reserved |L|G| Response MAC |
1171 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
1173 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1174 | Request Nonce |
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) )
1205 case 0:
1206 msg_Err(p_access, "AMT relay membership query receive time-out");
1207 /* fall through */
1208 case -1:
1209 return false;
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");
1217 return false;
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 );
1227 return false;
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;
1243 else
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 );
1253 return true;
1257 * Join SSM group based on input addresses, or use the defaults
1258 * */
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) );
1270 #else
1271 errno = EINVAL;
1272 return -1;
1273 #endif
1276 static int amt_joinASM_group( stream_t *p_access )
1278 struct ip_mreq imr;
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.
1289 * */
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) );
1301 #else
1302 errno = EINVAL;
1303 return -1;
1304 #endif
1308 * Leave ASM group that was joined earlier.
1309 * */
1310 static int amt_leaveASM_group( stream_t *p_access )
1312 struct ip_mreq imr;
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 );