Skip to content

Commit d1023bd

Browse files
committed
net_udp: multicast: multiple improvements
- support for v4-mapped IPv6 sockets (handled with v4 sockopts in Linux+Win and v6 sockopts in macOS) - for mcast4, except of Windows, the interface must be identified by local address, not device number - support also setting the bind address directly for v4 - leave mcast4 group with correct ID (not INADDR_ANY) The local address interface specification was actually the original one but was removed by the commit 92e24dd (in 2012).
1 parent 65e8bf3 commit d1023bd

File tree

2 files changed

+180
-18
lines changed

2 files changed

+180
-18
lines changed

src/rtp/net_udp.c

Lines changed: 179 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
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"
@@ -71,8 +75,11 @@
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);
7479
static int resolve_address(socket_udp *s, const char *addr, uint16_t tx_port);
7580
static 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 {
167174
struct _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+
767925
ADD_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

src/utils/net.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#endif
5757

5858
enum {
59+
IN4_MAX_ASCII_LEN = 4 * 3 + 3, ///< max len of IPv4 addr str (w/o '\0')
5960
/// maximal length of textual representation of IPv6 address including
6061
/// eventual scope ID but without terminating NUL byte
6162
IN6_MAX_ASCII_LEN = 40 /* 32 nibbles + 7 colons + "%" */ + IF_NAMESIZE,

0 commit comments

Comments
 (0)