diff --git a/CMakeLists.txt b/CMakeLists.txt index 277b12553..4b195eb08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,14 +253,29 @@ if(WITH_NETWORK_BACKEND) find_library(AVAHI_CLIENT_LIBRARIES avahi-client) find_library(AVAHI_COMMON_LIBRARIES avahi-common) - if(AVAHI_CLIENT_LIBRARIES AND AVAHI_COMMON_LIBRARIES) - message(STATUS "Building with Avahi, a zero-configuration networking (zeroconf) implementation") - set(HAVE_AVAHI ON) + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + find_library(CORE_SERVICES CoreServices) + + message(STATUS "Building with CFNetServices, an Apple DNS SD implementation") + set(HAVE_DNS_SD ON) + + list(APPEND LIBIIO_CFILES dns_sd_bonjour.c) + list(APPEND LIBS_TO_LINK ${CORE_SERVICES} ) + + elseif(AVAHI_CLIENT_LIBRARIES AND AVAHI_COMMON_LIBRARIES) + message(STATUS "Building with Avahi, a DNS SD implementation") + set(HAVE_DNS_SD ON) + + list(APPEND LIBIIO_CFILES dns_sd_avahi.c) set(AVAHI_LIBRARIES ${AVAHI_CLIENT_LIBRARIES} ${AVAHI_COMMON_LIBRARIES}) - set(LIBS_TO_LINK ${LIBS_TO_LINK} ${AVAHI_LIBRARIES}) + list(APPEND LIBS_TO_LINK ${AVAHI_LIBRARIES}) set(AVAHI_SERVICE_INSTALL_DIR /etc/avahi/services/ CACHE PATH "default install path for Avahi service files") else() - message(STATUS "Building without Avahi (zeroconf) support") + message(STATUS "Building without DNS-SD (Zeroconf) support") + endif() + + if (HAVE_DNS_SD) + add_definitions(-DHAVE_DNS_SD=1) endif() set(NEED_THREADS 1) diff --git a/dns_sd_bonjour.c b/dns_sd_bonjour.c new file mode 100644 index 000000000..eb44a56e0 --- /dev/null +++ b/dns_sd_bonjour.c @@ -0,0 +1,167 @@ +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2020 Matej Kenda. + * Author: Matej Kenda gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * */ + +#include +#include +#include +#include + +#include + +#include "debug.h" + +/* + Implementation for DNS SD discovery for macOS using CFNetServices. +*/ + +struct cfnet_discovery_data { + bool have_v4; + bool have_v6; + char address_v4[80]; + char address_v6[80]; + int16_t port; +}; + +static void __cfnet_browser_cb ( + CFNetServiceBrowserRef browser, + CFOptionFlags flags, + CFTypeRef domainOrService, + CFStreamError* error, + void* info) +{ + CFStreamError anError; + + if ((flags & kCFNetServiceFlagIsDomain) != 0) { + ERROR("DNS SD: FATAL. Callback called for domain, not service.\n"); + goto stop_browsing; + } + + struct cfnet_discovery_data *dd = (struct cfnet_discovery_data *)info; + if (dd == NULL) { + ERROR("DNS SD: Missing info structure. Stop browsing.\n"); + goto stop_browsing; + } + + if (dd->have_v4 || dd->have_v6) { + // Already resolved one IIO service. Stop. + DEBUG("DNS SD: first service already resolved. Skip.\n"); + goto stop_browsing; + } + + const CFNetServiceRef netService = (CFNetServiceRef)domainOrService; + if (netService == NULL) { + ERROR("DNS SD: Net service is null.\n"); + goto stop_browsing; + } + + if (!CFNetServiceResolveWithTimeout(netService, 10.0, &anError)) { + ERROR("DNS SD: Resolve error: %ld.%d\n", anError.domain, anError.error); + goto stop_browsing; + } + + dd->port = CFNetServiceGetPortNumber(netService); + + CFArrayRef addrArr = CFNetServiceGetAddressing(netService); + if (addrArr == NULL) { + ERROR("DNS SD: No valid addresses for service.\n"); + goto stop_browsing; + } + + for (CFIndex i = 0; i < CFArrayGetCount(addrArr); i++) { + struct sockaddr_in *sa = (struct sockaddr_in *) + CFDataGetBytePtr(CFArrayGetValueAtIndex(addrArr, i)); + switch(sa->sin_family) { + case AF_INET: + if (inet_ntop(sa->sin_family, &sa->sin_addr, + dd->address_v4, sizeof(dd->address_v4))) { + dd->have_v4 = TRUE; + } + case AF_INET6: + if (inet_ntop(sa->sin_family, &sa->sin_addr, + dd->address_v6, sizeof(dd->address_v6))) { + dd->have_v6 = TRUE; + } + } + } + + if ((flags & kCFNetServiceFlagMoreComing) == 0) { + DEBUG("DNS SD: No more entries coming.\n"); + goto stop_browsing; + } + + return; + +stop_browsing: + + CFNetServiceBrowserStopSearch(browser, &anError); +} + +int discover_host(char *addr_str, size_t addr_len, uint16_t *port) +{ + int ret = 0; + + struct cfnet_discovery_data ddata = { FALSE, FALSE }; + CFNetServiceClientContext clientContext = { 0, &ddata, NULL, NULL, NULL }; + CFStreamError error; + Boolean result; + + CFStringRef type = CFSTR("_iio._tcp."); + CFStringRef domain = CFSTR(""); + + DEBUG("DNS SD: Resolving hostname."); + + CFNetServiceBrowserRef gServiceBrowserRef = CFNetServiceBrowserCreate( + kCFAllocatorDefault, __cfnet_browser_cb, &clientContext); + + if (gServiceBrowserRef == NULL) { + ERROR("DNS SD: Failed to create service browser.\n"); + ret = ENOMEM; + goto exit; + } + result = CFNetServiceBrowserSearchForServices( + gServiceBrowserRef, domain, type, &error); + + if (result == false) { + ERROR("DNS SD: CFNetServiceBrowserSearchForServices failed (domain = %ld, error = %d)\n", + (long)error.domain, error.error); + ret = ENXIO; + goto free_browser; + } + + // Resolved address and port. ipv4 has precedence over ipv6. + *port = ddata.port; + if (ddata.have_v4) { + strncpy(addr_str, ddata.address_v4, addr_len); + } + else if (ddata.have_v6) { + strncpy(addr_str, ddata.address_v6, addr_len); + } + else { + ERROR("DNS SD: IIO service not found.\n"); + ret = ENXIO; + } + +free_browser: + + CFRelease(gServiceBrowserRef); + gServiceBrowserRef = NULL; + +exit: + return ret; +} +