From b0116b5c04b64e9352d1c63f0f667a1d9a5d5c11 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sun, 10 Nov 2013 14:00:02 +0000 Subject: [PATCH] server: implement multiple-unicast RTP Organization: Straylight/Edgeware From: Richard Kettlewell Updates the protocol definition and implementation and exposes the uaudio-rtp rtp_mode variable in the config. --- doc/disorder_config.5.in | 24 +++++++++++- doc/disorder_protocol.5.in | 11 +++++- lib/client-stubs.c | 8 ++++ lib/client-stubs.h | 22 ++++++++++- lib/configuration.c | 4 +- lib/configuration.h | 3 ++ lib/eclient-stubs.c | 8 ++++ lib/eclient-stubs.h | 26 ++++++++++++- lib/uaudio-rtp.c | 1 + scripts/protocol | 13 ++++++- server/disorder-server.h | 3 ++ server/server.c | 76 +++++++++++++++++++++++++++++++++++--- 12 files changed, 188 insertions(+), 11 deletions(-) diff --git a/doc/disorder_config.5.in b/doc/disorder_config.5.in index 68ae601..8f0c8f3 100644 --- a/doc/disorder_config.5.in +++ b/doc/disorder_config.5.in @@ -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: diff --git a/doc/disorder_protocol.5.in b/doc/disorder_protocol.5.in index 8865e8b..df32a4d 100644 --- a/doc/disorder_protocol.5.in +++ b/doc/disorder_protocol.5.in @@ -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. diff --git a/lib/client-stubs.c b/lib/client-stubs.c index 10a1fb2..1d2d0aa 100644 --- a/lib/client-stubs.c +++ b/lib/client-stubs.c @@ -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); } diff --git a/lib/client-stubs.h b/lib/client-stubs.h index 1962df2..da36a88 100644 --- a/lib/client-stubs.h +++ b/lib/client-stubs.h @@ -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. diff --git a/lib/configuration.c b/lib/configuration.c index e7698ec..71c6a1f 100644 --- a/lib/configuration.c +++ b/lib/configuration.c @@ -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; } diff --git a/lib/configuration.h b/lib/configuration.h index ed6d3f1..08304be 100644 --- a/lib/configuration.h +++ b/lib/configuration.h @@ -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 */ diff --git a/lib/eclient-stubs.c b/lib/eclient-stubs.c index 63f5163..4129f5a 100644 --- a/lib/eclient-stubs.c +++ b/lib/eclient-stubs.c @@ -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); } diff --git a/lib/eclient-stubs.h b/lib/eclient-stubs.h index af3b389..32e3237 100644 --- a/lib/eclient-stubs.h +++ b/lib/eclient-stubs.h @@ -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. diff --git a/lib/uaudio-rtp.c b/lib/uaudio-rtp.c index d89f297..7135420 100644 --- a/lib/uaudio-rtp.c +++ b/lib/uaudio-rtp.c @@ -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); diff --git a/scripts/protocol b/scripts/protocol index dec33e2..f2f9a3d 100755 --- a/scripts/protocol +++ b/scripts/protocol @@ -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.", diff --git a/server/disorder-server.h b/server/disorder-server.h index 7a6b73a..2256272 100644 --- a/server/disorder-server.h +++ b/server/disorder-server.h @@ -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 */ diff --git a/server/server.c b/server/server.c index 2d4ab79..4f06ab4 100644 --- a/server/server.c +++ b/server/server.c @@ -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 }, -- [mdw]