mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-24 06:05:56 -05:00
Merge branch 'generic_inet1'
This commit is contained in:
commit
ef95759782
@ -34,6 +34,10 @@ general {
|
|||||||
# Websocket port for the web interface.
|
# Websocket port for the web interface.
|
||||||
# websocket_port = 3688
|
# websocket_port = 3688
|
||||||
|
|
||||||
|
# Websocket interface to bind listener to (e.g. "eth0"). Default is
|
||||||
|
# disabled, which means listen on all interfaces.
|
||||||
|
# websocket_interface = ""
|
||||||
|
|
||||||
# Sets who is allowed to connect without authorisation. This applies to
|
# Sets who is allowed to connect without authorisation. This applies to
|
||||||
# client types like Remotes, DAAP clients (iTunes) and to the web
|
# client types like Remotes, DAAP clients (iTunes) and to the web
|
||||||
# interface. Options are "any", "localhost" or the prefix to one or
|
# interface. Options are "any", "localhost" or the prefix to one or
|
||||||
@ -43,6 +47,11 @@ general {
|
|||||||
# Enable/disable IPv6
|
# Enable/disable IPv6
|
||||||
ipv6 = yes
|
ipv6 = yes
|
||||||
|
|
||||||
|
# Set this if you want the server to bind to a specific IP address. Can
|
||||||
|
# be ipv6 or ipv4. Default (commented out or "::") is to listen on all
|
||||||
|
# IP addresses.
|
||||||
|
# bind_address = "::"
|
||||||
|
|
||||||
# Location of cache database
|
# Location of cache database
|
||||||
# cache_path = "@localstatedir@/cache/@PACKAGE@/cache.db"
|
# cache_path = "@localstatedir@/cache/@PACKAGE@/cache.db"
|
||||||
|
|
||||||
|
@ -51,8 +51,10 @@ static cfg_opt_t sec_general[] =
|
|||||||
CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel),
|
CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel),
|
||||||
CFG_STR("admin_password", NULL, CFGF_NONE),
|
CFG_STR("admin_password", NULL, CFGF_NONE),
|
||||||
CFG_INT("websocket_port", 3688, CFGF_NONE),
|
CFG_INT("websocket_port", 3688, CFGF_NONE),
|
||||||
|
CFG_STR("websocket_interface", NULL, CFGF_NONE),
|
||||||
CFG_STR_LIST("trusted_networks", "{localhost,192.168,fd}", CFGF_NONE),
|
CFG_STR_LIST("trusted_networks", "{localhost,192.168,fd}", CFGF_NONE),
|
||||||
CFG_BOOL("ipv6", cfg_true, CFGF_NONE),
|
CFG_BOOL("ipv6", cfg_true, CFGF_NONE),
|
||||||
|
CFG_STR("bind_address", NULL, CFGF_NONE),
|
||||||
CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
|
CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
|
||||||
CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
|
CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
|
||||||
CFG_BOOL("speaker_autoselect", cfg_false, CFGF_NONE),
|
CFG_BOOL("speaker_autoselect", cfg_false, CFGF_NONE),
|
||||||
|
30
src/httpd.c
30
src/httpd.c
@ -1499,7 +1499,7 @@ httpd_admin_check_auth(struct evhttp_request *req)
|
|||||||
|
|
||||||
evhttp_connection_get_peer(evcon, &addr, &port);
|
evhttp_connection_get_peer(evcon, &addr, &port);
|
||||||
|
|
||||||
if (peer_address_is_trusted(addr))
|
if (net_peer_address_is_trusted(addr))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
passwd = cfg_getstr(cfg_getsec(cfg, "general"), "admin_password");
|
passwd = cfg_getstr(cfg_getsec(cfg, "general"), "admin_password");
|
||||||
@ -1631,7 +1631,6 @@ int
|
|||||||
httpd_init(const char *webroot)
|
httpd_init(const char *webroot)
|
||||||
{
|
{
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
int v6enabled;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
httpd_exit = 0;
|
httpd_exit = 0;
|
||||||
@ -1761,9 +1760,6 @@ httpd_init(const char *webroot)
|
|||||||
goto evhttpd_fail;
|
goto evhttpd_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
|
|
||||||
httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
|
|
||||||
|
|
||||||
// For CORS headers
|
// For CORS headers
|
||||||
allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin");
|
allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin");
|
||||||
if (allow_origin)
|
if (allow_origin)
|
||||||
@ -1774,29 +1770,13 @@ httpd_init(const char *webroot)
|
|||||||
allow_origin = NULL;
|
allow_origin = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v6enabled)
|
httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
|
||||||
{
|
|
||||||
ret = evhttp_bind_socket(evhttpd, "::", httpd_port);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Could not bind to port %d with IPv6, falling back to IPv4\n", httpd_port);
|
|
||||||
v6enabled = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = evhttp_bind_socket(evhttpd, "0.0.0.0", httpd_port);
|
ret = net_evhttp_bind(evhttpd, httpd_port, "httpd");
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
if (!v6enabled)
|
DPRINTF(E_FATAL, L_HTTPD, "Could not bind to port %d (forked-daapd already running?)\n", httpd_port);
|
||||||
{
|
goto bind_fail;
|
||||||
DPRINTF(E_FATAL, L_HTTPD, "Could not bind to port %d (forked-daapd already running?)\n", httpd_port);
|
|
||||||
goto bind_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef __linux__
|
|
||||||
// Linux will listen on both ipv6 and ipv4, but FreeBSD won't
|
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Could not bind to port %d with IPv4, listening on IPv6 only\n", httpd_port);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
evhttp_set_gencb(evhttpd, httpd_gen_cb, NULL);
|
evhttp_set_gencb(evhttpd, httpd_gen_cb, NULL);
|
||||||
|
@ -717,7 +717,7 @@ daap_request_authorize(struct httpd_request *hreq)
|
|||||||
char *passwd;
|
char *passwd;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (peer_address_is_trusted(hreq->peer_address))
|
if (net_peer_address_is_trusted(hreq->peer_address))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Regular DAAP clients like iTunes will login with /login, and we will reply
|
// Regular DAAP clients like iTunes will login with /login, and we will reply
|
||||||
@ -924,7 +924,7 @@ daap_reply_login(struct httpd_request *hreq)
|
|||||||
CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 32));
|
CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 32));
|
||||||
|
|
||||||
param = evhttp_find_header(hreq->query, "pairing-guid");
|
param = evhttp_find_header(hreq->query, "pairing-guid");
|
||||||
if (param && !peer_address_is_trusted(hreq->peer_address))
|
if (param && !net_peer_address_is_trusted(hreq->peer_address))
|
||||||
{
|
{
|
||||||
if (strlen(param) < 3)
|
if (strlen(param) < 3)
|
||||||
{
|
{
|
||||||
|
@ -609,7 +609,7 @@ dacp_request_authorize(struct httpd_request *hreq)
|
|||||||
int32_t id;
|
int32_t id;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (peer_address_is_trusted(hreq->peer_address))
|
if (net_peer_address_is_trusted(hreq->peer_address))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
param = evhttp_find_header(hreq->query, "session-id");
|
param = evhttp_find_header(hreq->query, "session-id");
|
||||||
|
@ -295,7 +295,7 @@ rsp_request_authorize(struct httpd_request *hreq)
|
|||||||
char *passwd;
|
char *passwd;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (peer_address_is_trusted(hreq->peer_address))
|
if (net_peer_address_is_trusted(hreq->peer_address))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password");
|
passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password");
|
||||||
|
@ -8,6 +8,8 @@ enum mdns_options
|
|||||||
{
|
{
|
||||||
// Test connection to device and only call back if successful
|
// Test connection to device and only call back if successful
|
||||||
MDNS_CONNECTION_TEST = (1 << 1),
|
MDNS_CONNECTION_TEST = (1 << 1),
|
||||||
|
// Only browse for ipv4 services
|
||||||
|
MDNS_IPV4ONLY = (1 << 2),
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void (* mdns_browse_cb)(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt);
|
typedef void (* mdns_browse_cb)(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt);
|
||||||
@ -58,13 +60,11 @@ mdns_cname(char *name);
|
|||||||
* Call only from the main thread!
|
* Call only from the main thread!
|
||||||
*
|
*
|
||||||
* @in type Type of service to look for, e.g. "_raop._tcp"
|
* @in type Type of service to look for, e.g. "_raop._tcp"
|
||||||
* @in family AF_INET to browse for ipv4 services, AF_INET6 for ipv6,
|
|
||||||
AF_UNSPEC for both
|
|
||||||
* @in flags See mdns_options (only supported by Avahi implementation)
|
* @in flags See mdns_options (only supported by Avahi implementation)
|
||||||
* @in cb Callback when service state changes (e.g. appears/disappears)
|
* @in cb Callback when service state changes (e.g. appears/disappears)
|
||||||
* @return 0 on success, -1 on error
|
* @return 0 on success, -1 on error
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
mdns_browse(char *type, int family, mdns_browse_cb cb, enum mdns_options flags);
|
mdns_browse(char *type, mdns_browse_cb cb, enum mdns_options flags);
|
||||||
|
|
||||||
#endif /* !__MDNS_H__ */
|
#endif /* !__MDNS_H__ */
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "conffile.h"
|
||||||
#include "mdns.h"
|
#include "mdns.h"
|
||||||
|
|
||||||
#define MDNSERR avahi_strerror(avahi_client_errno(mdns_client))
|
#define MDNSERR avahi_strerror(avahi_client_errno(mdns_client))
|
||||||
@ -1160,15 +1161,21 @@ mdns_cname(char *name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
mdns_browse(char *type, int family, mdns_browse_cb cb, enum mdns_options flags)
|
mdns_browse(char *type, mdns_browse_cb cb, enum mdns_options flags)
|
||||||
{
|
{
|
||||||
struct mdns_browser *mb;
|
struct mdns_browser *mb;
|
||||||
AvahiServiceBrowser *b;
|
AvahiServiceBrowser *b;
|
||||||
|
int family;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_MDNS, "Adding service browser for type %s\n", type);
|
DPRINTF(E_DBG, L_MDNS, "Adding service browser for type %s\n", type);
|
||||||
|
|
||||||
CHECK_NULL(L_MDNS, mb = calloc(1, sizeof(struct mdns_browser)));
|
CHECK_NULL(L_MDNS, mb = calloc(1, sizeof(struct mdns_browser)));
|
||||||
|
|
||||||
|
if (flags & MDNS_IPV4ONLY || !cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"))
|
||||||
|
family = AF_INET;
|
||||||
|
else
|
||||||
|
family = AF_UNSPEC;
|
||||||
|
|
||||||
mb->protocol = avahi_af_to_proto(family);
|
mb->protocol = avahi_af_to_proto(family);
|
||||||
mb->type = strdup(type);
|
mb->type = strdup(type);
|
||||||
mb->flags = flags;
|
mb->flags = flags;
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "conffile.h"
|
||||||
|
|
||||||
/* Main event base, from main.c */
|
/* Main event base, from main.c */
|
||||||
extern struct event_base *evbase_main;
|
extern struct event_base *evbase_main;
|
||||||
@ -872,10 +873,11 @@ mdns_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
mdns_browse(char *regtype, int family, mdns_browse_cb cb, enum mdns_options flags)
|
mdns_browse(char *regtype, mdns_browse_cb cb, enum mdns_options flags)
|
||||||
{
|
{
|
||||||
struct mdns_browser *mb;
|
struct mdns_browser *mb;
|
||||||
DNSServiceErrorType err;
|
DNSServiceErrorType err;
|
||||||
|
int family;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_MDNS, "Adding service browser for type %s\n", regtype);
|
DPRINTF(E_DBG, L_MDNS, "Adding service browser for type %s\n", regtype);
|
||||||
|
|
||||||
@ -884,6 +886,11 @@ mdns_browse(char *regtype, int family, mdns_browse_cb cb, enum mdns_options flag
|
|||||||
mb->flags = flags;
|
mb->flags = flags;
|
||||||
mb->cb = cb;
|
mb->cb = cb;
|
||||||
|
|
||||||
|
if (flags & MDNS_IPV4ONLY || !cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"))
|
||||||
|
family = AF_INET;
|
||||||
|
else
|
||||||
|
family = AF_UNSPEC;
|
||||||
|
|
||||||
/* flags are ignored in DNS-SD implementation */
|
/* flags are ignored in DNS-SD implementation */
|
||||||
switch(family) {
|
switch(family) {
|
||||||
case AF_UNSPEC:
|
case AF_UNSPEC:
|
||||||
|
932
src/misc.c
932
src/misc.c
File diff suppressed because it is too large
Load Diff
200
src/misc.h
200
src/misc.h
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
#ifndef __MISC_H__
|
#ifndef __MISC_H__
|
||||||
#define __MISC_H__
|
#define __MISC_H__
|
||||||
|
|
||||||
@ -7,11 +6,49 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <time.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
/* Samples to bytes, bytes to samples */
|
|
||||||
|
/* ------------------------ Network utility functions ----------------------- */
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <event2/http.h>
|
||||||
|
|
||||||
|
union net_sockaddr
|
||||||
|
{
|
||||||
|
struct sockaddr_in sin;
|
||||||
|
struct sockaddr_in6 sin6;
|
||||||
|
struct sockaddr sa;
|
||||||
|
struct sockaddr_storage ss;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Checks if the address is in a network that is configured as trusted
|
||||||
|
bool
|
||||||
|
net_peer_address_is_trusted(const char *addr);
|
||||||
|
|
||||||
|
int
|
||||||
|
net_address_get(char *addr, size_t addr_len, union net_sockaddr *naddr);
|
||||||
|
|
||||||
|
int
|
||||||
|
net_port_get(short unsigned *port, union net_sockaddr *naddr);
|
||||||
|
|
||||||
|
// Returns the socket fd from socket(), -1 on error
|
||||||
|
int
|
||||||
|
net_connect(const char *addr, unsigned short port, int type, const char *log_service_name);
|
||||||
|
|
||||||
|
// Returns the socket fd from socket(), -1 on error
|
||||||
|
int
|
||||||
|
net_bind(short unsigned *port, int type, const char *log_service_name);
|
||||||
|
|
||||||
|
int
|
||||||
|
net_evhttp_bind(struct evhttp *evhttp, short unsigned port, const char *log_service_name);
|
||||||
|
|
||||||
|
|
||||||
|
/* ----------------------- Conversion/hashing/sanitizers -------------------- */
|
||||||
|
|
||||||
|
// Samples to bytes, bytes to samples
|
||||||
#define STOB(s, bits, c) ((s) * (c) * (bits) / 8)
|
#define STOB(s, bits, c) ((s) * (c) * (bits) / 8)
|
||||||
#define BTOS(b, bits, c) ((b) / ((c) * (bits) / 8))
|
#define BTOS(b, bits, c) ((b) / ((c) * (bits) / 8))
|
||||||
|
|
||||||
@ -30,41 +67,6 @@
|
|||||||
#define NTOSTR_HELPER(x) #x
|
#define NTOSTR_HELPER(x) #x
|
||||||
#define NTOSTR(x) NTOSTR_HELPER(x)
|
#define NTOSTR(x) NTOSTR_HELPER(x)
|
||||||
|
|
||||||
|
|
||||||
// Remember to adjust quality_is_equal() if adding elements
|
|
||||||
struct media_quality {
|
|
||||||
int sample_rate;
|
|
||||||
int bits_per_sample;
|
|
||||||
int channels;
|
|
||||||
int bit_rate;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct onekeyval {
|
|
||||||
char *name;
|
|
||||||
char *value;
|
|
||||||
|
|
||||||
struct onekeyval *next;
|
|
||||||
struct onekeyval *sort;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct keyval {
|
|
||||||
struct onekeyval *head;
|
|
||||||
struct onekeyval *tail;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ringbuffer {
|
|
||||||
uint8_t *buffer;
|
|
||||||
size_t size;
|
|
||||||
size_t write_avail;
|
|
||||||
size_t read_avail;
|
|
||||||
size_t write_pos;
|
|
||||||
size_t read_pos;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
char **
|
|
||||||
buildopts_get(void);
|
|
||||||
|
|
||||||
int
|
int
|
||||||
safe_atoi32(const char *str, int32_t *val);
|
safe_atoi32(const char *str, int32_t *val);
|
||||||
|
|
||||||
@ -95,32 +97,6 @@ safe_snprintf_cat(char *dst, size_t n, const char *fmt, ...) __attribute__ ((for
|
|||||||
int
|
int
|
||||||
safe_snreplace(char *s, size_t sz, const char *pattern, const char *replacement);
|
safe_snreplace(char *s, size_t sz, const char *pattern, const char *replacement);
|
||||||
|
|
||||||
/* Key/value functions */
|
|
||||||
struct keyval *
|
|
||||||
keyval_alloc(void);
|
|
||||||
|
|
||||||
int
|
|
||||||
keyval_add(struct keyval *kv, const char *name, const char *value);
|
|
||||||
|
|
||||||
int
|
|
||||||
keyval_add_size(struct keyval *kv, const char *name, const char *value, size_t size);
|
|
||||||
|
|
||||||
void
|
|
||||||
keyval_remove(struct keyval *kv, const char *name);
|
|
||||||
|
|
||||||
const char *
|
|
||||||
keyval_get(struct keyval *kv, const char *name);
|
|
||||||
|
|
||||||
void
|
|
||||||
keyval_clear(struct keyval *kv);
|
|
||||||
|
|
||||||
void
|
|
||||||
keyval_sort(struct keyval *kv);
|
|
||||||
|
|
||||||
|
|
||||||
char **
|
|
||||||
m_readfile(const char *path, int num_lines);
|
|
||||||
|
|
||||||
char *
|
char *
|
||||||
unicode_fixup_string(char *str, const char *fromcode);
|
unicode_fixup_string(char *str, const char *fromcode);
|
||||||
|
|
||||||
@ -150,18 +126,54 @@ b64_encode(const uint8_t *src, int srclen);
|
|||||||
uint64_t
|
uint64_t
|
||||||
murmur_hash64(const void *key, int len, uint32_t seed);
|
murmur_hash64(const void *key, int len, uint32_t seed);
|
||||||
|
|
||||||
void
|
|
||||||
uuid_make(char *str);
|
/* --------------------------- Key/value functions -------------------------- */
|
||||||
|
|
||||||
|
struct onekeyval {
|
||||||
|
char *name;
|
||||||
|
char *value;
|
||||||
|
|
||||||
|
struct onekeyval *next;
|
||||||
|
struct onekeyval *sort;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct keyval {
|
||||||
|
struct onekeyval *head;
|
||||||
|
struct onekeyval *tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct keyval *
|
||||||
|
keyval_alloc(void);
|
||||||
|
|
||||||
int
|
int
|
||||||
linear_regression(double *m, double *b, double *r, const double *x, const double *y, int n);
|
keyval_add(struct keyval *kv, const char *name, const char *value);
|
||||||
|
|
||||||
bool
|
int
|
||||||
quality_is_equal(struct media_quality *a, struct media_quality *b);
|
keyval_add_size(struct keyval *kv, const char *name, const char *value, size_t size);
|
||||||
|
|
||||||
// Checks if the address is in a network that is configured as trusted
|
void
|
||||||
bool
|
keyval_remove(struct keyval *kv, const char *name);
|
||||||
peer_address_is_trusted(const char *addr);
|
|
||||||
|
const char *
|
||||||
|
keyval_get(struct keyval *kv, const char *name);
|
||||||
|
|
||||||
|
void
|
||||||
|
keyval_clear(struct keyval *kv);
|
||||||
|
|
||||||
|
void
|
||||||
|
keyval_sort(struct keyval *kv);
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------- Ringbuffer ------------------------------- */
|
||||||
|
|
||||||
|
struct ringbuffer {
|
||||||
|
uint8_t *buffer;
|
||||||
|
size_t size;
|
||||||
|
size_t write_avail;
|
||||||
|
size_t read_avail;
|
||||||
|
size_t write_pos;
|
||||||
|
size_t read_pos;
|
||||||
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
ringbuffer_init(struct ringbuffer *buf, size_t size);
|
ringbuffer_init(struct ringbuffer *buf, size_t size);
|
||||||
@ -176,6 +188,10 @@ size_t
|
|||||||
ringbuffer_read(uint8_t **dst, size_t dstlen, struct ringbuffer *buf);
|
ringbuffer_read(uint8_t **dst, size_t dstlen, struct ringbuffer *buf);
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------- Clock utility functions ------------------------ */
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#ifndef HAVE_CLOCK_GETTIME
|
#ifndef HAVE_CLOCK_GETTIME
|
||||||
|
|
||||||
#ifndef CLOCK_REALTIME
|
#ifndef CLOCK_REALTIME
|
||||||
@ -217,7 +233,7 @@ timer_getoverrun(timer_t timer_id);
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Timer function for platforms without hi-res timers */
|
// Timer function for platforms without hi-res timers
|
||||||
int
|
int
|
||||||
clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec *res);
|
clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec *res);
|
||||||
|
|
||||||
@ -230,10 +246,44 @@ timespec_cmp(struct timespec time1, struct timespec time2);
|
|||||||
struct timespec
|
struct timespec
|
||||||
timespec_reltoabs(struct timespec relative);
|
timespec_reltoabs(struct timespec relative);
|
||||||
|
|
||||||
/* initialize mutex with error checking (not default on all platforms) */
|
|
||||||
|
/* ------------------------------- Media quality ---------------------------- */
|
||||||
|
|
||||||
|
// Remember to adjust quality_is_equal() if adding elements
|
||||||
|
struct media_quality {
|
||||||
|
int sample_rate;
|
||||||
|
int bits_per_sample;
|
||||||
|
int channels;
|
||||||
|
int bit_rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
quality_is_equal(struct media_quality *a, struct media_quality *b);
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------------- Misc utility functions ------------------------ */
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
char **
|
||||||
|
buildopts_get(void);
|
||||||
|
|
||||||
|
// initialize mutex with error checking (not default on all platforms)
|
||||||
int
|
int
|
||||||
mutex_init(pthread_mutex_t *mutex);
|
mutex_init(pthread_mutex_t *mutex);
|
||||||
|
|
||||||
|
void
|
||||||
|
uuid_make(char *str);
|
||||||
|
|
||||||
|
int
|
||||||
|
linear_regression(double *m, double *b, double *r, const double *x, const double *y, int n);
|
||||||
|
|
||||||
|
char **
|
||||||
|
m_readfile(const char *path, int num_lines);
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------------------- Assertion ------------------------------- */
|
||||||
|
|
||||||
/* Check that the function returns 0, logging a fatal error referencing
|
/* Check that the function returns 0, logging a fatal error referencing
|
||||||
returned error (type errno) if it fails, and aborts the process.
|
returned error (type errno) if it fails, and aborts the process.
|
||||||
Example: CHECK_ERR(L_MAIN, my_function()); */
|
Example: CHECK_ERR(L_MAIN, my_function()); */
|
||||||
|
192
src/mpd.c
192
src/mpd.c
@ -77,8 +77,9 @@ static struct commands_base *cmdbase;
|
|||||||
|
|
||||||
static struct evhttp *evhttpd;
|
static struct evhttp *evhttpd;
|
||||||
|
|
||||||
struct evconnlistener *mpd_listener6;
|
static struct evconnlistener *mpd_listener;
|
||||||
struct evconnlistener *mpd_listener;
|
static int mpd_sockfd;
|
||||||
|
|
||||||
|
|
||||||
// Virtual path to the default playlist directory
|
// Virtual path to the default playlist directory
|
||||||
static char *default_pl_dir;
|
static char *default_pl_dir;
|
||||||
@ -4511,7 +4512,7 @@ mpd_accept_conn_cb(struct evconnlistener *listener,
|
|||||||
if (!client_ctx->authenticated)
|
if (!client_ctx->authenticated)
|
||||||
{
|
{
|
||||||
sockaddr_to_string(address, addr_str, sizeof(addr_str));
|
sockaddr_to_string(address, addr_str, sizeof(addr_str));
|
||||||
client_ctx->authenticated = peer_address_is_trusted(addr_str);
|
client_ctx->authenticated = net_peer_address_is_trusted(addr_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
client_ctx->next = mpd_clients;
|
client_ctx->next = mpd_clients;
|
||||||
@ -4739,18 +4740,47 @@ artwork_cb(struct evhttp_request *req, void *arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Thread: main */
|
/* Thread: main */
|
||||||
int mpd_init(void)
|
static int mpd_httpd_init(void)
|
||||||
{
|
{
|
||||||
struct sockaddr *saddr;
|
|
||||||
size_t saddr_length;
|
|
||||||
struct sockaddr_in sin;
|
|
||||||
struct sockaddr_in6 sin6;
|
|
||||||
unsigned short port;
|
|
||||||
unsigned short http_port;
|
unsigned short http_port;
|
||||||
int v6enabled;
|
|
||||||
const char *pl_dir;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
http_port = cfg_getint(cfg_getsec(cfg, "mpd"), "http_port");
|
||||||
|
if (http_port == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
evhttpd = evhttp_new(evbase_mpd);
|
||||||
|
if (!evhttpd)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
ret = net_evhttp_bind(evhttpd, http_port, "mpd artwork");
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
evhttp_free(evhttpd);
|
||||||
|
evhttpd = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
evhttp_set_gencb(evhttpd, artwork_cb, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thread: main */
|
||||||
|
static void mpd_httpd_deinit(void)
|
||||||
|
{
|
||||||
|
if (evhttpd)
|
||||||
|
evhttp_free(evhttpd);
|
||||||
|
|
||||||
|
evhttpd = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thread: main */
|
||||||
|
int mpd_init(void)
|
||||||
|
{
|
||||||
|
unsigned short port;
|
||||||
|
const char *pl_dir;
|
||||||
|
int ret;
|
||||||
|
|
||||||
port = cfg_getint(cfg_getsec(cfg, "mpd"), "port");
|
port = cfg_getint(cfg_getsec(cfg, "mpd"), "port");
|
||||||
if (port <= 0)
|
if (port <= 0)
|
||||||
@ -4759,112 +4789,29 @@ int mpd_init(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
|
CHECK_NULL(L_MPD, evbase_mpd = event_base_new());
|
||||||
|
CHECK_NULL(L_MPD, cmdbase = commands_base_new(evbase_mpd, NULL));
|
||||||
|
|
||||||
evbase_mpd = event_base_new();
|
mpd_sockfd = net_bind(&port, SOCK_STREAM | SOCK_NONBLOCK, "mpd");
|
||||||
if (!evbase_mpd)
|
if (mpd_sockfd < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_MPD, "Could not create an event base\n");
|
DPRINTF(E_LOG, L_MPD, "Could not bind mpd server to port %hu\n", port);
|
||||||
goto evbase_fail;
|
goto bind_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdbase = commands_base_new(evbase_mpd, NULL);
|
mpd_listener = evconnlistener_new(evbase_mpd, mpd_accept_conn_cb, NULL, 0, -1, mpd_sockfd);
|
||||||
|
|
||||||
if (v6enabled)
|
|
||||||
{
|
|
||||||
saddr_length = sizeof(sin6);
|
|
||||||
memset(&sin6, 0, saddr_length);
|
|
||||||
sin6.sin6_family = AF_INET6;
|
|
||||||
sin6.sin6_port = htons(port);
|
|
||||||
saddr = (struct sockaddr *)&sin6;
|
|
||||||
|
|
||||||
mpd_listener6 = evconnlistener_new_bind(
|
|
||||||
evbase_mpd,
|
|
||||||
mpd_accept_conn_cb,
|
|
||||||
NULL,
|
|
||||||
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
|
|
||||||
-1,
|
|
||||||
saddr,
|
|
||||||
saddr_length);
|
|
||||||
|
|
||||||
if (!mpd_listener6)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_MPD, "Could not bind to port %d, falling back to IPv4\n", port);
|
|
||||||
v6enabled = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
evconnlistener_set_error_cb(mpd_listener6, mpd_accept_error_cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
saddr_length = sizeof(struct sockaddr_in);
|
|
||||||
memset(&sin, 0, saddr_length);
|
|
||||||
sin.sin_family = AF_INET;
|
|
||||||
sin.sin_addr.s_addr = htonl(0);
|
|
||||||
sin.sin_port = htons(port);
|
|
||||||
saddr = (struct sockaddr *)&sin;
|
|
||||||
|
|
||||||
mpd_listener = evconnlistener_new_bind(
|
|
||||||
evbase_mpd,
|
|
||||||
mpd_accept_conn_cb,
|
|
||||||
NULL,
|
|
||||||
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
|
|
||||||
-1,
|
|
||||||
saddr,
|
|
||||||
saddr_length);
|
|
||||||
|
|
||||||
if (!mpd_listener)
|
if (!mpd_listener)
|
||||||
{
|
{
|
||||||
if (!v6enabled)
|
DPRINTF(E_LOG, L_MPD, "Could not create connection listener for mpd clients on port %d\n", port);
|
||||||
{
|
goto connew_fail;
|
||||||
DPRINTF(E_LOG, L_MPD, "Could not create connection listener for mpd clients on port %d\n", port);
|
|
||||||
goto connew_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef __linux__
|
|
||||||
// Linux will listen on both ipv6 and ipv4, but FreeBSD won't
|
|
||||||
DPRINTF(E_LOG, L_MPD, "Could not bind to port %d with IPv4, listening on IPv6 only\n", port);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else
|
evconnlistener_set_error_cb(mpd_listener, mpd_accept_error_cb);
|
||||||
evconnlistener_set_error_cb(mpd_listener, mpd_accept_error_cb);
|
|
||||||
|
|
||||||
http_port = cfg_getint(cfg_getsec(cfg, "mpd"), "http_port");
|
ret = mpd_httpd_init();
|
||||||
if (http_port > 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
evhttpd = evhttp_new(evbase_mpd);
|
DPRINTF(E_LOG, L_MPD, "Could not initialize HTTP artwork server\n");
|
||||||
if (!evhttpd)
|
goto httpd_fail;
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_MPD, "Could not create HTTP artwork server\n");
|
|
||||||
|
|
||||||
goto evhttp_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
evhttp_set_gencb(evhttpd, artwork_cb, NULL);
|
|
||||||
|
|
||||||
if (v6enabled)
|
|
||||||
{
|
|
||||||
ret = evhttp_bind_socket(evhttpd, "::", http_port);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_MPD, "Could not bind HTTP artwork server to port %d with IPv6, falling back to IPv4\n", http_port);
|
|
||||||
v6enabled = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = evhttp_bind_socket(evhttpd, "0.0.0.0", http_port);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
if (!v6enabled)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_MPD, "Could not bind HTTP artwork server to port %d with IPv4\n", http_port);
|
|
||||||
goto bind_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef __linux__
|
|
||||||
// Linux will listen on both ipv6 and ipv4, but FreeBSD won't
|
|
||||||
DPRINTF(E_LOG, L_MPD, "Could not bind HTTP artwork server to port %d with IPv4, listening on IPv6 only\n", http_port);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "library"), "allow_modifying_stored_playlists");
|
allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "library"), "allow_modifying_stored_playlists");
|
||||||
@ -4908,23 +4855,17 @@ int mpd_init(void)
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
||||||
thread_fail:
|
thread_fail:
|
||||||
bind_fail:
|
mpd_httpd_deinit();
|
||||||
if (http_port > 0)
|
httpd_fail:
|
||||||
evhttp_free(evhttpd);
|
evconnlistener_free(mpd_listener);
|
||||||
evhttp_fail:
|
|
||||||
// Note evconnlistener_free segfaults if you give it a null pointer, so we need the if
|
|
||||||
if (mpd_listener)
|
|
||||||
evconnlistener_free(mpd_listener);
|
|
||||||
if (mpd_listener6)
|
|
||||||
evconnlistener_free(mpd_listener6);
|
|
||||||
connew_fail:
|
connew_fail:
|
||||||
|
close(mpd_sockfd);
|
||||||
|
bind_fail:
|
||||||
commands_base_free(cmdbase);
|
commands_base_free(cmdbase);
|
||||||
event_base_free(evbase_mpd);
|
event_base_free(evbase_mpd);
|
||||||
evbase_mpd = NULL;
|
evbase_mpd = NULL;
|
||||||
|
|
||||||
evbase_fail:
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4932,7 +4873,6 @@ int mpd_init(void)
|
|||||||
void mpd_deinit(void)
|
void mpd_deinit(void)
|
||||||
{
|
{
|
||||||
unsigned short port;
|
unsigned short port;
|
||||||
unsigned short http_port;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
port = cfg_getint(cfg_getsec(cfg, "mpd"), "port");
|
port = cfg_getint(cfg_getsec(cfg, "mpd"), "port");
|
||||||
@ -4958,15 +4898,11 @@ void mpd_deinit(void)
|
|||||||
free_mpd_client_ctx(mpd_clients);
|
free_mpd_client_ctx(mpd_clients);
|
||||||
}
|
}
|
||||||
|
|
||||||
http_port = cfg_getint(cfg_getsec(cfg, "mpd"), "http_port");
|
mpd_httpd_deinit();
|
||||||
if (http_port > 0)
|
|
||||||
evhttp_free(evhttpd);
|
|
||||||
|
|
||||||
// Note evconnlistener_free segfaults if you give it a null pointer, so we need the if
|
evconnlistener_free(mpd_listener);
|
||||||
if (mpd_listener)
|
|
||||||
evconnlistener_free(mpd_listener);
|
close(mpd_sockfd);
|
||||||
if (mpd_listener6)
|
|
||||||
evconnlistener_free(mpd_listener6);
|
|
||||||
|
|
||||||
// Free event base (should free events too)
|
// Free event base (should free events too)
|
||||||
event_base_free(evbase_mpd);
|
event_base_free(evbase_mpd);
|
||||||
|
@ -102,14 +102,6 @@
|
|||||||
// This is an arbitrary value which just needs to be kept in sync with the config
|
// This is an arbitrary value which just needs to be kept in sync with the config
|
||||||
#define AIRPLAY_CONFIG_MAX_VOLUME 11
|
#define AIRPLAY_CONFIG_MAX_VOLUME 11
|
||||||
|
|
||||||
union sockaddr_all
|
|
||||||
{
|
|
||||||
struct sockaddr_in sin;
|
|
||||||
struct sockaddr_in6 sin6;
|
|
||||||
struct sockaddr sa;
|
|
||||||
struct sockaddr_storage ss;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Keep in sync with const char *airplay_devtype */
|
/* Keep in sync with const char *airplay_devtype */
|
||||||
enum airplay_devtype {
|
enum airplay_devtype {
|
||||||
AIRPLAY_DEV_APEX2_80211N,
|
AIRPLAY_DEV_APEX2_80211N,
|
||||||
@ -264,6 +256,8 @@ struct airplay_session
|
|||||||
char *address;
|
char *address;
|
||||||
int family;
|
int family;
|
||||||
|
|
||||||
|
union net_sockaddr naddr;
|
||||||
|
|
||||||
int volume;
|
int volume;
|
||||||
|
|
||||||
char *local_address;
|
char *local_address;
|
||||||
@ -287,8 +281,6 @@ struct airplay_session
|
|||||||
int events_fd;
|
int events_fd;
|
||||||
struct event *eventsev;
|
struct event *eventsev;
|
||||||
|
|
||||||
union sockaddr_all sa;
|
|
||||||
|
|
||||||
struct airplay_service *timing_svc;
|
struct airplay_service *timing_svc;
|
||||||
struct airplay_service *control_svc;
|
struct airplay_service *control_svc;
|
||||||
|
|
||||||
@ -441,12 +433,10 @@ static struct media_quality airplay_quality_default =
|
|||||||
extern struct event_base *evbase_player;
|
extern struct event_base *evbase_player;
|
||||||
|
|
||||||
/* AirTunes v2 time synchronization */
|
/* AirTunes v2 time synchronization */
|
||||||
static struct airplay_service timing_4svc;
|
static struct airplay_service airplay_timing_svc;
|
||||||
static struct airplay_service timing_6svc;
|
|
||||||
|
|
||||||
/* AirTunes v2 playback synchronization / control */
|
/* AirTunes v2 playback synchronization / control */
|
||||||
static struct airplay_service control_4svc;
|
static struct airplay_service airplay_control_svc;
|
||||||
static struct airplay_service control_6svc;
|
|
||||||
|
|
||||||
/* Metadata */
|
/* Metadata */
|
||||||
static struct output_metadata *airplay_cur_metadata;
|
static struct output_metadata *airplay_cur_metadata;
|
||||||
@ -517,7 +507,7 @@ ntp_to_timespec(struct ntp_stamp *ns, struct timespec *ts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
airplay_timing_get_clock_ntp(struct ntp_stamp *ns)
|
timing_get_clock_ntp(struct ntp_stamp *ns)
|
||||||
{
|
{
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
int ret;
|
int ret;
|
||||||
@ -1348,38 +1338,33 @@ session_connection_setup(struct airplay_session *rs, struct output_device *rd, i
|
|||||||
unsigned short port;
|
unsigned short port;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
rs->sa.ss.ss_family = family;
|
rs->naddr.ss.ss_family = family;
|
||||||
|
|
||||||
switch (family)
|
switch (family)
|
||||||
{
|
{
|
||||||
case AF_INET:
|
case AF_INET:
|
||||||
/* We always have the v4 services, so no need to check */
|
|
||||||
if (!rd->v4_address)
|
if (!rd->v4_address)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
address = rd->v4_address;
|
address = rd->v4_address;
|
||||||
port = rd->v4_port;
|
port = rd->v4_port;
|
||||||
|
|
||||||
rs->timing_svc = &timing_4svc;
|
|
||||||
rs->control_svc = &control_4svc;
|
|
||||||
|
|
||||||
ret = inet_pton(AF_INET, address, &rs->sa.sin.sin_addr);
|
ret = inet_pton(AF_INET, address, &rs->naddr.sin.sin_addr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AF_INET6:
|
case AF_INET6:
|
||||||
if (!rd->v6_address || rd->v6_disabled || (timing_6svc.fd < 0) || (control_6svc.fd < 0))
|
if (!rd->v6_address)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
address = rd->v6_address;
|
address = rd->v6_address;
|
||||||
port = rd->v6_port;
|
port = rd->v6_port;
|
||||||
|
|
||||||
rs->timing_svc = &timing_6svc;
|
|
||||||
rs->control_svc = &control_6svc;
|
|
||||||
|
|
||||||
intf = strchr(address, '%');
|
intf = strchr(address, '%');
|
||||||
if (intf)
|
if (intf)
|
||||||
*intf = '\0';
|
*intf = '\0';
|
||||||
|
|
||||||
ret = inet_pton(AF_INET6, address, &rs->sa.sin6.sin6_addr);
|
ret = inet_pton(AF_INET6, address, &rs->naddr.sin6.sin6_addr);
|
||||||
|
|
||||||
if (intf)
|
if (intf)
|
||||||
{
|
{
|
||||||
@ -1387,8 +1372,8 @@ session_connection_setup(struct airplay_session *rs, struct output_device *rd, i
|
|||||||
|
|
||||||
intf++;
|
intf++;
|
||||||
|
|
||||||
rs->sa.sin6.sin6_scope_id = if_nametoindex(intf);
|
rs->naddr.sin6.sin6_scope_id = if_nametoindex(intf);
|
||||||
if (rs->sa.sin6.sin6_scope_id == 0)
|
if (rs->naddr.sin6.sin6_scope_id == 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not find interface %s\n", intf);
|
DPRINTF(E_LOG, L_AIRPLAY, "Could not find interface %s\n", intf);
|
||||||
|
|
||||||
@ -1461,7 +1446,7 @@ session_cipher_setup(struct airplay_session *rs, const uint8_t *key, size_t key_
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_INFO, L_AIRPLAY, "Ciphering setup of '%s' completed succesfully, now using encrypted mode\n", rs->devname);
|
DPRINTF(E_DBG, L_AIRPLAY, "Ciphering setup of '%s' completed succesfully, now using encrypted mode\n", rs->devname);
|
||||||
|
|
||||||
rs->state = AIRPLAY_STATE_ENCRYPTED;
|
rs->state = AIRPLAY_STATE_ENCRYPTED;
|
||||||
rs->control_cipher_ctx = control_cipher_ctx;
|
rs->control_cipher_ctx = control_cipher_ctx;
|
||||||
@ -1528,6 +1513,35 @@ session_ids_set(struct airplay_session *rs)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct airplay_session *
|
||||||
|
session_find_by_address(union net_sockaddr *peer_addr)
|
||||||
|
{
|
||||||
|
struct airplay_session *rs;
|
||||||
|
uint32_t *addr_ptr;
|
||||||
|
int family = peer_addr->sa.sa_family;
|
||||||
|
|
||||||
|
for (rs = airplay_sessions; rs; rs = rs->next)
|
||||||
|
{
|
||||||
|
if (family == rs->family)
|
||||||
|
{
|
||||||
|
if (family == AF_INET && peer_addr->sin.sin_addr.s_addr == rs->naddr.sin.sin_addr.s_addr)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&peer_addr->sin6.sin6_addr, &rs->naddr.sin6.sin6_addr))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&peer_addr->sin6.sin6_addr))
|
||||||
|
{
|
||||||
|
// ipv4 mapped to ipv6 consists of 16 bytes/4 words: 0x00000000 0x00000000 0x0000ffff 0x[IPv4]
|
||||||
|
addr_ptr = (uint32_t *)(&peer_addr->sin6.sin6_addr);
|
||||||
|
if (addr_ptr[3] == rs->naddr.sin.sin_addr.s_addr)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs;
|
||||||
|
}
|
||||||
|
|
||||||
static struct airplay_session *
|
static struct airplay_session *
|
||||||
session_make(struct output_device *rd, int callback_id)
|
session_make(struct output_device *rd, int callback_id)
|
||||||
{
|
{
|
||||||
@ -1561,6 +1575,9 @@ session_make(struct output_device *rd, int callback_id)
|
|||||||
|
|
||||||
rs->next_seq = AIRPLAY_SEQ_CONTINUE;
|
rs->next_seq = AIRPLAY_SEQ_CONTINUE;
|
||||||
|
|
||||||
|
rs->timing_svc = &airplay_timing_svc;
|
||||||
|
rs->control_svc = &airplay_control_svc;
|
||||||
|
|
||||||
ret = session_connection_setup(rs, rd, AF_INET6);
|
ret = session_connection_setup(rs, rd, AF_INET6);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -1918,27 +1935,27 @@ packet_send(struct airplay_session *rs, struct rtp_packet *pkt)
|
|||||||
static void
|
static void
|
||||||
control_packet_send(struct airplay_session *rs, struct rtp_packet *pkt)
|
control_packet_send(struct airplay_session *rs, struct rtp_packet *pkt)
|
||||||
{
|
{
|
||||||
int len;
|
socklen_t addrlen;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
switch (rs->sa.ss.ss_family)
|
switch (rs->family)
|
||||||
{
|
{
|
||||||
case AF_INET:
|
case AF_INET:
|
||||||
rs->sa.sin.sin_port = htons(rs->control_port);
|
rs->naddr.sin.sin_port = htons(rs->control_port);
|
||||||
len = sizeof(rs->sa.sin);
|
addrlen = sizeof(rs->naddr.sin);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AF_INET6:
|
case AF_INET6:
|
||||||
rs->sa.sin6.sin6_port = htons(rs->control_port);
|
rs->naddr.sin6.sin6_port = htons(rs->control_port);
|
||||||
len = sizeof(rs->sa.sin6);
|
addrlen = sizeof(rs->naddr.sin6);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
DPRINTF(E_WARN, L_AIRPLAY, "Unknown family %d\n", rs->sa.ss.ss_family);
|
DPRINTF(E_WARN, L_AIRPLAY, "Unknown family %d\n", rs->family);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sendto(rs->control_svc->fd, pkt->data, pkt->data_len, 0, &rs->sa.sa, len);
|
ret = sendto(rs->control_svc->fd, pkt->data, pkt->data_len, 0, &rs->naddr.sa, addrlen);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not send playback sync to device '%s': %s\n", rs->devname, strerror(errno));
|
DPRINTF(E_LOG, L_AIRPLAY, "Could not send playback sync to device '%s': %s\n", rs->devname, strerror(errno));
|
||||||
}
|
}
|
||||||
@ -2086,51 +2103,93 @@ packets_sync_send(struct airplay_master_session *rms)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------ Time service ------------------------------ */
|
/* ------------------------- Time and control service ----------------------- */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
airplay_timing_cb(int fd, short what, void *arg)
|
service_stop(struct airplay_service *svc)
|
||||||
{
|
{
|
||||||
union sockaddr_all sa;
|
if (svc->ev)
|
||||||
|
event_free(svc->ev);
|
||||||
|
|
||||||
|
if (svc->fd >= 0)
|
||||||
|
close(svc->fd);
|
||||||
|
|
||||||
|
svc->ev = NULL;
|
||||||
|
svc->fd = -1;
|
||||||
|
svc->port = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
service_start(struct airplay_service *svc, event_callback_fn cb, unsigned short port, const char *log_service_name)
|
||||||
|
{
|
||||||
|
memset(svc, 0, sizeof(struct airplay_service));
|
||||||
|
|
||||||
|
svc->fd = net_bind(&port, SOCK_DGRAM, log_service_name);
|
||||||
|
if (svc->fd < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_AIRPLAY, "Could not start '%s' service\n", log_service_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
svc->ev = event_new(evbase_player, svc->fd, EV_READ | EV_PERSIST, cb, svc);
|
||||||
|
if (!svc->ev)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_AIRPLAY, "Could not create event for '%s' service\n", log_service_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
event_add(svc->ev, NULL);
|
||||||
|
|
||||||
|
svc->port = port;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
service_stop(svc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
timing_svc_cb(int fd, short what, void *arg)
|
||||||
|
{
|
||||||
|
struct airplay_service *svc = arg;
|
||||||
|
union net_sockaddr peer_addr;
|
||||||
|
socklen_t peer_addrlen = sizeof(peer_addr);
|
||||||
|
char address[INET6_ADDRSTRLEN];
|
||||||
uint8_t req[32];
|
uint8_t req[32];
|
||||||
uint8_t res[32];
|
uint8_t res[32];
|
||||||
struct ntp_stamp recv_stamp;
|
struct ntp_stamp recv_stamp;
|
||||||
struct ntp_stamp xmit_stamp;
|
struct ntp_stamp xmit_stamp;
|
||||||
struct airplay_service *svc;
|
|
||||||
int len;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
svc = (struct airplay_service *)arg;
|
ret = timing_get_clock_ntp(&recv_stamp);
|
||||||
|
|
||||||
ret = airplay_timing_get_clock_ntp(&recv_stamp);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't get receive timestamp\n");
|
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't get receive timestamp\n");
|
||||||
|
return;
|
||||||
goto readd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
len = sizeof(sa.ss);
|
peer_addrlen = sizeof(peer_addr);
|
||||||
ret = recvfrom(svc->fd, req, sizeof(req), 0, &sa.sa, (socklen_t *)&len);
|
ret = recvfrom(svc->fd, req, sizeof(req), 0, &peer_addr.sa, &peer_addrlen);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Error reading timing request: %s\n", strerror(errno));
|
net_address_get(address, sizeof(address), &peer_addr);
|
||||||
|
DPRINTF(E_LOG, L_AIRPLAY, "Error reading timing request from %s: %s\n", address, strerror(errno));
|
||||||
goto readd;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret != 32)
|
if (ret != 32)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_AIRPLAY, "Got timing request with size %d\n", ret);
|
net_address_get(address, sizeof(address), &peer_addr);
|
||||||
|
DPRINTF(E_WARN, L_AIRPLAY, "Got timing request from %s with size %d\n", address, ret);
|
||||||
goto readd;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((req[0] != 0x80) || (req[1] != 0xd2))
|
if ((req[0] != 0x80) || (req[1] != 0xd2))
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Packet header doesn't match timing request (got 0x%02x%02x, expected 0x80d2)\n", req[0], req[1]);
|
net_address_get(address, sizeof(address), &peer_addr);
|
||||||
|
DPRINTF(E_WARN, L_AIRPLAY, "Packet header from %s doesn't match timing request (got 0x%02x%02x, expected 0x80d2)\n", address, req[0], req[1]);
|
||||||
goto readd;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(res, 0, sizeof(res));
|
memset(res, 0, sizeof(res));
|
||||||
@ -2150,7 +2209,7 @@ airplay_timing_cb(int fd, short what, void *arg)
|
|||||||
memcpy(res + 20, &recv_stamp.frac, 4);
|
memcpy(res + 20, &recv_stamp.frac, 4);
|
||||||
|
|
||||||
/* Transmit timestamp */
|
/* Transmit timestamp */
|
||||||
ret = airplay_timing_get_clock_ntp(&xmit_stamp);
|
ret = timing_get_clock_ntp(&xmit_stamp);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't get transmit timestamp, falling back to receive timestamp\n");
|
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't get transmit timestamp, falling back to receive timestamp\n");
|
||||||
@ -2169,258 +2228,55 @@ airplay_timing_cb(int fd, short what, void *arg)
|
|||||||
memcpy(res + 28, &xmit_stamp.frac, 4);
|
memcpy(res + 28, &xmit_stamp.frac, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sendto(svc->fd, res, sizeof(res), 0, &sa.sa, len);
|
ret = sendto(svc->fd, res, sizeof(res), 0, &peer_addr.sa, peer_addrlen);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not send timing reply: %s\n", strerror(errno));
|
net_address_get(address, sizeof(address), &peer_addr);
|
||||||
|
DPRINTF(E_LOG, L_AIRPLAY, "Could not send timing reply to %s: %s\n", address, strerror(errno));
|
||||||
goto readd;
|
|
||||||
}
|
|
||||||
|
|
||||||
readd:
|
|
||||||
ret = event_add(svc->ev, NULL);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't re-add event for timing requests\n");
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
airplay_timing_start_one(struct airplay_service *svc, int family)
|
|
||||||
{
|
|
||||||
union sockaddr_all sa;
|
|
||||||
int on;
|
|
||||||
int len;
|
|
||||||
int ret;
|
|
||||||
int timing_port;
|
|
||||||
|
|
||||||
#ifdef SOCK_CLOEXEC
|
|
||||||
svc->fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
|
||||||
#else
|
|
||||||
svc->fd = socket(family, SOCK_DGRAM, 0);
|
|
||||||
#endif
|
|
||||||
if (svc->fd < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't make timing socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (family == AF_INET6)
|
|
||||||
{
|
|
||||||
on = 1;
|
|
||||||
ret = setsockopt(svc->fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not set IPV6_V6ONLY on timing socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&sa, 0, sizeof(union sockaddr_all));
|
|
||||||
sa.ss.ss_family = family;
|
|
||||||
|
|
||||||
timing_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "timing_port");
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
sa.sin.sin_addr.s_addr = INADDR_ANY;
|
|
||||||
sa.sin.sin_port = htons(timing_port);
|
|
||||||
len = sizeof(sa.sin);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
sa.sin6.sin6_addr = in6addr_any;
|
|
||||||
sa.sin6.sin6_port = htons(timing_port);
|
|
||||||
len = sizeof(sa.sin6);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bind(svc->fd, &sa.sa, len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't bind timing socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = sizeof(sa.ss);
|
|
||||||
ret = getsockname(svc->fd, &sa.sa, (socklen_t *)&len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't get timing socket name: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
svc->port = ntohs(sa.sin.sin_port);
|
|
||||||
DPRINTF(E_DBG, L_AIRPLAY, "Timing IPv4 port: %d\n", svc->port);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
svc->port = ntohs(sa.sin6.sin6_port);
|
|
||||||
DPRINTF(E_DBG, L_AIRPLAY, "Timing IPv6 port: %d\n", svc->port);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
svc->ev = event_new(evbase_player, svc->fd, EV_READ, airplay_timing_cb, svc);
|
|
||||||
if (!svc->ev)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Out of memory for airplay_service event\n");
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
event_add(svc->ev, NULL);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
out_fail:
|
|
||||||
close(svc->fd);
|
|
||||||
svc->fd = -1;
|
|
||||||
svc->port = 0;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
airplay_timing_stop(void)
|
control_svc_cb(int fd, short what, void *arg)
|
||||||
{
|
|
||||||
if (timing_4svc.ev)
|
|
||||||
event_free(timing_4svc.ev);
|
|
||||||
|
|
||||||
if (timing_6svc.ev)
|
|
||||||
event_free(timing_6svc.ev);
|
|
||||||
|
|
||||||
close(timing_4svc.fd);
|
|
||||||
|
|
||||||
timing_4svc.fd = -1;
|
|
||||||
timing_4svc.port = 0;
|
|
||||||
|
|
||||||
close(timing_6svc.fd);
|
|
||||||
|
|
||||||
timing_6svc.fd = -1;
|
|
||||||
timing_6svc.port = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
airplay_timing_start(int v6enabled)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (v6enabled)
|
|
||||||
{
|
|
||||||
ret = airplay_timing_start_one(&timing_6svc, AF_INET6);
|
|
||||||
if (ret < 0)
|
|
||||||
DPRINTF(E_WARN, L_AIRPLAY, "Could not start timing service on IPv6\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = airplay_timing_start_one(&timing_4svc, AF_INET);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not start timing service on IPv4\n");
|
|
||||||
|
|
||||||
airplay_timing_stop();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ----------------- Control service (retransmission and sync) ---------------*/
|
|
||||||
|
|
||||||
static void
|
|
||||||
airplay_control_cb(int fd, short what, void *arg)
|
|
||||||
{
|
{
|
||||||
|
struct airplay_service *svc = arg;
|
||||||
|
union net_sockaddr peer_addr;
|
||||||
|
socklen_t peer_addrlen = sizeof(peer_addr);
|
||||||
char address[INET6_ADDRSTRLEN];
|
char address[INET6_ADDRSTRLEN];
|
||||||
union sockaddr_all sa;
|
|
||||||
uint8_t req[8];
|
|
||||||
struct airplay_session *rs;
|
struct airplay_session *rs;
|
||||||
struct airplay_service *svc;
|
uint8_t req[8];
|
||||||
uint16_t seq_start;
|
uint16_t seq_start;
|
||||||
uint16_t seq_len;
|
uint16_t seq_len;
|
||||||
int len;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
svc = (struct airplay_service *)arg;
|
ret = recvfrom(svc->fd, req, sizeof(req), 0, &peer_addr.sa, &peer_addrlen);
|
||||||
|
|
||||||
len = sizeof(sa.ss);
|
|
||||||
ret = recvfrom(svc->fd, req, sizeof(req), 0, &sa.sa, (socklen_t *)&len);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Error reading control request: %s\n", strerror(errno));
|
DPRINTF(E_LOG, L_AIRPLAY, "Error reading control request: %s\n", strerror(errno));
|
||||||
|
return;
|
||||||
goto readd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret != 8)
|
if (ret != 8)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_AIRPLAY, "Got control request with size %d\n", ret);
|
net_address_get(address, sizeof(address), &peer_addr);
|
||||||
|
DPRINTF(E_WARN, L_AIRPLAY, "Got control request from %s with size %d\n", address, ret);
|
||||||
goto readd;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
switch (sa.ss.ss_family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
if (svc != &control_4svc)
|
|
||||||
goto readd;
|
|
||||||
|
|
||||||
for (rs = airplay_sessions; rs; rs = rs->next)
|
|
||||||
{
|
|
||||||
if ((rs->sa.ss.ss_family == AF_INET)
|
|
||||||
&& (sa.sin.sin_addr.s_addr == rs->sa.sin.sin_addr.s_addr))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rs)
|
|
||||||
ret = (inet_ntop(AF_INET, &sa.sin.sin_addr.s_addr, address, sizeof(address)) != NULL);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
if (svc != &control_6svc)
|
|
||||||
goto readd;
|
|
||||||
|
|
||||||
for (rs = airplay_sessions; rs; rs = rs->next)
|
|
||||||
{
|
|
||||||
if ((rs->sa.ss.ss_family == AF_INET6)
|
|
||||||
&& IN6_ARE_ADDR_EQUAL(&sa.sin6.sin6_addr, &rs->sa.sin6.sin6_addr))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rs)
|
|
||||||
ret = (inet_ntop(AF_INET6, &sa.sin6.sin6_addr.s6_addr, address, sizeof(address)) != NULL);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Control svc: Unknown address family %d\n", sa.ss.ss_family);
|
|
||||||
goto readd;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rs)
|
|
||||||
{
|
|
||||||
if (!ret)
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Control request from [error: %s]; not an AirPlay client\n", strerror(errno));
|
|
||||||
else
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Control request from %s; not an AirPlay client\n", address);
|
|
||||||
|
|
||||||
goto readd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((req[0] != 0x80) || (req[1] != 0xd5))
|
if ((req[0] != 0x80) || (req[1] != 0xd5))
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Packet header doesn't match retransmit request (got 0x%02x%02x, expected 0x80d5)\n", req[0], req[1]);
|
net_address_get(address, sizeof(address), &peer_addr);
|
||||||
|
DPRINTF(E_WARN, L_AIRPLAY, "Packet header from %s doesn't match retransmit request (got 0x%02x%02x, expected 0x80d5)\n", address, req[0], req[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
goto readd;
|
rs = session_find_by_address(&peer_addr);
|
||||||
|
if (!rs)
|
||||||
|
{
|
||||||
|
net_address_get(address, sizeof(address), &peer_addr);
|
||||||
|
DPRINTF(E_WARN, L_AIRPLAY, "Control request from %s; not a AirPlay client\n", address);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(&seq_start, req + 4, 2);
|
memcpy(&seq_start, req + 4, 2);
|
||||||
@ -2430,161 +2286,6 @@ airplay_control_cb(int fd, short what, void *arg)
|
|||||||
seq_len = be16toh(seq_len);
|
seq_len = be16toh(seq_len);
|
||||||
|
|
||||||
packets_resend(rs, seq_start, seq_len);
|
packets_resend(rs, seq_start, seq_len);
|
||||||
|
|
||||||
readd:
|
|
||||||
ret = event_add(svc->ev, NULL);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't re-add event for control requests\n");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
airplay_control_start_one(struct airplay_service *svc, int family)
|
|
||||||
{
|
|
||||||
union sockaddr_all sa;
|
|
||||||
int on;
|
|
||||||
int len;
|
|
||||||
int ret;
|
|
||||||
int control_port;
|
|
||||||
|
|
||||||
#ifdef SOCK_CLOEXEC
|
|
||||||
svc->fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
|
||||||
#else
|
|
||||||
svc->fd = socket(family, SOCK_DGRAM, 0);
|
|
||||||
#endif
|
|
||||||
if (svc->fd < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't make control socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (family == AF_INET6)
|
|
||||||
{
|
|
||||||
on = 1;
|
|
||||||
ret = setsockopt(svc->fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not set IPV6_V6ONLY on control socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&sa, 0, sizeof(union sockaddr_all));
|
|
||||||
sa.ss.ss_family = family;
|
|
||||||
|
|
||||||
control_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "control_port");
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
sa.sin.sin_addr.s_addr = INADDR_ANY;
|
|
||||||
sa.sin.sin_port = htons(control_port);
|
|
||||||
len = sizeof(sa.sin);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
sa.sin6.sin6_addr = in6addr_any;
|
|
||||||
sa.sin6.sin6_port = htons(control_port);
|
|
||||||
len = sizeof(sa.sin6);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bind(svc->fd, &sa.sa, len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't bind control socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = sizeof(sa.ss);
|
|
||||||
ret = getsockname(svc->fd, &sa.sa, (socklen_t *)&len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Couldn't get control socket name: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
svc->port = ntohs(sa.sin.sin_port);
|
|
||||||
DPRINTF(E_DBG, L_AIRPLAY, "Control IPv4 port: %d\n", svc->port);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
svc->port = ntohs(sa.sin6.sin6_port);
|
|
||||||
DPRINTF(E_DBG, L_AIRPLAY, "Control IPv6 port: %d\n", svc->port);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
svc->ev = event_new(evbase_player, svc->fd, EV_READ, airplay_control_cb, svc);
|
|
||||||
if (!svc->ev)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Out of memory for control event\n");
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
event_add(svc->ev, NULL);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
out_fail:
|
|
||||||
close(svc->fd);
|
|
||||||
svc->fd = -1;
|
|
||||||
svc->port = 0;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
airplay_control_stop(void)
|
|
||||||
{
|
|
||||||
if (control_4svc.ev)
|
|
||||||
event_free(control_4svc.ev);
|
|
||||||
|
|
||||||
if (control_6svc.ev)
|
|
||||||
event_free(control_6svc.ev);
|
|
||||||
|
|
||||||
close(control_4svc.fd);
|
|
||||||
|
|
||||||
control_4svc.fd = -1;
|
|
||||||
control_4svc.port = 0;
|
|
||||||
|
|
||||||
close(control_6svc.fd);
|
|
||||||
|
|
||||||
control_6svc.fd = -1;
|
|
||||||
control_6svc.port = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
airplay_control_start(int v6enabled)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (v6enabled)
|
|
||||||
{
|
|
||||||
ret = airplay_control_start_one(&control_6svc, AF_INET6);
|
|
||||||
if (ret < 0)
|
|
||||||
DPRINTF(E_WARN, L_AIRPLAY, "Could not start control service on IPv6\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = airplay_control_start_one(&control_4svc, AF_INET);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not start control service on IPv4\n");
|
|
||||||
|
|
||||||
airplay_control_stop();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3099,55 +2800,6 @@ payload_make_pair_verify2(struct evrtsp_request *req, struct airplay_session *rs
|
|||||||
|
|
||||||
/* ------------------------------ Session startup --------------------------- */
|
/* ------------------------------ Session startup --------------------------- */
|
||||||
|
|
||||||
static int
|
|
||||||
device_connect(struct airplay_session *rs, unsigned short port, int type)
|
|
||||||
{
|
|
||||||
int len;
|
|
||||||
int fd;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_AIRPLAY, "Connecting to %s (family=%d), port %u\n", rs->address, rs->family, port);
|
|
||||||
|
|
||||||
#ifdef SOCK_CLOEXEC
|
|
||||||
fd = socket(rs->sa.ss.ss_family, type | SOCK_CLOEXEC, 0);
|
|
||||||
#else
|
|
||||||
fd = socket(rs->sa.ss.ss_family, type, 0);
|
|
||||||
#endif
|
|
||||||
if (fd < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not create socket: %s\n", strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (rs->sa.ss.ss_family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
rs->sa.sin.sin_port = htons(port);
|
|
||||||
len = sizeof(rs->sa.sin);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
rs->sa.sin6.sin6_port = htons(port);
|
|
||||||
len = sizeof(rs->sa.sin6);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
DPRINTF(E_WARN, L_AIRPLAY, "Unknown family %d\n", rs->sa.ss.ss_family);
|
|
||||||
close(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = connect(fd, &rs->sa.sa, len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "connect() to [%s]:%u failed: %s\n", rs->address, port, strerror(errno));
|
|
||||||
close(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
start_failure(struct airplay_session *rs)
|
start_failure(struct airplay_session *rs)
|
||||||
{
|
{
|
||||||
@ -3232,6 +2884,8 @@ response_handler_setup_stream(struct evrtsp_request *req, struct airplay_session
|
|||||||
uint64_t uintval;
|
uint64_t uintval;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
DPRINTF(E_INFO, L_AIRPLAY, "Setting up AirPlay session %u (%s -> %s)\n", rs->session_id, rs->local_address, rs->address);
|
||||||
|
|
||||||
ret = wplist_from_evbuf(&response, req->input_buffer);
|
ret = wplist_from_evbuf(&response, req->input_buffer);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -3273,9 +2927,9 @@ response_handler_setup_stream(struct evrtsp_request *req, struct airplay_session
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_AIRPLAY, "Negotiated AirTunes v2 UDP streaming session; ports d=%u c=%u t=%u e=%u\n", rs->data_port, rs->control_port, rs->timing_port, rs->events_port);
|
DPRINTF(E_DBG, L_AIRPLAY, "Negotiated UDP streaming session; ports d=%u c=%u t=%u e=%u\n", rs->data_port, rs->control_port, rs->timing_port, rs->events_port);
|
||||||
|
|
||||||
rs->server_fd = device_connect(rs, rs->data_port, SOCK_DGRAM);
|
rs->server_fd = net_connect(rs->address, rs->data_port, SOCK_DGRAM, "AirPlay data");
|
||||||
if (rs->server_fd < 0)
|
if (rs->server_fd < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_WARN, L_AIRPLAY, "Could not connect to data port\n");
|
DPRINTF(E_WARN, L_AIRPLAY, "Could not connect to data port\n");
|
||||||
@ -3283,7 +2937,7 @@ response_handler_setup_stream(struct evrtsp_request *req, struct airplay_session
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reverse connection, used to receive playback events from device
|
// Reverse connection, used to receive playback events from device
|
||||||
rs->events_fd = device_connect(rs, rs->events_port, SOCK_STREAM);
|
rs->events_fd = net_connect(rs->address, rs->events_port, SOCK_STREAM, "AirPlay events");
|
||||||
if (rs->events_fd < 0)
|
if (rs->events_fd < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_WARN, L_AIRPLAY, "Could not connect to '%s' events port %u, proceeding anyway\n", rs->devname, rs->events_port);
|
DPRINTF(E_WARN, L_AIRPLAY, "Could not connect to '%s' events port %u, proceeding anyway\n", rs->devname, rs->events_port);
|
||||||
@ -3413,7 +3067,7 @@ response_handler_info_generic(struct evrtsp_request *req, struct airplay_session
|
|||||||
|
|
||||||
plist_free(response);
|
plist_free(response);
|
||||||
|
|
||||||
DPRINTF(E_INFO, L_AIRPLAY, "Status flags from '%s' was %" PRIu64 ": cable attached %d, one time pairing %d, password %d, PIN %d\n",
|
DPRINTF(E_DBG, L_AIRPLAY, "Status flags from '%s' was %" PRIu64 ": cable attached %d, one time pairing %d, password %d, PIN %d\n",
|
||||||
rs->devname, rs->statusflags, (bool)(rs->statusflags & AIRPLAY_FLAG_AUDIO_CABLE_ATTACHED), (bool)(rs->statusflags & AIRPLAY_FLAG_ONE_TIME_PAIRING_REQUIRED),
|
rs->devname, rs->statusflags, (bool)(rs->statusflags & AIRPLAY_FLAG_AUDIO_CABLE_ATTACHED), (bool)(rs->statusflags & AIRPLAY_FLAG_ONE_TIME_PAIRING_REQUIRED),
|
||||||
(bool)(rs->statusflags & AIRPLAY_FLAG_PASSWORD_REQUIRED), (bool)(rs->statusflags & AIRPLAY_FLAG_PIN_REQUIRED));
|
(bool)(rs->statusflags & AIRPLAY_FLAG_PASSWORD_REQUIRED), (bool)(rs->statusflags & AIRPLAY_FLAG_PIN_REQUIRED));
|
||||||
|
|
||||||
@ -4362,22 +4016,10 @@ airplay_write(struct output_buffer *obuf)
|
|||||||
static int
|
static int
|
||||||
airplay_init(void)
|
airplay_init(void)
|
||||||
{
|
{
|
||||||
int v6enabled;
|
|
||||||
int family;
|
|
||||||
int ret;
|
int ret;
|
||||||
int i;
|
int i;
|
||||||
|
int timing_port;
|
||||||
timing_4svc.fd = -1;
|
int control_port;
|
||||||
timing_4svc.port = 0;
|
|
||||||
|
|
||||||
timing_6svc.fd = -1;
|
|
||||||
timing_6svc.port = 0;
|
|
||||||
|
|
||||||
control_4svc.fd = -1;
|
|
||||||
control_4svc.port = 0;
|
|
||||||
|
|
||||||
control_6svc.fd = -1;
|
|
||||||
control_6svc.port = 0;
|
|
||||||
|
|
||||||
airplay_device_id = libhash;
|
airplay_device_id = libhash;
|
||||||
|
|
||||||
@ -4394,46 +4036,35 @@ airplay_init(void)
|
|||||||
|
|
||||||
CHECK_NULL(L_AIRPLAY, keep_alive_timer = evtimer_new(evbase_player, airplay_keep_alive_timer_cb, NULL));
|
CHECK_NULL(L_AIRPLAY, keep_alive_timer = evtimer_new(evbase_player, airplay_keep_alive_timer_cb, NULL));
|
||||||
|
|
||||||
v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
|
timing_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "timing_port");
|
||||||
|
ret = service_start(&airplay_timing_svc, timing_svc_cb, timing_port, "AirPlay timing");
|
||||||
ret = airplay_timing_start(v6enabled);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "AirPlay time synchronization failed to start\n");
|
DPRINTF(E_LOG, L_AIRPLAY, "AirPlay time synchronization failed to start\n");
|
||||||
|
|
||||||
goto out_free_timer;
|
goto out_free_timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = airplay_control_start(v6enabled);
|
control_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "control_port");
|
||||||
|
ret = service_start(&airplay_control_svc, control_svc_cb, control_port, "AirPlay control");
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "AirPlay playback control failed to start\n");
|
DPRINTF(E_LOG, L_AIRPLAY, "AirPlay playback control failed to start\n");
|
||||||
|
|
||||||
goto out_stop_timing;
|
goto out_stop_timing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v6enabled)
|
ret = mdns_browse("_airplay._tcp", airplay_device_cb, MDNS_CONNECTION_TEST);
|
||||||
v6enabled = !((timing_6svc.fd < 0) || (control_6svc.fd < 0));
|
|
||||||
|
|
||||||
if (v6enabled)
|
|
||||||
family = AF_UNSPEC;
|
|
||||||
else
|
|
||||||
family = AF_INET;
|
|
||||||
|
|
||||||
ret = mdns_browse("_airplay._tcp", family, airplay_device_cb, MDNS_CONNECTION_TEST);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not add mDNS browser for AirPlay devices\n");
|
DPRINTF(E_LOG, L_AIRPLAY, "Could not add mDNS browser for AirPlay devices\n");
|
||||||
|
|
||||||
goto out_stop_control;
|
goto out_stop_control;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out_stop_control:
|
out_stop_control:
|
||||||
airplay_control_stop();
|
service_stop(&airplay_control_svc);
|
||||||
out_stop_timing:
|
out_stop_timing:
|
||||||
airplay_timing_stop();
|
service_stop(&airplay_timing_svc);
|
||||||
out_free_timer:
|
out_free_timer:
|
||||||
event_free(keep_alive_timer);
|
event_free(keep_alive_timer);
|
||||||
|
|
||||||
@ -4445,17 +4076,17 @@ airplay_deinit(void)
|
|||||||
{
|
{
|
||||||
struct airplay_session *rs;
|
struct airplay_session *rs;
|
||||||
|
|
||||||
|
service_stop(&airplay_control_svc);
|
||||||
|
service_stop(&airplay_timing_svc);
|
||||||
|
|
||||||
|
event_free(keep_alive_timer);
|
||||||
|
|
||||||
for (rs = airplay_sessions; airplay_sessions; rs = airplay_sessions)
|
for (rs = airplay_sessions; airplay_sessions; rs = airplay_sessions)
|
||||||
{
|
{
|
||||||
airplay_sessions = rs->next;
|
airplay_sessions = rs->next;
|
||||||
|
|
||||||
session_free(rs);
|
session_free(rs);
|
||||||
}
|
}
|
||||||
|
|
||||||
airplay_control_stop();
|
|
||||||
airplay_timing_stop();
|
|
||||||
|
|
||||||
event_free(keep_alive_timer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct output_definition output_airplay =
|
struct output_definition output_airplay =
|
||||||
|
@ -147,14 +147,6 @@
|
|||||||
|
|
||||||
//#define DEBUG_CHROMECAST 1
|
//#define DEBUG_CHROMECAST 1
|
||||||
|
|
||||||
union sockaddr_all
|
|
||||||
{
|
|
||||||
struct sockaddr_in sin;
|
|
||||||
struct sockaddr_in6 sin6;
|
|
||||||
struct sockaddr sa;
|
|
||||||
struct sockaddr_storage ss;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct cast_session;
|
struct cast_session;
|
||||||
struct cast_msg_payload;
|
struct cast_msg_payload;
|
||||||
|
|
||||||
@ -481,69 +473,6 @@ static struct media_quality cast_quality_default = { CAST_QUALITY_SAMPLE_RATE_DE
|
|||||||
|
|
||||||
/* ------------------------------- MISC HELPERS ----------------------------- */
|
/* ------------------------------- MISC HELPERS ----------------------------- */
|
||||||
|
|
||||||
static int
|
|
||||||
cast_connect(const char *address, unsigned short port, int family, int type)
|
|
||||||
{
|
|
||||||
union sockaddr_all sa;
|
|
||||||
int fd;
|
|
||||||
int len;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_CAST, "Connecting to %s (family=%d), port %u\n", address, family, port);
|
|
||||||
|
|
||||||
// TODO Open non-block right away so we don't block the player while connecting
|
|
||||||
// and during TLS handshake (we would probably need to introduce a deferredev)
|
|
||||||
#ifdef SOCK_CLOEXEC
|
|
||||||
fd = socket(family, type | SOCK_CLOEXEC, 0);
|
|
||||||
#else
|
|
||||||
fd = socket(family, type, 0);
|
|
||||||
#endif
|
|
||||||
if (fd < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_CAST, "Could not create socket: %s\n", strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
sa.sin.sin_port = htons(port);
|
|
||||||
ret = inet_pton(AF_INET, address, &sa.sin.sin_addr);
|
|
||||||
len = sizeof(sa.sin);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
sa.sin6.sin6_port = htons(port);
|
|
||||||
ret = inet_pton(AF_INET6, address, &sa.sin6.sin6_addr);
|
|
||||||
len = sizeof(sa.sin6);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
DPRINTF(E_WARN, L_CAST, "Unknown family %d\n", family);
|
|
||||||
close(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret <= 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_CAST, "Device address not valid (%s)\n", address);
|
|
||||||
close(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sa.ss.ss_family = family;
|
|
||||||
|
|
||||||
ret = connect(fd, &sa.sa, len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_CAST, "connect() to [%s]:%u failed: %s\n", address, port, strerror(errno));
|
|
||||||
close(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cast_disconnect(int fd)
|
cast_disconnect(int fd)
|
||||||
{
|
{
|
||||||
@ -1468,7 +1397,7 @@ cast_cb_startup_offer(struct cast_session *cs, struct cast_msg_payload *payload)
|
|||||||
|
|
||||||
cs->udp_port = payload->udp_port;
|
cs->udp_port = payload->udp_port;
|
||||||
|
|
||||||
cs->udp_fd = cast_connect(cs->address, cs->udp_port, cs->family, SOCK_DGRAM);
|
cs->udp_fd = net_connect(cs->address, cs->udp_port, SOCK_DGRAM, "Chromecast data");
|
||||||
if (cs->udp_fd < 0)
|
if (cs->udp_fd < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
@ -2013,7 +1942,7 @@ cast_session_make(struct output_device *device, int family, int callback_id)
|
|||||||
goto out_free_master_session;
|
goto out_free_master_session;
|
||||||
}
|
}
|
||||||
|
|
||||||
cs->server_fd = cast_connect(address, port, family, SOCK_STREAM);
|
cs->server_fd = net_connect(address, port, SOCK_STREAM, "Chomecast control");
|
||||||
if (cs->server_fd < 0)
|
if (cs->server_fd < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_CAST, "Could not connect to %s\n", device->name);
|
DPRINTF(E_LOG, L_CAST, "Could not connect to %s\n", device->name);
|
||||||
@ -2445,7 +2374,6 @@ static int
|
|||||||
cast_init(void)
|
cast_init(void)
|
||||||
{
|
{
|
||||||
struct decode_ctx *decode_ctx;
|
struct decode_ctx *decode_ctx;
|
||||||
int family;
|
|
||||||
int i;
|
int i;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@ -2484,12 +2412,7 @@ cast_init(void)
|
|||||||
goto out_tls_deinit;
|
goto out_tls_deinit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"))
|
ret = mdns_browse("_googlecast._tcp", cast_device_cb, 0);
|
||||||
family = AF_UNSPEC;
|
|
||||||
else
|
|
||||||
family = AF_INET;
|
|
||||||
|
|
||||||
ret = mdns_browse("_googlecast._tcp", family, cast_device_cb, 0);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_CAST, "Could not add mDNS browser for Chromecast devices\n");
|
DPRINTF(E_LOG, L_CAST, "Could not add mDNS browser for Chromecast devices\n");
|
||||||
|
@ -97,14 +97,6 @@
|
|||||||
// This is an arbitrary value which just needs to be kept in sync with the config
|
// This is an arbitrary value which just needs to be kept in sync with the config
|
||||||
#define RAOP_CONFIG_MAX_VOLUME 11
|
#define RAOP_CONFIG_MAX_VOLUME 11
|
||||||
|
|
||||||
union sockaddr_all
|
|
||||||
{
|
|
||||||
struct sockaddr_in sin;
|
|
||||||
struct sockaddr_in6 sin6;
|
|
||||||
struct sockaddr sa;
|
|
||||||
struct sockaddr_storage ss;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum raop_devtype {
|
enum raop_devtype {
|
||||||
RAOP_DEV_APEX1_80211G,
|
RAOP_DEV_APEX1_80211G,
|
||||||
RAOP_DEV_APEX2_80211N,
|
RAOP_DEV_APEX2_80211N,
|
||||||
@ -212,6 +204,8 @@ struct raop_session
|
|||||||
char *address;
|
char *address;
|
||||||
int family;
|
int family;
|
||||||
|
|
||||||
|
union net_sockaddr naddr;
|
||||||
|
|
||||||
int volume;
|
int volume;
|
||||||
|
|
||||||
/* AirTunes v2 */
|
/* AirTunes v2 */
|
||||||
@ -225,8 +219,6 @@ struct raop_session
|
|||||||
|
|
||||||
int server_fd;
|
int server_fd;
|
||||||
|
|
||||||
union sockaddr_all sa;
|
|
||||||
|
|
||||||
struct raop_service *timing_svc;
|
struct raop_service *timing_svc;
|
||||||
struct raop_service *control_svc;
|
struct raop_service *control_svc;
|
||||||
|
|
||||||
@ -319,12 +311,10 @@ static char *raop_aes_key_b64;
|
|||||||
static char *raop_aes_iv_b64;
|
static char *raop_aes_iv_b64;
|
||||||
|
|
||||||
/* AirTunes v2 time synchronization */
|
/* AirTunes v2 time synchronization */
|
||||||
static struct raop_service timing_4svc;
|
static struct raop_service raop_timing_svc;
|
||||||
static struct raop_service timing_6svc;
|
|
||||||
|
|
||||||
/* AirTunes v2 playback synchronization / control */
|
/* AirTunes v2 playback synchronization / control */
|
||||||
static struct raop_service control_4svc;
|
static struct raop_service raop_control_svc;
|
||||||
static struct raop_service control_6svc;
|
|
||||||
|
|
||||||
/* Metadata */
|
/* Metadata */
|
||||||
static struct output_metadata *raop_cur_metadata;
|
static struct output_metadata *raop_cur_metadata;
|
||||||
@ -442,7 +432,7 @@ ntp_to_timespec(struct ntp_stamp *ns, struct timespec *ts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
raop_v2_timing_get_clock_ntp(struct ntp_stamp *ns)
|
timing_get_clock_ntp(struct ntp_stamp *ns)
|
||||||
{
|
{
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
int ret;
|
int ret;
|
||||||
@ -1969,38 +1959,32 @@ session_connection_setup(struct raop_session *rs, struct output_device *rd, int
|
|||||||
unsigned short port;
|
unsigned short port;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
rs->sa.ss.ss_family = family;
|
rs->naddr.ss.ss_family = family;
|
||||||
|
|
||||||
switch (family)
|
switch (family)
|
||||||
{
|
{
|
||||||
case AF_INET:
|
case AF_INET:
|
||||||
/* We always have the v4 services, so no need to check */
|
|
||||||
if (!rd->v4_address)
|
if (!rd->v4_address)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
address = rd->v4_address;
|
address = rd->v4_address;
|
||||||
port = rd->v4_port;
|
port = rd->v4_port;
|
||||||
|
|
||||||
rs->timing_svc = &timing_4svc;
|
ret = inet_pton(AF_INET, address, &rs->naddr.sin.sin_addr);
|
||||||
rs->control_svc = &control_4svc;
|
|
||||||
|
|
||||||
ret = inet_pton(AF_INET, address, &rs->sa.sin.sin_addr);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AF_INET6:
|
case AF_INET6:
|
||||||
if (!rd->v6_address || rd->v6_disabled || (timing_6svc.fd < 0) || (control_6svc.fd < 0))
|
if (!rd->v6_address)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
address = rd->v6_address;
|
address = rd->v6_address;
|
||||||
port = rd->v6_port;
|
port = rd->v6_port;
|
||||||
|
|
||||||
rs->timing_svc = &timing_6svc;
|
|
||||||
rs->control_svc = &control_6svc;
|
|
||||||
|
|
||||||
intf = strchr(address, '%');
|
intf = strchr(address, '%');
|
||||||
if (intf)
|
if (intf)
|
||||||
*intf = '\0';
|
*intf = '\0';
|
||||||
|
|
||||||
ret = inet_pton(AF_INET6, address, &rs->sa.sin6.sin6_addr);
|
ret = inet_pton(AF_INET6, address, &rs->naddr.sin6.sin6_addr);
|
||||||
|
|
||||||
if (intf)
|
if (intf)
|
||||||
{
|
{
|
||||||
@ -2008,8 +1992,8 @@ session_connection_setup(struct raop_session *rs, struct output_device *rd, int
|
|||||||
|
|
||||||
intf++;
|
intf++;
|
||||||
|
|
||||||
rs->sa.sin6.sin6_scope_id = if_nametoindex(intf);
|
rs->naddr.sin6.sin6_scope_id = if_nametoindex(intf);
|
||||||
if (rs->sa.sin6.sin6_scope_id == 0)
|
if (rs->naddr.sin6.sin6_scope_id == 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not find interface %s\n", intf);
|
DPRINTF(E_LOG, L_RAOP, "Could not find interface %s\n", intf);
|
||||||
|
|
||||||
@ -2045,6 +2029,35 @@ session_connection_setup(struct raop_session *rs, struct output_device *rd, int
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct raop_session *
|
||||||
|
session_find_by_address(union net_sockaddr *peer_addr)
|
||||||
|
{
|
||||||
|
struct raop_session *rs;
|
||||||
|
uint32_t *addr_ptr;
|
||||||
|
int family = peer_addr->sa.sa_family;
|
||||||
|
|
||||||
|
for (rs = raop_sessions; rs; rs = rs->next)
|
||||||
|
{
|
||||||
|
if (family == rs->family)
|
||||||
|
{
|
||||||
|
if (family == AF_INET && peer_addr->sin.sin_addr.s_addr == rs->naddr.sin.sin_addr.s_addr)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&peer_addr->sin6.sin6_addr, &rs->naddr.sin6.sin6_addr))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&peer_addr->sin6.sin6_addr))
|
||||||
|
{
|
||||||
|
// ipv4 mapped to ipv6 consists of 16 bytes/4 words: 0x00000000 0x00000000 0x0000ffff 0x[IPv4]
|
||||||
|
addr_ptr = (uint32_t *)(&peer_addr->sin6.sin6_addr);
|
||||||
|
if (addr_ptr[3] == rs->naddr.sin.sin_addr.s_addr)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs;
|
||||||
|
}
|
||||||
|
|
||||||
static struct raop_session *
|
static struct raop_session *
|
||||||
session_make(struct output_device *rd, int callback_id, bool only_probe)
|
session_make(struct output_device *rd, int callback_id, bool only_probe)
|
||||||
{
|
{
|
||||||
@ -2108,6 +2121,9 @@ session_make(struct output_device *rd, int callback_id, bool only_probe)
|
|||||||
rs->auth_quirk_itunes = 0;
|
rs->auth_quirk_itunes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rs->timing_svc = &raop_timing_svc;
|
||||||
|
rs->control_svc = &raop_control_svc;
|
||||||
|
|
||||||
ret = session_connection_setup(rs, rd, AF_INET6);
|
ret = session_connection_setup(rs, rd, AF_INET6);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -2755,27 +2771,27 @@ packet_send(struct raop_session *rs, struct rtp_packet *pkt)
|
|||||||
static void
|
static void
|
||||||
control_packet_send(struct raop_session *rs, struct rtp_packet *pkt)
|
control_packet_send(struct raop_session *rs, struct rtp_packet *pkt)
|
||||||
{
|
{
|
||||||
int len;
|
socklen_t addrlen;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
switch (rs->sa.ss.ss_family)
|
switch (rs->family)
|
||||||
{
|
{
|
||||||
case AF_INET:
|
case AF_INET:
|
||||||
rs->sa.sin.sin_port = htons(rs->control_port);
|
rs->naddr.sin.sin_port = htons(rs->control_port);
|
||||||
len = sizeof(rs->sa.sin);
|
addrlen = sizeof(rs->naddr.sin);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AF_INET6:
|
case AF_INET6:
|
||||||
rs->sa.sin6.sin6_port = htons(rs->control_port);
|
rs->naddr.sin6.sin6_port = htons(rs->control_port);
|
||||||
len = sizeof(rs->sa.sin6);
|
addrlen = sizeof(rs->naddr.sin6);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
DPRINTF(E_WARN, L_RAOP, "Unknown family %d\n", rs->sa.ss.ss_family);
|
DPRINTF(E_WARN, L_RAOP, "Unknown family %d\n", rs->family);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sendto(rs->control_svc->fd, pkt->data, pkt->data_len, 0, &rs->sa.sa, len);
|
ret = sendto(rs->control_svc->fd, pkt->data, pkt->data_len, 0, &rs->naddr.sa, addrlen);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not send playback sync to device '%s': %s\n", rs->devname, strerror(errno));
|
DPRINTF(E_LOG, L_RAOP, "Could not send playback sync to device '%s': %s\n", rs->devname, strerror(errno));
|
||||||
}
|
}
|
||||||
@ -2921,51 +2937,91 @@ packets_sync_send(struct raop_master_session *rms)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------ Time service ------------------------------ */
|
/* ------------------------- Time and control service ----------------------- */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
raop_v2_timing_cb(int fd, short what, void *arg)
|
service_stop(struct raop_service *svc)
|
||||||
{
|
{
|
||||||
union sockaddr_all sa;
|
if (svc->ev)
|
||||||
|
event_free(svc->ev);
|
||||||
|
|
||||||
|
if (svc->fd >= 0)
|
||||||
|
close(svc->fd);
|
||||||
|
|
||||||
|
svc->ev = NULL;
|
||||||
|
svc->fd = -1;
|
||||||
|
svc->port = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
service_start(struct raop_service *svc, event_callback_fn cb, unsigned short port, const char *log_service_name)
|
||||||
|
{
|
||||||
|
memset(svc, 0, sizeof(struct raop_service));
|
||||||
|
|
||||||
|
svc->fd = net_bind(&port, SOCK_DGRAM, log_service_name);
|
||||||
|
if (svc->fd < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Could not start '%s' service\n", log_service_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
svc->ev = event_new(evbase_player, svc->fd, EV_READ | EV_PERSIST, cb, svc);
|
||||||
|
if (!svc->ev)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Could not create event for '%s' service\n", log_service_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
event_add(svc->ev, NULL);
|
||||||
|
|
||||||
|
svc->port = port;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
service_stop(svc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
timing_svc_cb(int fd, short what, void *arg)
|
||||||
|
{
|
||||||
|
struct raop_service *svc;
|
||||||
|
union net_sockaddr peer_addr;
|
||||||
|
socklen_t peer_addrlen = sizeof(peer_addr);
|
||||||
uint8_t req[32];
|
uint8_t req[32];
|
||||||
uint8_t res[32];
|
uint8_t res[32];
|
||||||
struct ntp_stamp recv_stamp;
|
struct ntp_stamp recv_stamp;
|
||||||
struct ntp_stamp xmit_stamp;
|
struct ntp_stamp xmit_stamp;
|
||||||
struct raop_service *svc;
|
|
||||||
int len;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
svc = (struct raop_service *)arg;
|
svc = (struct raop_service *)arg;
|
||||||
|
|
||||||
ret = raop_v2_timing_get_clock_ntp(&recv_stamp);
|
ret = timing_get_clock_ntp(&recv_stamp);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't get receive timestamp\n");
|
DPRINTF(E_LOG, L_RAOP, "Couldn't get receive timestamp\n");
|
||||||
|
return;
|
||||||
goto readd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
len = sizeof(sa.ss);
|
peer_addrlen = sizeof(peer_addr);
|
||||||
ret = recvfrom(svc->fd, req, sizeof(req), 0, &sa.sa, (socklen_t *)&len);
|
ret = recvfrom(svc->fd, req, sizeof(req), 0, &peer_addr.sa, &peer_addrlen);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Error reading timing request: %s\n", strerror(errno));
|
DPRINTF(E_LOG, L_RAOP, "Error reading timing request: %s\n", strerror(errno));
|
||||||
|
return;
|
||||||
goto readd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret != 32)
|
if (ret != 32)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_RAOP, "Got timing request with size %d\n", ret);
|
DPRINTF(E_WARN, L_RAOP, "Got timing request with size %d\n", ret);
|
||||||
|
return;
|
||||||
goto readd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((req[0] != 0x80) || (req[1] != 0xd2))
|
if ((req[0] != 0x80) || (req[1] != 0xd2))
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Packet header doesn't match timing request (got 0x%02x%02x, expected 0x80d2)\n", req[0], req[1]);
|
DPRINTF(E_WARN, L_RAOP, "Packet header doesn't match timing request (got 0x%02x%02x, expected 0x80d2)\n", req[0], req[1]);
|
||||||
|
return;
|
||||||
goto readd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(res, 0, sizeof(res));
|
memset(res, 0, sizeof(res));
|
||||||
@ -2985,7 +3041,7 @@ raop_v2_timing_cb(int fd, short what, void *arg)
|
|||||||
memcpy(res + 20, &recv_stamp.frac, 4);
|
memcpy(res + 20, &recv_stamp.frac, 4);
|
||||||
|
|
||||||
/* Transmit timestamp */
|
/* Transmit timestamp */
|
||||||
ret = raop_v2_timing_get_clock_ntp(&xmit_stamp);
|
ret = timing_get_clock_ntp(&xmit_stamp);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't get transmit timestamp, falling back to receive timestamp\n");
|
DPRINTF(E_LOG, L_RAOP, "Couldn't get transmit timestamp, falling back to receive timestamp\n");
|
||||||
@ -3004,258 +3060,54 @@ raop_v2_timing_cb(int fd, short what, void *arg)
|
|||||||
memcpy(res + 28, &xmit_stamp.frac, 4);
|
memcpy(res + 28, &xmit_stamp.frac, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sendto(svc->fd, res, sizeof(res), 0, &sa.sa, len);
|
ret = sendto(svc->fd, res, sizeof(res), 0, &peer_addr.sa, peer_addrlen);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not send timing reply: %s\n", strerror(errno));
|
DPRINTF(E_LOG, L_RAOP, "Could not send timing reply: %s\n", strerror(errno));
|
||||||
|
|
||||||
goto readd;
|
|
||||||
}
|
|
||||||
|
|
||||||
readd:
|
|
||||||
ret = event_add(svc->ev, NULL);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't re-add event for timing requests\n");
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
raop_v2_timing_start_one(struct raop_service *svc, int family)
|
|
||||||
{
|
|
||||||
union sockaddr_all sa;
|
|
||||||
int on;
|
|
||||||
int len;
|
|
||||||
int ret;
|
|
||||||
int timing_port;
|
|
||||||
|
|
||||||
#ifdef SOCK_CLOEXEC
|
|
||||||
svc->fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
|
||||||
#else
|
|
||||||
svc->fd = socket(family, SOCK_DGRAM, 0);
|
|
||||||
#endif
|
|
||||||
if (svc->fd < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't make timing socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (family == AF_INET6)
|
|
||||||
{
|
|
||||||
on = 1;
|
|
||||||
ret = setsockopt(svc->fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not set IPV6_V6ONLY on timing socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&sa, 0, sizeof(union sockaddr_all));
|
|
||||||
sa.ss.ss_family = family;
|
|
||||||
|
|
||||||
timing_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "timing_port");
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
sa.sin.sin_addr.s_addr = INADDR_ANY;
|
|
||||||
sa.sin.sin_port = htons(timing_port);
|
|
||||||
len = sizeof(sa.sin);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
sa.sin6.sin6_addr = in6addr_any;
|
|
||||||
sa.sin6.sin6_port = htons(timing_port);
|
|
||||||
len = sizeof(sa.sin6);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bind(svc->fd, &sa.sa, len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't bind timing socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = sizeof(sa.ss);
|
|
||||||
ret = getsockname(svc->fd, &sa.sa, (socklen_t *)&len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't get timing socket name: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
svc->port = ntohs(sa.sin.sin_port);
|
|
||||||
DPRINTF(E_DBG, L_RAOP, "Timing IPv4 port: %d\n", svc->port);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
svc->port = ntohs(sa.sin6.sin6_port);
|
|
||||||
DPRINTF(E_DBG, L_RAOP, "Timing IPv6 port: %d\n", svc->port);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
svc->ev = event_new(evbase_player, svc->fd, EV_READ, raop_v2_timing_cb, svc);
|
|
||||||
if (!svc->ev)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Out of memory for raop_service event\n");
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
event_add(svc->ev, NULL);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
out_fail:
|
|
||||||
close(svc->fd);
|
|
||||||
svc->fd = -1;
|
|
||||||
svc->port = 0;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
raop_v2_timing_stop(void)
|
control_svc_cb(int fd, short what, void *arg)
|
||||||
{
|
{
|
||||||
if (timing_4svc.ev)
|
|
||||||
event_free(timing_4svc.ev);
|
|
||||||
|
|
||||||
if (timing_6svc.ev)
|
|
||||||
event_free(timing_6svc.ev);
|
|
||||||
|
|
||||||
close(timing_4svc.fd);
|
|
||||||
|
|
||||||
timing_4svc.fd = -1;
|
|
||||||
timing_4svc.port = 0;
|
|
||||||
|
|
||||||
close(timing_6svc.fd);
|
|
||||||
|
|
||||||
timing_6svc.fd = -1;
|
|
||||||
timing_6svc.port = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
raop_v2_timing_start(int v6enabled)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (v6enabled)
|
|
||||||
{
|
|
||||||
ret = raop_v2_timing_start_one(&timing_6svc, AF_INET6);
|
|
||||||
if (ret < 0)
|
|
||||||
DPRINTF(E_WARN, L_RAOP, "Could not start timing service on IPv6\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = raop_v2_timing_start_one(&timing_4svc, AF_INET);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not start timing service on IPv4\n");
|
|
||||||
|
|
||||||
raop_v2_timing_stop();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ----------------- Control service (retransmission and sync) ---------------*/
|
|
||||||
|
|
||||||
static void
|
|
||||||
raop_v2_control_cb(int fd, short what, void *arg)
|
|
||||||
{
|
|
||||||
char address[INET6_ADDRSTRLEN];
|
|
||||||
union sockaddr_all sa;
|
|
||||||
uint8_t req[8];
|
|
||||||
struct raop_session *rs;
|
|
||||||
struct raop_service *svc;
|
struct raop_service *svc;
|
||||||
|
union net_sockaddr peer_addr;
|
||||||
|
socklen_t peer_addrlen = sizeof(peer_addr);
|
||||||
|
char address[INET6_ADDRSTRLEN];
|
||||||
|
struct raop_session *rs;
|
||||||
|
uint8_t req[8];
|
||||||
uint16_t seq_start;
|
uint16_t seq_start;
|
||||||
uint16_t seq_len;
|
uint16_t seq_len;
|
||||||
int len;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
svc = (struct raop_service *)arg;
|
svc = (struct raop_service *)arg;
|
||||||
|
|
||||||
len = sizeof(sa.ss);
|
ret = recvfrom(svc->fd, req, sizeof(req), 0, &peer_addr.sa, &peer_addrlen);
|
||||||
ret = recvfrom(svc->fd, req, sizeof(req), 0, &sa.sa, (socklen_t *)&len);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Error reading control request: %s\n", strerror(errno));
|
DPRINTF(E_LOG, L_RAOP, "Error reading control request: %s\n", strerror(errno));
|
||||||
|
return;
|
||||||
goto readd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret != 8)
|
if (ret != 8)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_RAOP, "Got control request with size %d\n", ret);
|
DPRINTF(E_WARN, L_RAOP, "Got control request with size %d\n", ret);
|
||||||
|
return;
|
||||||
goto readd;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (sa.ss.ss_family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
if (svc != &control_4svc)
|
|
||||||
goto readd;
|
|
||||||
|
|
||||||
for (rs = raop_sessions; rs; rs = rs->next)
|
|
||||||
{
|
|
||||||
if ((rs->sa.ss.ss_family == AF_INET)
|
|
||||||
&& (sa.sin.sin_addr.s_addr == rs->sa.sin.sin_addr.s_addr))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rs)
|
|
||||||
ret = (inet_ntop(AF_INET, &sa.sin.sin_addr.s_addr, address, sizeof(address)) != NULL);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
if (svc != &control_6svc)
|
|
||||||
goto readd;
|
|
||||||
|
|
||||||
for (rs = raop_sessions; rs; rs = rs->next)
|
|
||||||
{
|
|
||||||
if ((rs->sa.ss.ss_family == AF_INET6)
|
|
||||||
&& IN6_ARE_ADDR_EQUAL(&sa.sin6.sin6_addr, &rs->sa.sin6.sin6_addr))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rs)
|
|
||||||
ret = (inet_ntop(AF_INET6, &sa.sin6.sin6_addr.s6_addr, address, sizeof(address)) != NULL);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Control svc: Unknown address family %d\n", sa.ss.ss_family);
|
|
||||||
goto readd;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rs)
|
|
||||||
{
|
|
||||||
if (!ret)
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Control request from [error: %s]; not a RAOP client\n", strerror(errno));
|
|
||||||
else
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Control request from %s; not a RAOP client\n", address);
|
|
||||||
|
|
||||||
goto readd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((req[0] != 0x80) || (req[1] != 0xd5))
|
if ((req[0] != 0x80) || (req[1] != 0xd5))
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Packet header doesn't match retransmit request (got 0x%02x%02x, expected 0x80d5)\n", req[0], req[1]);
|
DPRINTF(E_WARN, L_RAOP, "Packet header doesn't match retransmit request (got 0x%02x%02x, expected 0x80d5)\n", req[0], req[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
goto readd;
|
rs = session_find_by_address(&peer_addr);
|
||||||
|
if (!rs)
|
||||||
|
{
|
||||||
|
net_address_get(address, sizeof(address), &peer_addr);
|
||||||
|
DPRINTF(E_WARN, L_RAOP, "Control request from %s; not a RAOP client\n", address);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(&seq_start, req + 4, 2);
|
memcpy(&seq_start, req + 4, 2);
|
||||||
@ -3265,161 +3117,6 @@ raop_v2_control_cb(int fd, short what, void *arg)
|
|||||||
seq_len = be16toh(seq_len);
|
seq_len = be16toh(seq_len);
|
||||||
|
|
||||||
packets_resend(rs, seq_start, seq_len);
|
packets_resend(rs, seq_start, seq_len);
|
||||||
|
|
||||||
readd:
|
|
||||||
ret = event_add(svc->ev, NULL);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't re-add event for control requests\n");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
raop_v2_control_start_one(struct raop_service *svc, int family)
|
|
||||||
{
|
|
||||||
union sockaddr_all sa;
|
|
||||||
int on;
|
|
||||||
int len;
|
|
||||||
int ret;
|
|
||||||
int control_port;
|
|
||||||
|
|
||||||
#ifdef SOCK_CLOEXEC
|
|
||||||
svc->fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
|
||||||
#else
|
|
||||||
svc->fd = socket(family, SOCK_DGRAM, 0);
|
|
||||||
#endif
|
|
||||||
if (svc->fd < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't make control socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (family == AF_INET6)
|
|
||||||
{
|
|
||||||
on = 1;
|
|
||||||
ret = setsockopt(svc->fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not set IPV6_V6ONLY on control socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&sa, 0, sizeof(union sockaddr_all));
|
|
||||||
sa.ss.ss_family = family;
|
|
||||||
|
|
||||||
control_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "control_port");
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
sa.sin.sin_addr.s_addr = INADDR_ANY;
|
|
||||||
sa.sin.sin_port = htons(control_port);
|
|
||||||
len = sizeof(sa.sin);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
sa.sin6.sin6_addr = in6addr_any;
|
|
||||||
sa.sin6.sin6_port = htons(control_port);
|
|
||||||
len = sizeof(sa.sin6);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bind(svc->fd, &sa.sa, len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't bind control socket: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = sizeof(sa.ss);
|
|
||||||
ret = getsockname(svc->fd, &sa.sa, (socklen_t *)&len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Couldn't get control socket name: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
svc->port = ntohs(sa.sin.sin_port);
|
|
||||||
DPRINTF(E_DBG, L_RAOP, "Control IPv4 port: %d\n", svc->port);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
svc->port = ntohs(sa.sin6.sin6_port);
|
|
||||||
DPRINTF(E_DBG, L_RAOP, "Control IPv6 port: %d\n", svc->port);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
svc->ev = event_new(evbase_player, svc->fd, EV_READ, raop_v2_control_cb, svc);
|
|
||||||
if (!svc->ev)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Out of memory for control event\n");
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
event_add(svc->ev, NULL);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
out_fail:
|
|
||||||
close(svc->fd);
|
|
||||||
svc->fd = -1;
|
|
||||||
svc->port = 0;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
raop_v2_control_stop(void)
|
|
||||||
{
|
|
||||||
if (control_4svc.ev)
|
|
||||||
event_free(control_4svc.ev);
|
|
||||||
|
|
||||||
if (control_6svc.ev)
|
|
||||||
event_free(control_6svc.ev);
|
|
||||||
|
|
||||||
close(control_4svc.fd);
|
|
||||||
|
|
||||||
control_4svc.fd = -1;
|
|
||||||
control_4svc.port = 0;
|
|
||||||
|
|
||||||
close(control_6svc.fd);
|
|
||||||
|
|
||||||
control_6svc.fd = -1;
|
|
||||||
control_6svc.port = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
raop_v2_control_start(int v6enabled)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (v6enabled)
|
|
||||||
{
|
|
||||||
ret = raop_v2_control_start_one(&control_6svc, AF_INET6);
|
|
||||||
if (ret < 0)
|
|
||||||
DPRINTF(E_WARN, L_RAOP, "Could not start control service on IPv6\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = raop_v2_control_start_one(&control_4svc, AF_INET);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not start control service on IPv4\n");
|
|
||||||
|
|
||||||
raop_v2_control_stop();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3514,60 +3211,6 @@ raop_cb_pin_start(struct evrtsp_request *req, void *arg)
|
|||||||
session_failure(rs);
|
session_failure(rs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
raop_v2_stream_open(struct raop_session *rs)
|
|
||||||
{
|
|
||||||
int len;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
#ifdef SOCK_CLOEXEC
|
|
||||||
rs->server_fd = socket(rs->sa.ss.ss_family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
|
||||||
#else
|
|
||||||
rs->server_fd = socket(rs->sa.ss.ss_family, SOCK_DGRAM, 0);
|
|
||||||
#endif
|
|
||||||
if (rs->server_fd < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not create socket for streaming: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (rs->sa.ss.ss_family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
rs->sa.sin.sin_port = htons(rs->server_port);
|
|
||||||
len = sizeof(rs->sa.sin);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
rs->sa.sin6.sin6_port = htons(rs->server_port);
|
|
||||||
len = sizeof(rs->sa.sin6);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
DPRINTF(E_WARN, L_RAOP, "Unknown family %d\n", rs->sa.ss.ss_family);
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = connect(rs->server_fd, &rs->sa.sa, len);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "connect() to [%s]:%u failed: %s\n", rs->address, rs->server_port, strerror(errno));
|
|
||||||
|
|
||||||
goto out_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
rs->state = RAOP_STATE_CONNECTED;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
out_fail:
|
|
||||||
close(rs->server_fd);
|
|
||||||
rs->server_fd = -1;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
raop_cb_startup_volume(struct evrtsp_request *req, void *arg)
|
raop_cb_startup_volume(struct evrtsp_request *req, void *arg)
|
||||||
{
|
{
|
||||||
@ -3594,10 +3237,12 @@ raop_cb_startup_volume(struct evrtsp_request *req, void *arg)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
ret = raop_v2_stream_open(rs);
|
rs->server_fd = net_connect(rs->address, rs->server_port, SOCK_DGRAM, "RAOP data");
|
||||||
if (ret < 0)
|
if (rs->server_fd < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
|
rs->state = RAOP_STATE_CONNECTED;
|
||||||
|
|
||||||
/* Session startup and setup is done, tell our user */
|
/* Session startup and setup is done, tell our user */
|
||||||
raop_status(rs);
|
raop_status(rs);
|
||||||
|
|
||||||
@ -4814,22 +4459,10 @@ raop_init(void)
|
|||||||
char ebuf[64];
|
char ebuf[64];
|
||||||
char *ptr;
|
char *ptr;
|
||||||
gpg_error_t gc_err;
|
gpg_error_t gc_err;
|
||||||
int v6enabled;
|
int timing_port;
|
||||||
int family;
|
int control_port;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
timing_4svc.fd = -1;
|
|
||||||
timing_4svc.port = 0;
|
|
||||||
|
|
||||||
timing_6svc.fd = -1;
|
|
||||||
timing_6svc.port = 0;
|
|
||||||
|
|
||||||
control_4svc.fd = -1;
|
|
||||||
control_4svc.port = 0;
|
|
||||||
|
|
||||||
control_6svc.fd = -1;
|
|
||||||
control_6svc.port = 0;
|
|
||||||
|
|
||||||
// Generate AES key and IV
|
// Generate AES key and IV
|
||||||
gcry_randomize(raop_aes_key, sizeof(raop_aes_key), GCRY_STRONG_RANDOM);
|
gcry_randomize(raop_aes_key, sizeof(raop_aes_key), GCRY_STRONG_RANDOM);
|
||||||
gcry_randomize(raop_aes_iv, sizeof(raop_aes_iv), GCRY_STRONG_RANDOM);
|
gcry_randomize(raop_aes_iv, sizeof(raop_aes_iv), GCRY_STRONG_RANDOM);
|
||||||
@ -4882,9 +4515,8 @@ raop_init(void)
|
|||||||
|
|
||||||
CHECK_NULL(L_RAOP, keep_alive_timer = evtimer_new(evbase_player, raop_keep_alive_timer_cb, NULL));
|
CHECK_NULL(L_RAOP, keep_alive_timer = evtimer_new(evbase_player, raop_keep_alive_timer_cb, NULL));
|
||||||
|
|
||||||
v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
|
timing_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "timing_port");
|
||||||
|
ret = service_start(&raop_timing_svc, timing_svc_cb, timing_port, "RAOP timing");
|
||||||
ret = raop_v2_timing_start(v6enabled);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "AirPlay time synchronization failed to start\n");
|
DPRINTF(E_LOG, L_RAOP, "AirPlay time synchronization failed to start\n");
|
||||||
@ -4892,7 +4524,8 @@ raop_init(void)
|
|||||||
goto out_free_timer;
|
goto out_free_timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = raop_v2_control_start(v6enabled);
|
control_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "control_port");
|
||||||
|
ret = service_start(&raop_control_svc, control_svc_cb, control_port, "RAOP control");
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "AirPlay playback control failed to start\n");
|
DPRINTF(E_LOG, L_RAOP, "AirPlay playback control failed to start\n");
|
||||||
@ -4900,15 +4533,7 @@ raop_init(void)
|
|||||||
goto out_stop_timing;
|
goto out_stop_timing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v6enabled)
|
ret = mdns_browse("_raop._tcp", raop_device_cb, MDNS_CONNECTION_TEST);
|
||||||
v6enabled = !((timing_6svc.fd < 0) || (control_6svc.fd < 0));
|
|
||||||
|
|
||||||
if (v6enabled)
|
|
||||||
family = AF_UNSPEC;
|
|
||||||
else
|
|
||||||
family = AF_INET;
|
|
||||||
|
|
||||||
ret = mdns_browse("_raop._tcp", family, raop_device_cb, MDNS_CONNECTION_TEST);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices\n");
|
DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices\n");
|
||||||
@ -4919,9 +4544,9 @@ raop_init(void)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out_stop_control:
|
out_stop_control:
|
||||||
raop_v2_control_stop();
|
service_stop(&raop_control_svc);
|
||||||
out_stop_timing:
|
out_stop_timing:
|
||||||
raop_v2_timing_stop();
|
service_stop(&raop_timing_svc);
|
||||||
out_free_timer:
|
out_free_timer:
|
||||||
event_free(keep_alive_timer);
|
event_free(keep_alive_timer);
|
||||||
free(raop_aes_iv_b64);
|
free(raop_aes_iv_b64);
|
||||||
@ -4938,6 +4563,11 @@ raop_deinit(void)
|
|||||||
{
|
{
|
||||||
struct raop_session *rs;
|
struct raop_session *rs;
|
||||||
|
|
||||||
|
service_stop(&raop_control_svc);
|
||||||
|
service_stop(&raop_timing_svc);
|
||||||
|
|
||||||
|
event_free(keep_alive_timer);
|
||||||
|
|
||||||
for (rs = raop_sessions; raop_sessions; rs = raop_sessions)
|
for (rs = raop_sessions; raop_sessions; rs = raop_sessions)
|
||||||
{
|
{
|
||||||
raop_sessions = rs->next;
|
raop_sessions = rs->next;
|
||||||
@ -4945,11 +4575,6 @@ raop_deinit(void)
|
|||||||
session_free(rs);
|
session_free(rs);
|
||||||
}
|
}
|
||||||
|
|
||||||
raop_v2_control_stop();
|
|
||||||
raop_v2_timing_stop();
|
|
||||||
|
|
||||||
event_free(keep_alive_timer);
|
|
||||||
|
|
||||||
gcry_cipher_close(raop_aes_ctx);
|
gcry_cipher_close(raop_aes_ctx);
|
||||||
|
|
||||||
free(raop_aes_key_b64);
|
free(raop_aes_key_b64);
|
||||||
|
@ -783,7 +783,7 @@ remote_pairing_init(void)
|
|||||||
cmdbase = commands_base_new(evbase_main, NULL);
|
cmdbase = commands_base_new(evbase_main, NULL);
|
||||||
|
|
||||||
// No ipv6 for remote at the moment
|
// No ipv6 for remote at the moment
|
||||||
ret = mdns_browse("_touch-remote._tcp", AF_INET, touch_remote_cb, 0);
|
ret = mdns_browse("_touch-remote._tcp", touch_remote_cb, MDNS_IPV4ONLY);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_FATAL, L_REMOTE, "Could not browse for Remote services\n");
|
DPRINTF(E_FATAL, L_REMOTE, "Could not browse for Remote services\n");
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
static struct lws_context *context;
|
static struct lws_context *context;
|
||||||
static pthread_t tid_websocket;
|
static pthread_t tid_websocket;
|
||||||
|
|
||||||
|
static const char *websocket_interface;
|
||||||
static int websocket_port;
|
static int websocket_port;
|
||||||
static bool websocket_exit = false;
|
static bool websocket_exit = false;
|
||||||
|
|
||||||
@ -377,6 +378,7 @@ websocket_init(void)
|
|||||||
struct lws_context_creation_info info;
|
struct lws_context_creation_info info;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
websocket_interface = cfg_getstr(cfg_getsec(cfg, "general"), "websocket_interface");
|
||||||
websocket_port = cfg_getint(cfg_getsec(cfg, "general"), "websocket_port");
|
websocket_port = cfg_getint(cfg_getsec(cfg, "general"), "websocket_port");
|
||||||
|
|
||||||
if (websocket_port <= 0)
|
if (websocket_port <= 0)
|
||||||
@ -387,6 +389,7 @@ websocket_init(void)
|
|||||||
|
|
||||||
memset(&info, 0, sizeof(info));
|
memset(&info, 0, sizeof(info));
|
||||||
info.port = websocket_port;
|
info.port = websocket_port;
|
||||||
|
info.iface = websocket_interface;
|
||||||
info.protocols = protocols;
|
info.protocols = protocols;
|
||||||
if (!cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"))
|
if (!cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"))
|
||||||
info.options |= LWS_SERVER_OPTION_DISABLE_IPV6;
|
info.options |= LWS_SERVER_OPTION_DISABLE_IPV6;
|
||||||
|
Loading…
Reference in New Issue
Block a user