diff --git a/CMakeLists.txt b/CMakeLists.txt index aad9b7426..a5cf80a24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,7 +265,7 @@ if(WITH_NETWORK_BACKEND) 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 LIBIIO_CFILES dns_sd_bonjour.c dns_sd.c) list(APPEND LIBS_TO_LINK ${CORE_SERVICES} ) elseif(AVAHI_CLIENT_LIBRARIES AND AVAHI_COMMON_LIBRARIES) @@ -273,7 +273,7 @@ if(WITH_NETWORK_BACKEND) set(HAVE_DNS_SD ON) set(HAVE_AVAHI ON) - list(APPEND LIBIIO_CFILES dns_sd_avahi.c) + list(APPEND LIBIIO_CFILES dns_sd_avahi.c dns_sd.c) set(AVAHI_LIBRARIES ${AVAHI_CLIENT_LIBRARIES} ${AVAHI_COMMON_LIBRARIES}) list(APPEND LIBS_TO_LINK ${AVAHI_LIBRARIES}) else() diff --git a/Doxyfile.in b/Doxyfile.in index 1caa83b88..02165b088 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -663,8 +663,7 @@ RECURSIVE = NO # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = @CMAKE_SOURCE_DIR@/iio-private.h @CMAKE_SOURCE_DIR@/debug.h @CMAKE_SOURCE_DIR@/iio-lock.h @CMAKE_SOURCE_DIR@/iiod-client.h @CMAKE_SOURCE_DIR@/sort.h - +EXCLUDE = @CMAKE_SOURCE_DIR@/iio-private.h @CMAKE_SOURCE_DIR@/debug.h @CMAKE_SOURCE_DIR@/iio-lock.h @CMAKE_SOURCE_DIR@/iiod-client.h @CMAKE_SOURCE_DIR@/sort.h @CMAKE_SOURCE_DIR@/network.h # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/dns_sd.c b/dns_sd.c new file mode 100644 index 000000000..387e60d84 --- /dev/null +++ b/dns_sd.c @@ -0,0 +1,175 @@ +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2014-2020 Analog Devices, Inc. + * Author: Robin Getz + * Matej Kenda + * + * 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. + * + * Some of this is insipred from libavahi's example: + * https://avahi.org/doxygen/html/client-browse-services_8c-example.html + * which is also LGPL 2.1 or later. + * + * */ + +#include "iio-lock.h" +#include "iio-private.h" +#include "network.h" + +#include "debug.h" + +/* Some functions for handling common linked list operations */ +static void dnssd_remove_node(struct dns_sd_discovery_data **ddata, int n) +{ + + struct dns_sd_discovery_data *d, *ndata, *ldata, *tdata; + int i; + + d = *ddata; + + if (n == 0) { + tdata = d->next; + dnssd_free_discovery_data(d); + d = tdata; + } else { + for (i = 0, ndata = d; ndata->next != NULL; ndata = ndata->next) { + if (i == n) { + /* Could be NULL or node, both are OK */ + tdata = ndata->next; + /* free the node to be removed */ + dnssd_free_discovery_data(ndata); + /* snip it out */ + ldata->next = tdata; + break; + } + ldata = ndata; + i++; + } + } + + *ddata = d; +} + +/* + * remove the ones in the list that you can't connect to + * This is sort of silly, but we have seen non-iio devices advertised + * and discovered on the network. Oh well.... + */ +void port_knock_discovery_data(struct dns_sd_discovery_data **ddata) +{ + struct dns_sd_discovery_data *d, *ndata; + int i, ret; + + d = *ddata; + iio_mutex_lock(d->lock); + for (i = 0, ndata = d; ndata->next != NULL; ndata = ndata->next) { + char port_str[6]; + struct addrinfo hints, *res, *rp; + int fd; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + iio_snprintf(port_str, sizeof(port_str), "%hu", ndata->port); + ret = getaddrinfo(ndata->addr_str, port_str, &hints, &res); + + /* getaddrinfo() returns a list of address structures */ + if (ret) { + DEBUG("Unable to find host ('%s'): %s\n", + ndata->hostname, + gai_strerror(ret)); + dnssd_remove_node(&d, i); + } else { + for (rp = res; rp != NULL; rp = rp->ai_next) { + fd = create_socket(rp, DEFAULT_TIMEOUT_MS); + if (fd < 0) { + DEBUG("Unable to open %s%s socket ('%s:%d' %s)\n", + rp->ai_family == AF_INET ? "ipv4" : "", + rp->ai_family == AF_INET6? "ipv6" : "", + ndata->hostname, ndata->port, ndata->addr_str); + dnssd_remove_node(&d, i); + } else { + close(fd); + DEBUG("Something %s%s at '%s:%d' %s)\n", + rp->ai_family == AF_INET ? "ipv4" : "", + rp->ai_family == AF_INET6? "ipv6" : "", + ndata->hostname, ndata->port, ndata->addr_str); + i++; + } + } + } + freeaddrinfo(res); + } + iio_mutex_unlock(d->lock); + *ddata = d; + + return; +} + +void remove_dup_discovery_data(struct dns_sd_discovery_data **ddata) +{ + struct dns_sd_discovery_data *d, *ndata, *mdata; + int i, j; + + d = *ddata; + + if (!d) + return; + + if (!d->next) + return; + + iio_mutex_lock(d->lock); + for (i = 0, ndata = d; ndata->next != NULL; ndata = ndata->next) { + for (j = i + 1, mdata = ndata->next; mdata->next != NULL; mdata = mdata->next) { + if (!strcmp(mdata->hostname, ndata->hostname) && + !strcmp(mdata->addr_str, ndata->addr_str)){ + DEBUG("Removing duplicate in list: '%s'\n", + ndata->hostname); + dnssd_remove_node(&d, j); + } + j++; + } + i++; + } + iio_mutex_unlock(d->lock); + + *ddata = d; +} + +int dnssd_discover_host(char *addr_str, size_t addr_len, uint16_t *port) +{ + struct dns_sd_discovery_data *ddata; + int ret = 0; + + ret = dnssd_find_hosts(&ddata); + + if (ret < 0) + return ret; + + if (ddata) { + *port = ddata->port; + strncpy(addr_str, ddata->addr_str, addr_len); + } + + dnssd_free_all_discovery_data(ddata); + + /* negative error codes, 0 for no data */ + return ret; +} + +void dnssd_free_all_discovery_data(struct dns_sd_discovery_data *d) +{ + while (d) + dnssd_remove_node(&d, 0); +} diff --git a/dns_sd_avahi.c b/dns_sd_avahi.c index c4a4cc014..fe35a3440 100644 --- a/dns_sd_avahi.c +++ b/dns_sd_avahi.c @@ -3,6 +3,7 @@ * * Copyright (C) 2014-2020 Analog Devices, Inc. * Author: Paul Cercueil + * Robin Getz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -14,42 +15,116 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * + * Some of this is insipred from libavahi's example: + * https://avahi.org/doxygen/html/client-browse-services_8c-example.html + * which is also LGPL 2.1 or later. + * * */ -#include "iio.h" +#include "iio-private.h" +#include "network.h" +#include "iio-lock.h" -#include -#include -#include #include -#include -#include -#include -#include - +#include #include "debug.h" -struct avahi_discovery_data { - AvahiSimplePoll *poll; - AvahiAddress *address; - uint16_t *port; - bool found, resolved; -}; +/* + * Fundamentally, this builds up a linked list to manage + * potential clients on the network + */ + +static int new_discovery_data(struct dns_sd_discovery_data **data) +{ + struct dns_sd_discovery_data *d; + + d = zalloc(sizeof(struct dns_sd_discovery_data)); + if (!d) + return -ENOMEM; + + d->address = zalloc(sizeof(struct AvahiAddress)); + if (!d->address) { + free(data); + return -ENOMEM; + } + + *data = d; + return 0; +} + +void dnssd_free_discovery_data(struct dns_sd_discovery_data *d) +{ + free(d->hostname); + free(d->address); + free(d); +} + +/* + * libavahi callbacks for browser and resolver + * for more info, check out libavahi docs at: + * https://avahi.org/doxygen/html/index.html + */ static void __avahi_resolver_cb(AvahiServiceResolver *resolver, __notused AvahiIfIndex iface, __notused AvahiProtocol proto, - __notused AvahiResolverEvent event, __notused const char *name, - __notused const char *type, __notused const char *domain, - __notused const char *host_name, const AvahiAddress *address, - uint16_t port, __notused AvahiStringList *txt, + AvahiResolverEvent event, const char *name, + const char *type, const char *domain, + const char *host_name, const AvahiAddress *address, + uint16_t port, AvahiStringList *txt, __notused AvahiLookupResultFlags flags, void *d) { - struct avahi_discovery_data *ddata = (struct avahi_discovery_data *) d; + struct dns_sd_discovery_data *ddata = (struct dns_sd_discovery_data *) d; + + if (!resolver) { + ERROR("Fatal Error in Avahi Resolver\n"); + return; + } + + switch(event) { + case AVAHI_RESOLVER_FAILURE: + ERROR("Avahi Resolver: Failed resolve service '%s' " + "of type '%s' in domain '%s': %s\n", + name, type, domain, + avahi_strerror( + avahi_client_errno( + avahi_service_resolver_get_client( + resolver)))); + break; + case AVAHI_RESOLVER_FOUND: { + /* Avahi is multi-threaded, so lock the list */ + iio_mutex_lock(ddata->lock); + ddata->resolved++; + + /* Find empty data to store things*/ + while (ddata->next) + ddata = ddata->next; + + /* link a new placeholder to the list */ + avahi_address_snprint(ddata->addr_str, + sizeof(ddata->addr_str), address); + memcpy(ddata->address, address, sizeof(*address)); + ddata->port = port; + ddata->hostname = strdup(host_name); + ddata->resolved = true; + /* link a new, empty placeholder to the list */ + if (!new_discovery_data(&ddata->next)) { + /* duplicate poll & lock info, + * since we don't know which might be discarded */ + ddata->next->poll = ddata->poll; + ddata->next->lock = ddata->lock; + } else { + ERROR("Avahi Resolver : memory failure\n"); + } + iio_mutex_unlock(ddata->lock); - memcpy(ddata->address, address, sizeof(*address)); - *ddata->port = port; - ddata->resolved = true; + DEBUG("Avahi Resolver : service '%s' of type '%s' in domain '%s':\n", + name, type, domain); + DEBUG("\t\t%s:%u (%s)\n", host_name, port, ddata->addr_str); + + break; + } + } avahi_service_resolver_free(resolver); } @@ -59,49 +134,90 @@ static void __avahi_browser_cb(AvahiServiceBrowser *browser, const char *type, const char *domain, __notused AvahiLookupResultFlags flags, void *d) { - struct avahi_discovery_data *ddata = (struct avahi_discovery_data *) d; + struct dns_sd_discovery_data *ddata = (struct dns_sd_discovery_data *) d; struct AvahiClient *client = avahi_service_browser_get_client(browser); + int i; + + if (!browser) { + ERROR("Fatal Error in Avahi Browser\n"); + return; + } switch (event) { - default: + case AVAHI_BROWSER_REMOVE: + DEBUG("Avahi Browser : REMOVE : " + "service '%s' of type '%s' in domain '%s'\n", + name, type, domain); + break; case AVAHI_BROWSER_NEW: - ddata->found = !!avahi_service_resolver_new(client, iface, + DEBUG("Avahi Browser : NEW: " + "service '%s' of type '%s' in domain '%s'\n", + name, type, domain); + if(!avahi_service_resolver_new(client, iface, proto, name, type, domain, AVAHI_PROTO_UNSPEC, 0, - __avahi_resolver_cb, d); + __avahi_resolver_cb, d)) { + ERROR("Failed to resolve service '%s\n", name); + } else { + iio_mutex_lock(ddata->lock); + ddata->found++; + iio_mutex_unlock(ddata->lock); + } break; case AVAHI_BROWSER_ALL_FOR_NOW: - if (ddata->found) { - while (!ddata->resolved) { - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = 4000000; - nanosleep(&ts, NULL); - } + /* Wait for a max of 1 second */ + i = 0; + DEBUG("Avahi Browser : ALL_FOR_NOW Browser : %d, Resolved : %d\n", + ddata->found, ddata->resolved); + /* 200 * 5ms = wait 1 second */ + while ((ddata->found != ddata->resolved) && i <= 200) { + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 5e6; /* 5ms in ns*/ + nanosleep(&ts, NULL); + i++; } - /* fall-through */ + avahi_simple_poll_quit(ddata->poll); + break; case AVAHI_BROWSER_FAILURE: + DEBUG("Avahi Browser : FAILURE\n"); avahi_simple_poll_quit(ddata->poll); - /* fall-through */ + break; case AVAHI_BROWSER_CACHE_EXHAUSTED: + DEBUG("Avahi Browser : CACHE_EXHAUSTED\n"); break; } } -int discover_host(char *addr_str, size_t addr_len, uint16_t *port) +/* + * This creates the linked lists, tests it (make sure a context is there) + * and returns. The structure must be freed with free_all_discovery_data(); + * The returned value is zero on success, negative error code on failure. + */ + +int dnssd_find_hosts(struct dns_sd_discovery_data **ddata) { - struct avahi_discovery_data ddata; - int ret = 0; - AvahiAddress address; + struct dns_sd_discovery_data *d; AvahiClient *client; AvahiServiceBrowser *browser; - AvahiSimplePoll *poll = avahi_simple_poll_new(); + int ret = 0; + + if (new_discovery_data(&d) < 0) + return -ENOMEM; + + d->lock = iio_mutex_create(); + if (!d->lock) { + dnssd_free_all_discovery_data(d); + return -ENOMEM; + } - memset(&address, 0, sizeof(address)); - if (!poll) + d->poll = avahi_simple_poll_new(); + if (!d->poll) { + dnssd_free_all_discovery_data(d); return -ENOMEM; + } - client = avahi_client_new(avahi_simple_poll_get(poll), + client = avahi_client_new(avahi_simple_poll_get(d->poll), 0, NULL, NULL, &ret); if (!client) { ERROR("Unable to create Avahi DNS-SD client :%s\n", @@ -109,14 +225,9 @@ int discover_host(char *addr_str, size_t addr_len, uint16_t *port) goto err_free_poll; } - memset(&ddata, 0, sizeof(ddata)); - ddata.poll = poll; - ddata.address = &address; - ddata.port = port; - browser = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - "_iio._tcp", NULL, 0, __avahi_browser_cb, &ddata); + "_iio._tcp", NULL, 0, __avahi_browser_cb, d); if (!browser) { ret = avahi_client_errno(client); ERROR("Unable to create Avahi DNS-SD browser: %s\n", @@ -125,18 +236,21 @@ int discover_host(char *addr_str, size_t addr_len, uint16_t *port) } DEBUG("Trying to discover host\n"); - avahi_simple_poll_loop(poll); - - if (!ddata.found) - ret = ENXIO; + avahi_simple_poll_loop(d->poll); - if (ret == 0) - avahi_address_snprint(addr_str, addr_len, &address); + if (d->resolved) { + port_knock_discovery_data(&d); + remove_dup_discovery_data(&d); + } else + ret = -ENXIO; avahi_service_browser_free(browser); err_free_client: avahi_client_free(client); err_free_poll: - avahi_simple_poll_free(poll); - return -ret; /* we want a negative error code */ + avahi_simple_poll_free(d->poll); + iio_mutex_destroy(d->lock); + *ddata = d; + + return ret; } diff --git a/dns_sd_bonjour.c b/dns_sd_bonjour.c index eb44a56e0..00a337175 100644 --- a/dns_sd_bonjour.c +++ b/dns_sd_bonjour.c @@ -3,6 +3,7 @@ * * Copyright (C) 2020 Matej Kenda. * Author: Matej Kenda gmail.com> + * Robin Getz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,26 +17,34 @@ * * */ -#include -#include -#include -#include - #include +#include "iio-lock.h" +#include "iio-private.h" +#include "network.h" #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 int new_discovery_data(struct dns_sd_discovery_data **data) +{ + struct dns_sd_discovery_data *d; + + d = zalloc(sizeof(struct dns_sd_discovery_data)); + if (!d) + return -ENOMEM; + + *data = d; + return 0; +} + +void dnssd_free_discovery_data(struct dns_sd_discovery_data *d) +{ + free(d->hostname); + free(d); +} static void __cfnet_browser_cb ( CFNetServiceBrowserRef browser, @@ -44,124 +53,198 @@ static void __cfnet_browser_cb ( CFStreamError* error, void* info) { - CFStreamError anError; - + CFStreamError anError; + if ((flags & kCFNetServiceFlagIsDomain) != 0) { - ERROR("DNS SD: FATAL. Callback called for domain, not service.\n"); + ERROR("DNS SD: FATAL! Callback called for domain, not service.\n"); goto stop_browsing; } - struct cfnet_discovery_data *dd = (struct cfnet_discovery_data *)info; + struct dns_sd_discovery_data *dd = (struct dns_sd_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; + + if ((flags & kCFNetServiceFlagRemove) != 0) { + DEBUG("DNS SD: Callback to remove service. Ignore.\n"); + return; } + iio_mutex_lock(dd->lock); + const CFNetServiceRef netService = (CFNetServiceRef)domainOrService; if (netService == NULL) { - ERROR("DNS SD: Net service is null.\n"); - goto stop_browsing; + DEBUG("DNS SD: Net service is null.\n"); + goto verify_flags; } if (!CFNetServiceResolveWithTimeout(netService, 10.0, &anError)) { - ERROR("DNS SD: Resolve error: %ld.%d\n", anError.domain, anError.error); - goto stop_browsing; + DEBUG("DNS SD: Resolve error: %ld.%d\n", anError.domain, anError.error); + goto exit; } - dd->port = CFNetServiceGetPortNumber(netService); + CFStringRef targetHost = CFNetServiceGetTargetHost(netService); + if (targetHost == NULL) { + DEBUG("DNS SD: No valid target host for service.\n"); + goto exit; + } + + char hostname[MAXHOSTNAMELEN]; + if (!CFStringGetCString(targetHost, hostname, sizeof(hostname), kCFStringEncodingASCII)) { + ERROR("DNS SD: Could not translate hostname\n"); + goto exit; + } + + CFStringRef svcName = CFNetServiceGetName(netService); + char name[MAXHOSTNAMELEN]; + if (!CFStringGetCString(svcName, name, sizeof(name), kCFStringEncodingASCII)) { + ERROR("DNS SD: Could not translate service name\n"); + goto exit; + } + + SInt32 port = CFNetServiceGetPortNumber(netService); CFArrayRef addrArr = CFNetServiceGetAddressing(netService); if (addrArr == NULL) { - ERROR("DNS SD: No valid addresses for service.\n"); - goto stop_browsing; + WARNING("DNS SD: No valid addresses for service %s.\n", name); + goto exit; } + bool have_v4 = FALSE; + bool have_v6 = FALSE; + char address_v4[DNS_SD_ADDRESS_STR_MAX+1] = ""; + char address_v6[DNS_SD_ADDRESS_STR_MAX+1] = ""; for (CFIndex i = 0; i < CFArrayGetCount(addrArr); i++) { struct sockaddr_in *sa = (struct sockaddr_in *) - CFDataGetBytePtr(CFArrayGetValueAtIndex(addrArr, i)); + 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; + address_v4, sizeof(address_v4))) { + 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; + address_v6, sizeof(address_v6))) { + have_v6 = TRUE; } } } + if (!have_v4 && !have_v6) { + WARNING("DNS SD: Can't resolve valid address for service %s.\n", name); + goto exit; + } + + /* Set properties on the last element on the list. */ + while (dd->next) + dd = dd->next; + + dd->port = port; + dd->hostname = strdup(hostname); + if (have_v4) { + strncpy(dd->addr_str, address_v4, sizeof(dd->addr_str)); + } else if(have_v6) { + strncpy(dd->addr_str, address_v6, sizeof(dd->addr_str)); + } + + DEBUG("DNS SD: added %s (%s:%d)\n", hostname, dd->addr_str, port); + + if (have_v4 || have_v6) { + // A list entry was filled, prepare new item on the list. + if (!new_discovery_data(&dd->next)) { + /* duplicate lock */ + dd->next->lock = dd->lock; + } else { + ERROR("DNS SD Bonjour Resolver : memory failure\n"); + } + } + +verify_flags: if ((flags & kCFNetServiceFlagMoreComing) == 0) { DEBUG("DNS SD: No more entries coming.\n"); - goto stop_browsing; + CFNetServiceBrowserStopSearch(browser, &anError); } - + +exit: + iio_mutex_unlock(dd->lock); return; stop_browsing: - CFNetServiceBrowserStopSearch(browser, &anError); } -int discover_host(char *addr_str, size_t addr_len, uint16_t *port) +int dnssd_find_hosts(struct dns_sd_discovery_data ** ddata) { int ret = 0; - - struct cfnet_discovery_data ddata = { FALSE, FALSE }; - CFNetServiceClientContext clientContext = { 0, &ddata, NULL, NULL, NULL }; - CFStreamError error; - Boolean result; + struct dns_sd_discovery_data *d; - CFStringRef type = CFSTR("_iio._tcp."); - CFStringRef domain = CFSTR(""); + DEBUG("DNS SD: Start service discovery.\n"); - DEBUG("DNS SD: Resolving hostname."); + if (new_discovery_data(&d) < 0) { + return -ENOMEM; + } - CFNetServiceBrowserRef gServiceBrowserRef = CFNetServiceBrowserCreate( + d->lock = iio_mutex_create(); + if (!d->lock) { + dnssd_free_all_discovery_data(d); + return -ENOMEM; + } + + CFNetServiceClientContext clientContext = { 0, d, NULL, NULL, NULL }; + CFNetServiceBrowserRef serviceBrowser = CFNetServiceBrowserCreate( kCFAllocatorDefault, __cfnet_browser_cb, &clientContext); - if (gServiceBrowserRef == NULL) { + if (serviceBrowser == NULL) { ERROR("DNS SD: Failed to create service browser.\n"); - ret = ENOMEM; + dnssd_free_all_discovery_data(d); + ret = -ENOMEM; goto exit; } - result = CFNetServiceBrowserSearchForServices( - gServiceBrowserRef, domain, type, &error); + + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + CFNetServiceBrowserScheduleWithRunLoop(serviceBrowser, runLoop, kCFRunLoopDefaultMode); + + CFStringRef type = CFSTR("_iio._tcp."); + CFStringRef domain = CFSTR(""); + CFStreamError error; + Boolean result = CFNetServiceBrowserSearchForServices(serviceBrowser, 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; + + ret = -ENXIO; + } else { + CFRunLoopRunResult runRes = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, TRUE); + + if (runRes != kCFRunLoopRunHandledSource && runRes != kCFRunLoopRunTimedOut) { + if (runRes == kCFRunLoopRunFinished) + ERROR("DSN SD: CFRunLoopRunInMode completed kCFRunLoopRunFinished (%d)\n", runRes); + else if (runRes == kCFRunLoopRunStopped) + ERROR("DSN SD: CFRunLoopRunInMode completed kCFRunLoopRunStopped (%d)\n", runRes); + else + ERROR("DSN SD: CFRunLoopRunInMode completed for unknown reason (%d)\n", runRes); + } else { + if (runRes == kCFRunLoopRunHandledSource) + DEBUG("DSN SD: CFRunLoopRunInMode completed kCFRunLoopRunHandledSource (%d)\n", runRes); + else + DEBUG("DSN SD: CFRunLoopRunInMode completed kCFRunLoopRunTimedOut (%d)\n", runRes); + } + + port_knock_discovery_data(&d); + remove_dup_discovery_data(&d); + *ddata = d; } -free_browser: + CFNetServiceBrowserUnscheduleFromRunLoop(serviceBrowser, runLoop, kCFRunLoopDefaultMode); + CFRelease(serviceBrowser); + serviceBrowser = NULL; - CFRelease(gServiceBrowserRef); - gServiceBrowserRef = NULL; + DEBUG("DNS SD: Completed service discovery, return code : %d\n", ret); exit: + iio_mutex_destroy(d->lock); return ret; } - diff --git a/network.c b/network.c index 07156ddfc..839df52e0 100644 --- a/network.c +++ b/network.c @@ -18,56 +18,16 @@ #include "iio-config.h" #include "iio-private.h" +#include "network.h" #include "iio-lock.h" #include "iiod-client.h" - -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#define close(s) closesocket(s) - -/* winsock2.h defines ERROR, we don't want that */ -#undef ERROR - -#else /* _WIN32 */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#endif /* _WIN32 */ - #include "debug.h" -#define DEFAULT_TIMEOUT_MS 5000 - #define _STRINGIFY(x) #x #define STRINGIFY(x) _STRINGIFY(x) -#define IIOD_PORT 30431 #define IIOD_PORT_STR STRINGIFY(IIOD_PORT) -#ifdef HAVE_DNS_SD -extern int discover_host(char *addr_str, size_t addr_len, uint16_t *port); -#ifdef HAVE_AVAHI -#include -#define DNS_SD_ADDRESS_STR_MAX AVAHI_ADDRESS_STR_MAX -#else -#define DNS_SD_ADDRESS_STR_MAX (40) -#endif -#endif - struct iio_network_io_context { int fd; @@ -594,7 +554,7 @@ static int do_connect(int fd, const struct addrinfo *addrinfo, return 0; } -static int create_socket(const struct addrinfo *addrinfo, unsigned int timeout) +int create_socket(const struct addrinfo *addrinfo, unsigned int timeout) { int ret, fd, yes = 1; @@ -1348,7 +1308,7 @@ struct iio_context * network_create_context(const char *host) char port_str[6]; uint16_t port = IIOD_PORT; - ret = discover_host(addr_str, sizeof(addr_str), &port); + ret = dnssd_discover_host(addr_str, sizeof(addr_str), &port); if (ret < 0) { char buf[1024]; iio_strerror(-ret, buf, sizeof(buf)); @@ -1356,6 +1316,11 @@ struct iio_context * network_create_context(const char *host) errno = -ret; return NULL; } + if (!strlen(addr_str)) { + DEBUG("No DNS Service Discovery hosts on network\n"); + errno = ENOENT; + return NULL; + } iio_snprintf(port_str, sizeof(port_str), "%hu", port); ret = getaddrinfo(addr_str, port_str, &hints, &res); diff --git a/network.h b/network.h new file mode 100644 index 000000000..4a2c76c4e --- /dev/null +++ b/network.h @@ -0,0 +1,124 @@ +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2014-2020 Analog Devices, Inc. + * Author: Paul Cercueil + * Robin Getz + * + * 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. + * + * */ + +#ifndef __IIO_NET_PRIVATE_H +#define __IIO_NET_PRIVATE_H + +#include "iio-config.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#define close(s) closesocket(s) +/* winsock2.h defines ERROR, we don't want that */ +#undef ERROR +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN (MAX_COMPUTERNAME_LENGTH+1) +#endif /* MAXHOSTNAMELEN */ +#else /* !_WIN32 */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif /* _WIN32 */ + +#ifdef HAVE_DNS_SD +#ifdef HAVE_AVAHI +#include +#include +#include +#include +#include +#include +#define DNS_SD_ADDRESS_STR_MAX AVAHI_ADDRESS_STR_MAX +#else /* !HAVE_AVAHI */ +#define DNS_SD_ADDRESS_STR_MAX (40) /* IPv6 Max = 4*8 + 7 + 1 for NUL */ +#endif /* HAVE_AVAHI */ + +/* MacOS doesn't include ENOMEDIUM (No medium found) like Linux does */ +#ifndef ENOMEDIUM +#define ENOMEDIUM ENOENT +#endif + +/* Common structure which all dns_sd_[*] files fill out + * Anything that is dynamically allocated (malloc) needs to be managed + */ +struct dns_sd_discovery_data { + struct iio_mutex *lock; +#ifdef HAVE_AVAHI + AvahiSimplePoll *poll; + AvahiAddress *address; + uint16_t found, resolved; +#endif /* HAVE_AVAHI */ + char addr_str[DNS_SD_ADDRESS_STR_MAX]; + char *hostname; + uint16_t port; + struct dns_sd_discovery_data *next; +}; + + +/* This functions is implemented in network.c, but used in dns_sd.c + */ +int create_socket(const struct addrinfo *addrinfo, unsigned int timeout); + +/* These fuctions are common, and implemented in dns_sd_[*].c based on the + * implementations: avahi (linux), bonjour (mac), or ServiceDiscovery (Win10) + */ + +/* Resolves all IIO hosts on the available networks, and passes back a linked list */ +int dnssd_find_hosts(struct dns_sd_discovery_data ** ddata); + +/* Frees memory of one entry on the list */ +void dnssd_free_discovery_data(struct dns_sd_discovery_data *d); + +/* Deallocates complete list of discovery data */ +void dnssd_free_all_discovery_data(struct dns_sd_discovery_data *d); + +/* These functions are common, and found in dns_sd.c, but are used in the + * dns_sd_[*].c implementations or network.c + */ + +/* Passed back the first (random) IIOD service resolved by DNS DS. */ +int dnssd_discover_host(char *addr_str, size_t addr_len, uint16_t *port); + +/* remove duplicates from the list */ +void remove_dup_discovery_data(struct dns_sd_discovery_data **ddata); + +/* port knocks */ +void port_knock_discovery_data(struct dns_sd_discovery_data **ddata); + +#endif /* HAVE_DNS_SD */ + +/* Used everywhere */ +#define DEFAULT_TIMEOUT_MS 5000 +#define IIOD_PORT 30431 + +#endif /* __IIO_NET_PRIVATE_H */