mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-29 00:23:23 -05:00
[mdns] Fix mdns problems with ATV4 and ipv6
Avahi gives us several ipv6 addresses for an ATV4 that aren't actually connectable. Here we simply try to make a connection to the address, and if it is not possible within a timeout we ignore the announcement. We also now don't start a mdns record browser if the address from the resolver passes the connection test and isn't link-local.
This commit is contained in:
parent
0d4dd06b51
commit
970769cab6
182
src/mdns_avahi.c
182
src/mdns_avahi.c
@ -29,6 +29,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
@ -55,6 +56,9 @@
|
|||||||
|
|
||||||
#define MDNSERR avahi_strerror(avahi_client_errno(mdns_client))
|
#define MDNSERR avahi_strerror(avahi_client_errno(mdns_client))
|
||||||
|
|
||||||
|
// Seconds to wait before timing out when making device connection test
|
||||||
|
#define MDNS_CONNECT_TEST_TIMEOUT 2
|
||||||
|
|
||||||
/* Main event base, from main.c */
|
/* Main event base, from main.c */
|
||||||
extern struct event_base *evbase_main;
|
extern struct event_base *evbase_main;
|
||||||
|
|
||||||
@ -353,7 +357,7 @@ struct mdns_record_browser {
|
|||||||
|
|
||||||
char *name;
|
char *name;
|
||||||
char *domain;
|
char *domain;
|
||||||
struct keyval txt_kv;
|
struct keyval *txt_kv;
|
||||||
|
|
||||||
int port;
|
int port;
|
||||||
};
|
};
|
||||||
@ -470,6 +474,114 @@ resolvers_cleanup(const char *name, AvahiProtocol proto)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
connection_test(int family, const char *address, const char *address_log, int port)
|
||||||
|
{
|
||||||
|
struct addrinfo hints;
|
||||||
|
struct addrinfo *ai;
|
||||||
|
char strport[32];
|
||||||
|
int sock;
|
||||||
|
fd_set fdset;
|
||||||
|
struct timeval timeout = { MDNS_CONNECT_TEST_TIMEOUT, 0 };
|
||||||
|
socklen_t len;
|
||||||
|
int retval;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
retval = -1;
|
||||||
|
|
||||||
|
memset(&hints, 0, sizeof(struct addrinfo));
|
||||||
|
hints.ai_family = family;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
|
||||||
|
snprintf(strport, sizeof(strport), "%d", port);
|
||||||
|
|
||||||
|
ret = getaddrinfo(address, strport, &hints, &ai);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_MDNS, "Connection test to %s:%d failed with getaddrinfo error: %s\n", address_log, port, gai_strerror(ret));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sock = socket(ai->ai_family, ai->ai_socktype | SOCK_NONBLOCK, ai->ai_protocol);
|
||||||
|
if (sock < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_MDNS, "Connection test to %s:%d failed with socket error: %s\n", address_log, port, strerror(errno));
|
||||||
|
goto out_free_ai;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = connect(sock, ai->ai_addr, ai->ai_addrlen);
|
||||||
|
if (ret < 0 && errno != EALREADY && errno != EINPROGRESS)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_MDNS, "Connection test to %s:%d failed with connect error: %s\n", address_log, port, strerror(errno));
|
||||||
|
goto out_close_socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
FD_ZERO(&fdset);
|
||||||
|
FD_SET(sock, &fdset);
|
||||||
|
|
||||||
|
ret = select(sock + 1, NULL, &fdset, NULL, &timeout);
|
||||||
|
if (ret != 1)
|
||||||
|
{
|
||||||
|
if (errno == EINPROGRESS)
|
||||||
|
DPRINTF(E_DBG, L_MDNS, "Connection test to %s:%d timed out (limit is %d seconds)\n", address_log, port, MDNS_CONNECT_TEST_TIMEOUT);
|
||||||
|
else
|
||||||
|
DPRINTF(E_DBG, L_MDNS, "Connection test to %s:%d failed with select error: %s\n", address_log, port, strerror(errno));
|
||||||
|
goto out_close_socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = sizeof(retval);
|
||||||
|
ret = getsockopt(sock, SOL_SOCKET, SO_ERROR, &retval, &len);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_MDNS, "Connection test to %s:%d failed with getsockopt error: %s\n", address_log, port, strerror(errno));
|
||||||
|
goto out_close_socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_MDNS, "Connection test to %s:%d completed successfully (return value %d)\n", address_log, port, retval);
|
||||||
|
|
||||||
|
out_close_socket:
|
||||||
|
close(sock);
|
||||||
|
out_free_ai:
|
||||||
|
freeaddrinfo(ai);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avahi will sometimes give us link-local addresses in 169.254.0.0/16 or
|
||||||
|
// fe80::/10, which (most of the time) are useless. We also check if we can make
|
||||||
|
// a connection to the address
|
||||||
|
// - see also https://lists.freedesktop.org/archives/avahi/2012-September/002183.html
|
||||||
|
static int
|
||||||
|
address_check(AvahiProtocol proto, const char *hostname, const AvahiAddress *addr, int port)
|
||||||
|
{
|
||||||
|
char address[AVAHI_ADDRESS_STR_MAX];
|
||||||
|
char address_log[AVAHI_ADDRESS_STR_MAX];
|
||||||
|
int family;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
avahi_address_snprint(address, sizeof(address), addr);
|
||||||
|
family = avahi_proto_to_af(proto);
|
||||||
|
if (family == AF_INET)
|
||||||
|
snprintf(address_log, sizeof(address_log), "%s", address);
|
||||||
|
else
|
||||||
|
snprintf(address_log, sizeof(address_log), "[%s]", address);
|
||||||
|
|
||||||
|
if ((proto == AVAHI_PROTO_INET && is_v4ll(&(addr->data.ipv4))) || (proto == AVAHI_PROTO_INET6 && is_v6ll(&(addr->data.ipv6))))
|
||||||
|
{
|
||||||
|
DPRINTF(E_WARN, L_MDNS, "Ignoring announcement from %s, address %s is link-local\n", hostname, address_log);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = connection_test(family, address, address_log, port);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_WARN, L_MDNS, "Ignoring announcement from %s, address %s is not connectable\n", hostname, address_log);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
browse_record_callback(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
|
browse_record_callback(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
|
||||||
AvahiBrowserEvent event, const char *hostname, uint16_t clazz, uint16_t type,
|
AvahiBrowserEvent event, const char *hostname, uint16_t clazz, uint16_t type,
|
||||||
@ -502,23 +614,19 @@ browse_record_callback(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol p
|
|||||||
family = avahi_proto_to_af(proto);
|
family = avahi_proto_to_af(proto);
|
||||||
avahi_address_snprint(address, sizeof(address), &addr);
|
avahi_address_snprint(address, sizeof(address), &addr);
|
||||||
|
|
||||||
// Avahi will sometimes give us link-local addresses in 169.254.0.0/16 or
|
|
||||||
// fe80::/10, which (most of the time) are useless
|
|
||||||
// - see also https://lists.freedesktop.org/archives/avahi/2012-September/002183.html
|
|
||||||
if ((proto == AVAHI_PROTO_INET && is_v4ll(&addr.data.ipv4)) || (proto == AVAHI_PROTO_INET6 && is_v6ll(&addr.data.ipv6)))
|
|
||||||
{
|
|
||||||
DPRINTF(E_WARN, L_MDNS, "Ignoring announcement from %s, address %s is link-local\n", hostname, address);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s, proto %d): NEW record %s for service type '%s'\n", hostname, proto, address, rb_data->mb->type);
|
DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s, proto %d): NEW record %s for service type '%s'\n", hostname, proto, address, rb_data->mb->type);
|
||||||
|
|
||||||
// Execute callback (mb->cb) with all the data
|
ret = address_check(proto, hostname, &addr, rb_data->port);
|
||||||
rb_data->mb->cb(rb_data->name, rb_data->mb->type, rb_data->domain, hostname, family, address, rb_data->port, &rb_data->txt_kv);
|
if (ret < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
// Stop record browser
|
// Execute callback (mb->cb) with all the data
|
||||||
|
rb_data->mb->cb(rb_data->name, rb_data->mb->type, rb_data->domain, hostname, family, address, rb_data->port, rb_data->txt_kv);
|
||||||
|
|
||||||
|
// Stop record browser, we found an address (or there was an error)
|
||||||
out_free_record_browser:
|
out_free_record_browser:
|
||||||
keyval_clear(&rb_data->txt_kv);
|
keyval_clear(rb_data->txt_kv);
|
||||||
|
free(rb_data->txt_kv);
|
||||||
free(rb_data->name);
|
free(rb_data->name);
|
||||||
free(rb_data->domain);
|
free(rb_data->domain);
|
||||||
free(rb_data);
|
free(rb_data);
|
||||||
@ -534,6 +642,8 @@ browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtoco
|
|||||||
AvahiRecordBrowser *rb;
|
AvahiRecordBrowser *rb;
|
||||||
struct mdns_browser *mb;
|
struct mdns_browser *mb;
|
||||||
struct mdns_record_browser *rb_data;
|
struct mdns_record_browser *rb_data;
|
||||||
|
struct keyval *txt_kv;
|
||||||
|
char address[AVAHI_ADDRESS_STR_MAX];
|
||||||
char *key;
|
char *key;
|
||||||
char *value;
|
char *value;
|
||||||
uint16_t dns_type;
|
uint16_t dns_type;
|
||||||
@ -542,6 +652,8 @@ browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtoco
|
|||||||
|
|
||||||
mb = (struct mdns_browser *)userdata;
|
mb = (struct mdns_browser *)userdata;
|
||||||
|
|
||||||
|
family = avahi_proto_to_af(proto);
|
||||||
|
|
||||||
if (event != AVAHI_RESOLVER_FOUND)
|
if (event != AVAHI_RESOLVER_FOUND)
|
||||||
{
|
{
|
||||||
if (event == AVAHI_RESOLVER_FAILURE)
|
if (event == AVAHI_RESOLVER_FAILURE)
|
||||||
@ -549,7 +661,6 @@ browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtoco
|
|||||||
else
|
else
|
||||||
DPRINTF(E_LOG, L_MDNS, "Avahi Resolver empty callback\n");
|
DPRINTF(E_LOG, L_MDNS, "Avahi Resolver empty callback\n");
|
||||||
|
|
||||||
family = avahi_proto_to_af(proto);
|
|
||||||
if (family != AF_UNSPEC)
|
if (family != AF_UNSPEC)
|
||||||
mb->cb(name, type, domain, NULL, family, NULL, -1, NULL);
|
mb->cb(name, type, domain, NULL, family, NULL, -1, NULL);
|
||||||
|
|
||||||
@ -559,14 +670,11 @@ browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtoco
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_MDNS, "Avahi Resolver: resolved service '%s' type '%s' proto %d, host %s\n", name, type, proto, hostname);
|
avahi_address_snprint(address, sizeof(address), addr);
|
||||||
|
|
||||||
CHECK_NULL(L_MDNS, rb_data = calloc(1, sizeof(struct mdns_record_browser)));
|
DPRINTF(E_DBG, L_MDNS, "Avahi Resolver: resolved service '%s' type '%s' proto %d, host %s, address %s\n", name, type, proto, hostname, address);
|
||||||
|
|
||||||
rb_data->name = strdup(name);
|
CHECK_NULL(L_MDNS, txt_kv = keyval_alloc());
|
||||||
rb_data->domain = strdup(domain);
|
|
||||||
rb_data->mb = mb;
|
|
||||||
rb_data->port = port;
|
|
||||||
|
|
||||||
while (txt)
|
while (txt)
|
||||||
{
|
{
|
||||||
@ -578,25 +686,45 @@ browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtoco
|
|||||||
|
|
||||||
if (value)
|
if (value)
|
||||||
{
|
{
|
||||||
keyval_add(&rb_data->txt_kv, key, value);
|
keyval_add(txt_kv, key, value);
|
||||||
avahi_free(value);
|
avahi_free(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
avahi_free(key);
|
avahi_free(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to implement a record browser because the announcement from some
|
||||||
|
// devices (e.g. ApEx 1 gen) will include multiple records, and we need to
|
||||||
|
// filter out those records that won't work (notably link-local). The value of
|
||||||
|
// *addr given by browse_resolve_callback is just the first record.
|
||||||
|
ret = address_check(proto, hostname, addr, port);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
CHECK_NULL(L_MDNS, rb_data = calloc(1, sizeof(struct mdns_record_browser)));
|
||||||
|
|
||||||
|
rb_data->name = strdup(name);
|
||||||
|
rb_data->domain = strdup(domain);
|
||||||
|
rb_data->mb = mb;
|
||||||
|
rb_data->port = port;
|
||||||
|
rb_data->txt_kv = txt_kv;
|
||||||
|
|
||||||
if (proto == AVAHI_PROTO_INET6)
|
if (proto == AVAHI_PROTO_INET6)
|
||||||
dns_type = AVAHI_DNS_TYPE_AAAA;
|
dns_type = AVAHI_DNS_TYPE_AAAA;
|
||||||
else
|
else
|
||||||
dns_type = AVAHI_DNS_TYPE_A;
|
dns_type = AVAHI_DNS_TYPE_A;
|
||||||
|
|
||||||
// We need to implement a record browser because the announcement from some
|
|
||||||
// devices (e.g. ApEx 1 gen) will include multiple records, and we need to
|
|
||||||
// filter out those records that won't work (notably link-local). The value of
|
|
||||||
// *addr given by browse_resolve_callback is just the first record.
|
|
||||||
rb = avahi_record_browser_new(mdns_client, intf, proto, hostname, AVAHI_DNS_CLASS_IN, dns_type, 0, browse_record_callback, rb_data);
|
rb = avahi_record_browser_new(mdns_client, intf, proto, hostname, AVAHI_DNS_CLASS_IN, dns_type, 0, browse_record_callback, rb_data);
|
||||||
if (!rb)
|
if (!rb)
|
||||||
DPRINTF(E_LOG, L_MDNS, "Could not create record browser for host %s: %s\n", hostname, MDNSERR);
|
DPRINTF(E_LOG, L_MDNS, "Could not create record browser for host %s: %s\n", hostname, MDNSERR);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute callback (mb->cb) with all the data
|
||||||
|
mb->cb(name, mb->type, domain, hostname, family, address, port, txt_kv);
|
||||||
|
|
||||||
|
keyval_clear(txt_kv);
|
||||||
|
free(txt_kv);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -630,7 +758,7 @@ browse_callback(AvahiServiceBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
|
|||||||
|
|
||||||
CHECK_NULL(L_MDNS, r = calloc(1, sizeof(struct mdns_resolver)));
|
CHECK_NULL(L_MDNS, r = calloc(1, sizeof(struct mdns_resolver)));
|
||||||
|
|
||||||
r->resolver = avahi_service_resolver_new(mdns_client, intf, proto, name, type, domain, proto, 0, browse_resolve_callback, mb);
|
r->resolver = avahi_service_resolver_new(mdns_client, intf, proto, name, type, domain, AVAHI_PROTO_UNSPEC, 0, browse_resolve_callback, mb);
|
||||||
if (!r->resolver)
|
if (!r->resolver)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver: %s\n", MDNSERR);
|
DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver: %s\n", MDNSERR);
|
||||||
|
Loading…
Reference in New Issue
Block a user