Allow to specify UDPv4/6 multicast interface
authorGerrit Renker <gerrit@erg.abdn.ac.uk>
Sat, 14 Feb 2009 17:36:42 +0000 (18:36 +0100)
committerAndre Noll <maan@systemlinux.org>
Mon, 16 Feb 2009 16:45:00 +0000 (17:45 +0100)
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
ggo/server.m4
ggo/udp_recv.ggo
udp_header.h
udp_recv.c
udp_send.c

index afb993a..13ec59b 100644 (file)
@@ -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 <netdb.h>
+       #include <net/if.h>
+],[
+       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))
index 65da743..8aa753b 100644 (file)
@@ -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"
index 6ad09f4..f19d319 100644 (file)
@@ -11,3 +11,7 @@ option "port" p "udp port"
 int typestr="portnumber"
 default="8000"
 optional
+
+option "iface" I "receiving udp multicast interface"
+string
+optional
index 83157e6..7e94b58 100644 (file)
@@ -5,6 +5,7 @@
  */
 
 /** \file udp_header.h some macros used by udp_send.c and udp_recv.c. */
+#include <net/if.h>
 
 /**
  * Number of bytes of the paraslash udp header.
index ccd769b..9ea35d8 100644 (file)
@@ -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;
index 8eee7e6..140458e 100644 (file)
@@ -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;