chiark / gitweb /
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 68ae6011a85b3eb913af017f5932525d3000f04d..8f0c8f30f89e5a8d28b4c91166804d17ff04638b 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 8865e8bd91d9b5b816a43da237628af367971c78..df32a4d19c02aee80490820c86506f56fb1bb0ef 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 10a1fb2e218741190fc11bbd53d10291e859a1bf..1d2d0aaf9c86e479a8b7b40792709644f70c9ee6 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 1962df2799ddfa5163ac0acd19121158c980788c..da36a88322f50f9f31a14b510514098d2fba2b67 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 e7698ec533da6af5b4b61ac099a530d25c1f05e0..71c6a1fa4b49160cda04b5d2785492a4fb125f4a 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 ed6d3f11875929ecc634595a8845be5d335866ef..08304bedbc7ff82cd4101734e5fa7917e0d52f55 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 63f5163d35b0cc4488292cb6769480eb99c0e08d..4129f5ad36ebf0b78ba6bbeba6b88ed0f9701992 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 af3b389f0d180f298a0dc5df4fd149834cbd8d2a..32e3237e30b27f858acb53cf6219f188b52d963e 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 d89f29724b17b8dcafc498e5cdcf87f19c89dbc9..71354205ff998c8ba8560076baa57ea2d57604fe 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 dec33e26be4c3cf3227612f4295a6a325565e1ec..f2f9a3d5275bb8cabcc990189ddb6cfff9588026 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 7a6b73a7e9554f59ca00477d4286caea9a24e5c9..22562728a37cd1132085f4c4463de4793f0a3723 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 2d4ab797e7a6e761f2b105977b679661c25acfbd..4f06ab48f9b959c90a6fc612fa5df5bb7f7d3aa3 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 },