5151#include <pthread.h>
5252#include <stdalign.h>
5353
54+ #ifndef _WIN32
55+ #include <ifaddrs.h>
56+ #endif
57+
5458#include "debug.h"
5559#include "host.h"
5660#include "memory.h"
7175
7276#define DEFAULT_MAX_UDP_READER_QUEUE_LEN (1920/3*8*1080/1152) //< 10-bit FullHD frame divided by 1280 MTU packets (minus headers)
7377
78+ static unsigned get_ifindex (const char * iface );
7479static int resolve_address (socket_udp * s , const char * addr , uint16_t tx_port );
7580static void * udp_reader (void * arg );
81+ static void udp_leave_mcast_grp4 (unsigned long addr , int fd ,
82+ const char iface []);
7683
7784#define IPv4 4
7885#define IPv6 6
@@ -167,7 +174,7 @@ struct socket_udp_local {
167174struct _socket_udp {
168175 struct sockaddr_storage sock ;
169176 socklen_t sock_len ;
170- unsigned int ifindex ; ///< iface index for multicast
177+ char iface [ IF_NAMESIZE ] ; ///< iface for multicast
171178
172179 struct socket_udp_local * local ;
173180 bool local_is_slave ; // whether is the local
@@ -364,16 +371,70 @@ static bool udp_addr_valid4(const char *dst)
364371 return false;
365372}
366373
367- static bool udp_join_mcast_grp4 (unsigned long addr , int rx_fd , int tx_fd , int ttl , unsigned int ifindex )
374+ /**
375+ * @returns 1. 0 (htonl(INADDR_ANY)) if iface is "";
376+ * 2. representation of the address if iface is an adress
377+ * 3a. [Windows only] interface index
378+ * 3b. [otherwise] iface local address
379+ */
380+ static in_addr_t
381+ get_iface_local_addr4 (const char iface [])
382+ {
383+ if (iface [0 ] == '\0' ) {
384+ return htonl (INADDR_ANY );
385+ }
386+
387+ // address specified by bind address
388+ struct in_addr iface_addr ;
389+ if (inet_pton (AF_INET , iface , & iface_addr ) == 1 ) {
390+ return iface_addr .s_addr ;
391+ }
392+
393+ unsigned int ifindex = get_ifindex (iface );
394+ if (ifindex == (unsigned ) -1 ) {
395+ return htonl (INADDR_ANY );
396+ }
397+
398+ #ifdef _WIN32
399+ // Windows allow the interface specification by ifindex
400+ return htonl (ifindex );
401+ #else
402+ struct ifaddrs * a = NULL ;
403+ getifaddrs (& a );
404+ struct ifaddrs * p = a ;
405+ while (NULL != p ) {
406+ if (p -> ifa_addr != NULL && p -> ifa_addr -> sa_family == AF_INET &&
407+ strcmp (iface , p -> ifa_name ) == 0 ) {
408+ struct sockaddr_in * sin = (void * ) p -> ifa_addr ;
409+ in_addr_t ret = sin -> sin_addr .s_addr ;
410+ freeifaddrs (a );
411+ return ret ;
412+ }
413+ p = p -> ifa_next ;
414+ }
415+ freeifaddrs (a );
416+ MSG (ERROR , "Interface %s has assigned no IPv4 address!\n" , iface );
417+ return htonl (INADDR_ANY );
418+ #endif
419+ }
420+
421+ static bool
422+ udp_join_mcast_grp4 (unsigned long addr , int rx_fd , int tx_fd , int ttl ,
423+ const char iface [])
368424{
369425 if (IN_MULTICAST (ntohl (addr ))) {
370426#ifndef _WIN32
371427 char loop = 1 ;
372428#endif
373429 struct ip_mreq imr ;
374430
431+ in_addr_t iface_addr = get_iface_local_addr4 (iface );
432+ char buf [IN4_MAX_ASCII_LEN + 1 ] = "(err. unknown)" ;
433+ inet_ntop (AF_INET , & iface_addr , buf , sizeof buf );
434+ MSG (INFO , "mcast4 iface bound to: %s\n" , buf );
435+
375436 imr .imr_multiaddr .s_addr = addr ;
376- imr .imr_interface .s_addr = ifindex ;
437+ imr .imr_interface .s_addr = iface_addr ;
377438
378439 if (SETSOCKOPT
379440 (rx_fd , IPPROTO_IP , IP_ADD_MEMBERSHIP , (char * )& imr ,
@@ -401,20 +462,21 @@ static bool udp_join_mcast_grp4(unsigned long addr, int rx_fd, int tx_fd, int tt
401462 }
402463 if (SETSOCKOPT
403464 (tx_fd , IPPROTO_IP , IP_MULTICAST_IF ,
404- (char * )& ifindex , sizeof ( ifindex ) ) != 0 ) {
465+ (char * ) & iface_addr , sizeof iface_addr ) != 0 ) {
405466 socket_error ("setsockopt IP_MULTICAST_IF" );
406467 return false;
407468 }
408469 }
409470 return true;
410471}
411472
412- static void udp_leave_mcast_grp4 (unsigned long addr , int fd )
473+ static void
474+ udp_leave_mcast_grp4 (unsigned long addr , int fd , const char iface [])
413475{
414476 if (IN_MULTICAST (ntohl (addr ))) {
415477 struct ip_mreq imr ;
416478 imr .imr_multiaddr .s_addr = addr ;
417- imr .imr_interface .s_addr = INADDR_ANY ;
479+ imr .imr_interface .s_addr = get_iface_local_addr4 ( iface ) ;
418480 if (SETSOCKOPT
419481 (fd , IPPROTO_IP , IP_DROP_MEMBERSHIP , (char * )& imr ,
420482 sizeof (struct ip_mreq )) != 0 ) {
@@ -470,16 +532,32 @@ static bool udp_addr_valid6(const char *dst)
470532 return false;
471533}
472534
473- static bool udp_join_mcast_grp6 (struct in6_addr sin6_addr , int rx_fd , int tx_fd , int ttl , unsigned int ifindex )
535+ static bool
536+ udp_join_mcast_grp6 (struct in6_addr sin6_addr , int rx_fd , int tx_fd , int ttl ,
537+ const char iface [])
474538{
475539#ifdef HAVE_IPv6
540+ // macOS handles v4-mapped addresses transparently as other v6 addrs;
541+ // Linux/Win need to use the v4 sockpts
542+ #ifdef __APPLE__
543+ in_addr_t v4mapped = 0 ;
544+ memcpy ((void * ) & v4mapped , sin6_addr .s6_addr + 12 , sizeof v4mapped );
545+ if (IN6_IS_ADDR_MULTICAST (& sin6_addr ) ||
546+ (IN6_IS_ADDR_V4MAPPED (& sin6_addr ) &&
547+ IN_MULTICAST (ntohl (v4mapped )))) {
548+ #else
476549 if (IN6_IS_ADDR_MULTICAST (& sin6_addr )) {
550+ #endif
477551 unsigned int loop = 1 ;
478552 struct ipv6_mreq imr ;
479553#ifdef MUSICA_IPV6
480554 imr .i6mr_interface = 1 ;
481555 imr .i6mr_multiaddr = sin6_addr ;
482556#else
557+ unsigned int ifindex = get_ifindex (iface );
558+ if (ifindex == (unsigned ) -1 ) {
559+ return false;
560+ }
483561 imr .ipv6mr_multiaddr = sin6_addr ;
484562 imr .ipv6mr_interface = ifindex ;
485563#endif
@@ -508,28 +586,61 @@ static bool udp_join_mcast_grp6(struct in6_addr sin6_addr, int rx_fd, int tx_fd,
508586 MSG (WARNING , "Using multicast but not setting TTL.\n" );
509587 }
510588 if (ifindex != 0 ) {
589+ #ifdef __APPLE__
590+ MSG (WARNING ,
591+ "Interface specification may not work "
592+ "with IPv6 sockets on macOS.\n%s\n" ,
593+ IN6_IS_ADDR_MULTICAST (& sin6_addr )
594+ ? "Please contact us for details or resolution."
595+ : "Use '-4' to enforce IPv4 socket" );
596+ #endif
511597 if (SETSOCKOPT (tx_fd , IPPROTO_IPV6 , IPV6_MULTICAST_IF ,
512598 (char * ) & ifindex ,
513599 sizeof (ifindex )) != 0 ) {
514600 socket_error ("setsockopt IPV6_MULTICAST_IF" );
515601 return false;
516602 }
517603 }
604+
605+ return true;
606+ }
607+ if (IN6_IS_ADDR_V4MAPPED (& sin6_addr )) {
608+ in_addr_t v4mapped = 0 ;
609+ memcpy ((void * ) & v4mapped , sin6_addr .s6_addr + 12 ,
610+ sizeof v4mapped );
611+ return udp_join_mcast_grp4 (v4mapped , rx_fd , tx_fd , ttl ,
612+ iface );
518613 }
519614
520615 return true;
616+ #else
617+ return false;
521618#endif
522619}
523620
524- static void udp_leave_mcast_grp6 (struct in6_addr sin6_addr , int fd , unsigned int ifindex )
621+ static void
622+ udp_leave_mcast_grp6 (struct in6_addr sin6_addr , int fd , const char iface [])
525623{
526624#ifdef HAVE_IPv6
625+ // see udp_join_mcast_grp for mac/Linux+Win difference
626+ #ifdef __APPLE__
627+ in_addr_t v4mapped = 0 ;
628+ memcpy ((void * ) & v4mapped , sin6_addr .s6_addr + 12 , sizeof v4mapped );
629+ if (IN6_IS_ADDR_MULTICAST (& sin6_addr ) ||
630+ (IN6_IS_ADDR_V4MAPPED (& sin6_addr ) &&
631+ IN_MULTICAST (ntohl (v4mapped )))) {
632+ #else
527633 if (IN6_IS_ADDR_MULTICAST (& sin6_addr )) {
634+ #endif
528635 struct ipv6_mreq imr ;
529636#ifdef MUSICA_IPV6
530637 imr .i6mr_interface = 1 ;
531638 imr .i6mr_multiaddr = sin6_addr ;
532639#else
640+ unsigned int ifindex = get_ifindex (iface );
641+ if (ifindex == (unsigned ) -1 ) {
642+ return ;
643+ }
533644 imr .ipv6mr_multiaddr = sin6_addr ;
534645 imr .ipv6mr_interface = ifindex ;
535646#endif
@@ -539,7 +650,13 @@ static void udp_leave_mcast_grp6(struct in6_addr sin6_addr, int fd, unsigned int
539650 sizeof (struct ipv6_mreq )) != 0 ) {
540651 socket_error ("setsockopt IPV6_DROP_MEMBERSHIP" );
541652 }
653+ } else if (IN6_IS_ADDR_V4MAPPED (& sin6_addr )) {
654+ in_addr_t v4mapped = 0 ;
655+ memcpy ((void * ) & v4mapped , sin6_addr .s6_addr + 12 ,
656+ sizeof v4mapped );
657+ udp_leave_mcast_grp4 (v4mapped , fd , iface );
542658 }
659+
543660#else
544661 UNUSED (s );
545662#endif /* HAVE_IPv6 */
@@ -764,6 +881,47 @@ static bool set_sock_opts_and_bind(fd_t fd, bool ipv6, uint16_t rx_port, int ttl
764881 return true;
765882}
766883
884+ static unsigned
885+ get_ifindex (const char * iface )
886+ {
887+ if (iface [0 ] == '\0' ) {
888+ return 0 ; // default
889+ }
890+ if (strcmp (iface , "help" ) == 0 ) {
891+ printf (
892+ "mcast interface specification can be one of following:\n" );
893+ printf ( "1. interface name\n" );
894+ printf ( "2. interface index\n" );
895+ printf ( "3. bind address (IPv4 only)\n" );
896+ printf ("\n" );
897+ return (unsigned ) -1 ;
898+ }
899+ const unsigned ret = if_nametoindex (iface );
900+ if (ret != 0 ) {
901+ return ret ;
902+ }
903+ // check if the value isn't the index itself
904+ char * endptr = NULL ;
905+ const long val = strtol (iface , & endptr , 0 );
906+ if (* endptr == '\0' && val >= 0 && (unsigned ) val <= UINT_MAX ) {
907+ char buf [IF_NAMESIZE ];
908+ if (if_indextoname (val , buf ) != NULL ) {
909+ MSG (INFO , "Using mcast interface %s\n" , buf );
910+ return val ;
911+ }
912+ }
913+ struct in_addr iface_addr ;
914+ if (inet_pton (AF_INET , iface , & iface_addr ) == 1 ) {
915+ error_msg ("Interface identified with addres %s not allowed "
916+ "here. Try '-4'...\n" ,
917+ iface );
918+ return (unsigned ) -1 ;
919+ }
920+ error_msg ("Illegal interface specification '%s': %s\n" , iface ,
921+ ug_strerror (errno ));
922+ return (unsigned ) -1 ;
923+ }
924+
767925ADD_TO_PARAM ("udp-queue-len" ,
768926 "* udp-queue-len=<l>\n"
769927 " Use different queue size than default DEFAULT_MAX_UDP_READER_QUEUE_LEN\n" );
@@ -814,12 +972,7 @@ socket_udp *udp_init_if(const char *addr, const char *iface, uint16_t rx_port,
814972 goto error ;
815973 }
816974 if (iface != NULL ) {
817- if ((s -> ifindex = if_nametoindex (iface )) == 0 ) {
818- error_msg ("Illegal interface specification\n" );
819- goto error ;
820- }
821- } else {
822- s -> ifindex = 0 ;
975+ snprintf_ch (s -> iface , "%s" , iface );
823976 }
824977#ifdef _WIN32
825978 if (!is_host_loopback (addr )) {
@@ -877,12 +1030,16 @@ socket_udp *udp_init_if(const char *addr, const char *iface, uint16_t rx_port,
8771030
8781031 switch (s -> local -> mode ) {
8791032 case IPv4 :
880- if (!udp_join_mcast_grp4 (((struct sockaddr_in * )& s -> sock )-> sin_addr .s_addr , s -> local -> rx_fd , s -> local -> tx_fd , ttl , s -> ifindex )) {
1033+ if (!udp_join_mcast_grp4 (
1034+ ((struct sockaddr_in * ) & s -> sock )-> sin_addr .s_addr ,
1035+ s -> local -> rx_fd , s -> local -> tx_fd , ttl , s -> iface )) {
8811036 goto error ;
8821037 }
8831038 break ;
8841039 case IPv6 :
885- if (!udp_join_mcast_grp6 (((struct sockaddr_in6 * )& s -> sock )-> sin6_addr , s -> local -> rx_fd , s -> local -> tx_fd , ttl , s -> ifindex )) {
1040+ if (!udp_join_mcast_grp6 (
1041+ ((struct sockaddr_in6 * ) & s -> sock )-> sin6_addr ,
1042+ s -> local -> rx_fd , s -> local -> tx_fd , ttl , s -> iface )) {
8861043 goto error ;
8871044 }
8881045 break ;
@@ -981,10 +1138,14 @@ void udp_exit(socket_udp * s)
9811138 }
9821139 switch (s -> local -> mode ) {
9831140 case IPv4 :
984- udp_leave_mcast_grp4 (((struct sockaddr_in * )& s -> sock )-> sin_addr .s_addr , s -> local -> rx_fd );
1141+ udp_leave_mcast_grp4 (
1142+ ((struct sockaddr_in * ) & s -> sock )-> sin_addr .s_addr ,
1143+ s -> local -> rx_fd , s -> iface );
9851144 break ;
9861145 case IPv6 :
987- udp_leave_mcast_grp6 (((struct sockaddr_in6 * )& s -> sock )-> sin6_addr , s -> local -> rx_fd , s -> ifindex );
1146+ udp_leave_mcast_grp6 (
1147+ ((struct sockaddr_in6 * ) & s -> sock )-> sin6_addr ,
1148+ s -> local -> rx_fd , s -> iface );
9881149 break ;
9891150 }
9901151
0 commit comments