server: implement multiple-unicast RTP
authorRichard Kettlewell <rjk@greenend.org.uk>
Sun, 10 Nov 2013 14:00:02 +0000 (14:00 +0000)
committerRichard Kettlewell <rjk@terraraq.org.uk>
Sun, 10 Nov 2013 14:04:18 +0000 (14:04 +0000)
Updates the protocol definition and implementation and
exposes the uaudio-rtp rtp_mode variable in the config.

12 files changed:
doc/disorder_config.5.in
doc/disorder_protocol.5.in
lib/client-stubs.c
lib/client-stubs.h
lib/configuration.c
lib/configuration.h
lib/eclient-stubs.c
lib/eclient-stubs.h
lib/uaudio-rtp.c
scripts/protocol
server/disorder-server.h
server/server.c

index 68ae601..8f0c8f3 100644 (file)
@@ -1,5 +1,5 @@
 .\"
-.\" Copyright (C) 2004-2009 Richard Kettlewell
+.\" Copyright (C) 2004-2011, 2013 Richard Kettlewell
 .\"
 .\" This program is free software: you can redistribute it and/or modify
 .\" it under the terms of the GNU General Public License as published by
@@ -654,6 +654,28 @@ anything currently listed in the recently-played list.
 New values of this option may be picked up from the configuration file even
 without a reload.
 .TP
+.B rtp_mode \fIMODE\fR
+The network transmission mode for the \fBrtp\fR backend.
+Possible values are:
+.RS
+.TP
+.B unicast
+Unicast transmission to the address given by \fBbroadcast\fR.
+.TP
+.B broadcast
+Broadcast transmission to the address given by \fBbroadcast\fR.
+.TP
+.B multicast
+Multicast transmission to the address given by \fBbroadcast\fR.
+.TP
+.B request
+Unicast transmission to addresses requested by clients.
+.TP
+.B auto
+Choose one of the above based on the destination address.
+This is the default, for backwards compatibility reasons.
+.RE
+.TP
 .B sample_format \fIBITS\fB/\fIRATE\fB/\fICHANNELS
 Describes the sample format expected by the \fBspeaker_command\fR (below).
 The components of the format specification are as follows:
index 8865e8b..df32a4d 100644 (file)
@@ -1,5 +1,5 @@
 .\"
-.\" Copyright (C) 2004-2011 Richard Kettlewell
+.\" Copyright (C) 2004-2011, 2013 Richard Kettlewell
 .\"
 .\" This program is free software: you can redistribute it and/or modify
 .\" it under the terms of the GNU General Public License as published by
@@ -372,8 +372,17 @@ It will not be possible to use the cookie in the future.
 .B rtp\-address
 Report the RTP broadcast (or multicast) address, in the form \fIADDRESS
 PORT\fR.
+If the server is in RTP request mode then the result is \fB- -\fR.
 This command does not require authentication.
 .TP
+.B rtp\-cancel
+Cancel the unicast RTP stream associated with this connection.
+.TP
+.B rtp\-request \fIADDRESS PORT\fR
+Request that an RTP stream be transmitted to a given destination address.
+Only one unicast stream may be requested per connection.
+WHen the connection is closed the stream is terminated.
+.TP
 .B scratch \fR[\fIID\fR]
 Remove the track identified by \fIID\fR, or the currently playing track if no
 \fIID\fR is specified.
index 10a1fb2..1d2d0aa 100644 (file)
@@ -352,6 +352,14 @@ int disorder_rtp_address(disorder_client *c, char **addressp, char **portp) {
   return 0;
 }
 
+int disorder_rtp_cancel(disorder_client *c) {
+  return disorder_simple(c, NULL, "rtp-cancel", (char *)NULL);
+}
+
+int disorder_rtp_request(disorder_client *c, const char *address, const char *port) {
+  return disorder_simple(c, NULL, "rtp-request", address, port, (char *)NULL);
+}
+
 int disorder_scratch(disorder_client *c, const char *id) {
   return disorder_simple(c, NULL, "scratch", id, (char *)NULL);
 }
index 1962df2..da36a88 100644 (file)
@@ -25,7 +25,7 @@
 /** @file lib/client-stubs.h
  * @brief Generated client API
  *
- * Don't include this file directly - use @ref client.h instead.
+ * Don't include this file directly - use @ref lib/client.h instead.
  */
 
 /** @brief Adopt a track
@@ -549,6 +549,26 @@ int disorder_revoke(disorder_client *c);
  */
 int disorder_rtp_address(disorder_client *c, char **addressp, char **portp);
 
