diff --git a/configure.ac b/configure.ac index 4bef2391..12014472 100644 --- a/configure.ac +++ b/configure.ac @@ -55,13 +55,23 @@ AC_CHECK_FUNCS_ONCE([posix_fadvise euidaccess pipe2]) AC_CHECK_FUNCS([strptime strtok_r], [], [AC_MSG_ERROR([[Missing function required to build forked-daapd]])]) -dnl check for clock_gettime -AC_SEARCH_LIBS([clock_gettime], [rt], [], - [AC_MSG_ERROR([[Missing clock_gettime]])]) +dnl check for clock_gettime or replace it +AC_SEARCH_LIBS([clock_gettime], [rt], + [AC_DEFINE([HAVE_CLOCK_GETTIME], 1, + [Define to 1 if have clock_gettime function])], + [AC_CHECK_HEADER([mach/mach_time.h], + [AC_DEFINE([HAVE_MACH_CLOCK], 1, + [Define to 1 if mach kernel clock replacement available])], + [AC_MSG_ERROR([[Missing clock_gettime and any replacement]])])]) -dnl check for timer_settime -AC_SEARCH_LIBS([timer_settime], [rt], [], - [AC_MSG_ERROR([[Missing timer_settime]])]) +dnl check for timer_settime or replace it +AC_SEARCH_LIBS([timer_settime], [rt], + [AC_DEFINE([HAVE_TIMER_SETTIME], 1, + [Define to 1 if have timer_settime function])], + [AC_CHECK_HEADER([mach/mach_time.h], + [AC_DEFINE([HAVE_MACH_TIMER], 1, + [Define to 1 if mach kernel clock replacement available])], + [AC_MSG_ERROR([[Missing timer_settime and any replacement]])])]) AC_SEARCH_LIBS([pthread_exit], [pthread], [], [AC_MSG_ERROR([[pthreads library is required]])]) @@ -91,20 +101,17 @@ AC_SUBST([FORKED_CPPFLAGS]) AM_ICONV dnl All FORK_ macros defined in m4/fork_checks.m4 FORK_FUNC_REQUIRE([COMMON], [GNU libunistring], [LIBUNISTRING], [unistring], - [u8_strconv_from_locale], [uniconv.h], [], - [dnl Retry test with iconv library - FORK_VARS_APPEND([COMMON], [LIBICONV], [INCICONV]) - FORK_FUNC_REQUIRE([COMMON], [GNU libunistring], [LIBUNISTRING], - [unistring], [u8_strconv_from_locale], [uniconv.h])]) + [u8_strconv_from_locale], [uniconv.h], [], + [dnl Retry test with iconv library + FORK_VARS_APPEND([COMMON], [LIBICONV], [INCICONV]) + FORK_FUNC_REQUIRE([COMMON], [GNU libunistring], [LIBUNISTRING], + [unistring], [u8_strconv_from_locale], [uniconv.h])]) FORK_MODULES_CHECK([FORKED], [ZLIB], [zlib], [deflate], [zlib.h]) FORK_MODULES_CHECK([FORKED], [CONFUSE], [libconfuse], [cfg_init], [confuse.h]) FORK_MODULES_CHECK([FORKED], [MINIXML], [mxml], [mxmlNewElement], [mxml.h], [AC_CHECK_FUNCS([mxmlGetOpaque])]) -FORK_MODULES_CHECK([FORKED], [AVAHI], [avahi-client >= 0.6.24], - [avahi_client_new], [avahi-client/client.h]) - dnl SQLite3 requires extra checks FORK_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0], [sqlite3_initialize], [sqlite3.h], @@ -187,7 +194,8 @@ AS_IF([[test "$have_signal" = "no"]], AC_CHECK_HEADERS_ONCE([endian.h sys/endian.h]) AC_CHECK_DECL([htobe16], [], - [AC_MSG_FAILURE([[Missing functions to swap byte order]])], + [AC_CHECK_HEADERS([libkern/OSByteOrder.h], [], + [AC_MSG_ERROR([[Missing functions to swap byte order]])])], [AC_INCLUDES_DEFAULT[ #ifdef HAVE_ENDIAN_H # include @@ -227,6 +235,15 @@ FORK_ARG_WITH_CHECK([FORKED], [json-c support], [json], [JSON_C], [with_json=no]] )]) +dnl Build with Avahi (or Bonjour if not) +FORK_ARG_WITH_CHECK([FORKED], [Avahi mDNS], [avahi], [AVAHI], + [avahi-client >= 0.6.24], [avahi_client_new], [avahi-client/client.h]) +AS_IF([[test "x$with_avahi" = "xno"]], + [FORK_FUNC_REQUIRE([FORKED], [Bonjour DNS_SD], [DNSSD], [dns_sd], + [DNSServiceGetAddrInfo], [dns_sd.h], [], + [AC_MSG_ERROR([[Avahi client or Bonjour DNS_SD required, please install one.]])])]) +AM_CONDITIONAL([COND_AVAHI], [[test "x$with_avahi" = "xyes"]]) + dnl iTunes playlists with libplist FORK_ARG_ENABLE([iTunes Music Library XML support], [itunes], [ITUNES], [FORK_MODULES_CHECK([FORKED], [LIBPLIST], [libplist >= 0.16], diff --git a/src/Makefile.am b/src/Makefile.am index fb6d93d8..dd7f40fe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,6 +33,12 @@ if COND_PULSEAUDIO PULSEAUDIO_SRC=outputs/pulse.c endif +if COND_AVAHI +MDNS_SRC=mdns_avahi.c +else +MDNS_SRC=mdns_dnssd.c +endif + GPERF_FILES = \ daap_query.gperf \ rsp_query.gperf \ @@ -83,7 +89,7 @@ forked_daapd_SOURCES = main.c \ library/filescanner_ffmpeg.c library/filescanner_playlist.c \ library/filescanner_smartpl.c $(ITUNES_SRC) \ library.c library.h \ - mdns_avahi.c mdns.h \ + $(MDNS_SRC) mdns.h \ remote_pairing.c remote_pairing.h \ avio_evbuffer.c avio_evbuffer.h \ httpd.c httpd.h \ diff --git a/src/mdns_avahi.c b/src/mdns_avahi.c index 63b9dce4..0abfb7ab 100644 --- a/src/mdns_avahi.c +++ b/src/mdns_avahi.c @@ -636,11 +636,6 @@ entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_U } } -#ifndef HOST_NAME_MAX -/* some systems want programs to query this, just define a reasonable limit */ -#define HOST_NAME_MAX 64 -#endif - static int create_group_entry(struct mdns_group_entry *ge, int commit) { @@ -683,8 +678,9 @@ create_group_entry(struct mdns_group_entry *ge, int commit) DPRINTF(E_LOG, L_MDNS, "Could not add CNAME %s, gethostname failed\n", ge->name); return -1; } - // Note, gethostname does not guarantee 0-termination + hostname[HOST_NAME_MAX] = 0; + ret = snprintf(rdata, sizeof(rdata), ".%s.local", hostname); if (!(ret > 0 && ret < sizeof(rdata))) { diff --git a/src/mdns_dnssd.c b/src/mdns_dnssd.c new file mode 100644 index 00000000..2b2d5b41 --- /dev/null +++ b/src/mdns_dnssd.c @@ -0,0 +1,926 @@ +/* + * Bonjour mDNS backend, with libevent polling + * + * Copyright (c) Scott Shambarger + * Copyright (C) 2009-2011 Julien BLACHE + * Copyright (C) 2005 Sebastian Dr�ge + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "mdns.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* timeout for service resolves */ +#define MDNS_RESOLVE_TIMEOUT_SECS 5 + +// Hack for FreeBSD, don't want to bother with sysconf() +#ifndef HOST_NAME_MAX +# include +# define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#endif + +#include "logger.h" + +/* Main event base, from main.c */ +extern struct event_base *evbase_main; + +static DNSServiceRef mdns_sdref_main; +static struct event *mdns_ev_main; + +/* registered services last the life of the program */ +struct mdns_service { + struct mdns_service *next; + /* allocated */ + DNSServiceRef sdref; + TXTRecordRef txtRecord; +}; + +static struct mdns_service *mdns_services = NULL; + +/* we keep records forever to display names in logs when + registered or renamed */ +struct mdns_record { + struct mdns_record *next; + /* allocated */ + char *name; + /* references */ + DNSRecordRef recRef; + uint16_t rrtype; +}; + +static struct mdns_record *mdns_records = NULL; + +struct mdns_addr_lookup { + struct mdns_addr_lookup *next; + /* allocated */ + DNSServiceRef sdref; + struct keyval txt_kv; + /* references */ + u_int16_t port; + struct mdns_resolver *rs; +}; + +/* resolvers and address lookups clean themselves up */ +struct mdns_resolver +{ + struct mdns_resolver *next; + /* allocated */ + DNSServiceRef sdref; + char *service; + char *regtype; + char *domain; + struct event *timer; + struct mdns_addr_lookup *lookups; + /* references */ + void *uuid; + uint32_t interface; + struct mdns_browser *mb; +}; + +/* browsers keep running for the life of the program */ +struct mdns_browser +{ + struct mdns_browser *next; + /* allocated */ + DNSServiceRef sdref; + struct mdns_resolver *resolvers; + char *regtype; + /* references */ + mdns_browse_cb cb; + DNSServiceProtocol protocol; + void *res_uuid; +}; + +static struct mdns_browser *mdns_browsers = NULL; + +#define IPV4LL_NETWORK 0xA9FE0000 +#define IPV4LL_NETMASK 0xFFFF0000 +#define IPV6LL_NETWORK 0xFE80 +#define IPV6LL_NETMASK 0xFFC0 + +static int +is_v4ll(struct in_addr *addr) +{ + return ((ntohl(addr->s_addr) & IPV4LL_NETMASK) == IPV4LL_NETWORK); +} + +static int +is_v6ll(struct in6_addr *addr) +{ + return ((((addr->s6_addr[0] << 8) | addr->s6_addr[1]) & IPV6LL_NETMASK) + == IPV6LL_NETWORK); +} + +/* mDNS interface - to be called only from the main thread */ + +static int +mdns_service_free(struct mdns_service *s) +{ + if(! s) + return -1; + + /* free sdref, then everything else */ + if(s->sdref) + DNSServiceRefDeallocate(s->sdref); + TXTRecordDeallocate(&(s->txtRecord)); + free(s); + + return -1; +} + +static int +mdns_addr_lookup_free(struct mdns_addr_lookup *lu) +{ + if (! lu) + return -1; + + if(lu->sdref) + DNSServiceRefDeallocate(lu->sdref); + keyval_clear(&lu->txt_kv); + free(lu); + + return -1; +} + +static int +mdns_resolver_free(struct mdns_resolver *rs) { + + struct mdns_addr_lookup *lu; + + if (! rs) + return -1; + + /* free/cancel all lookups */ + for(lu = rs->lookups; lu; lu = rs->lookups) + { + rs->lookups = lu->next; + mdns_addr_lookup_free(lu); + } + + if(rs->timer) + event_free(rs->timer); + if(rs->sdref) + DNSServiceRefDeallocate(rs->sdref); + free(rs->service); + free(rs->regtype); + free(rs->domain); + free(rs); + + return -1; +} + +static int +mdns_browser_free(struct mdns_browser *mb) +{ + struct mdns_resolver *rs; + + if(! mb) + return -1; + + /* free all resolvers */ + for(rs = mb->resolvers; rs; rs = mb->resolvers) + { + mb->resolvers = rs->next; + mdns_resolver_free(rs); + } + /* free sdref, then everything else */ + if(mb->sdref) + DNSServiceRefDeallocate(mb->sdref); + free(mb->regtype); + free(mb); + + return -1; +} + +static int +mdns_record_free(struct mdns_record *r) +{ + if (! r) + return -1; + + free(r->name); + free(r); + + return -1; +} + +static int +mdns_main_free(void) +{ + struct mdns_service *s; + struct mdns_browser *mb; + struct mdns_record *r; + + for(s = mdns_services; mdns_services; s = mdns_services) + { + mdns_services = s->next; + mdns_service_free(s); + } + + for (mb = mdns_browsers; mdns_browsers; mb = mdns_browsers) + { + mdns_browsers = mb->next; + mdns_browser_free(mb); + } + + for (r = mdns_records; mdns_records; r = mdns_records) + { + mdns_records = r->next; + mdns_record_free(r); + } + + if(mdns_ev_main) + event_free(mdns_ev_main); + mdns_ev_main = NULL; + if(mdns_sdref_main) + DNSServiceRefDeallocate(mdns_sdref_main); + mdns_sdref_main = NULL; + + return -1; +} + +void +mdns_deinit(void) +{ + mdns_main_free(); +} + +static void +mdns_event_cb(evutil_socket_t fd, short flags, void *data) { + + DNSServiceErrorType err; + + err = DNSServiceProcessResult(mdns_sdref_main); + + if (err != kDNSServiceErr_NoError) + DPRINTF(E_LOG, L_MDNS, "DNSServiceProcessResult error %d\n", err); +} + +int +mdns_init(void) +{ + DNSServiceErrorType err; + int fd; + int ret; + + DPRINTF(E_DBG, L_MDNS, "Initializing DNS_SD mDNS\n"); + + mdns_services = NULL; + mdns_browsers = NULL; + mdns_records = NULL; + mdns_sdref_main = NULL; + mdns_ev_main = NULL; + + err = DNSServiceCreateConnection(&mdns_sdref_main); + if (err != kDNSServiceErr_NoError) + { + DPRINTF(E_LOG, L_MDNS, "Could not create mDNS connection\n"); + return -1; + } + + fd = DNSServiceRefSockFD(mdns_sdref_main); + if (fd == -1) { + DPRINTF(E_LOG, L_MDNS, "DNSServiceRefSockFD failed\n"); + return mdns_main_free(); + } + mdns_ev_main = event_new(evbase_main, fd, EV_PERSIST | EV_READ, + mdns_event_cb, NULL); + if (! mdns_ev_main) + { + DPRINTF(E_LOG, L_MDNS, "Could not make new event in mdns\n"); + return mdns_main_free(); + } + + ret = event_add(mdns_ev_main, NULL); + if (ret != 0) + { + DPRINTF(E_LOG, L_MDNS, "Could not add new event in mdns\n"); + return mdns_main_free(); + } + + return 0; +} + +static void +mdns_register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, + DNSServiceErrorType errorCode, const char *name, + const char *regtype, const char *domain, + void *context) { + + switch (errorCode) { + case kDNSServiceErr_NoError: + DPRINTF(E_DBG, L_MDNS, "Successfully added mDNS service '%s.%s'\n", + name, regtype); + break; + + case kDNSServiceErr_NameConflict: + DPRINTF(E_DBG, L_MDNS, + "Name collision for service '%s.%s' - automatically assigning new name\n", + name, regtype); + break; + + case kDNSServiceErr_NoMemory: + DPRINTF(E_DBG, L_MDNS, "Out of memory registering service %s\n", name); + break; + + default: + DPRINTF(E_DBG, L_MDNS, "Unspecified error registering service %s, error %d\n", + name, errorCode); + } +} + +int +mdns_register(char *name, char *regtype, int port, char **txt) +{ + struct mdns_service *s; + DNSServiceErrorType err; + int i; + char *eq; + + DPRINTF(E_DBG, L_MDNS, "Adding mDNS service '%s.%s'\n", name, regtype); + + s = calloc(1, sizeof(*s)); + if (!s) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory registering service.\n"); + return -1; + } + TXTRecordCreate(&(s->txtRecord), 0, NULL); + + for (i = 0; txt && txt[i]; i++) + { + if ((eq = strchr(txt[i], '='))) + { + *eq = '\0'; + eq++; + err = TXTRecordSetValue(&(s->txtRecord), txt[i], strlen(eq) * sizeof(char), eq); + *(--eq) = '='; + if (err != kDNSServiceErr_NoError) + { + DPRINTF(E_LOG, L_MDNS, "Could not set TXT record value\n"); + return mdns_service_free(s); + } + } + } + + s->sdref = mdns_sdref_main; + err = DNSServiceRegister(&(s->sdref), kDNSServiceFlagsShareConnection, 0, + name, regtype, NULL, NULL, htons(port), + TXTRecordGetLength(&(s->txtRecord)), + TXTRecordGetBytesPtr(&(s->txtRecord)), + mdns_register_callback, NULL); + + if (err != kDNSServiceErr_NoError) + { + DPRINTF(E_LOG, L_MDNS, "Error registering service '%s.%s'\n", + name, regtype); + s->sdref = NULL; + return mdns_service_free(s); + } + + s->next = mdns_services; + mdns_services = s; + + return 0; +} + +static void +mdns_record_callback(DNSServiceRef sdRef, DNSRecordRef RecordRef, + DNSServiceFlags flags, DNSServiceErrorType errorCode, + void *context) +{ + struct mdns_record *r; + + r = context; + + switch (errorCode) { + case kDNSServiceErr_NoError: + DPRINTF(E_DBG, L_MDNS, "Successfully added mDNS record %s\n", r->name); + break; + + case kDNSServiceErr_NameConflict: + DPRINTF(E_DBG, L_MDNS, "Record ame collision - automatically assigning new name\n"); + break; + + case kDNSServiceErr_NoMemory: + DPRINTF(E_DBG, L_MDNS, "Out of memory registering record %s\n", r->name); + break; + + default: + DPRINTF(E_DBG, L_MDNS, "Unspecified error registering record %s, error %d\n", + r->name, errorCode); + } + +} + +static int +mdns_register_record(uint16_t rrtype, const char *name, uint16_t rdlen, + const void *rdata) +{ + struct mdns_record *r; + DNSServiceErrorType err; + + DPRINTF(E_DBG, L_MDNS, "Adding mDNS record %s/%u\n", name, rrtype); + + r = calloc(1, sizeof(*r)); + if (!r) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory adding record.\n"); + return -1; + } + r->name = strdup(name); + if (!(r->name)) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory adding record.\n"); + return mdns_record_free(r); + } + + r->rrtype = rrtype; + + err = DNSServiceRegisterRecord(mdns_sdref_main, &r->recRef, + kDNSServiceFlagsShared, 0, r->name, + r->rrtype, kDNSServiceClass_IN, rdlen, rdata, + 0, mdns_record_callback, r); + + if (err != kDNSServiceErr_NoError) + { + DPRINTF(E_LOG, L_MDNS, "Error registering record %s, error %d\n", name, + err); + return mdns_record_free(r); + } + + /* keep these around so we can display r->name in the callback */ + r->next = mdns_records; + mdns_records = r; + + return 0; +} + +int +mdns_cname(char *name) +{ + char hostname[HOST_NAME_MAX + 1]; + // Includes room for "..local" and 0-terminator + char rdata[HOST_NAME_MAX + 8]; + int count; + int i; + int ret; + + ret = gethostname(hostname, HOST_NAME_MAX); + if (ret < 0) + { + DPRINTF(E_LOG, L_MDNS, "Could not add CNAME %s, gethostname failed\n", + name); + return -1; + } + // Note, gethostname does not guarantee 0-termination + hostname[HOST_NAME_MAX] = 0; + + ret = snprintf(rdata, sizeof(rdata), ".%s.local", hostname); + if (!(ret > 0 && ret < sizeof(rdata))) + { + DPRINTF(E_LOG, L_MDNS, "Could not add CNAME %s, hostname is invalid\n", + name); + return -1; + } + + // Convert to dns string: .forked-daapd.local -> \12forked-daapd\6local + count = 0; + for (i = ret - 1; i >= 0; i--) + { + if (rdata[i] == '.') + { + rdata[i] = count; + count = 0; + } + else + count++; + } + + return mdns_register_record(kDNSServiceType_CNAME, name, (uint16_t)ret, + rdata); +} + +static void +mdns_browse_call_cb(struct mdns_addr_lookup *lu, const char *hostname, + const struct sockaddr *address) +{ + char addr_str[INET6_ADDRSTRLEN]; + + if (address->sa_family == AF_INET) + { + struct sockaddr_in *addr = (struct sockaddr_in *)address; + + if (!inet_ntop(AF_INET, &addr->sin_addr, addr_str, sizeof(addr_str))) + { + DPRINTF(E_LOG, L_MDNS, "Could not print IPv4 address: %s\n", + strerror(errno)); + return; + } + + if (!(lu->rs->mb->protocol & kDNSServiceProtocol_IPv4)) + { + DPRINTF(E_DBG, L_MDNS, + "Discarding IPv4, not interested (service %s)\n", + lu->rs->service); + return; + } + else if (is_v4ll(&addr->sin_addr)) + { + DPRINTF(E_WARN, L_MDNS, + "Ignoring announcement from %s, address %s is link-local\n", + hostname, addr_str); + return; + } + + } + else if (address->sa_family == AF_INET6) + { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)address; + + if (!inet_ntop(AF_INET6, &addr6->sin6_addr, addr_str, sizeof(addr_str))) + { + DPRINTF(E_LOG, L_MDNS, "Could not print IPv6 address: %s\n", + strerror(errno)); + return; + } + + if (!(lu->rs->mb->protocol & kDNSServiceProtocol_IPv6)) + { + DPRINTF(E_DBG, L_MDNS, + "Discarding IPv6, not interested (service %s)\n", + lu->rs->service); + return; + } + else if (is_v6ll(&addr6->sin6_addr)) + { + DPRINTF(E_WARN, L_MDNS, + "Ignoring announcement from %s, address %s is link-local\n", + hostname, addr_str); + return; + } + } + + DPRINTF(E_DBG, L_MDNS, "Service %s, hostname %s resolved to %s\n", + lu->rs->service, hostname, addr_str); + + /* Execute callback (mb->cb) with all the data */ + lu->rs->mb->cb(lu->rs->service, lu->rs->regtype, lu->rs->domain, hostname, + address->sa_family, addr_str, lu->port, &lu->txt_kv); +} + +static void +mdns_lookup_callback(DNSServiceRef sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, DNSServiceErrorType errorCode, + const char *hostname, const struct sockaddr *address, + uint32_t ttl, void *context) +{ + struct mdns_addr_lookup *lu; + + lu = context; + + if (errorCode != kDNSServiceErr_NoError ) + { + DPRINTF(E_LOG, L_MDNS, "Error resolving hostname '%s', error %d\n", + hostname, errorCode); + return; + } + + if (flags & kDNSServiceFlagsAdd) + mdns_browse_call_cb(lu, hostname, address); +} + +static int +mdns_addr_lookup_start(struct mdns_resolver *rs, uint32_t interfaceIndex, + const char *hosttarget, uint16_t port, uint16_t txtLen, + const unsigned char *txtRecord) +{ + struct mdns_addr_lookup *lu; + DNSServiceErrorType err; + char key[256]; + int i; + uint8_t valueLen; + const char *value; + int ret; + + lu = calloc(1, sizeof(*lu)); + if (!lu) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory creating address lookup.\n"); + return -1; + } + lu->port = port; + lu->rs = rs; + + for (i=0; TXTRecordGetItemAtIndex(txtLen, txtRecord, i, sizeof(key), + key, &valueLen, (const void **)&value) + != kDNSServiceErr_Invalid; i++) + { + ret = keyval_add_size(&lu->txt_kv, key, value, valueLen); + if (ret < 0) + { + DPRINTF(E_LOG, L_MDNS, "Could not build TXT record keyval\n"); + return mdns_addr_lookup_free(lu); + } + } + + lu->sdref = mdns_sdref_main; + err = DNSServiceGetAddrInfo(&lu->sdref, kDNSServiceFlagsShareConnection, + interfaceIndex, rs->mb->protocol, hosttarget, + mdns_lookup_callback, lu); + if (err != kDNSServiceErr_NoError) + { + DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver.\n"); + lu->sdref = NULL; + return mdns_addr_lookup_free(lu); + } + + /* resolver now owns the lookup */ + lu->next = rs->lookups; + rs->lookups = lu; + + return 0; +} + +static void +mdns_resolver_remove(struct mdns_resolver *rs) +{ + struct mdns_resolver *cur; + + /* remove from browser's resolver list */ + if(rs->mb->resolvers == rs) + rs->mb->resolvers = rs->next; + else + { + for(cur = rs->mb->resolvers; cur; cur = cur->next) + if (cur->next == rs) + { + cur->next = rs->next; + break; + } + } + + /* free resolver (which cancels resolve) */ + mdns_resolver_free(rs); +} + +static void +mdns_resolve_timeout_cb(evutil_socket_t fd, short flags, void *uuid) { + + struct mdns_browser *mb; + struct mdns_resolver *rs = NULL; + + for(mb = mdns_browsers; mb && !rs; mb = mb->next) + for(rs = mb->resolvers; rs; rs = rs->next) + if(rs->uuid == uuid) + { + DPRINTF(E_DBG, L_MDNS, + "Resolve finished for '%s' type '%s' interface %d\n", + rs->service, rs->regtype, rs->interface); + mdns_resolver_remove(rs); + break; + } +} + +static void +mdns_resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, DNSServiceErrorType errorCode, + const char *fullname, const char *hosttarget, + uint16_t port, uint16_t txtLen, + const unsigned char *txtRecord, void *context) +{ + struct mdns_resolver *rs; + + rs = context; + + /* convert port to host order */ + port = ntohs(port); + + if (errorCode != kDNSServiceErr_NoError) + { + DPRINTF(E_LOG, L_MDNS, "Error resolving service '%s', error %d\n", + rs->service, errorCode); + } + else + { + DPRINTF(E_DBG, L_MDNS, "Bonjour resolved '%s' as '%s:%u' on interface %d\n", + fullname, hosttarget, port, interfaceIndex); + + mdns_addr_lookup_start(rs, interfaceIndex, hosttarget, port, + txtLen, txtRecord); + } +} + +static int +mdns_resolve_start(struct mdns_browser *mb, uint32_t interfaceIndex, + const char *serviceName, const char *regtype, + const char *replyDomain) +{ + DNSServiceErrorType err; + struct mdns_resolver *rs; + struct timeval tv; + + rs = calloc(1, sizeof(*rs)); + if (!rs) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver.\n"); + return -1; + } + + rs->service = strdup(serviceName); + if (!rs->service) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver.\n"); + return mdns_resolver_free(rs); + } + rs->regtype = strdup(regtype); + if (!rs->regtype) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver.\n"); + return mdns_resolver_free(rs); + } + rs->domain = strdup(replyDomain); + if (!rs->domain) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver.\n"); + return mdns_resolver_free(rs); + } + rs->mb = mb; + rs->interface = interfaceIndex; + /* create a timer with a uuid, so we can search for resolver without + leaking */ + rs->uuid = ++(mb->res_uuid); + rs->timer = evtimer_new(evbase_main, mdns_resolve_timeout_cb, rs->uuid); + if(! rs->timer) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver timer.\n"); + return mdns_resolver_free(rs); + } + + rs->sdref = mdns_sdref_main; + err = DNSServiceResolve(&(rs->sdref), kDNSServiceFlagsShareConnection, + interfaceIndex, serviceName, regtype, replyDomain, + mdns_resolve_callback, rs); + if (err != kDNSServiceErr_NoError) + { + DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver.\n"); + rs->sdref = NULL; + return mdns_resolver_free(rs); + } + + /* add to browser's resolvers */ + rs->next = mb->resolvers; + mb->resolvers = rs; + + /* setup a timeout to cancel the resolve */ + tv.tv_sec = MDNS_RESOLVE_TIMEOUT_SECS; + tv.tv_usec = 0; + evtimer_add(rs->timer, &tv); + + return 0; +} + +static void +mdns_resolve_cancel(const struct mdns_browser *mb, uint32_t interfaceIndex, + const char *serviceName, const char *regtype, + const char *replyDomain) { + + struct mdns_resolver *rs; + + for(rs = mb->resolvers; rs; rs = rs->next) + { + if ((rs->interface == interfaceIndex) + && (! strcasecmp(rs->service, serviceName)) + && (! strcmp(rs->regtype, regtype)) + && (! strcasecmp(rs->domain, replyDomain))) + { + /* remove from resolvers, and free (which cancels resolve) */ + DPRINTF(E_DBG, L_MDNS, "Cancelling resolve for '%s'\n", rs->service); + mdns_resolver_remove(rs); + break; + } + } + + return; +} + +static void +mdns_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, DNSServiceErrorType errorCode, + const char *serviceName, const char *regtype, + const char *replyDomain, void *context) +{ + struct mdns_browser *mb; + + if (errorCode != kDNSServiceErr_NoError) + { + // FIXME: if d/c, we sould recreate the browser? + DPRINTF(E_LOG, L_MDNS, "Bonjour browsing error %d\n", errorCode); + return; + } + + mb = context; + + if (flags & kDNSServiceFlagsAdd) + { + DPRINTF(E_DBG, L_MDNS, + "Bonjour Browser: NEW service '%s' type '%s' interface %d\n", + serviceName, regtype, interfaceIndex); + mdns_resolve_start(mb, interfaceIndex, serviceName, regtype, + replyDomain); + } + else + { + DPRINTF(E_DBG, L_MDNS, + "Bonjour Browser: REMOVE service '%s' type '%s' interface %d\n", + serviceName, regtype, interfaceIndex); + mdns_resolve_cancel(mb, interfaceIndex, serviceName, regtype, + replyDomain); + mb->cb(serviceName, regtype, replyDomain, NULL, 0, NULL, -1, NULL); + } +} + +int +mdns_browse(char *regtype, int family, mdns_browse_cb cb) +{ + struct mdns_browser *mb; + DNSServiceErrorType err; + + DPRINTF(E_DBG, L_MDNS, "Adding service browser for type %s\n", regtype); + + mb = calloc(1, sizeof(*mb)); + if (!mb) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory creating service browser.\n"); + return -1; + } + + mb->cb = cb; + + /* flags are ignored in DNS-SD implementation */ + switch(family) { + case AF_UNSPEC: + mb->protocol = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6; + break; + case AF_INET: + mb->protocol = kDNSServiceProtocol_IPv4; + break; + case AF_INET6: + mb->protocol = kDNSServiceProtocol_IPv6; + break; + default: + DPRINTF(E_LOG, L_MDNS, "Unrecognized protocol family %d.\n", family); + return mdns_browser_free(mb); + } + + mb->regtype = strdup(regtype); + if (!mb->regtype) + { + DPRINTF(E_LOG, L_MDNS, "Out of memory creating service browser.\n"); + return mdns_browser_free(mb); + } + mb->sdref = mdns_sdref_main; + err = DNSServiceBrowse(&(mb->sdref), kDNSServiceFlagsShareConnection, 0, + regtype, NULL, mdns_browse_callback, mb); + if (err != kDNSServiceErr_NoError) + { + DPRINTF(E_LOG, L_MDNS, "Failed to create service browser.\n"); + mb->sdref = NULL; + return mdns_browser_free(mb); + } + + mb->next = mdns_browsers; + mdns_browsers = mb; + + return 0; +} diff --git a/src/misc.c b/src/misc.c index bbacb8ea..7976c587 100644 --- a/src/misc.c +++ b/src/misc.c @@ -928,6 +928,138 @@ timespec_cmp(struct timespec time1, struct timespec time2) return 0; } +#if defined(HAVE_MACH_CLOCK) || defined(HAVE_MACH_TIMER) + +#include /* mach_absolute_time */ +#include /* host_get_clock_service */ +#include /* clock_get_time */ + +/* mach monotonic clock port */ +extern mach_port_t clock_port; + +#ifndef HAVE_CLOCK_GETTIME + +int +clock_gettime(clockid_t clock_id, struct timespec *tp) { + + static int clock_init = 0; + static clock_serv_t clock; + + mach_timespec_t mts; + int ret; + + if (! clock_init) { + clock_init = 1; + if (host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &clock)) + abort(); /* unlikely */ + } + + if(! tp) + return -1; + + switch (clock_id) { + + case CLOCK_REALTIME: + + /* query mach for calendar time */ + ret = clock_get_time(clock, &mts); + if (! ret) { + tp->tv_sec = mts.tv_sec; + tp->tv_nsec = mts.tv_nsec; + } + break; + + case CLOCK_MONOTONIC: + + /* query mach for monotinic time */ + ret = clock_get_time(clock_port, &mts); + if (! ret) { + tp->tv_sec = mts.tv_sec; + tp->tv_nsec = mts.tv_nsec; + } + break; + + default: + ret = -1; + break; + } + + return ret; +} + +int +clock_getres(clockid_t clock_id, struct timespec *res) { + + if (! res) + return -1; + + /* hardcode ms resolution */ + res->tv_sec = 0; + res->tv_nsec = 1000; + + return 0; +} + +#endif /* HAVE_CLOCK_GETTIME */ + +#ifndef HAVE_TIMER_SETTIME + +#include /* ITIMER_REAL */ + +int +timer_create(clockid_t clock_id, void *sevp, timer_t *timer_id) { + + if (clock_id != CLOCK_MONOTONIC) + return -1; + if (sevp) + return -1; + + /* setitimer only supports one timer */ + *timer_id = 0; + + return 0; +} + +int +timer_delete(timer_t timer_id) { + + struct itimerval timerval; + + if (timer_id != 0) + return -1; + + memset(&timerval, 0, sizeof(struct itimerval)); + + return setitimer(ITIMER_REAL, &timerval, NULL); +} + +int +timer_settime(timer_t timer_id, int flags, const struct itimerspec *tp, + struct itimerspec *old) { + + struct itimerval tv; + + if (timer_id != 0 || ! tp || old) + return -1; + + TIMESPEC_TO_TIMEVAL(&(tv.it_value), &(tp->it_value)); + TIMESPEC_TO_TIMEVAL(&(tv.it_interval), &(tp->it_interval)); + + return setitimer(ITIMER_REAL, &tv, NULL); +} + +int +timer_getoverrun(timer_t timer_id) { + + /* since we don't know if there have been signals that weren't delivered, + assume none */ + return 0; +} + +#endif /* HAVE_TIMER_SETTIME */ + +#endif /* HAVE_MACH_CLOCK */ + int mutex_init(pthread_mutex_t *mutex) { diff --git a/src/misc.h b/src/misc.h index 88ed1f31..2a3172b5 100644 --- a/src/misc.h +++ b/src/misc.h @@ -89,6 +89,47 @@ b64_encode(const uint8_t *in, size_t len); uint64_t murmur_hash64(const void *key, int len, uint32_t seed); +#ifndef HAVE_CLOCK_GETTIME + +#ifndef CLOCK_REALTIME +# define CLOCK_REALTIME 0 +#endif +#ifndef CLOCK_MONOTONIC +# define CLOCK_MONOTONIC 1 +#endif + +typedef int clockid_t; + +int +clock_gettime(clockid_t clock_id, struct timespec *tp); + +int +clock_getres(clockid_t clock_id, struct timespec *res); +#endif + +#ifndef HAVE_TIMER_SETTIME + +struct itimerspec { + struct timespec it_interval; + struct timespec it_value; +}; +typedef uint64_t timer_t; + +int +timer_create(clockid_t clock_id, void *sevp, timer_t *timer_id); + +int +timer_delete(timer_t timer_id); + +int +timer_settime(timer_t timer_id, int flags, const struct itimerspec *tp, + struct itimerspec *old); + +int +timer_getoverrun(timer_t timer_id); + +#endif + /* Timer function for platforms without hi-res timers */ int clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec *res); diff --git a/src/outputs/cast.c b/src/outputs/cast.c index cc75e989..e57f13c9 100644 --- a/src/outputs/cast.c +++ b/src/outputs/cast.c @@ -39,6 +39,10 @@ # include #elif defined(HAVE_SYS_ENDIAN_H) # include +#elif defined(HAVE_LIBKERN_OSBYTEORDER_H) +#include +#define htobe32(x) OSSwapHostToBigInt32(x) +#define be32toh(x) OSSwapBigToHostInt32(x) #endif #include #include diff --git a/src/outputs/raop.c b/src/outputs/raop.c index 8ae51354..0c5b8fbb 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -51,6 +51,11 @@ # include #elif defined(HAVE_SYS_ENDIAN_H) # include +#elif defined(HAVE_LIBKERN_OSBYTEORDER_H) +#include +#define htobe16(x) OSSwapHostToBigInt16(x) +#define be16toh(x) OSSwapBigToHostInt16(x) +#define htobe32(x) OSSwapHostToBigInt32(x) #endif #include