Cooking since 2014-02-02.
* t/aes:
Implement aes_ctr128 and prefer it over RC4.
server: Lookup user only once.
NEWS
====
+---------------------------------------------
+0.5.2 (to be announced) "orthogonal interior"
+---------------------------------------------
+
+The new sync filter, the AES_CTR128 stream cipher and the overhauled
+network code are the highlights of this release.
+
+ - The new sync filter synchronizes playback between multiple
+ clients.
+ - Connections between para_server and para_client are now
+ encrypted by means of AES rather than RC4 if both sides
+ support it. RC4 is still available as a fallback. This
+ feature is fully transparent, i.e. no command line options
+ are necessary, and a client linked against openssl can
+ speak with a server linked against libgcrypt and vice versa.
+ - Major cleanup of the networking subsystem.
+ - Improved user manual.
+ - Minor fixes to avoid clang warnings.
+
------------------------------------------
0.5.1 (2013-12-20) "temporary implication"
------------------------------------------
q = p + ret + ret2 + 8;
if (q + size2 > buf + buflen)
break;
- if (!atom_cmp(type1, "©ART"))
+ if (!atom_cmp(type1, "\xa9" "ART"))
afhi->tags.artist = get_tag(q, size2);
- else if (!atom_cmp(type1, "©alb"))
+ else if (!atom_cmp(type1, "\xa9" "alb"))
afhi->tags.album = get_tag(q, size2);
- else if (!atom_cmp(type1, "©nam"))
+ else if (!atom_cmp(type1, "\xa9" "nam"))
afhi->tags.title = get_tag(q, size2);
- else if (!atom_cmp(type1, "©cmt"))
+ else if (!atom_cmp(type1, "\xa9" "cmt"))
afhi->tags.comment = get_tag(q, size2);
- else if (!atom_cmp(type1, "©day"))
+ else if (!atom_cmp(type1, "\xa9" "day"))
afhi->tags.year = get_tag(q, size2);
p += size1;
}
/** \file acl.c Access control lists for paraslash senders. */
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
/** \file afs.c Paraslash's audio file selector. */
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <signal.h>
#include <fnmatch.h>
#include <osl.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "server.cmdline.h"
#include "para.h"
H: Use it when adding large directories if only a few files
H: where added.
H:
-H:-f Force adding/updating. Recompute the audio format handler data
+H: -f Force adding/updating. Recompute the audio format handler data
H: even if a file with the same path and the same hash value exists.
H:
H: -v Verbose mode. Print what is being done.
H:
H: -l Change listing mode. Defaults to short listing if not given.
H:
-H: -ls: short listing mode
-H:
-H: -ll: long listing mode (equivalent to -l)
-H:
-H: -lv: verbose listing mode
-H:
-H: -lp: parser-friendly mode
-H:
-H: -lm: mbox listing mode
-H:
-H: -lc: chunk-table listing mode
+H: -ls: short listing mode
+H: -ll: long listing mode (equivalent to -l)
+H: -lv: verbose listing mode
+H: -lp: parser-friendly mode
+H: -lm: mbox listing mode
+H: -lc: chunk-table listing mode
H:
H: -p List full path of audio file. If not specified, only the basename
H: of each file is printed.
H:
H: -s Change sort order. Defaults to alphabetical path sort if not given.
H:
-H: -sp: sort by path.
-H:
-H: -sl: sort by last played time.
-H:
-H: -ss: sort by score (implies -a).
-H:
-H: -sn: sort by num played count.
-H:
-H: -sf: sort by frequency.
-H:
-H: -sc: sort by number of channels.
-H:
-H: -si: sort by image id.
-H:
-H: -sy: sort by lyrics id.
-H:
-H: -sb: sort by bit rate.
-H:
-H: -sd: sort by duration.
-H:
-H: -sa: sort by audio format.
+H: -sp: by path
+H: -sl: by last played time
+H: -ss: by score (implies -a)
+H: -sn: by num played count
+H: -sf: by frequency
+H: -sc: by number of channels
+H: -si: by image id
+H: -sy: by lyrics id
+H: -sb: by bit rate
+H: -sd: by duration
+H: -sa: by audio format
---
N: lsatt
P: AFS_READ
/** \file audioc.c The client program used to connect to para_audiod. */
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include <stdbool.h>
#include <signal.h>
*/
/** \file audiod.c The paraslash's audio daemon. */
+
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include <signal.h>
#include "para.h"
H:
H: -m Change grab mode. Defaults to sloppy grab if not given.
H:
-H: -ms: sloppy grab
+H: -ms: sloppy grab
+H: -mp: pedantic grab
+H: -ma: aggressive grab
H:
-H: -mp: pedantic grab
-H:
-H: -ma: aggressive grab
-H:
-H: The various grab modes only differ in what happens if the
-H: file descriptor to write the grabbed audio data to is not
-H: ready for writing (i.e. would block). Sloppy mode ignores
-H: the write, pedantic mode aborts and aggressive mode tries
-H: to write anyway.
+H: The various grab modes only differ in what happens if an attempt to
+H: write the grabbed audio data would block. Sloppy mode ignores the
+H: write, pedantic mode aborts and aggressive mode tries to write anyway.
H:
H: -p Grab output of node PARENT of the buffer tree.
H:
/** \file audiod_command.c Commands for para_audiod. */
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "audiod.cmdline.h"
/** \file client_common.c Common functions of para_client and para_audiod. */
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
/** \file command.c Client authentication and server commands. */
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <signal.h>
#include <sys/types.h>
#include <osl.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
msg = para_strcat(msg, tmp);
free(tmp);
}
+ assert(msg);
return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
}
amp_filter
udp_recv
prebuffer_filter
+ sync_filter
"
audiod_errlist_objs="$audiod_errlist_objs
audiod
wma_common
wmadec_filter
buffer_tree
+ sync_filter
"
if test "$have_openssl" = "yes"; then
audiod_errlist_objs="$audiod_errlist_objs crypt"
fecdec
wmadec
prebuffer
+ sync
"
filter_errlist_objs="
filter_common
wmadec_filter
buffer_tree
net
+ sync_filter
"
filter_cmdline_objs="
filter
compress_filter
amp_filter
prebuffer_filter
+ sync_filter
"
if test "$have_vorbis" = "yes"; then
write_common
file_write
version
+ sync_filter
"
play_cmdline_objs="
http_recv
prebuffer_filter
file_write
play
+ sync_filter
"
if test "$have_core_audio" = "yes"; then
play_errlist_objs="$play_errlist_objs osx_write ipc"
* (C) 2005 Ian McDonald <imcdnzl@gmail.com>
*/
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
}
fd = makesock(IPPROTO_DCCP, 0, conf->host_arg, conf->port_arg, fo);
+ flowopt_cleanup(fo);
free(ccids);
if (fd < 0)
return fd;
* (C) 2005 Ian McDonald <imcdnzl@gmail.com>
*/
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <sys/types.h>
#include <osl.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
#define VERSION_ERRORS
#define SCHED_ERRORS
+
extern const char **para_errlist[];
+#define SYNC_FILTER_ERRORS\
+ PARA_ERROR(SYNC_COMPLETE, "all buddies in sync"), \
+ PARA_ERROR(SYNC_LISTEN_FD, "no fd to listen on"), \
+
#define OSS_MIX_ERRORS \
PARA_ERROR(OSS_MIXER_CHANNEL, "invalid mixer channel"), \
/** \file http_recv.c paraslash's http receiver */
#include <regex.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
/** \file http_send.c paraslash's http sender */
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <sys/types.h>
#include <osl.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
--- /dev/null
+args "--no-version --no-help"
+
+purpose "Synchronize playback between multiple clients."
+
+option "buddy" b
+#~~~~~~~~~~~~~~~
+"host to synchronize with"
+multiple
+string typestr = "url"
+optional
+details = "
+ This option may be given multiple times, one per buddy. Each
+ value may be given as a host, port pair in either IPv4 or
+ IPv6 form, with port being optional. If no port was specified
+ the listening port (as specified with --port, see below)
+ is used to send the synchronization packet to this buddy.
+"
+
+option "port" p
+#~~~~~~~~~~~~~~
+"UDP port for incoming synchronization packets"
+int typestr = "portnumber"
+default = "29900"
+optional
+details = "
+ The sync filter receives incoming synchronization packets on
+ this UDP port.
+"
+
+option "timeout" t
+#~~~~~~~~~~~~~~~~~
+"how long to wait for other clients"
+int typestr = "milliseconds"
+default = "2000"
+optional
+details = "
+ Once the sync filter receives its first chunk of input, a
+ synchronization period of the given number of milliseconds
+ begins. Playback is deferred until a synchronization packet
+ has been received from each defined buddy, or until the end
+ of the period. Buddies which did not send a synchronization
+ packet in time are temporarily disabled and are not waited for
+ during subsequent synchronization periods. They are re-enabled
+ automatically when another synchronization packet arrives.
+"
*/
#define _GNU_SOURCE
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <sys/socket.h>
#include <netdb.h>
/* At least NetBSD needs these. */
* \param hostlen The maximum length of \a host.
* \param port To return the port number (if any) of \a url.
*
- * \return Pointer to \a host, or NULL if failed.
- * If NULL is returned, \a host and \a portnum are undefined. If no
- * port number was present in \a url, \a portnum is set to -1.
+ * \return Pointer to \a host, or \p NULL if failed. If \p NULL is returned,
+ * \a host and \a port are undefined. If no port number was present in \a url,
+ * \a port is set to -1.
*
* \sa RFC 3986, 3.2.2/3.2.3
*/
if (*o++ != ']' || (*o != '\0' && *o != ':'))
goto failed;
} else {
- for (; (*c = *o == ':'? '\0' : *o); c++, o++)
- if (c == end)
+ for (; (*c = *o == ':'? '\0' : *o); c++, o++) {
+ if (c == end && o[1])
goto failed;
+ }
}
if (*o == ':')
if (para_atoi32(++o, port) < 0 ||
*port < 0 || *port > 0xffff)
goto failed;
-
if (host_string_ok(host))
return host;
failed:
}
}
-static void flowopt_cleanup(struct flowopts *fo)
+/**
+ * Deallocate all resources of a flowopts structure.
+ *
+ * \param fo A pointer as returned from flowopt_new().
+ *
+ * It's OK to pass \p NULL here in which case the function does nothing.
+ */
+void flowopt_cleanup(struct flowopts *fo)
{
struct pre_conn_opt *cur, *next;
}
/**
- * Resolve IPv4/IPv6 address and create a ready-to-use active or passive socket.
+ * Resolve an IPv4/IPv6 address.
*
* \param l4type The layer-4 type (\p IPPROTO_xxx).
- * \param passive Whether this is a passive (1) or active (0) socket.
+ * \param passive Whether \p AI_PASSIVE should be included as hint.
* \param host Remote or local hostname or IPv/6 address string.
- * \param port_number Decimal port number.
- * \param fo Socket options to be set before making the connection.
+ * \param port_number Used to set the port in each returned address structure.
+ * \param result addrinfo structures are returned here.
*
- * This creates a ready-made IPv4/v6 socket structure after looking up the
- * necessary parameters. The interpretation of \a host depends on the value of
- * \a passive:
- * - on a passive socket host is interpreted as an interface IPv4/6 address
- * (can be left NULL);
- * - on an active socket, \a host is the peer DNS name or IPv4/6 address
- * to connect to;
- * - \a port_number is in either case the numeric port number (not service
- * string).
- *
- * Furthermore, bind(2) is called on passive sockets, and connect(2) on active
- * sockets. The algorithm tries all possible address combinations until it
- * succeeds. If \a fo is supplied, options are set and cleanup is performed.
- *
- * \return This function returns 1 on success and \a -E_ADDRESS_LOOKUP when no
- * matching connection could be set up (with details in the error log).
- *
- * \sa ipv6(7), getaddrinfo(3), bind(2), connect(2).
- */
-int makesock(unsigned l4type, bool passive,
- const char *host, uint16_t port_number,
- struct flowopts *fo)
-{
- struct addrinfo *local = NULL, *src = NULL, *remote = NULL,
- *dst = NULL, hints;
- unsigned int l3type = AF_UNSPEC;
- int rc, on = 1, sockfd = -1,
- socktype = sock_type(l4type);
+ * The interpretation of \a host depends on the value of \a passive. On a
+ * passive socket host is interpreted as an interface IPv4/6 address (can be
+ * left NULL). On an active socket, \a host is the peer DNS name or IPv4/6
+ * address to connect to.
+ *
+ * \return Standard.
+ *
+ * \sa getaddrinfo(3).
+ */
+int lookup_address(unsigned l4type, bool passive, const char *host,
+ int port_number, struct addrinfo **result)
+{
+ int ret;
char port[6]; /* port number has at most 5 digits */
+ struct addrinfo *addr = NULL, hints;
- sprintf(port, "%u", port_number);
+ *result = NULL;
+ sprintf(port, "%u", port_number & 0xffff);
/* Set up address hint structure */
memset(&hints, 0, sizeof(hints));
- hints.ai_family = l3type;
- hints.ai_socktype = socktype;
- /*
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = sock_type(l4type);
+ /*
* getaddrinfo does not support SOCK_DCCP, so for the sake of lookup
* (and only then) pretend to be UDP.
*/
if (l4type == IPPROTO_DCCP)
hints.ai_socktype = SOCK_DGRAM;
-
/* only use addresses available on the host */
hints.ai_flags = AI_ADDRCONFIG;
- if (l3type == AF_INET6)
- /* use v4-mapped-v6 if no v6 addresses found */
- hints.ai_flags |= AI_V4MAPPED | AI_ALL;
-
if (passive && host == NULL)
hints.ai_flags |= AI_PASSIVE;
-
/* Obtain local/remote address information */
- if ((rc = getaddrinfo(host, port, &hints, passive ? &local : &remote))) {
- PARA_ERROR_LOG("can not resolve %s address %s#%s: %s.\n",
- layer4_name(l4type),
- host? host : (passive? "[loopback]" : "[localhost]"),
- port, gai_strerror(rc));
- rc = -E_ADDRESS_LOOKUP;
- goto out;
+ ret = getaddrinfo(host, port, &hints, &addr);
+ if (ret != 0) {
+ PARA_ERROR_LOG("can not resolve %s address %s#%s: %s\n",
+ layer4_name(l4type),
+ host? host : (passive? "[loopback]" : "[localhost]"),
+ port, gai_strerror(ret));
+ return -E_ADDRESS_LOOKUP;
}
+ *result = addr;
+ return 1;
+}
- /* Iterate over all src/dst combination, exhausting dst first */
- for (src = local, dst = remote; src != NULL || dst != NULL; /* no op */ ) {
- if (src && dst && src->ai_family == AF_INET
- && dst->ai_family == AF_INET6)
- goto get_next_dst; /* v4 -> v6 is not possible */
-
- sockfd = socket(src ? src->ai_family : dst->ai_family,
- socktype, l4type);
- if (sockfd < 0)
- goto get_next_dst;
+/**
+ * Create an active or passive socket.
+ *
+ * \param l4type \p IPPROTO_TCP, \p IPPROTO_UDP, or \p IPPROTO_DCCP.
+ * \param passive Whether to call bind(2) or connect(2).
+ * \param ai Address information as obtained from \ref lookup_address().
+ * \param fo Socket options to be set before making the connection.
+ *
+ * bind(2) is called on passive sockets, and connect(2) on active sockets. The
+ * algorithm tries all possible address combinations until it succeeds. If \a
+ * fo is supplied, options are set but cleanup must be performed in the caller.
+ *
+ * \return File descriptor on success, \p E_MAKESOCK on errors.
+ *
+ * \sa \ref lookup_address(), \ref makesock(), ip(7), ipv6(7), bind(2),
+ * connect(2).
+ */
+int makesock_addrinfo(unsigned l4type, bool passive, struct addrinfo *ai,
+ struct flowopts *fo)
+{
+ int ret = -E_MAKESOCK, on = 1;
+ for (; ai; ai = ai->ai_next) {
+ int fd;
+ ret = socket(ai->ai_family, sock_type(l4type), l4type);
+ if (ret < 0)
+ continue;
+ fd = ret;
+ flowopt_setopts(fd, fo);
+ if (!passive) {
+ if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0)
+ return fd;
+ close(fd);
+ continue;
+ }
/*
* Reuse the address on passive sockets to avoid failure on
* restart (protocols using listen()) and when creating
* multiple listener instances (UDP multicast).
*/
- if (passive && setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
- &on, sizeof(on)) == -1) {
- rc = errno;
- close(sockfd);
- PARA_ERROR_LOG("can not set SO_REUSEADDR: %s\n",
- strerror(rc));
- rc = -ERRNO_TO_PARA_ERROR(rc);
- break;
- }
- flowopt_setopts(sockfd, fo);
-
- if (src) {
- if (bind(sockfd, src->ai_addr, src->ai_addrlen) < 0) {
- close(sockfd);
- goto get_next_src;
- }
- if (!dst) /* bind-only completed successfully */
- break;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on,
+ sizeof(on)) == -1) {
+ close(fd);
+ continue;
}
-
- if (dst && connect(sockfd, dst->ai_addr, dst->ai_addrlen) == 0)
- break; /* connection completed successfully */
- close(sockfd);
-get_next_dst:
- if (dst && (dst = dst->ai_next))
+ if (bind(fd, ai->ai_addr, ai->ai_addrlen) < 0) {
+ close(fd);
continue;
-get_next_src:
- if (src && (src = src->ai_next)) /* restart inner loop */
- dst = remote;
+ }
+ return fd;
}
-out:
- if (local)
- freeaddrinfo(local);
- if (remote)
- freeaddrinfo(remote);
- flowopt_cleanup(fo);
-
- if (src == NULL && dst == NULL) {
- if (rc >= 0)
- rc = -E_MAKESOCK;
- PARA_ERROR_LOG("can not create %s socket %s#%s.\n",
- layer4_name(l4type), host? host : (passive?
- "[loopback]" : "[localhost]"), port);
- return rc;
+ return -E_MAKESOCK;
+}
+
+/**
+ * Resolve IPv4/IPv6 address and create a ready-to-use active or passive socket.
+ *
+ * \param l4type The layer-4 type (\p IPPROTO_xxx).
+ * \param passive Whether this is a passive or active socket.
+ * \param host Passed to \ref lookup_address().
+ * \param port_number Passed to \ref lookup_address().
+ * \param fo Passed to \ref makesock_addrinfo().
+ *
+ * This creates a ready-made IPv4/v6 socket structure after looking up the
+ * necessary parameters. The function first calls \ref lookup_address() and
+ * passes the address information to makesock_addrinfo() to create and
+ * initialize the socket.
+ *
+ * \return The newly created file descriptor on success, a negative error code
+ * on failure.
+ *
+ * \sa \ref lookup_address(), \ref makesock_addrinfo().
+ */
+int makesock(unsigned l4type, bool passive, const char *host, uint16_t port_number,
+ struct flowopts *fo)
+{
+ struct addrinfo *ai;
+ int ret = lookup_address(l4type, passive, host, port_number, &ai);
+
+ if (ret >= 0)
+ ret = makesock_addrinfo(l4type, passive, ai, fo);
+ if (ai)
+ freeaddrinfo(ai);
+ if (ret < 0) {
+ PARA_ERROR_LOG("can not create %s socket %s#%d.\n",
+ layer4_name(l4type), host? host : (passive?
+ "[loopback]" : "[localhost]"), port_number);
}
- return sockfd;
+ return ret;
}
/**
*ia = ((struct sockaddr_in *)sa)->sin_addr;
}
+/**
+ * Compare the address part of IPv4/6 addresses.
+ *
+ * \param sa1 First address.
+ * \param sa2 Second address.
+ *
+ * \return True iff the IP address of \a sa1 and \a sa2 match.
+ */
+bool sockaddr_equal(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+ if (!sa1 || !sa2)
+ return false;
+ if (sa1->sa_family != sa2->sa_family)
+ return false;
+ if (sa1->sa_family == AF_INET) {
+ struct sockaddr_in *a1 = (typeof(a1))sa1,
+ *a2 = (typeof (a2))sa2;
+ return a1->sin_addr.s_addr == a2->sin_addr.s_addr;
+ } else if (sa1->sa_family == AF_INET6) {
+ struct sockaddr_in6 *a1 = (typeof(a1))sa1,
+ *a2 = (typeof (a2))sa2;
+ return !memcmp(a1, a2, sizeof(*a1));
+ } else
+ return false;
+}
+
/**
* Receive data from a file descriptor.
*
extern struct flowopts *flowopt_new(void);
extern void flowopt_add(struct flowopts *fo, int level, int opt,
const char *name, const void *val, int len);
+void flowopt_cleanup(struct flowopts *fo);
/** Flowopt shortcut macros */
#define OPT_ADD(fo, lev, opt, val, len) flowopt_add(fo, lev, opt, #opt, val, len)
return inet_pton(AF_INET6, address, &test_it) != 0;
}
+int lookup_address(unsigned l4type, bool passive, const char *host,
+ int port_number, struct addrinfo **result);
+
/**
* Generic socket creation (passive and active sockets).
*/
-extern int makesock(unsigned l4type, bool passive,
- const char *host, uint16_t port_number,
- struct flowopts *fo);
+int makesock(unsigned l4type, bool passive, const char *host,
+ uint16_t port_number, struct flowopts *fo);
+
+int makesock_addrinfo(unsigned l4type, bool passive, struct addrinfo *ai,
+ struct flowopts *fo);
static inline int para_connect_simple(unsigned l4type,
const char *host, uint16_t port)
}
void extract_v4_addr(const struct sockaddr_storage *ss, struct in_addr *ia);
+bool sockaddr_equal(const struct sockaddr *sa1, const struct sockaddr *sa2);
/**
* Functions to support listening sockets.
#include <limits.h>
#include <stdarg.h>
#include <ctype.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <sys/socket.h>
-#include <sys/un.h> /* needed by create_pf_socket */
#include <string.h>
#include <assert.h>
#include <stdbool.h>
+#include <inttypes.h>
+#include <sys/uio.h>
#include "gcc-compat.h"
/** used in various contexts */
/** \file send_common.c Functions used by more than one paraslash sender. */
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <osl.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
* - Forward error correction: \ref fec.c.
*/
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <signal.h>
#include <regex.h>
#include <osl.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
--- /dev/null
+/*
+ * Copyright (C) 2013 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file sync_filter.c Playback synchronization filter. */
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
+
+#include "para.h"
+#include "sync_filter.cmdline.h"
+#include "list.h"
+#include "net.h"
+#include "sched.h"
+#include "ggo.h"
+#include "buffer_tree.h"
+#include "filter.h"
+#include "string.h"
+#include "fd.h"
+#include "error.h"
+
+struct sync_buddy_info {
+ const char *url;
+ char *host;
+ int port;
+ struct addrinfo *ai;
+ bool disabled;
+};
+
+/* per open/close data */
+struct sync_buddy {
+ int fd;
+ struct sync_buddy_info *sbi;
+ bool ping_received;
+ struct list_head node;
+};
+
+struct sync_filter_context {
+ int listen_fd;
+ struct list_head buddies;
+ struct timeval timeout;
+ bool ping_sent;
+};
+
+struct sync_filter_config {
+ struct sync_filter_args_info *conf;
+ struct sync_buddy_info *buddy_info;
+};
+
+#define FOR_EACH_BUDDY(_buddy, _list) \
+ list_for_each_entry(_buddy, _list, node)
+#define FOR_EACH_BUDDY_SAFE(_buddy, _tmp_buddy, _list) \
+ list_for_each_entry_safe(_buddy, _tmp_buddy, _list, node)
+
+static void sync_close_buddy(struct sync_buddy *buddy)
+{
+ if (buddy->fd < 0)
+ return;
+ PARA_DEBUG_LOG("closing %s\n", buddy->sbi->url);
+ close(buddy->fd);
+ buddy->fd = -1;
+}
+
+static void sync_close_buddies(struct sync_filter_context *ctx)
+{
+ struct sync_buddy *buddy;
+
+ FOR_EACH_BUDDY(buddy, &ctx->buddies)
+ sync_close_buddy(buddy);
+}
+
+static void sync_close(struct filter_node *fn)
+{
+ struct sync_filter_context *ctx = fn->private_data;
+
+ sync_close_buddies(ctx);
+ if (ctx->listen_fd >= 0) {
+ close(ctx->listen_fd);
+ ctx->listen_fd = -1;
+ }
+ free(ctx);
+ fn->private_data = NULL;
+}
+
+static void sync_free_config(void *conf)
+{
+ struct sync_filter_config *sfc = conf;
+ int i;
+
+ for (i = 0; i < sfc->conf->buddy_given; i++) {
+ free(sfc->buddy_info[i].host);
+ freeaddrinfo(sfc->buddy_info[i].ai);
+ }
+ sync_filter_cmdline_parser_free(sfc->conf);
+ free(sfc);
+}
+
+static void sync_open(struct filter_node *fn)
+{
+ int i, ret;
+ struct sync_filter_config *sfc = fn->conf;
+ struct sync_buddy *buddy;
+ struct sync_filter_context *ctx;
+
+ assert(sfc);
+
+ ctx = fn->private_data = para_calloc(sizeof(*ctx));
+ INIT_LIST_HEAD(&ctx->buddies);
+ ctx->listen_fd = -1;
+
+ /* create socket to listen for incoming packets */
+ ret = makesock(
+ IPPROTO_UDP,
+ true /* passive */,
+ NULL /* no host required */,
+ sfc->conf->port_arg,
+ NULL /* no flowopts */
+ );
+ if (ret < 0) {
+ PARA_ERROR_LOG("could not create UDP listening socket %d\n",
+ sfc->conf->port_arg);
+ return;
+ }
+ ctx->listen_fd = ret;
+ PARA_INFO_LOG("listening on fd %d\n", ctx->listen_fd);
+
+ for (i = 0; i < sfc->conf->buddy_given; i++) {
+ struct sync_buddy_info *sbi = sfc->buddy_info + i;
+ const char *url = sfc->conf->buddy_arg[i];
+ int fd;
+
+ /* make buddy udp socket from address info */
+ assert(sbi->ai);
+ ret = makesock_addrinfo(
+ IPPROTO_UDP,
+ false /* not passive */,
+ sbi->ai,
+ NULL /* no flowopts */
+ );
+ if (ret < 0) {
+ PARA_WARNING_LOG("could not make socket for %s\n",
+ url);
+ goto fail;
+ }
+ fd = ret;
+ ret = mark_fd_nonblocking(fd);
+ if (ret < 0) {
+ PARA_ERROR_LOG("unable to set nonblock mode for %s\n",
+ url);
+ close(fd);
+ goto fail;
+ }
+ buddy = para_malloc(sizeof(*buddy));
+ buddy->fd = fd;
+ buddy->sbi = sbi;
+ buddy->ping_received = false;
+ para_list_add(&buddy->node, &ctx->buddies);
+
+ PARA_INFO_LOG("opened buddy %s on fd %d\n", url, fd);
+ continue;
+fail:
+ PARA_WARNING_LOG("%s\n", para_strerror(-ret));
+ }
+}
+
+static int sync_parse_config(int argc, char **argv, void **result)
+{
+ int i, ret, n;
+ struct sync_filter_config *sfc;
+ struct sync_filter_args_info *conf = para_malloc(sizeof(*conf));
+
+ sync_filter_cmdline_parser(argc, argv, conf); /* exits on error */
+ sfc = para_calloc(sizeof(*sfc));
+ sfc->conf = conf;
+ n = conf->buddy_given;
+ sfc->buddy_info = para_malloc((n + 1) * sizeof(*sfc->buddy_info));
+ PARA_INFO_LOG("initializing buddy info array of length %d\n", n);
+ for (i = 0; i < n; i++) {
+ const char *url = conf->buddy_arg[i];
+ size_t len = strlen(url);
+ char *host = para_malloc(len + 1);
+ int port;
+ struct addrinfo *ai;
+ struct sync_buddy_info *sbi = sfc->buddy_info + i;
+
+ if (!parse_url(url, host, len, &port)) {
+ free(host);
+ PARA_ERROR_LOG("could not parse url %s\n", url);
+ ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+ goto fail;
+ }
+ if (port < 0)
+ port = conf->port_arg;
+ ret = lookup_address(IPPROTO_UDP, false /* not passive */,
+ host, port, &ai);
+ if (ret < 0) {
+ PARA_ERROR_LOG("host lookup failure for %s\n", url);
+ free(host);
+ goto fail;
+ }
+ sbi->url = url;
+ sbi->host = host;
+ sbi->port = port;
+ sbi->ai = ai;
+ sbi->disabled = false;
+ PARA_DEBUG_LOG("buddy #%d: %s\n", i, url);
+ }
+ *result = sfc;
+ return 1;
+fail:
+ assert(ret < 0);
+ PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ sync_free_config(sfc);
+ return ret;
+}
+
+/*
+ * True if we sent a packet to all budies and received a packet from each
+ * enabled buddy.
+ */
+static bool sync_complete(struct sync_filter_context *ctx)
+{
+ struct sync_buddy *buddy;
+
+ if (!ctx->ping_sent)
+ return false;
+ FOR_EACH_BUDDY(buddy, &ctx->buddies)
+ if (!buddy->sbi->disabled && !buddy->ping_received)
+ return false;
+ return true;
+}
+
+static void sync_disable_active_buddies(struct sync_filter_context *ctx)
+{
+ struct sync_buddy *buddy;
+
+ FOR_EACH_BUDDY(buddy, &ctx->buddies) {
+ if (buddy->sbi->disabled)
+ continue;
+ PARA_NOTICE_LOG("disabling %s\n", buddy->sbi->url);
+ buddy->sbi->disabled = true;
+ }
+}
+
+static void sync_set_timeout(struct sync_filter_context *ctx,
+ struct sync_filter_config *sfc)
+{
+ struct timeval to;
+
+ ms2tv(sfc->conf->timeout_arg, &to);
+ tv_add(now, &to, &ctx->timeout);
+}
+
+static void sync_pre_select(struct sched *s, struct task *t)
+{
+ int ret;
+ struct filter_node *fn = container_of(t, struct filter_node, task);
+ struct sync_filter_context *ctx = fn->private_data;
+ struct sync_filter_config *sfc = fn->conf;
+
+ if (list_empty(&ctx->buddies))
+ return sched_min_delay(s);
+ if (ctx->listen_fd < 0)
+ return sched_min_delay(s);
+ ret = btr_node_status(fn->btrn, 0, BTR_NT_INTERNAL);
+ if (ret < 0)
+ return sched_min_delay(s);
+ para_fd_set(ctx->listen_fd, &s->rfds, &s->max_fileno);
+ if (ret == 0)
+ return;
+ if (ctx->timeout.tv_sec == 0) { /* must ping buddies */
+ sync_set_timeout(ctx, sfc);
+ return sched_min_delay(s);
+ }
+ if (sync_complete(ctx)) /* push down what we have */
+ return sched_min_delay(s);
+ sched_request_barrier_or_min_delay(&ctx->timeout, s);
+}
+
+static struct sync_buddy *sync_find_buddy(struct sockaddr *addr,
+ struct list_head *list)
+{
+ struct sync_buddy *buddy;
+
+ FOR_EACH_BUDDY(buddy, list)
+ if (sockaddr_equal(buddy->sbi->ai->ai_addr, addr))
+ return buddy;
+ return NULL;
+}
+
+static int sync_post_select(__a_unused struct sched *s, struct task *t)
+{
+ int ret;
+ struct filter_node *fn = container_of(t, struct filter_node, task);
+ struct sync_filter_context *ctx = fn->private_data;
+ struct sync_filter_config *sfc = fn->conf;
+ struct sync_buddy *buddy, *tmp;
+
+ if (list_empty(&ctx->buddies))
+ goto success;
+ ret = -E_SYNC_LISTEN_FD;
+ if (ctx->listen_fd < 0)
+ goto fail;
+ ret = btr_node_status(fn->btrn, 0, BTR_NT_INTERNAL);
+ if (ret < 0)
+ goto fail;
+ if (ret == 0)
+ return 0;
+ if (ctx->timeout.tv_sec == 0)
+ sync_set_timeout(ctx, sfc);
+ else {
+ if (tv_diff(&ctx->timeout, now, NULL) < 0) {
+ sync_disable_active_buddies(ctx);
+ goto success;
+ }
+ }
+ if (!ctx->ping_sent) {
+ FOR_EACH_BUDDY_SAFE(buddy, tmp, &ctx->buddies) {
+ char c = '\0';
+ PARA_INFO_LOG("pinging %s (%s)\n",
+ buddy->sbi->url, buddy->sbi->disabled?
+ "disabled" : "enabled");
+ ret = xwrite(buddy->fd, &c, 1);
+ sync_close_buddy(buddy);
+ if (ret < 0) {
+ PARA_WARNING_LOG("failed to write to %s: %s\n",
+ buddy->sbi->url, para_strerror(-ret));
+ list_del(&buddy->node);
+ }
+ }
+ ctx->ping_sent = true;
+ }
+ if (FD_ISSET(ctx->listen_fd, &s->rfds)) {
+ char c;
+ for (;;) {
+ struct sockaddr src_addr;
+ socklen_t len = sizeof(src_addr);
+ ret = recvfrom(ctx->listen_fd, &c, 1, MSG_DONTWAIT,
+ &src_addr, &len);
+ if (ret < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ break;
+ ret = -ERRNO_TO_PARA_ERROR(errno);
+ goto fail;
+ }
+ buddy = sync_find_buddy(&src_addr, &ctx->buddies);
+ if (!buddy) {
+ PARA_NOTICE_LOG("pinged by unknown\n");
+ continue;
+ }
+ PARA_DEBUG_LOG("pinged by %s\n", buddy->sbi->url);
+ if (buddy->sbi->disabled) {
+ PARA_NOTICE_LOG("enabling %s\n",
+ buddy->sbi->url);
+ buddy->sbi->disabled = false;
+ }
+ list_del(&buddy->node);
+ }
+ }
+ if (!sync_complete(ctx))
+ return 1;
+ /*
+ * Although all enabled buddies are in sync we do not splice out
+ * ourselves immediately. We rather wait until the timout expires,
+ * or the buddy list has become empty. This opens a time window
+ * for disabled buddies to become enabled by sending us a packet.
+ */
+ btr_pushdown(fn->btrn);
+ return 1;
+success:
+ ret = -E_SYNC_COMPLETE; /* success */
+ goto out;
+fail:
+ PARA_WARNING_LOG("%s\n", para_strerror(-ret));
+out:
+ sync_close_buddies(ctx);
+ btr_splice_out_node(&fn->btrn);
+ assert(ret < 0);
+ return ret;
+}
+
+/**
+ * The synchronization filter.
+ *
+ * \param f Pointer to the struct to initialize.
+ */
+void sync_filter_init(struct filter *f)
+{
+ struct sync_filter_args_info dummy;
+
+ sync_filter_cmdline_parser_init(&dummy);
+ f->open = sync_open;
+ f->close = sync_close;
+ f->pre_select = sync_pre_select;
+ f->post_select = sync_post_select;
+ f->parse_config = sync_parse_config;
+ f->free_config = sync_free_config;
+ f->help = (struct ggo_help)DEFINE_GGO_HELP(sync_filter);
+}
*/
/** \file udp_recv.c Paraslash's udp receiver */
+#include <netinet/in.h>
#include <regex.h>
#include <sys/socket.h>
#include <net/if.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
}
if (memcmp(iov[0].iov_base, FEC_EOF_PACKET, iov[0].iov_len) != 0)
return 0;
- if (memcmp(iov[1].iov_base, FEC_EOF_PACKET + iov[0].iov_len,
+ if (memcmp(iov[1].iov_base, &FEC_EOF_PACKET[iov[0].iov_len],
FEC_EOF_PACKET_LEN - iov[0].iov_len) != 0)
return 0;
return -E_RECV_EOF;
/** \file udp_send.c Para_server's udp sender. */
-
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/udp.h>
#include <net/if.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
#include <osl.h>
+#include <netdb.h>
#include "server.cmdline.h"
#include "para.h"
* senders.
*/
+#include <sys/socket.h>
+#include <netinet/in.h>
#include <regex.h>
#include <osl.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#include "para.h"
#include "error.h"
Troubleshooting
~~~~~~~~~~~~~~~
-Use the debug loglevel (option -l debug for most commands) to show
-debugging info. Almost all paraslash executables have a brief online
-help which is displayed by using the -h switch. The --detailed-help
-option prints the full help text.
+Use the debug loglevel (-l debug) to show debugging info. All paraslash
+executables have a brief online help which is displayed when -h is
+given. The --detailed-help option prints the full help text.
If para_server crashed or was killed by SIGKILL (signal 9), it
may refuse to start again because of "dirty osl tables". In this
case you'll have to run the oslfsck program of libosl to fix your
-database. It might be necessary to use --force (even if your name
-isn't Luke). However, make sure para_server isn't running before
-executing oslfsck --force.
+database:
+
+ oslfsck -fd ~/.paraslash/afs_database-0.4
+
+However, make sure para_server isn't running before executing oslfsck.
If you don't mind to recreate your database you can start
from scratch by removing the entire database directory, i.e.
This prints out references to missing audio files as well as invalid
playlists and mood definitions.
+Similarly, para_audiod refuses to start if its socket file exists, since
+this indicates that another instance of para_audiod is running. After
+a crash a stale socket file might remain and you must run
+
+ para_audiod --force
+
+once to fix it up.
+
---------------------------------------
Audio formats and audio format handlers
---------------------------------------
over IP applications, has modest complexity and a small memory
footprint. Wideband and narrowband (telephone quality) speech are
supported. As for Vorbis audio, Speex bit-streams are often stored
-in OGG files.
+in OGG files. As of 2012 this codec is considered obsolete since the
+Oppus codec, described below, surpasses its performance in all areas.
+
+*OGG/Opus*
+
+Opus is a lossy audio compression format standardized through RFC
+6716 in 2012. It combines the speech-oriented SILK codec and the
+low-latency CELT (Constrained Energy Lapped Transform) codec. Like
+OGG/Vorbis and OGG/Speex, Opus data is usually encapsulated in OGG
+containers. All known software patents which cover Opus are licensed
+under royalty-free terms.
*AAC*
The audio format handler code is linked into para_server and executed
via the _add_ command. The same code is also available as a stand-alone
tool, para_afh, which can be used to print the technical data, the
-chunk table and the meta data of a file. Furthermore, one can use
-para_afh to cut an audio file, i.e. to select some of its chunks to
-produce a new file containing only these chunks.
+chunk table and the meta data of a file.
----------
Networking
connected to a receiver which produces the input stream and a writer
which absorbs the output stream.
-Some filters depend on a specific library being installed and are
-not compiled in if this library was not found at compile time. To
-see the list of supported filters, run para_filter and para_audiod
-with the --help option. The output looks similar to the following:
+Some filters depend on a specific library and are not compiled in
+if this library was not found at compile time. To see the list of
+supported filters, run para_filter and para_audiod with the --help
+option. The output looks similar to the following:
Available filters:
compress wav amp fecdec wmadec prebuffer oggdec aacdec mp3dec
Congestion Control ID 2: TCP-like Congestion Control
- XREFERENCE(http://www.ietf.org/rfc/rfc4342.txt, RFC 4342) (2006):
Congestion Control ID 3: TCP-Friendly Rate Control (TFRC)
+ - XREFERENCE(http://www.ietf.org/rfc/rfc6716.txt, RFC 6716) (2012):
+ Definition of the Opus Audio Codec
Application web pages
~~~~~~~~~~~~~~~~~~~~~