From 1e6a73e98fecaba8226d5a4aa3cdc84e410f8029 Mon Sep 17 00:00:00 2001 From: Gerrit Renker Date: Sat, 14 Feb 2009 18:36:42 +0100 Subject: [PATCH] Allow to specify UDPv4/6 multicast interface Using a Posix function, this allows to specify outgoing/incoming multicast interfaces, e.g. para_server --udp_target=224.0.1.38:8000 --udp_mcast_iface=eth1 or para_audiod -D -r 'ogg:udp -i 224.0.1.38 -I eth0' The option simplifies multicast streaming, which is now possible without having to set a multicast route when there is more than one network interface. This option works unconditionally for UDPv6. For UDPv4 it is only enabled on hosts that support `struct ip_mreqn', support for which is detected via configure. On OSes that do not support this struct, a warning message is printed; while it is possible to add the same functionality also in those cases, it would complicate the implementation and thus has been left out. --- configure.ac | 14 ++++++++++++++ ggo/server.m4 | 6 ++++++ ggo/udp_recv.ggo | 4 ++++ udp_header.h | 1 + udp_recv.c | 26 ++++++++++++++++++++++---- udp_send.c | 32 ++++++++++++++++++++++++++++---- 6 files changed, 75 insertions(+), 8 deletions(-) diff --git a/configure.ac b/configure.ac index afb993af..13ec59ba 100644 --- a/configure.ac +++ b/configure.ac @@ -267,6 +267,20 @@ fi CPPFLAGS="$OLD_CPPFLAGS" LDFLAGS="$OLD_LDFLAGS" LIBS="$OLD_LIBS" + +########################################################################### ip_mreqn +AC_MSG_CHECKING(for struct ip_mreqn (UDPv4 multicast)) +AC_TRY_LINK([ + #include + #include +],[ + struct ip_mreqn mn; + mn.imr_ifindex = 0; +],[have_ip_mreqn=yes],[have_ip_mreqn=no]) +AC_MSG_RESULT($have_ip_mreqn) +if test ${have_ip_mreqn} = yes; then + AC_DEFINE(HAVE_IP_MREQN, 1, define to 1 you have struct ip_mreqn) +fi ########################################################################### osx AC_MSG_CHECKING(for CoreAudio (MacOs)) diff --git a/ggo/server.m4 b/ggo/server.m4 index 65da7436..8aa753b9 100644 --- a/ggo/server.m4 +++ b/ggo/server.m4 @@ -271,6 +271,12 @@ int typestr="port" default="8000" optional +option "udp_mcast_iface" - +#~~~~~~~~~~~~~~~~~~~~~~~~~~ +"outgoing udp multicast interface" +string +optional + option "udp_header_interval" H #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ "duration for sending header" diff --git a/ggo/udp_recv.ggo b/ggo/udp_recv.ggo index 6ad09f47..f19d3194 100644 --- a/ggo/udp_recv.ggo +++ b/ggo/udp_recv.ggo @@ -11,3 +11,7 @@ option "port" p "udp port" int typestr="portnumber" default="8000" optional + +option "iface" I "receiving udp multicast interface" +string +optional diff --git a/udp_header.h b/udp_header.h index 83157e6b..7e94b584 100644 --- a/udp_header.h +++ b/udp_header.h @@ -5,6 +5,7 @@ */ /** \file udp_header.h some macros used by udp_send.c and udp_recv.c. */ +#include /** * Number of bytes of the paraslash udp header. diff --git a/udp_recv.c b/udp_recv.c index ccd769b0..9ea35d8d 100644 --- a/udp_recv.c +++ b/udp_recv.c @@ -221,24 +221,40 @@ static void *udp_recv_parse_config(int argc, char **argv) * Perform AF-independent joining of multicast receive addresses. * * \param fd Bound socket descriptor. + * \param iface The receiving multicast interface, or NULL for the default. * * \return Zero if okay, negative on error. */ -static int mcast_receiver_setup(int fd) +static int mcast_receiver_setup(int fd, const char *iface) { struct sockaddr_storage ss; socklen_t sslen = sizeof(ss); + int id = iface == NULL ? 0 : if_nametoindex(iface); if (getsockname(fd, (struct sockaddr *)&ss, &sslen) < 0) goto err; + if (iface != NULL && id == 0) + PARA_WARNING_LOG("could not resolve interface %s, using default", iface); + switch (ss.ss_family) { case AF_INET: if (IN_MULTICAST(htonl(((struct sockaddr_in *)&ss)->sin_addr.s_addr))) { +#ifdef HAVE_IP_MREQN + struct ip_mreqn m4; + + m4.imr_address.s_addr = INADDR_ANY; + m4.imr_ifindex = id; +#else struct ip_mreq m4; - memset(&m4, 0, sizeof(m4)); - m4.imr_multiaddr = ((struct sockaddr_in *)&ss)->sin_addr; + m4.imr_interface.s_addr = INADDR_ANY; + if (id != 0) + PARA_ERROR_LOG("Setting IPv4 receiver mcast interface not supported."); + +#endif + m4.imr_multiaddr = ((struct sockaddr_in *)&ss)->sin_addr; + if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &m4, sizeof(m4)) < 0) break; } @@ -249,6 +265,7 @@ static int mcast_receiver_setup(int fd) memset(&m6, 0, sizeof(m6)); memcpy(&m6.ipv6mr_multiaddr, &((struct sockaddr_in6 *)&ss)->sin6_addr, 16); + m6.ipv6mr_interface = id; if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &m6, sizeof(m6)) < 0) break; } @@ -265,6 +282,7 @@ static int udp_recv_open(struct receiver_node *rn) { struct private_udp_recv_data *purd; struct udp_recv_args_info *c = rn->conf; + char *iface = c->iface_given ? c->iface_arg : NULL; int ret; rn->buf = para_calloc(UDP_RECV_CHUNK_SIZE); @@ -276,7 +294,7 @@ static int udp_recv_open(struct receiver_node *rn) goto err; purd->fd = ret; - ret = mcast_receiver_setup(purd->fd); + ret = mcast_receiver_setup(purd->fd, iface); if (ret < 0) { close(purd->fd); return ret; diff --git a/udp_send.c b/udp_send.c index 8eee7e61..140458e7 100644 --- a/udp_send.c +++ b/udp_send.c @@ -74,19 +74,24 @@ static void udp_delete_target(struct udp_target *ut, const char *msg) * \param fd The connected socket descriptor. * \param ttl UDPv4 multicast TTL or UDPv6 multicast number of hops. * Use -1 to mean default, 0..255 otherwise. - - ** \return Zero if okay, negative on error. + * \param iface The outgoing multicast interface, or NULL for the default. + * + * \return Zero if okay, negative on error. */ -static int mcast_sender_setup(struct udp_target *ut, int ttl) +static int mcast_sender_setup(struct udp_target *ut, int ttl, char *iface) { struct sockaddr_storage ss; socklen_t sslen = sizeof(ss); const int on = 1; + int id = iface == NULL ? 0 : if_nametoindex(iface); if (getpeername(ut->fd, (struct sockaddr *)&ss, &sslen) < 0) goto err; + if (iface != NULL && id == 0) + PARA_WARNING_LOG("could not resolve interface %s, using default", iface); + /* RFC 3493, 5.2: -1 means 'use kernel default' */ if (ttl < 0 || ttl > 255) ttl = -1; @@ -95,6 +100,18 @@ static int mcast_sender_setup(struct udp_target *ut, int ttl) case AF_INET: if (!IN_MULTICAST(htonl(((struct sockaddr_in *)&ss)->sin_addr.s_addr))) return 0; + if (id != 0) { +#ifdef HAVE_IP_MREQN + struct ip_mreqn mn; + + memset(&mn, 0, sizeof(mn)); + mn.imr_ifindex = id; + if (setsockopt(ut->fd, IPPROTO_IP, IP_MULTICAST_IF, &mn, sizeof(mn)) < 0) + goto err; +#else + PARA_ERROR_LOG("No support for setting outgoing IPv4 mcast interface."); +#endif + } /* * Enable receiving multicast messages generated on the local host * At least on Linux, this is enabled by default. @@ -111,6 +128,9 @@ static int mcast_sender_setup(struct udp_target *ut, int ttl) case AF_INET6: if (!IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)&ss)->sin6_addr)) return 0; + if (id != 0 && + setsockopt(ut->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &id, sizeof(id)) < 0) + break; if (setsockopt(ut->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &on, sizeof(on)) < 0) break; if (setsockopt(ut->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) < 0) @@ -130,6 +150,7 @@ err: static int udp_init_session(struct udp_target *ut) { int ret; + char *iface = NULL; if (ut->fd >= 0) /* nothing to do */ return 0; @@ -139,7 +160,10 @@ static int udp_init_session(struct udp_target *ut) return ret; ut->fd = ret; - ret = mcast_sender_setup(ut, conf.udp_ttl_arg); + if (conf.udp_mcast_iface_given) + iface = conf.udp_mcast_iface_arg; + + ret = mcast_sender_setup(ut, conf.udp_ttl_arg, iface); if (ret < 0) { close(ut->fd); return ret; -- 2.30.2