From 7871d71a9c2a495bd2c76e015f739c46a2af2042 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 26 Feb 2021 19:23:00 +0100 Subject: [PATCH 01/11] [misc] Add utility network functions, incl configurable addr binding Also make misc.c/h a bit less messy. --- forked-daapd.conf.in | 5 + src/conffile.c | 1 + src/httpd.c | 2 +- src/httpd_daap.c | 4 +- src/httpd_dacp.c | 2 +- src/httpd_rsp.c | 2 +- src/misc.c | 894 ++++++++++++++++++++++++++----------------- src/misc.h | 194 ++++++---- src/mpd.c | 2 +- 9 files changed, 678 insertions(+), 428 deletions(-) diff --git a/forked-daapd.conf.in b/forked-daapd.conf.in index d2cc7802..d3221528 100644 --- a/forked-daapd.conf.in +++ b/forked-daapd.conf.in @@ -43,6 +43,11 @@ general { # Enable/disable IPv6 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 # cache_path = "@localstatedir@/cache/@PACKAGE@/cache.db" diff --git a/src/conffile.c b/src/conffile.c index bd6b14a2..abb436bf 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -53,6 +53,7 @@ static cfg_opt_t sec_general[] = CFG_INT("websocket_port", 3688, CFGF_NONE), CFG_STR_LIST("trusted_networks", "{localhost,192.168,fd}", 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_INT("cache_daap_threshold", 1000, CFGF_NONE), CFG_BOOL("speaker_autoselect", cfg_false, CFGF_NONE), diff --git a/src/httpd.c b/src/httpd.c index 8a45e606..0eae8027 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -1499,7 +1499,7 @@ httpd_admin_check_auth(struct evhttp_request *req) evhttp_connection_get_peer(evcon, &addr, &port); - if (peer_address_is_trusted(addr)) + if (net_peer_address_is_trusted(addr)) return true; passwd = cfg_getstr(cfg_getsec(cfg, "general"), "admin_password"); diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 90a23ae3..46b3186d 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -717,7 +717,7 @@ daap_request_authorize(struct httpd_request *hreq) char *passwd; int ret; - if (peer_address_is_trusted(hreq->peer_address)) + if (net_peer_address_is_trusted(hreq->peer_address)) return 0; // 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)); 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) { diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index c4ddb28e..ab9326a8 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -609,7 +609,7 @@ dacp_request_authorize(struct httpd_request *hreq) int32_t id; int ret; - if (peer_address_is_trusted(hreq->peer_address)) + if (net_peer_address_is_trusted(hreq->peer_address)) return 0; param = evhttp_find_header(hreq->query, "session-id"); diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c index 825e4c2d..f295997e 100644 --- a/src/httpd_rsp.c +++ b/src/httpd_rsp.c @@ -295,7 +295,7 @@ rsp_request_authorize(struct httpd_request *hreq) char *passwd; int ret; - if (peer_address_is_trusted(hreq->peer_address)) + if (net_peer_address_is_trusted(hreq->peer_address)) return 0; passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password"); diff --git a/src/misc.c b/src/misc.c index 8687ffc0..c0823883 100644 --- a/src/misc.c +++ b/src/misc.c @@ -37,6 +37,7 @@ #include #include #include +#include #ifndef CLOCK_REALTIME #include #endif @@ -44,6 +45,9 @@ #include #endif +#include +#include + #include #include @@ -107,12 +111,223 @@ static char *buildopts[] = NULL }; -char ** -buildopts_get() + +/* ------------------------ Network utility functions ----------------------- */ + +bool +net_peer_address_is_trusted(const char *addr) { - return buildopts; + cfg_t *section; + const char *network; + int i; + int n; + + if (!addr) + return false; + + if (strncmp(addr, "::ffff:", strlen("::ffff:")) == 0) + addr += strlen("::ffff:"); + + section = cfg_getsec(cfg, "general"); + + n = cfg_size(section, "trusted_networks"); + for (i = 0; i < n; i++) + { + network = cfg_getnstr(section, "trusted_networks", i); + + if (!network || network[0] == '\0') + return false; + + if (strncmp(network, addr, strlen(network)) == 0) + return true; + + if ((strcmp(network, "localhost") == 0) && (strcmp(addr, "127.0.0.1") == 0 || strcmp(addr, "::1") == 0)) + return true; + + if (strcmp(network, "any") == 0) + return true; + } + + return false; } +int +net_address_get(char *addr, size_t addr_len, union net_sockaddr *naddr) +{ + const char *s; + + memset(addr, 0, addr_len); // Just in case caller doesn't check for errors + + if (naddr->sa.sa_family == AF_INET6) + s = inet_ntop(AF_INET6, &naddr->sin6.sin6_addr, addr, addr_len); + else + s = inet_ntop(AF_INET, &naddr->sin.sin_addr, addr, addr_len); + + if (!s) + return -1; + + return 0; +} + +int +net_port_get(short unsigned *port, union net_sockaddr *naddr) +{ + if (naddr->sa.sa_family == AF_INET6) + *port = ntohs(naddr->sin6.sin6_port); + else + *port = ntohs(naddr->sin.sin_port); + + return 0; +} + +int +net_connect(const char *addr, unsigned short port, int type) +{ + struct addrinfo hints = { 0 }; + struct addrinfo *servinfo; + struct addrinfo *ptr; + char strport[8]; + int fd; + int ret; + + DPRINTF(E_DBG, L_MISC, "Connecting to %s, port %u\n", addr, port); + + hints.ai_socktype = type; + hints.ai_family = (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) ? AF_UNSPEC : AF_INET; + + snprintf(strport, sizeof(strport), "%hu", port); + ret = getaddrinfo(addr, strport, &hints, &servinfo); + if (ret < 0) + { + gai_strerror(ret); + return -1; + } + + for (ptr = servinfo; ptr; ptr = ptr->ai_next) + { + fd = socket(ptr->ai_family, ptr->ai_socktype | SOCK_CLOEXEC, ptr->ai_protocol); + if (fd < 0) + { + continue; + } + + ret = connect(fd, ptr->ai_addr, ptr->ai_addrlen); + if (ret < 0) + { + close(fd); + continue; + } + + break; + } + + freeaddrinfo(servinfo); + + if (!ptr) + { + DPRINTF(E_LOG, L_MISC, "Failed to connect to %s, port %u: %s\n", addr, port, strerror(errno)); + return -1; + } + + // net_address_get(ipaddr, sizeof(ipaddr), (union net_sockaddr *)ptr->ai-addr); + + return fd; +} + +// If *port is 0 then a random port will be assigned, and *port will be updated +// with the port number +int +net_bind(short unsigned *port, int type, const char *log_service_name) +{ + struct addrinfo hints = { 0 }; + struct addrinfo *servinfo; + struct addrinfo *ptr; + const char *cfgaddr; + char addr[INET6_ADDRSTRLEN]; + char strport[8]; + int yes = 1; + int no = 0; + int fd; + int ret; + + cfgaddr = cfg_getstr(cfg_getsec(cfg, "general"), "bind_address"); + + hints.ai_socktype = type; + hints.ai_family = (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) ? AF_INET6 : AF_INET; + hints.ai_flags = cfgaddr ? 0 : AI_PASSIVE; + + snprintf(strport, sizeof(strport), "%hu", *port); + ret = getaddrinfo(cfgaddr, strport, &hints, &servinfo); + if (ret < 0) + { + DPRINTF(E_LOG, L_MISC, "Failure creating '%s' service, could not resolve '%s' (port %s): %s\n", log_service_name, cfgaddr ? cfgaddr : "(ANY)", strport, gai_strerror(ret)); + return -1; + } + + for (ptr = servinfo, fd = -1; ptr != NULL; ptr = ptr->ai_next) + { + if (fd >= 0) + close(fd); + + fd = socket(ptr->ai_family, ptr->ai_socktype | SOCK_CLOEXEC, ptr->ai_protocol); + if (fd < 0) + continue; + + // TODO libevent sets this, we do the same? + ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)); + if (ret < 0) + continue; + + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + if (ret < 0) + continue; + + if (ptr->ai_family == AF_INET6) + { + // We want to be sure the service is dual stack + ret = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(no)); + if (ret < 0) + continue; + } + + ret = bind(fd, ptr->ai_addr, ptr->ai_addrlen); + if (ret < 0) + continue; + + break; + } + + freeaddrinfo(servinfo); + + if (!ptr) + { + DPRINTF(E_LOG, L_MISC, "Could not create service '%s' with address %s, port %hu: %s\n", log_service_name, cfgaddr ? cfgaddr : "(ANY)", *port, strerror(errno)); + goto error; + } + + // Get the port that was assigned + ret = getsockname(fd, ptr->ai_addr, &ptr->ai_addrlen); + if (ret < 0) + { + DPRINTF(E_LOG, L_MISC, "Could not find address of service '%s': %s\n", log_service_name, strerror(errno)); + goto error; + } + + net_port_get(port, (union net_sockaddr *)ptr->ai_addr); + net_address_get(addr, sizeof(addr), (union net_sockaddr *)ptr->ai_addr); + + DPRINTF(E_DBG, L_MISC, "Service '%s' bound to %s, port %hu, socket %d\n", log_service_name, addr, *port, fd); + + return fd; + + error: + close(fd); + return -1; +} + + +/* ----------------------- Conversion/hashing/sanitizers -------------------- */ + int safe_atoi32(const char *str, int32_t *val) { @@ -441,259 +656,6 @@ safe_snreplace(char *s, size_t sz, const char *pattern, const char *replacement) } -/* Key/value functions */ -struct keyval * -keyval_alloc(void) -{ - struct keyval *kv; - - kv = calloc(1, sizeof(struct keyval)); - if (!kv) - { - DPRINTF(E_LOG, L_MISC, "Out of memory for keyval alloc\n"); - - return NULL; - } - - return kv; -} - -int -keyval_add_size(struct keyval *kv, const char *name, const char *value, size_t size) -{ - struct onekeyval *okv; - const char *val; - - if (!kv) - return -1; - - /* Check for duplicate key names */ - val = keyval_get(kv, name); - if (val) - { - /* Same value, fine */ - if (strcmp(val, value) == 0) - return 0; - else /* Different value, bad */ - return -1; - } - - okv = (struct onekeyval *)malloc(sizeof(struct onekeyval)); - if (!okv) - { - DPRINTF(E_LOG, L_MISC, "Out of memory for new keyval\n"); - - return -1; - } - - okv->name = strdup(name); - if (!okv->name) - { - DPRINTF(E_LOG, L_MISC, "Out of memory for new keyval name\n"); - - free(okv); - return -1; - } - - okv->value = (char *)malloc(size + 1); - if (!okv->value) - { - DPRINTF(E_LOG, L_MISC, "Out of memory for new keyval value\n"); - - free(okv->name); - free(okv); - return -1; - } - - memcpy(okv->value, value, size); - okv->value[size] = '\0'; - - okv->next = NULL; - - if (!kv->head) - kv->head = okv; - - if (kv->tail) - kv->tail->next = okv; - - kv->tail = okv; - - return 0; -} - -int -keyval_add(struct keyval *kv, const char *name, const char *value) -{ - return keyval_add_size(kv, name, value, strlen(value)); -} - -void -keyval_remove(struct keyval *kv, const char *name) -{ - struct onekeyval *okv; - struct onekeyval *pokv; - - if (!kv) - return; - - for (pokv = NULL, okv = kv->head; okv; pokv = okv, okv = okv->next) - { - if (strcasecmp(okv->name, name) == 0) - break; - } - - if (!okv) - return; - - if (okv == kv->head) - kv->head = okv->next; - - if (okv == kv->tail) - kv->tail = pokv; - - if (pokv) - pokv->next = okv->next; - - free(okv->name); - free(okv->value); - free(okv); -} - -const char * -keyval_get(struct keyval *kv, const char *name) -{ - struct onekeyval *okv; - - if (!kv) - return NULL; - - for (okv = kv->head; okv; okv = okv->next) - { - if (strcasecmp(okv->name, name) == 0) - return okv->value; - } - - return NULL; -} - -void -keyval_clear(struct keyval *kv) -{ - struct onekeyval *hokv; - struct onekeyval *okv; - - if (!kv) - return; - - hokv = kv->head; - - for (okv = hokv; hokv; okv = hokv) - { - hokv = okv->next; - - free(okv->name); - free(okv->value); - free(okv); - } - - kv->head = NULL; - kv->tail = NULL; -} - -void -keyval_sort(struct keyval *kv) -{ - struct onekeyval *head; - struct onekeyval *okv; - struct onekeyval *sokv; - - if (!kv || !kv->head) - return; - - head = kv->head; - for (okv = kv->head; okv; okv = okv->next) - { - okv->sort = NULL; - for (sokv = kv->head; sokv; sokv = sokv->next) - { - // We try to find a name which is greater than okv->name - // but less than our current candidate (okv->sort->name) - if ( (strcmp(sokv->name, okv->name) > 0) && - ((okv->sort == NULL) || (strcmp(sokv->name, okv->sort->name) < 0)) ) - okv->sort = sokv; - } - - // Find smallest name, which will be the new head - if (strcmp(okv->name, head->name) < 0) - head = okv; - } - - while ((okv = kv->head)) - { - kv->head = okv->next; - okv->next = okv->sort; - } - - kv->head = head; - for (okv = kv->head; okv; okv = okv->next) - kv->tail = okv; - - DPRINTF(E_DBG, L_MISC, "Keyval sorted. New head: %s. New tail: %s.\n", kv->head->name, kv->tail->name); -} - - -char ** -m_readfile(const char *path, int num_lines) -{ - char buf[256]; - FILE *fp; - char **lines; - char *line; - int i; - - // Alloc array of char pointers - lines = calloc(num_lines, sizeof(char *)); - if (!lines) - return NULL; - - fp = fopen(path, "rb"); - if (!fp) - { - DPRINTF(E_LOG, L_MISC, "Could not open file '%s' for reading: %s\n", path, strerror(errno)); - free(lines); - return NULL; - } - - for (i = 0; i < num_lines; i++) - { - line = fgets(buf, sizeof(buf), fp); - if (!line) - { - DPRINTF(E_LOG, L_MISC, "File '%s' has fewer lines than expected (found %d, expected %d)\n", path, i, num_lines); - goto error; - } - - lines[i] = atrim(line); - if (!lines[i] || (strlen(lines[i]) == 0)) - { - DPRINTF(E_LOG, L_MISC, "Line %d in '%s' is invalid\n", i+1, path); - goto error; - } - } - - fclose(fp); - - return lines; - - error: - for (i = 0; i < num_lines; i++) - free(lines[i]); - - free(lines); - fclose(fp); - return NULL; -} - char * unicode_fixup_string(char *str, const char *fromcode) { @@ -1015,124 +977,211 @@ murmur_hash64(const void *key, int len, uint32_t seed) # error Platform not supported #endif -#ifdef HAVE_UUID -void -uuid_make(char *str) + +/* --------------------------- Key/value functions -------------------------- */ + +struct keyval * +keyval_alloc(void) { - uuid_t uu; + struct keyval *kv; - uuid_generate_random(uu); - uuid_unparse_upper(uu, str); -} -#else -void -uuid_make(char *str) -{ - uint16_t uuid[8]; - time_t now; - int i; - - now = time(NULL); - - srand((unsigned int)now); - - for (i = 0; i < ARRAY_SIZE(uuid); i++) + kv = calloc(1, sizeof(struct keyval)); + if (!kv) { - uuid[i] = (uint16_t)rand(); + DPRINTF(E_LOG, L_MISC, "Out of memory for keyval alloc\n"); - // time_hi_and_version, set version to 4 (=random) - if (i == 3) - uuid[i] = (uuid[i] & 0x0FFF) | 0x4000; - // clock_seq, variant 1 - if (i == 4) - uuid[i] = (uuid[i] & 0x3FFF) | 0x8000; - - - if (i == 2 || i == 3 || i == 4 || i == 5) - str += sprintf(str, "-"); - - str += sprintf(str, "%04" PRIX16, uuid[i]); + return NULL; } + + return kv; } -#endif int -linear_regression(double *m, double *b, double *r2, const double *x, const double *y, int n) +keyval_add_size(struct keyval *kv, const char *name, const char *value, size_t size) { - double x_val; - double sum_x = 0; - double sum_x2 = 0; - double sum_y = 0; - double sum_y2 = 0; - double sum_xy = 0; - double denom; - int i; + struct onekeyval *okv; + const char *val; - for (i = 0; i < n; i++) - { - x_val = x ? x[i] : (double)i; - sum_x += x_val; - sum_x2 += x_val * x_val; - sum_y += y[i]; - sum_y2 += y[i] * y[i]; - sum_xy += x_val * y[i]; - } - - denom = (n * sum_x2 - sum_x * sum_x); - if (denom == 0) + if (!kv) return -1; - *m = (n * sum_xy - sum_x * sum_y) / denom; - *b = (sum_y * sum_x2 - sum_x * sum_xy) / denom; - if (r2) - *r2 = (sum_xy - (sum_x * sum_y)/n) * (sum_xy - (sum_x * sum_y)/n) / ((sum_x2 - (sum_x * sum_x)/n) * (sum_y2 - (sum_y * sum_y)/n)); + /* Check for duplicate key names */ + val = keyval_get(kv, name); + if (val) + { + /* Same value, fine */ + if (strcmp(val, value) == 0) + return 0; + else /* Different value, bad */ + return -1; + } + + okv = (struct onekeyval *)malloc(sizeof(struct onekeyval)); + if (!okv) + { + DPRINTF(E_LOG, L_MISC, "Out of memory for new keyval\n"); + + return -1; + } + + okv->name = strdup(name); + if (!okv->name) + { + DPRINTF(E_LOG, L_MISC, "Out of memory for new keyval name\n"); + + free(okv); + return -1; + } + + okv->value = (char *)malloc(size + 1); + if (!okv->value) + { + DPRINTF(E_LOG, L_MISC, "Out of memory for new keyval value\n"); + + free(okv->name); + free(okv); + return -1; + } + + memcpy(okv->value, value, size); + okv->value[size] = '\0'; + + okv->next = NULL; + + if (!kv->head) + kv->head = okv; + + if (kv->tail) + kv->tail->next = okv; + + kv->tail = okv; return 0; } -bool -quality_is_equal(struct media_quality *a, struct media_quality *b) +int +keyval_add(struct keyval *kv, const char *name, const char *value) { - return (a->sample_rate == b->sample_rate && a->bits_per_sample == b->bits_per_sample && a->channels == b->channels && a->bit_rate == b->bit_rate); + return keyval_add_size(kv, name, value, strlen(value)); } -bool -peer_address_is_trusted(const char *addr) +void +keyval_remove(struct keyval *kv, const char *name) { - cfg_t *section; - const char *network; - int i; - int n; + struct onekeyval *okv; + struct onekeyval *pokv; - if (!addr) - return false; + if (!kv) + return; - if (strncmp(addr, "::ffff:", strlen("::ffff:")) == 0) - addr += strlen("::ffff:"); - - section = cfg_getsec(cfg, "general"); - - n = cfg_size(section, "trusted_networks"); - for (i = 0; i < n; i++) + for (pokv = NULL, okv = kv->head; okv; pokv = okv, okv = okv->next) { - network = cfg_getnstr(section, "trusted_networks", i); - - if (!network || network[0] == '\0') - return false; - - if (strncmp(network, addr, strlen(network)) == 0) - return true; - - if ((strcmp(network, "localhost") == 0) && (strcmp(addr, "127.0.0.1") == 0 || strcmp(addr, "::1") == 0)) - return true; - - if (strcmp(network, "any") == 0) - return true; + if (strcasecmp(okv->name, name) == 0) + break; } - return false; + if (!okv) + return; + + if (okv == kv->head) + kv->head = okv->next; + + if (okv == kv->tail) + kv->tail = pokv; + + if (pokv) + pokv->next = okv->next; + + free(okv->name); + free(okv->value); + free(okv); } +const char * +keyval_get(struct keyval *kv, const char *name) +{ + struct onekeyval *okv; + + if (!kv) + return NULL; + + for (okv = kv->head; okv; okv = okv->next) + { + if (strcasecmp(okv->name, name) == 0) + return okv->value; + } + + return NULL; +} + +void +keyval_clear(struct keyval *kv) +{ + struct onekeyval *hokv; + struct onekeyval *okv; + + if (!kv) + return; + + hokv = kv->head; + + for (okv = hokv; hokv; okv = hokv) + { + hokv = okv->next; + + free(okv->name); + free(okv->value); + free(okv); + } + + kv->head = NULL; + kv->tail = NULL; +} + +void +keyval_sort(struct keyval *kv) +{ + struct onekeyval *head; + struct onekeyval *okv; + struct onekeyval *sokv; + + if (!kv || !kv->head) + return; + + head = kv->head; + for (okv = kv->head; okv; okv = okv->next) + { + okv->sort = NULL; + for (sokv = kv->head; sokv; sokv = sokv->next) + { + // We try to find a name which is greater than okv->name + // but less than our current candidate (okv->sort->name) + if ( (strcmp(sokv->name, okv->name) > 0) && + ((okv->sort == NULL) || (strcmp(sokv->name, okv->sort->name) < 0)) ) + okv->sort = sokv; + } + + // Find smallest name, which will be the new head + if (strcmp(okv->name, head->name) < 0) + head = okv; + } + + while ((okv = kv->head)) + { + kv->head = okv->next; + okv->next = okv->sort; + } + + kv->head = head; + for (okv = kv->head; okv; okv = okv->next) + kv->tail = okv; + + DPRINTF(E_DBG, L_MISC, "Keyval sorted. New head: %s. New tail: %s.\n", kv->head->name, kv->tail->name); +} + + +/* ------------------------------- Ringbuffer ------------------------------- */ + int ringbuffer_init(struct ringbuffer *buf, size_t size) { @@ -1214,6 +1263,9 @@ ringbuffer_read(uint8_t **dst, size_t dstlen, struct ringbuffer *buf) return dstlen; } + +/* ------------------------- Clock utility functions ------------------------ */ + int clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec *res) { @@ -1411,6 +1463,24 @@ timer_getoverrun(timer_t timer_id) #endif /* HAVE_MACH_CLOCK */ + +/* ------------------------------- Media quality ---------------------------- */ + +bool +quality_is_equal(struct media_quality *a, struct media_quality *b) +{ + return (a->sample_rate == b->sample_rate && a->bits_per_sample == b->bits_per_sample && a->channels == b->channels && a->bit_rate == b->bit_rate); +} + + +/* -------------------------- Misc utility functions ------------------------ */ + +char ** +buildopts_get() +{ + return buildopts; +} + int mutex_init(pthread_mutex_t *mutex) { @@ -1425,6 +1495,136 @@ mutex_init(pthread_mutex_t *mutex) return err; } +#ifdef HAVE_UUID +void +uuid_make(char *str) +{ + uuid_t uu; + + uuid_generate_random(uu); + uuid_unparse_upper(uu, str); +} +#else +void +uuid_make(char *str) +{ + uint16_t uuid[8]; + time_t now; + int i; + + now = time(NULL); + + srand((unsigned int)now); + + for (i = 0; i < ARRAY_SIZE(uuid); i++) + { + uuid[i] = (uint16_t)rand(); + + // time_hi_and_version, set version to 4 (=random) + if (i == 3) + uuid[i] = (uuid[i] & 0x0FFF) | 0x4000; + // clock_seq, variant 1 + if (i == 4) + uuid[i] = (uuid[i] & 0x3FFF) | 0x8000; + + + if (i == 2 || i == 3 || i == 4 || i == 5) + str += sprintf(str, "-"); + + str += sprintf(str, "%04" PRIX16, uuid[i]); + } +} +#endif + +int +linear_regression(double *m, double *b, double *r2, const double *x, const double *y, int n) +{ + double x_val; + double sum_x = 0; + double sum_x2 = 0; + double sum_y = 0; + double sum_y2 = 0; + double sum_xy = 0; + double denom; + int i; + + for (i = 0; i < n; i++) + { + x_val = x ? x[i] : (double)i; + sum_x += x_val; + sum_x2 += x_val * x_val; + sum_y += y[i]; + sum_y2 += y[i] * y[i]; + sum_xy += x_val * y[i]; + } + + denom = (n * sum_x2 - sum_x * sum_x); + if (denom == 0) + return -1; + + *m = (n * sum_xy - sum_x * sum_y) / denom; + *b = (sum_y * sum_x2 - sum_x * sum_xy) / denom; + if (r2) + *r2 = (sum_xy - (sum_x * sum_y)/n) * (sum_xy - (sum_x * sum_y)/n) / ((sum_x2 - (sum_x * sum_x)/n) * (sum_y2 - (sum_y * sum_y)/n)); + + return 0; +} + +char ** +m_readfile(const char *path, int num_lines) +{ + char buf[256]; + FILE *fp; + char **lines; + char *line; + int i; + + // Alloc array of char pointers + lines = calloc(num_lines, sizeof(char *)); + if (!lines) + return NULL; + + fp = fopen(path, "rb"); + if (!fp) + { + DPRINTF(E_LOG, L_MISC, "Could not open file '%s' for reading: %s\n", path, strerror(errno)); + free(lines); + return NULL; + } + + for (i = 0; i < num_lines; i++) + { + line = fgets(buf, sizeof(buf), fp); + if (!line) + { + DPRINTF(E_LOG, L_MISC, "File '%s' has fewer lines than expected (found %d, expected %d)\n", path, i, num_lines); + goto error; + } + + lines[i] = atrim(line); + if (!lines[i] || (strlen(lines[i]) == 0)) + { + DPRINTF(E_LOG, L_MISC, "Line %d in '%s' is invalid\n", i+1, path); + goto error; + } + } + + fclose(fp); + + return lines; + + error: + for (i = 0; i < num_lines; i++) + free(lines[i]); + + free(lines); + fclose(fp); + return NULL; +} + + +/* -------------------------------- Assertion ------------------------------- */ + void log_fatal_err(int domain, const char *func, int line, int err) { diff --git a/src/misc.h b/src/misc.h index 834ea86b..0596f599 100644 --- a/src/misc.h +++ b/src/misc.h @@ -1,4 +1,3 @@ - #ifndef __MISC_H__ #define __MISC_H__ @@ -7,11 +6,43 @@ #endif #include +#include #include -#include -#include -/* Samples to bytes, bytes to samples */ + +/* ------------------------ Network utility functions ----------------------- */ + +#include +#include + +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); + +int +net_connect(const char *addr, unsigned short port, int type); + +int +net_bind(short unsigned *port, int type, const char *log_service_name); + + +/* ----------------------- Conversion/hashing/sanitizers -------------------- */ + +// Samples to bytes, bytes to samples #define STOB(s, bits, c) ((s) * (c) * (bits) / 8) #define BTOS(b, bits, c) ((b) / ((c) * (bits) / 8)) @@ -30,41 +61,6 @@ #define NTOSTR_HELPER(x) #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 safe_atoi32(const char *str, int32_t *val); @@ -95,32 +91,6 @@ safe_snprintf_cat(char *dst, size_t n, const char *fmt, ...) __attribute__ ((for int 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 * unicode_fixup_string(char *str, const char *fromcode); @@ -150,18 +120,54 @@ b64_encode(const uint8_t *src, int srclen); uint64_t 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 -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 -quality_is_equal(struct media_quality *a, struct media_quality *b); +int +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 -bool -peer_address_is_trusted(const char *addr); +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); + + +/* ------------------------------- 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 ringbuffer_init(struct ringbuffer *buf, size_t size); @@ -176,6 +182,10 @@ size_t ringbuffer_read(uint8_t **dst, size_t dstlen, struct ringbuffer *buf); +/* ------------------------- Clock utility functions ------------------------ */ + +#include + #ifndef HAVE_CLOCK_GETTIME #ifndef CLOCK_REALTIME @@ -217,7 +227,7 @@ timer_getoverrun(timer_t timer_id); #endif -/* Timer function for platforms without hi-res timers */ +// Timer function for platforms without hi-res timers int clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec *res); @@ -230,10 +240,44 @@ timespec_cmp(struct timespec time1, struct timespec time2); struct timespec 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 + +char ** +buildopts_get(void); + +// initialize mutex with error checking (not default on all platforms) int 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 returned error (type errno) if it fails, and aborts the process. Example: CHECK_ERR(L_MAIN, my_function()); */ diff --git a/src/mpd.c b/src/mpd.c index a8e4f7f9..859b3b07 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -4511,7 +4511,7 @@ mpd_accept_conn_cb(struct evconnlistener *listener, if (!client_ctx->authenticated) { 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; From dd0f0ece785c76fa07e8b2c30a2b2227d86c1ccc Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 26 Feb 2021 22:16:04 +0100 Subject: [PATCH 02/11] [mdns] Remove ipv4/ipv6 argument to mdns_browse Make sure mdns_browse() always works the same, and follows user config. --- src/mdns.h | 6 +++--- src/mdns_avahi.c | 9 ++++++++- src/mdns_dnssd.c | 9 ++++++++- src/outputs/airplay.c | 11 +---------- src/outputs/cast.c | 8 +------- src/outputs/raop.c | 11 +---------- src/remote_pairing.c | 2 +- 7 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/mdns.h b/src/mdns.h index 63723ce7..c63d71be 100644 --- a/src/mdns.h +++ b/src/mdns.h @@ -8,6 +8,8 @@ enum mdns_options { // Test connection to device and only call back if successful 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); @@ -58,13 +60,11 @@ mdns_cname(char *name); * Call only from the main thread! * * @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 cb Callback when service state changes (e.g. appears/disappears) * @return 0 on success, -1 on error */ 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__ */ diff --git a/src/mdns_avahi.c b/src/mdns_avahi.c index 11a0e122..6f90c993 100644 --- a/src/mdns_avahi.c +++ b/src/mdns_avahi.c @@ -52,6 +52,7 @@ #endif #include "logger.h" +#include "conffile.h" #include "mdns.h" #define MDNSERR avahi_strerror(avahi_client_errno(mdns_client)) @@ -1160,15 +1161,21 @@ mdns_cname(char *name) } 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; AvahiServiceBrowser *b; + int family; DPRINTF(E_DBG, L_MDNS, "Adding service browser for type %s\n", type); 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->type = strdup(type); mb->flags = flags; diff --git a/src/mdns_dnssd.c b/src/mdns_dnssd.c index a7076ccc..2c11f614 100644 --- a/src/mdns_dnssd.c +++ b/src/mdns_dnssd.c @@ -50,6 +50,7 @@ #endif #include "logger.h" +#include "conffile.h" /* Main event base, from main.c */ extern struct event_base *evbase_main; @@ -872,10 +873,11 @@ mdns_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, } 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; DNSServiceErrorType err; + int family; 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->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 */ switch(family) { case AF_UNSPEC: diff --git a/src/outputs/airplay.c b/src/outputs/airplay.c index 2b14d8f1..d271c228 100644 --- a/src/outputs/airplay.c +++ b/src/outputs/airplay.c @@ -4363,7 +4363,6 @@ static int airplay_init(void) { int v6enabled; - int family; int ret; int i; @@ -4412,15 +4411,7 @@ airplay_init(void) goto out_stop_timing; } - if (v6enabled) - 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); + ret = mdns_browse("_airplay._tcp", airplay_device_cb, MDNS_CONNECTION_TEST); if (ret < 0) { DPRINTF(E_LOG, L_AIRPLAY, "Could not add mDNS browser for AirPlay devices\n"); diff --git a/src/outputs/cast.c b/src/outputs/cast.c index f8d5ec3d..cc2ab1c4 100644 --- a/src/outputs/cast.c +++ b/src/outputs/cast.c @@ -2445,7 +2445,6 @@ static int cast_init(void) { struct decode_ctx *decode_ctx; - int family; int i; int ret; @@ -2484,12 +2483,7 @@ cast_init(void) goto out_tls_deinit; } - if (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) - family = AF_UNSPEC; - else - family = AF_INET; - - ret = mdns_browse("_googlecast._tcp", family, cast_device_cb, 0); + ret = mdns_browse("_googlecast._tcp", cast_device_cb, 0); if (ret < 0) { DPRINTF(E_LOG, L_CAST, "Could not add mDNS browser for Chromecast devices\n"); diff --git a/src/outputs/raop.c b/src/outputs/raop.c index f17e0961..6e67abf5 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -4815,7 +4815,6 @@ raop_init(void) char *ptr; gpg_error_t gc_err; int v6enabled; - int family; int ret; timing_4svc.fd = -1; @@ -4900,15 +4899,7 @@ raop_init(void) goto out_stop_timing; } - if (v6enabled) - 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); + ret = mdns_browse("_raop._tcp", raop_device_cb, MDNS_CONNECTION_TEST); if (ret < 0) { DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices\n"); diff --git a/src/remote_pairing.c b/src/remote_pairing.c index 44ddafce..2625a3c3 100644 --- a/src/remote_pairing.c +++ b/src/remote_pairing.c @@ -783,7 +783,7 @@ remote_pairing_init(void) cmdbase = commands_base_new(evbase_main, NULL); // 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) { DPRINTF(E_FATAL, L_REMOTE, "Could not browse for Remote services\n"); From dfc1580f13d8310ffff660dffef1ff92da5396c3 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 26 Feb 2021 22:42:09 +0100 Subject: [PATCH 03/11] [misc] Add logging of net_connect() service --- src/misc.c | 6 +++--- src/misc.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/misc.c b/src/misc.c index c0823883..4a7bf596 100644 --- a/src/misc.c +++ b/src/misc.c @@ -181,7 +181,7 @@ net_port_get(short unsigned *port, union net_sockaddr *naddr) } int -net_connect(const char *addr, unsigned short port, int type) +net_connect(const char *addr, unsigned short port, int type, const char *log_service_name) { struct addrinfo hints = { 0 }; struct addrinfo *servinfo; @@ -190,7 +190,7 @@ net_connect(const char *addr, unsigned short port, int type) int fd; int ret; - DPRINTF(E_DBG, L_MISC, "Connecting to %s, port %u\n", addr, port); + DPRINTF(E_DBG, L_MISC, "Connecting to '%s' at %s (port %u)\n", log_service_name, addr, port); hints.ai_socktype = type; hints.ai_family = (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) ? AF_UNSPEC : AF_INET; @@ -225,7 +225,7 @@ net_connect(const char *addr, unsigned short port, int type) if (!ptr) { - DPRINTF(E_LOG, L_MISC, "Failed to connect to %s, port %u: %s\n", addr, port, strerror(errno)); + DPRINTF(E_LOG, L_MISC, "Could not connect to '%s' at %s (port %u): %s\n", log_service_name, addr, port, strerror(errno)); return -1; } diff --git a/src/misc.h b/src/misc.h index 0596f599..749defb0 100644 --- a/src/misc.h +++ b/src/misc.h @@ -34,7 +34,7 @@ int net_port_get(short unsigned *port, union net_sockaddr *naddr); int -net_connect(const char *addr, unsigned short port, int type); +net_connect(const char *addr, unsigned short port, int type, const char *log_service_name); int net_bind(short unsigned *port, int type, const char *log_service_name); From f3febb63d9d9a93bf992f087a0822e4164a2707d Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 26 Feb 2021 23:13:54 +0100 Subject: [PATCH 04/11] [raop] Convert services to dual stack, plus use net_bind() and net_connect() --- src/outputs/raop.c | 662 ++++++++++----------------------------------- 1 file changed, 148 insertions(+), 514 deletions(-) diff --git a/src/outputs/raop.c b/src/outputs/raop.c index 6e67abf5..86494c1c 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -97,14 +97,6 @@ // This is an arbitrary value which just needs to be kept in sync with the config #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 { RAOP_DEV_APEX1_80211G, RAOP_DEV_APEX2_80211N, @@ -212,6 +204,8 @@ struct raop_session char *address; int family; + union net_sockaddr naddr; + int volume; /* AirTunes v2 */ @@ -225,8 +219,6 @@ struct raop_session int server_fd; - union sockaddr_all sa; - struct raop_service *timing_svc; struct raop_service *control_svc; @@ -319,12 +311,10 @@ static char *raop_aes_key_b64; static char *raop_aes_iv_b64; /* AirTunes v2 time synchronization */ -static struct raop_service timing_4svc; -static struct raop_service timing_6svc; +static struct raop_service raop_timing_svc; /* AirTunes v2 playback synchronization / control */ -static struct raop_service control_4svc; -static struct raop_service control_6svc; +static struct raop_service raop_control_svc; /* 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 -raop_v2_timing_get_clock_ntp(struct ntp_stamp *ns) +timing_get_clock_ntp(struct ntp_stamp *ns) { struct timespec ts; int ret; @@ -1969,38 +1959,32 @@ session_connection_setup(struct raop_session *rs, struct output_device *rd, int unsigned short port; int ret; - rs->sa.ss.ss_family = family; + rs->naddr.ss.ss_family = family; + switch (family) { case AF_INET: - /* We always have the v4 services, so no need to check */ if (!rd->v4_address) return -1; address = rd->v4_address; 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; case AF_INET6: - if (!rd->v6_address || rd->v6_disabled || (timing_6svc.fd < 0) || (control_6svc.fd < 0)) + if (!rd->v6_address) return -1; address = rd->v6_address; port = rd->v6_port; - rs->timing_svc = &timing_6svc; - rs->control_svc = &control_6svc; - intf = strchr(address, '%'); if (intf) *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) { @@ -2008,8 +1992,8 @@ session_connection_setup(struct raop_session *rs, struct output_device *rd, int intf++; - rs->sa.sin6.sin6_scope_id = if_nametoindex(intf); - if (rs->sa.sin6.sin6_scope_id == 0) + rs->naddr.sin6.sin6_scope_id = if_nametoindex(intf); + if (rs->naddr.sin6.sin6_scope_id == 0) { 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; } +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 * 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->timing_svc = &raop_timing_svc; + rs->control_svc = &raop_control_svc; + ret = session_connection_setup(rs, rd, AF_INET6); if (ret < 0) { @@ -2755,27 +2771,27 @@ packet_send(struct raop_session *rs, struct rtp_packet *pkt) static void control_packet_send(struct raop_session *rs, struct rtp_packet *pkt) { - int len; + socklen_t addrlen; int ret; - switch (rs->sa.ss.ss_family) + switch (rs->family) { case AF_INET: - rs->sa.sin.sin_port = htons(rs->control_port); - len = sizeof(rs->sa.sin); + rs->naddr.sin.sin_port = htons(rs->control_port); + addrlen = sizeof(rs->naddr.sin); break; case AF_INET6: - rs->sa.sin6.sin6_port = htons(rs->control_port); - len = sizeof(rs->sa.sin6); + rs->naddr.sin6.sin6_port = htons(rs->control_port); + addrlen = sizeof(rs->naddr.sin6); break; 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; } - 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) 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 -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 res[32]; struct ntp_stamp recv_stamp; struct ntp_stamp xmit_stamp; - struct raop_service *svc; - int len; int ret; svc = (struct raop_service *)arg; - ret = raop_v2_timing_get_clock_ntp(&recv_stamp); + ret = timing_get_clock_ntp(&recv_stamp); if (ret < 0) { DPRINTF(E_LOG, L_RAOP, "Couldn't get receive timestamp\n"); - - goto readd; + return; } - len = sizeof(sa.ss); - ret = recvfrom(svc->fd, req, sizeof(req), 0, &sa.sa, (socklen_t *)&len); + peer_addrlen = sizeof(peer_addr); + ret = recvfrom(svc->fd, req, sizeof(req), 0, &peer_addr.sa, &peer_addrlen); if (ret < 0) { DPRINTF(E_LOG, L_RAOP, "Error reading timing request: %s\n", strerror(errno)); - - goto readd; + return; } if (ret != 32) { - DPRINTF(E_DBG, L_RAOP, "Got timing request with size %d\n", ret); - - goto readd; + DPRINTF(E_WARN, L_RAOP, "Got timing request with size %d\n", ret); + return; } 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]); - - goto readd; + DPRINTF(E_WARN, L_RAOP, "Packet header doesn't match timing request (got 0x%02x%02x, expected 0x80d2)\n", req[0], req[1]); + return; } 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); /* Transmit timestamp */ - ret = raop_v2_timing_get_clock_ntp(&xmit_stamp); + ret = timing_get_clock_ntp(&xmit_stamp); if (ret < 0) { 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); } - 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) { 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; } } -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 -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; + 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_len; - int len; int ret; svc = (struct raop_service *)arg; - len = sizeof(sa.ss); - 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) { DPRINTF(E_LOG, L_RAOP, "Error reading control request: %s\n", strerror(errno)); - - goto readd; + return; } if (ret != 8) { - DPRINTF(E_DBG, L_RAOP, "Got control request with size %d\n", ret); - - 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; + DPRINTF(E_WARN, L_RAOP, "Got control request with size %d\n", ret); + return; } 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); @@ -3265,161 +3117,6 @@ raop_v2_control_cb(int fd, short what, void *arg) seq_len = be16toh(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); } -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 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) goto cleanup; - ret = raop_v2_stream_open(rs); - if (ret < 0) + rs->server_fd = net_connect(rs->address, rs->server_port, SOCK_DGRAM, "RAOP data"); + if (rs->server_fd < 0) goto cleanup; + rs->state = RAOP_STATE_CONNECTED; + /* Session startup and setup is done, tell our user */ raop_status(rs); @@ -4814,21 +4459,10 @@ raop_init(void) char ebuf[64]; char *ptr; gpg_error_t gc_err; - int v6enabled; + int timing_port; + int control_port; 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 gcry_randomize(raop_aes_key, sizeof(raop_aes_key), GCRY_STRONG_RANDOM); gcry_randomize(raop_aes_iv, sizeof(raop_aes_iv), GCRY_STRONG_RANDOM); @@ -4881,9 +4515,8 @@ raop_init(void) 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"); - - ret = raop_v2_timing_start(v6enabled); + timing_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "timing_port"); + ret = service_start(&raop_timing_svc, timing_svc_cb, timing_port, "RAOP timing"); if (ret < 0) { DPRINTF(E_LOG, L_RAOP, "AirPlay time synchronization failed to start\n"); @@ -4891,7 +4524,8 @@ raop_init(void) 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) { DPRINTF(E_LOG, L_RAOP, "AirPlay playback control failed to start\n"); @@ -4910,9 +4544,9 @@ raop_init(void) return 0; out_stop_control: - raop_v2_control_stop(); + service_stop(&raop_control_svc); out_stop_timing: - raop_v2_timing_stop(); + service_stop(&raop_timing_svc); out_free_timer: event_free(keep_alive_timer); free(raop_aes_iv_b64); @@ -4929,6 +4563,11 @@ raop_deinit(void) { 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) { raop_sessions = rs->next; @@ -4936,11 +4575,6 @@ raop_deinit(void) session_free(rs); } - raop_v2_control_stop(); - raop_v2_timing_stop(); - - event_free(keep_alive_timer); - gcry_cipher_close(raop_aes_ctx); free(raop_aes_key_b64); From 98dad07b7f295f39664601335e52dd0d70740262 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 27 Feb 2021 00:29:12 +0100 Subject: [PATCH 05/11] [airplay] Convert services to dual stack, plus use net_connect() and net_bind() --- src/outputs/airplay.c | 670 ++++++++++-------------------------------- 1 file changed, 154 insertions(+), 516 deletions(-) diff --git a/src/outputs/airplay.c b/src/outputs/airplay.c index d271c228..28820d3b 100644 --- a/src/outputs/airplay.c +++ b/src/outputs/airplay.c @@ -102,14 +102,6 @@ // This is an arbitrary value which just needs to be kept in sync with the config #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 */ enum airplay_devtype { AIRPLAY_DEV_APEX2_80211N, @@ -264,6 +256,8 @@ struct airplay_session char *address; int family; + union net_sockaddr naddr; + int volume; char *local_address; @@ -287,8 +281,6 @@ struct airplay_session int events_fd; struct event *eventsev; - union sockaddr_all sa; - struct airplay_service *timing_svc; struct airplay_service *control_svc; @@ -441,12 +433,10 @@ static struct media_quality airplay_quality_default = extern struct event_base *evbase_player; /* AirTunes v2 time synchronization */ -static struct airplay_service timing_4svc; -static struct airplay_service timing_6svc; +static struct airplay_service airplay_timing_svc; /* AirTunes v2 playback synchronization / control */ -static struct airplay_service control_4svc; -static struct airplay_service control_6svc; +static struct airplay_service airplay_control_svc; /* 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 -airplay_timing_get_clock_ntp(struct ntp_stamp *ns) +timing_get_clock_ntp(struct ntp_stamp *ns) { struct timespec ts; int ret; @@ -1348,38 +1338,33 @@ session_connection_setup(struct airplay_session *rs, struct output_device *rd, i unsigned short port; int ret; - rs->sa.ss.ss_family = family; + rs->naddr.ss.ss_family = family; + switch (family) { case AF_INET: - /* We always have the v4 services, so no need to check */ if (!rd->v4_address) return -1; address = rd->v4_address; 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; case AF_INET6: - if (!rd->v6_address || rd->v6_disabled || (timing_6svc.fd < 0) || (control_6svc.fd < 0)) + if (!rd->v6_address) return -1; address = rd->v6_address; port = rd->v6_port; - rs->timing_svc = &timing_6svc; - rs->control_svc = &control_6svc; - intf = strchr(address, '%'); if (intf) *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) { @@ -1387,8 +1372,8 @@ session_connection_setup(struct airplay_session *rs, struct output_device *rd, i intf++; - rs->sa.sin6.sin6_scope_id = if_nametoindex(intf); - if (rs->sa.sin6.sin6_scope_id == 0) + rs->naddr.sin6.sin6_scope_id = if_nametoindex(intf); + if (rs->naddr.sin6.sin6_scope_id == 0) { DPRINTF(E_LOG, L_AIRPLAY, "Could not find interface %s\n", intf); @@ -1528,6 +1513,35 @@ session_ids_set(struct airplay_session *rs) 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 * 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->timing_svc = &airplay_timing_svc; + rs->control_svc = &airplay_control_svc; + ret = session_connection_setup(rs, rd, AF_INET6); if (ret < 0) { @@ -1918,27 +1935,27 @@ packet_send(struct airplay_session *rs, struct rtp_packet *pkt) static void control_packet_send(struct airplay_session *rs, struct rtp_packet *pkt) { - int len; + socklen_t addrlen; int ret; - switch (rs->sa.ss.ss_family) + switch (rs->family) { case AF_INET: - rs->sa.sin.sin_port = htons(rs->control_port); - len = sizeof(rs->sa.sin); + rs->naddr.sin.sin_port = htons(rs->control_port); + addrlen = sizeof(rs->naddr.sin); break; case AF_INET6: - rs->sa.sin6.sin6_port = htons(rs->control_port); - len = sizeof(rs->sa.sin6); + rs->naddr.sin6.sin6_port = htons(rs->control_port); + addrlen = sizeof(rs->naddr.sin6); break; 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; } - 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) 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 -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 res[32]; struct ntp_stamp recv_stamp; struct ntp_stamp xmit_stamp; - struct airplay_service *svc; - int len; int ret; - svc = (struct airplay_service *)arg; - - ret = airplay_timing_get_clock_ntp(&recv_stamp); + ret = timing_get_clock_ntp(&recv_stamp); if (ret < 0) { DPRINTF(E_LOG, L_AIRPLAY, "Couldn't get receive timestamp\n"); - - goto readd; + return; } - len = sizeof(sa.ss); - ret = recvfrom(svc->fd, req, sizeof(req), 0, &sa.sa, (socklen_t *)&len); + peer_addrlen = sizeof(peer_addr); + ret = recvfrom(svc->fd, req, sizeof(req), 0, &peer_addr.sa, &peer_addrlen); if (ret < 0) { - DPRINTF(E_LOG, L_AIRPLAY, "Error reading timing request: %s\n", strerror(errno)); - - goto readd; + net_address_get(address, sizeof(address), &peer_addr); + DPRINTF(E_LOG, L_AIRPLAY, "Error reading timing request from %s: %s\n", address, strerror(errno)); + return; } if (ret != 32) { - DPRINTF(E_DBG, L_AIRPLAY, "Got timing request with size %d\n", ret); - - goto readd; + net_address_get(address, sizeof(address), &peer_addr); + DPRINTF(E_WARN, L_AIRPLAY, "Got timing request from %s with size %d\n", address, ret); + return; } 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]); - - goto readd; + 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]); + return; } 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); /* Transmit timestamp */ - ret = airplay_timing_get_clock_ntp(&xmit_stamp); + ret = timing_get_clock_ntp(&xmit_stamp); if (ret < 0) { 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); } - 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) { - DPRINTF(E_LOG, L_AIRPLAY, "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_AIRPLAY, "Couldn't re-add event for timing requests\n"); - + 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)); 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 -airplay_timing_stop(void) -{ - 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) +control_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]; - union sockaddr_all sa; - uint8_t req[8]; struct airplay_session *rs; - struct airplay_service *svc; + uint8_t req[8]; uint16_t seq_start; uint16_t seq_len; - int len; int ret; - svc = (struct airplay_service *)arg; - - len = sizeof(sa.ss); - 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) { DPRINTF(E_LOG, L_AIRPLAY, "Error reading control request: %s\n", strerror(errno)); - - goto readd; + return; } if (ret != 8) { - DPRINTF(E_DBG, L_AIRPLAY, "Got control request with size %d\n", ret); - - goto readd; - } - - 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; + net_address_get(address, sizeof(address), &peer_addr); + DPRINTF(E_WARN, L_AIRPLAY, "Got control request from %s with size %d\n", address, ret); + return; } 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); @@ -2430,161 +2286,6 @@ airplay_control_cb(int fd, short what, void *arg) seq_len = be16toh(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 --------------------------- */ -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 start_failure(struct airplay_session *rs) { @@ -3275,7 +2927,7 @@ response_handler_setup_stream(struct evrtsp_request *req, struct airplay_session 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); - 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) { DPRINTF(E_WARN, L_AIRPLAY, "Could not connect to data port\n"); @@ -3283,7 +2935,7 @@ response_handler_setup_stream(struct evrtsp_request *req, struct airplay_session } // 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) { DPRINTF(E_WARN, L_AIRPLAY, "Could not connect to '%s' events port %u, proceeding anyway\n", rs->devname, rs->events_port); @@ -4362,21 +4014,10 @@ airplay_write(struct output_buffer *obuf) static int airplay_init(void) { - int v6enabled; int ret; int i; - - 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; + int timing_port; + int control_port; airplay_device_id = libhash; @@ -4393,21 +4034,19 @@ airplay_init(void) 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"); - - ret = airplay_timing_start(v6enabled); + timing_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "timing_port"); + ret = service_start(&airplay_timing_svc, timing_svc_cb, timing_port, "AirPlay timing"); if (ret < 0) { DPRINTF(E_LOG, L_AIRPLAY, "AirPlay time synchronization failed to start\n"); - 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) { DPRINTF(E_LOG, L_AIRPLAY, "AirPlay playback control failed to start\n"); - goto out_stop_timing; } @@ -4415,16 +4054,15 @@ airplay_init(void) if (ret < 0) { DPRINTF(E_LOG, L_AIRPLAY, "Could not add mDNS browser for AirPlay devices\n"); - goto out_stop_control; } return 0; out_stop_control: - airplay_control_stop(); + service_stop(&airplay_control_svc); out_stop_timing: - airplay_timing_stop(); + service_stop(&airplay_timing_svc); out_free_timer: event_free(keep_alive_timer); @@ -4436,17 +4074,17 @@ airplay_deinit(void) { 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) { airplay_sessions = rs->next; session_free(rs); } - - airplay_control_stop(); - airplay_timing_stop(); - - event_free(keep_alive_timer); } struct output_definition output_airplay = From e5fcfc65742a89d68be3160e85504b1641ea2341 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 27 Feb 2021 00:37:35 +0100 Subject: [PATCH 06/11] [cast] Use net_connect() instead of own cast_connect() --- src/outputs/cast.c | 75 ++-------------------------------------------- 1 file changed, 2 insertions(+), 73 deletions(-) diff --git a/src/outputs/cast.c b/src/outputs/cast.c index cc2ab1c4..746644a5 100644 --- a/src/outputs/cast.c +++ b/src/outputs/cast.c @@ -147,14 +147,6 @@ //#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_msg_payload; @@ -481,69 +473,6 @@ static struct media_quality cast_quality_default = { CAST_QUALITY_SAMPLE_RATE_DE /* ------------------------------- 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 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_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) goto error; @@ -2013,7 +1942,7 @@ cast_session_make(struct output_device *device, int family, int callback_id) 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) { DPRINTF(E_LOG, L_CAST, "Could not connect to %s\n", device->name); From c423f3af910bec85e841916c5adfabd0928f1e35 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 27 Feb 2021 23:01:26 +0100 Subject: [PATCH 07/11] [cfg] Add websocket_interface option --- forked-daapd.conf.in | 4 ++++ src/conffile.c | 1 + src/websocket.c | 3 +++ 3 files changed, 8 insertions(+) diff --git a/forked-daapd.conf.in b/forked-daapd.conf.in index d3221528..b0208d64 100644 --- a/forked-daapd.conf.in +++ b/forked-daapd.conf.in @@ -34,6 +34,10 @@ general { # Websocket port for the web interface. # 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 # client types like Remotes, DAAP clients (iTunes) and to the web # interface. Options are "any", "localhost" or the prefix to one or diff --git a/src/conffile.c b/src/conffile.c index abb436bf..41a13fcd 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -51,6 +51,7 @@ static cfg_opt_t sec_general[] = CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel), CFG_STR("admin_password", NULL, 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_BOOL("ipv6", cfg_true, CFGF_NONE), CFG_STR("bind_address", NULL, CFGF_NONE), diff --git a/src/websocket.c b/src/websocket.c index 29c17de8..0d531d9d 100644 --- a/src/websocket.c +++ b/src/websocket.c @@ -39,6 +39,7 @@ static struct lws_context *context; static pthread_t tid_websocket; +static const char *websocket_interface; static int websocket_port; static bool websocket_exit = false; @@ -377,6 +378,7 @@ websocket_init(void) struct lws_context_creation_info info; int ret; + websocket_interface = cfg_getstr(cfg_getsec(cfg, "general"), "websocket_interface"); websocket_port = cfg_getint(cfg_getsec(cfg, "general"), "websocket_port"); if (websocket_port <= 0) @@ -387,6 +389,7 @@ websocket_init(void) memset(&info, 0, sizeof(info)); info.port = websocket_port; + info.iface = websocket_interface; info.protocols = protocols; if (!cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) info.options |= LWS_SERVER_OPTION_DISABLE_IPV6; From 11151affbdd83834dbfe666dc1bbf39746c54d75 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 28 Feb 2021 00:06:01 +0100 Subject: [PATCH 08/11] [misc] Add net_evhttp_bind() for centralizing libevent based binding --- src/misc.c | 50 ++++++++++++++++++++++++++++++++++++++++++++------ src/misc.h | 6 ++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/misc.c b/src/misc.c index 4a7bf596..a615adda 100644 --- a/src/misc.c +++ b/src/misc.c @@ -192,27 +192,27 @@ net_connect(const char *addr, unsigned short port, int type, const char *log_ser DPRINTF(E_DBG, L_MISC, "Connecting to '%s' at %s (port %u)\n", log_service_name, addr, port); - hints.ai_socktype = type; + hints.ai_socktype = (type & (SOCK_STREAM | SOCK_DGRAM)); // filter since type can be SOCK_STREAM | SOCK_NONBLOCK hints.ai_family = (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) ? AF_UNSPEC : AF_INET; snprintf(strport, sizeof(strport), "%hu", port); ret = getaddrinfo(addr, strport, &hints, &servinfo); if (ret < 0) { - gai_strerror(ret); + DPRINTF(E_LOG, L_MISC, "Could not connect to '%s' at %s (port %u): %s\n", log_service_name, addr, port, gai_strerror(ret)); return -1; } for (ptr = servinfo; ptr; ptr = ptr->ai_next) { - fd = socket(ptr->ai_family, ptr->ai_socktype | SOCK_CLOEXEC, ptr->ai_protocol); + fd = socket(ptr->ai_family, type | SOCK_CLOEXEC, ptr->ai_protocol); if (fd < 0) { continue; } ret = connect(fd, ptr->ai_addr, ptr->ai_addrlen); - if (ret < 0) + if (ret < 0 && errno != EINPROGRESS) // EINPROGRESS in case of SOCK_NONBLOCK { close(fd); continue; @@ -252,7 +252,7 @@ net_bind(short unsigned *port, int type, const char *log_service_name) cfgaddr = cfg_getstr(cfg_getsec(cfg, "general"), "bind_address"); - hints.ai_socktype = type; + hints.ai_socktype = (type & (SOCK_STREAM | SOCK_DGRAM)); // filter since type can be SOCK_STREAM | SOCK_NONBLOCK hints.ai_family = (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) ? AF_INET6 : AF_INET; hints.ai_flags = cfgaddr ? 0 : AI_PASSIVE; @@ -269,7 +269,7 @@ net_bind(short unsigned *port, int type, const char *log_service_name) if (fd >= 0) close(fd); - fd = socket(ptr->ai_family, ptr->ai_socktype | SOCK_CLOEXEC, ptr->ai_protocol); + fd = socket(ptr->ai_family, type | SOCK_CLOEXEC, ptr->ai_protocol); if (fd < 0) continue; @@ -325,6 +325,44 @@ net_bind(short unsigned *port, int type, const char *log_service_name) return -1; } +int +net_evhttp_bind(struct evhttp *evhttp, short unsigned port, const char *log_service_name) +{ + const char *bind_address; + + bind_address = cfg_getstr(cfg_getsec(cfg, "general"), "bind_address"); + if (!bind_address) + bind_address = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6") ? "::" : "0.0.0.0"; + + return evhttp_bind_socket(evhttp, bind_address, port); +} +/* TODO check if the below is required for FreeBSD + if (v6enabled) + { + 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); + 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; + } + +#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 + } +*/ + /* ----------------------- Conversion/hashing/sanitizers -------------------- */ diff --git a/src/misc.h b/src/misc.h index 749defb0..53ba4280 100644 --- a/src/misc.h +++ b/src/misc.h @@ -14,6 +14,7 @@ #include #include +#include union net_sockaddr { @@ -33,12 +34,17 @@ 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 -------------------- */ From d4711decdcd54ccd47d35bea9593a2d80833e4a9 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 28 Feb 2021 00:07:17 +0100 Subject: [PATCH 09/11] [httpd] Bind via misc.c's net_xxx methods --- src/httpd.c | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/httpd.c b/src/httpd.c index 0eae8027..06b14de8 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -1631,7 +1631,6 @@ int httpd_init(const char *webroot) { struct stat sb; - int v6enabled; int ret; httpd_exit = 0; @@ -1761,9 +1760,6 @@ httpd_init(const char *webroot) goto evhttpd_fail; } - v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"); - httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); - // For CORS headers allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin"); if (allow_origin) @@ -1774,29 +1770,13 @@ httpd_init(const char *webroot) allow_origin = NULL; } - if (v6enabled) - { - 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; - } - } + httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); - ret = evhttp_bind_socket(evhttpd, "0.0.0.0", httpd_port); + ret = net_evhttp_bind(evhttpd, httpd_port, "httpd"); 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; - } - -#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 + DPRINTF(E_FATAL, L_HTTPD, "Could not bind to port %d (forked-daapd already running?)\n", httpd_port); + goto bind_fail; } evhttp_set_gencb(evhttpd, httpd_gen_cb, NULL); From d2a5f939f52c46a08e464450344a943a800e9772 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 28 Feb 2021 00:08:25 +0100 Subject: [PATCH 10/11] [mpd] Bind via misc.c's net_xxx methods --- src/mpd.c | 190 ++++++++++++++++++------------------------------------ 1 file changed, 63 insertions(+), 127 deletions(-) diff --git a/src/mpd.c b/src/mpd.c index 859b3b07..6b2ffc5f 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -77,8 +77,9 @@ static struct commands_base *cmdbase; static struct evhttp *evhttpd; -struct evconnlistener *mpd_listener6; -struct evconnlistener *mpd_listener; +static struct evconnlistener *mpd_listener; +static int mpd_sockfd; + // Virtual path to the default playlist directory static char *default_pl_dir; @@ -4739,18 +4740,47 @@ artwork_cb(struct evhttp_request *req, void *arg) } /* 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; - int v6enabled; - const char *pl_dir; 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"); if (port <= 0) @@ -4759,112 +4789,29 @@ int mpd_init(void) 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(); - if (!evbase_mpd) + mpd_sockfd = net_bind(&port, SOCK_STREAM | SOCK_NONBLOCK, "mpd"); + if (mpd_sockfd < 0) { - DPRINTF(E_LOG, L_MPD, "Could not create an event base\n"); - goto evbase_fail; + DPRINTF(E_LOG, L_MPD, "Could not bind mpd server to port %hu\n", port); + goto bind_fail; } - cmdbase = commands_base_new(evbase_mpd, NULL); - - 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); - + mpd_listener = evconnlistener_new(evbase_mpd, mpd_accept_conn_cb, NULL, 0, -1, mpd_sockfd); 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; - } - -#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 + DPRINTF(E_LOG, L_MPD, "Could not create connection listener for mpd clients on port %d\n", port); + goto connew_fail; } - 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"); - if (http_port > 0) + ret = mpd_httpd_init(); + if (ret < 0) { - evhttpd = evhttp_new(evbase_mpd); - if (!evhttpd) - { - 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 - } + DPRINTF(E_LOG, L_MPD, "Could not initialize HTTP artwork server\n"); + goto httpd_fail; } allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "library"), "allow_modifying_stored_playlists"); @@ -4908,23 +4855,17 @@ int mpd_init(void) return 0; - thread_fail: - bind_fail: - if (http_port > 0) - evhttp_free(evhttpd); - 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); + mpd_httpd_deinit(); + httpd_fail: + evconnlistener_free(mpd_listener); connew_fail: + close(mpd_sockfd); + bind_fail: commands_base_free(cmdbase); event_base_free(evbase_mpd); evbase_mpd = NULL; - evbase_fail: return -1; } @@ -4932,7 +4873,6 @@ int mpd_init(void) void mpd_deinit(void) { unsigned short port; - unsigned short http_port; int ret; port = cfg_getint(cfg_getsec(cfg, "mpd"), "port"); @@ -4958,15 +4898,11 @@ void mpd_deinit(void) free_mpd_client_ctx(mpd_clients); } - http_port = cfg_getint(cfg_getsec(cfg, "mpd"), "http_port"); - if (http_port > 0) - evhttp_free(evhttpd); + mpd_httpd_deinit(); - // 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); + evconnlistener_free(mpd_listener); + + close(mpd_sockfd); // Free event base (should free events too) event_base_free(evbase_mpd); From ed6176062ad524b5736229915246b01b06488b15 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 7 Mar 2021 23:19:51 +0100 Subject: [PATCH 11/11] [airplay] Logging adjustments --- src/outputs/airplay.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/outputs/airplay.c b/src/outputs/airplay.c index 28820d3b..e1678379 100644 --- a/src/outputs/airplay.c +++ b/src/outputs/airplay.c @@ -1446,7 +1446,7 @@ session_cipher_setup(struct airplay_session *rs, const uint8_t *key, size_t key_ 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->control_cipher_ctx = control_cipher_ctx; @@ -2884,6 +2884,8 @@ response_handler_setup_stream(struct evrtsp_request *req, struct airplay_session uint64_t uintval; 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); if (ret < 0) { @@ -2925,7 +2927,7 @@ response_handler_setup_stream(struct evrtsp_request *req, struct airplay_session 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 = net_connect(rs->address, rs->data_port, SOCK_DGRAM, "AirPlay data"); if (rs->server_fd < 0) @@ -3065,7 +3067,7 @@ response_handler_info_generic(struct evrtsp_request *req, struct airplay_session 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), (bool)(rs->statusflags & AIRPLAY_FLAG_PASSWORD_REQUIRED), (bool)(rs->statusflags & AIRPLAY_FLAG_PIN_REQUIRED));