+/** @brief Cancel RTP stream
+ *
+ * 
+ *
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+int disorder_rtp_cancel(disorder_client *c);
+
+/** @brief Request a unicast RTP stream
+ *
+ * 
+ *
+ * @param c Client
+ * @param address Destination address
+ * @param port Destination port number
+ * @return 0 on success, non-0 on error
+ */
+int disorder_rtp_request(disorder_client *c, const char *address, const char *port);
+
 /** @brief Terminate the playing track.
  *
  * Requires one of the 'scratch mine', 'scratch random' or 'scratch any' rights depending on how the track came to be added to the queue.
index e7698ec..71c6a1f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004-2010 Richard Kettlewell
+ * Copyright (C) 2004-2011, 2013 Richard Kettlewell
  * Portions copyright (C) 2007 Mark Wooding
  *
  * This program is free software: you can redistribute it and/or modify
@@ -1055,6 +1055,7 @@ static const struct conf conf[] = {
   { C(remote_userman),   &type_boolean,          validate_any },
   { C(replay_min),       &type_integer,          validate_non_negative },
   { C(rtp_delay_threshold), &type_integer,       validate_positive },
+  { C(rtp_mode),         &type_string,           validate_any },
   { C(rtp_verbose),      &type_boolean,          validate_any },
   { C(sample_format),    &type_sample_format,    validate_sample_format },
   { C(scratch),          &type_string_accum,     validate_isreg },
@@ -1338,6 +1339,7 @@ static struct config *config_default(void) {
   c->broadcast_from.af = -1;
   c->listen.af = -1;
   c->connect.af = -1;
+  c->rtp_mode = xstrdup("auto");
   return c;
 }
 
index ed6d3f1..08304be 100644 (file)
@@ -277,6 +277,9 @@ struct config {
   /** @brief Rescan on (un)mount */
   int mount_rescan;
 
+  /** @brief RTP mode */
+  const char *rtp_mode;
+
   /* derived values: */
   int nparts;                          /* number of distinct name parts */
   char **parts;                                /* name part list  */
index 63f5163..4129f5a 100644 (file)
@@ -204,6 +204,14 @@ int disorder_eclient_revoke(disorder_eclient *c, disorder_eclient_no_response *c
   return simple(c, no_response_opcallback, (void (*)())completed, v, "revoke", (char *)0);
 }
 
+int disorder_eclient_rtp_cancel(disorder_eclient *c, disorder_eclient_no_response *completed, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "rtp-cancel", (char *)0);
+}
+
+int disorder_eclient_rtp_request(disorder_eclient *c, disorder_eclient_no_response *completed, const char *address, const char *port, void *v) {
+  return simple(c, no_response_opcallback, (void (*)())completed, v, "rtp-request", address, port, (char *)0);
+}
+
 int disorder_eclient_scratch(disorder_eclient *c, disorder_eclient_no_response *completed, const char *id, void *v) {
   return simple(c, no_response_opcallback, (void (*)())completed, v, "scratch", id, (char *)0);
 }
index af3b389..32e3237 100644 (file)
@@ -25,7 +25,7 @@
 /** @file lib/client-stubs.h
  * @brief Generated asynchronous client API
  *
- * Don't include this file directly - use @ref client.h instead.
+ * Don't include this file directly - use @ref lib/eclient.h instead.
  */
 
 /** @brief Adopt a track
@@ -570,6 +570,30 @@ int disorder_eclient_resume(disorder_eclient *c, disorder_eclient_no_response *c
  */
 int disorder_eclient_revoke(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
 
+/** @brief Cancel RTP stream
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_rtp_cancel(disorder_eclient *c, disorder_eclient_no_response *completed, void *v);
+
+/** @brief Request a unicast RTP stream
+ *
+ * 
+ *
+ * @param c Client
+ * @param completed Called upon completion
+ * @param address Destination address
+ * @param port Destination port number
+ * @param v Passed to @p completed
+ * @return 0 if the command was queued successfuly, non-0 on error
+ */
+int disorder_eclient_rtp_request(disorder_eclient *c, disorder_eclient_no_response *completed, const char *address, const char *port, void *v);
+
 /** @brief Terminate the playing track.
  *
  * Requires one of the 'scratch mine', 'scratch random' or 'scratch any' rights depending on how the track came to be added to the queue.
index d89f297..7135420 100644 (file)
@@ -454,6 +454,7 @@ static void rtp_stop(void) {
 static void rtp_configure(void) {
   char buffer[64];
 
+  uaudio_set("rtp-mode", config->rtp_mode);
   rtp_set_netconfig("rtp-destination-af",
                     "rtp-destination",
                     "rtp-destination-port", &config->broadcast);
index dec33e2..f2f9a3d 100755 (executable)
@@ -1,7 +1,7 @@
 #! /usr/bin/perl -w
 #
 # This file is part of DisOrder.
-# Copyright (C) 2010-11 Richard Kettlewell
+# Copyright (C) 2010-11, 13 Richard Kettlewell
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -801,6 +801,17 @@ simple("rtp-address",
        [["string", "address", "Where to store hostname or address"],
         ["string", "port", "Where to store service name or port number"]]);
 
+simple("rtp-cancel",
+       "Cancel RTP stream",
+       "",
+       []);
+
+simple("rtp-request",
+       "Request a unicast RTP stream",
+       "",
+       [["string", "address", "Destination address"],
+        ["string", "port", "Destination port number"]]);
+
 simple("scratch",
        "Terminate the playing track.",
        "Requires one of the 'scratch mine', 'scratch random' or 'scratch any' rights depending on how the track came to be added to the queue.",
index 7a6b73a..2256272 100644 (file)
@@ -241,6 +241,9 @@ int server_start(ev_source *ev, int pf,
 int server_stop(ev_source *ev, int fd);
 /* Stop listening on @fd@ */
 
+void rtp_request(const struct sockaddr_storage *sa);
+void rtp_request_cancel(const struct sockaddr_storage *sa);
+
 extern int volume_left, volume_right;  /* last known volume */
 
 extern int wideopen;                   /* blindly accept all logins */
index 2d4ab79..4f06ab4 100644 (file)
@@ -118,6 +118,12 @@ struct conn {
   void *body_u;
   /** @brief Accumulating body */
   struct vector body[1];
+
+  /** @brief Nonzero if an active RTP request exists */
+  int rtp_requested;
+
+  /** @brief RTP destination (if @ref rtp_requested is nonzero) */
+  struct sockaddr_storage rtp_destination;
 };
 
 /** @brief Linked list of connections */
@@ -141,10 +147,18 @@ static int command(struct conn *c, char *line);
 
 static const char *noyes[] = { "no", "yes" };
 
-/** @brief Remove a connection from the connection list */
+/** @brief Remove a connection from the connection list
+ *
+ * This is a good place for cleaning things up when connections are closed for
+ * any reason.
+ */
 static void remove_connection(struct conn *c) {
   struct conn **cc;
 
+  if(c->rtp_requested) {
+    rtp_request_cancel(&c->rtp_destination);
+    c->rtp_requested = 0;
+  }
   for(cc = &connections; *cc && *cc != c; cc = &(*cc)->next)
     ;
   if(*cc)
@@ -1216,15 +1230,65 @@ static int c_rtp_address(struct conn *c,
   if(api == &uaudio_rtp) {
     char **addr;
 
-    netaddress_format(&config->broadcast, NULL, &addr);
-    sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
-               quoteutf8(addr[1]),
-               quoteutf8(addr[2]));
+    if(!strcmp(config->rtp_mode, "request"))
+      sink_printf(ev_writer_sink(c->w), "252 - -\n");
+    else {
+      netaddress_format(&config->broadcast, NULL, &addr);
+      sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
+                  quoteutf8(addr[1]),
+                  quoteutf8(addr[2]));
+    }
   } else
     sink_writes(ev_writer_sink(c->w), "550 No RTP\n");
   return 1;
 }
 
