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
 .\"
 .\" 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
 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:
 .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
 .\"
 .\" 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.
 .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
 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.
 .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;
 }
 
   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);
 }
 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
  *
 /** @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
  */
 
 /** @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);
 
  */
 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.
 /** @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.
 /*
  * 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
  * 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(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 },
   { 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->broadcast_from.af = -1;
   c->listen.af = -1;
   c->connect.af = -1;
+  c->rtp_mode = xstrdup("auto");
   return c;
 }
 
   return c;
 }
 
index ed6d3f11875929ecc634595a8845be5d335866ef..08304bedbc7ff82cd4101734e5fa7917e0d52f55 100644 (file)
@@ -277,6 +277,9 @@ struct config {
   /** @brief Rescan on (un)mount */
   int mount_rescan;
 
   /** @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  */
   /* 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);
 }
 
   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);
 }
 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
  *
 /** @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
  */
 
 /** @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);
 
  */
 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.
 /** @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];
 
 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);
   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.
 #! /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
 #
 # 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"]]);
 
        [["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.",
 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@ */
 
 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 */
 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];
   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 */
 };
 
 /** @brief Linked list of connections */
@@ -141,10 +147,18 @@ static int command(struct conn *c, char *line);
 
 static const char *noyes[] = { "no", "yes" };
 
 
 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;
 
 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)
   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;
 
   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;
 }
 
   } 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) {
 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 },
   { "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 },
   { "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 },