chiark / gitweb /
rationalize playrtp multicast support
authorRichard Kettlewell <rjk@greenend.org.uk>
Thu, 22 Nov 2007 16:47:33 +0000 (16:47 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Thu, 22 Nov 2007 16:47:33 +0000 (16:47 +0000)
clients/playrtp.c
doc/disorder-playrtp.1.in
lib/addr.c
lib/addr.h
server/speaker-network.c

index 908c619..9757be6 100644 (file)
@@ -199,7 +199,6 @@ static const struct option options[] = {
   { "max", required_argument, 0, 'x' },
   { "buffer", required_argument, 0, 'b' },
   { "rcvbuf", required_argument, 0, 'R' },
-  { "multicast", required_argument, 0, 'M' },
 #if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
   { "oss", no_argument, 0, 'o' },
 #endif
@@ -491,7 +490,6 @@ static void help(void) {
           "  --buffer, -b FRAMES     Buffer high water mark\n"
           "  --max, -x FRAMES        Buffer maximum size\n"
           "  --rcvbuf, -R BYTES      Socket receive buffer size\n"
-          "  --multicast, -M GROUP   Join multicast group\n"
           "  --config, -C PATH       Set configuration file\n"
 #if HAVE_ALSA_ASOUNDLIB_H
           "  --alsa, -a              Use ALSA to play audio\n"
@@ -523,11 +521,17 @@ int main(int argc, char **argv) {
   char *sockname;
   int rcvbuf, target_rcvbuf = 131072;
   socklen_t len;
-  char *multicast_group = 0;
   struct ip_mreq mreq;
   struct ipv6_mreq mreq6;
   disorder_client *c;
   char *address, *port;
+  int is_multicast;
+  union any_sockaddr {
+    struct sockaddr sa;
+    struct sockaddr_in in;
+    struct sockaddr_in6 in6;
+  };
+  union any_sockaddr mgroup;
 
   static const struct addrinfo prefs = {
     AI_PASSIVE,
@@ -553,7 +557,6 @@ int main(int argc, char **argv) {
     case 'x': maxbuffer = 2 * atol(optarg); break;
     case 'L': logfp = fopen(optarg, "w"); break;
     case 'R': target_rcvbuf = atoi(optarg); break;
-    case 'M': multicast_group = optarg; break;
 #if HAVE_ALSA_ASOUNDLIB_H
     case 'a': backend = playrtp_alsa; break;
 #endif
@@ -575,44 +578,70 @@ int main(int argc, char **argv) {
   argv += optind;
   switch(argc) {
   case 0:
-  case 1:
+    /* Get configuration from server */
     if(!(c = disorder_new(1))) exit(EXIT_FAILURE);
     if(disorder_connect(c)) exit(EXIT_FAILURE);
     if(disorder_rtp_address(c, &address, &port)) exit(EXIT_FAILURE);
-    sl.n = 1;
-    sl.s = &port;
-    /* set multicast_group if address is a multicast address */
+    sl.n = 2;
+    sl.s = xcalloc(2, sizeof *sl.s);
+    sl.s[0] = address;
+    sl.s[1] = port;
     break;
+  case 1:
   case 2:
+    /* Use command-line ADDRESS+PORT or just PORT */
     sl.n = argc;
     sl.s = argv;
     break;
   default:
-    fatal(0, "usage: disorder-playrtp [OPTIONS] [ADDRESS [PORT]]");
+    fatal(0, "usage: disorder-playrtp [OPTIONS] [[ADDRESS] PORT]");
   }
-  /* Listen for inbound audio data */
+  /* Look up address and port */
   if(!(res = get_address(&sl, &prefs, &sockname)))
     exit(1);
-  info("listening on %s", sockname);
+  /* Create the socket */
   if((rtpfd = socket(res->ai_family,
                      res->ai_socktype,
                      res->ai_protocol)) < 0)
     fatal(errno, "error creating socket");
+  /* Stash the multicast group address */
+  if((is_multicast = multicast(res->ai_addr))) {
+    memcpy(&mgroup, res->ai_addr, res->ai_addrlen);
+    switch(res->ai_addr->sa_family) {
+    case AF_INET:
+      mgroup.in.sin_port = 0;
+      break;
+    case AF_INET6:
+      mgroup.in6.sin6_port = 0;
+      break;
+    }
+  }
+  /* Bind to 0/port */
+  switch(res->ai_addr->sa_family) {
+  case AF_INET:
+    memset(&((struct sockaddr_in *)res->ai_addr)->sin_addr, 0,
+           sizeof (struct in_addr));
+    break;
+  case AF_INET6:
+    memset(&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 0,
+           sizeof (struct in6_addr));
+    break;
+  default:
+    fatal(0, "unsupported family %d", (int)res->ai_addr->sa_family);
+  }
   if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0)
     fatal(errno, "error binding socket to %s", sockname);
-  if(multicast_group) {
-    if((n = getaddrinfo(multicast_group, 0, &prefs, &res)))
-      fatal(0, "getaddrinfo %s: %s", multicast_group, gai_strerror(n));
-    switch(res->ai_family) {
+  if(is_multicast) {
+    switch(mgroup.sa.sa_family) {
     case PF_INET:
-      mreq.imr_multiaddr = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
+      mreq.imr_multiaddr = mgroup.in.sin_addr;
       mreq.imr_interface.s_addr = 0;      /* use primary interface */
       if(setsockopt(rtpfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                     &mreq, sizeof mreq) < 0)
         fatal(errno, "error calling setsockopt IP_ADD_MEMBERSHIP");
       break;
     case PF_INET6:
-      mreq6.ipv6mr_multiaddr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
+      mreq6.ipv6mr_multiaddr = mgroup.in6.sin6_addr;
       memset(&mreq6.ipv6mr_interface, 0, sizeof mreq6.ipv6mr_interface);
       if(setsockopt(rtpfd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
                     &mreq6, sizeof mreq6) < 0)
@@ -621,7 +650,10 @@ int main(int argc, char **argv) {
     default:
       fatal(0, "unsupported address family %d", res->ai_family);
     }
-  }
+    info("listening on %s multicast group %s",
+         format_sockaddr(res->ai_addr), format_sockaddr(&mgroup.sa));
+  } else
+    info("listening on %s", format_sockaddr(res->ai_addr));
   len = sizeof rcvbuf;
   if(getsockopt(rtpfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len) < 0)
     fatal(errno, "error calling getsockopt SO_RCVBUF");
index cc2f1ac..74ce6e3 100644 (file)
@@ -23,26 +23,27 @@ disorder-playrtp \- play DisOrder network broadcasts
 .B disorder-playrtp
 .RI [ OPTIONS ]
 .RB [ -- ]
-.I ADDRESS
-.I PORT
+.RI [[ GROUP ]
+.II PORT ]
 .SH DESCRIPTION
 \fBdisorder-playrtp\fR plays a network broadcast sent from the specified
 address.
 .PP
-Normally the \fIADDRESS\fR would be 0.0.0.0.  The \fIPORT\fR will depend on the
-server configuration.  You may need to specify the \fB--multicast\fR option
-(see below) and if you have more than one soundcard perhaps the \fB--device\fR
-option.
+If neither a group nor port are specified then the local DisOrder
+configuration is consulted to find the server and the server is asked where the
+RTP stream is.
+.PP
+If just a port is specified then the RTP stream is assumed to be unicast or
+broadcast to that port.
+.PP
+If a group and a port are specified then the RTP stream is assumed to be
+multicast to that group and port.
 .SH OPTIONS
 .TP
 .B --device \fIDEVICE\fR, \fB-D \fIDEVICE\fR
 Specifies the audio device to use.  The exact meaning of this is
 platform-dependent; on Linux it is the ALSA device name.
 .TP
-.B --multicast \fIGROUP\fR, \fB-M \fIGROUP\fR
-Specifies a multicast group to join.  This is necessary if the network
-broadcast is being multicasted rather than broadcast.
-.TP
 .B --help\fR, \fB-h
 Display a usage message.
 .TP
index 579fd0f..f1f1c21 100644 (file)
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <netdb.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
 
 #include "log.h"
 #include "printf.h"
 #include "configuration.h"
 #include "addr.h"
+#include "mem.h"
 
 /** @brief Convert a pair of strings to an address
  * @param a Pointer to string list
@@ -120,7 +123,75 @@ int addrinfocmp(const struct addrinfo *a,
     return memcmp(a->ai_addr, b->ai_addr, a->ai_addrlen); /* kludge */
   }
 }
-  
+
+static inline int multicast4(const struct sockaddr_in *sin) {
+  return IN_MULTICAST(ntohl(sin->sin_addr.s_addr));
+}
+
+static inline int multicast6(const struct sockaddr_in6 *sin6) {
+  return IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr);
+}
+
+/** @brief Return true if @p sa represents a multicast address */
+int multicast(const struct sockaddr *sa) {
+  switch(sa->sa_family) {
+  case AF_INET:
+    return multicast4((const struct sockaddr_in *)sa);
+  case AF_INET6:
+    return multicast6((const struct sockaddr_in6 *)sa);
+  default:
+    return 0;
+  }
+}
+
+static inline char *format_sockaddr4(const struct sockaddr_in *sin) {
+  char buffer[1024], *r;
+
+  if(sin->sin_port)
+    byte_xasprintf(&r, "%s port %u",
+                  inet_ntop(sin->sin_family, &sin->sin_addr,
+                            buffer, sizeof buffer),
+                  ntohs(sin->sin_port));
+  else
+    byte_xasprintf(&r, "%s",
+                  inet_ntop(sin->sin_family, &sin->sin_addr,
+                            buffer, sizeof buffer));
+  return r;
+}
+
+static inline char *format_sockaddr6(const struct sockaddr_in6 *sin6) {
+  char buffer[1024], *r;
+
+  if(sin6->sin6_port)
+    byte_xasprintf(&r, "%s port %u",
+                  inet_ntop(sin6->sin6_family, &sin6->sin6_addr,
+                            buffer, sizeof buffer),
+                  ntohs(sin6->sin6_port));
+  else
+    byte_xasprintf(&r, "%s",
+                  inet_ntop(sin6->sin6_family, &sin6->sin6_addr,
+                            buffer, sizeof buffer));
+  return r;
+}
+
+static inline char *format_sockaddrun(const struct sockaddr_un *sun) {
+  return xstrdup(sun->sun_path);
+}
+    
+/** @brief Construct a text description a sockaddr */
+char *format_sockaddr(const struct sockaddr *sa) {
+  switch(sa->sa_family) {
+  case AF_INET:
+    return format_sockaddr4((const struct sockaddr_in *)sa);
+  case AF_INET6:
+    return format_sockaddr6((const struct sockaddr_in6 *)sa);
+  case AF_UNIX:
+    return format_sockaddrun((const struct sockaddr_un *)sa);
+  default:
+    return 0;
+  }
+}
+
 /*
 Local Variables:
 c-basic-offset:2
index 9ce2720..fb1dd7f 100644 (file)
@@ -28,6 +28,9 @@ struct addrinfo *get_address(const struct stringlist *a,
 int addrinfocmp(const struct addrinfo *a,
                const struct addrinfo *b);
 
+int multicast(const struct sockaddr *sa);
+char *format_sockaddr(const struct sockaddr *sa);
+
 #endif /* ADDR_H */
 
 /*
index c8edbfe..40abca5 100644 (file)
@@ -118,14 +118,7 @@ static void network_init(void) {
                    res->ai_socktype,
                    res->ai_protocol)) < 0)
     fatal(errno, "error creating broadcast socket");
-  if((res->ai_family == PF_INET
-      && IN_MULTICAST(
-           ntohl(((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr)
-         ))
-     || (res->ai_family == PF_INET6
-         && IN6_IS_ADDR_MULTICAST(
-               &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr
-            ))) {
+  if(multicast(res->ai_addr)) {
     /* Multicasting */
     switch(res->ai_family) {
     case PF_INET: {