+static int c_rtp_cancel(struct conn *c,
+                        char attribute((unused)) **vec,
+                        int attribute((unused)) nvec) {
+  if(!c->rtp_requested) {
+    sink_writes(ev_writer_sink(c->w), "550 No active RTP stream\n");
+    return 1;
+  }
+  rtp_request_cancel(&c->rtp_destination);
+  c->rtp_requested = 0;
+  sink_writes(ev_writer_sink(c->w), "250 Cancelled RTP stream\n");
+  return 1;
+}
+
+static int c_rtp_request(struct conn *c,
+                         char **vec,
+                         int attribute((unused)) nvec) {
+  static const struct addrinfo hints = {
+    .ai_family = AF_UNSPEC,
+    .ai_socktype = SOCK_DGRAM,
+    .ai_protocol = IPPROTO_UDP,
+    .ai_flags = AI_NUMERICHOST|AI_NUMERICSERV,
+  };
+  struct addrinfo *res;
+  int rc = getaddrinfo(vec[0], vec[1], &hints, &res);
+  if(rc) {
+    disorder_error(0, "%s port %s: %s",
+                   vec[0], vec[1], gai_strerror(rc));
+    sink_writes(ev_writer_sink(c->w), "550 Invalid address\n");
+    return 1;
+  }
+  disorder_info("%s requested RTP stream to %s %s", c->who, vec[0], vec[1]);
+  /* TODO might be useful to tighten this up to restrict clients to targetting
+   * themselves only */
+  if(c->rtp_requested) {
+    rtp_request_cancel(&c->rtp_destination);
+    c->rtp_requested = 0;
+  }
+  memcpy(&c->rtp_destination, res->ai_addr, res->ai_addrlen);
+  freeaddrinfo(res);
+  rtp_request(&c->rtp_destination);
+  c->rtp_requested = 1;
+  sink_writes(ev_writer_sink(c->w), "250 Initiated RTP stream\n");
+  // TODO teardown on connection close
+  return 1;
+}
+
 static int c_cookie(struct conn *c,
                    char **vec,
                    int attribute((unused)) nvec) {
@@ -1917,6 +1981,8 @@ static const struct server_command {
   { "resume",         0, 0,       c_resume,         RIGHT_PAUSE },
   { "revoke",         0, 0,       c_revoke,         RIGHT_READ },
   { "rtp-address",    0, 0,       c_rtp_address,    0 },
+  { "rtp-cancel",     0, 0,       c_rtp_cancel,     0 },
+  { "rtp-request",    2, 2,       c_rtp_request,    RIGHT_READ },
   { "schedule-add",   3, INT_MAX, c_schedule_add,   RIGHT_READ },
   { "schedule-del",   1, 1,       c_schedule_del,   RIGHT_READ },
   { "schedule-get",   1, 1,       c_schedule_get,   RIGHT_READ },