diff --git a/iiod/CMakeLists.txt b/iiod/CMakeLists.txt index 61351a5b0..5f0c5b774 100644 --- a/iiod/CMakeLists.txt +++ b/iiod/CMakeLists.txt @@ -60,6 +60,10 @@ endif () add_definitions(-D_GNU_SOURCE=1) +if (HAVE_AVAHI) + target_sources(iiod PRIVATE dns-sd.c) +endif() + if(NOT SKIP_INSTALL_ALL) install(TARGETS iiod RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) endif() diff --git a/iiod/dns-sd.c b/iiod/dns-sd.c new file mode 100644 index 000000000..b153a991b --- /dev/null +++ b/iiod/dns-sd.c @@ -0,0 +1,334 @@ +// SPDX-license-identifier: LGPL-v2-or-later +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2021 Analog Devices, Inc. + * Author: Paul Cercueil + */ + +#include "dns-sd.h" +#include "ops.h" +#include "thread-pool.h" + +#include "../debug.h" +#include "../iio.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/*** + * Parts of the avahi code are borrowed from the client-publish-service.c + * https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html + * which is an example in the avahi library. Copyright Lennart Poettering + * released under the LGPL 2.1 or (at your option) any later version. + */ + +/* Global Data */ + +static struct avahi_data { + AvahiThreadedPoll *poll; + AvahiClient *client; + AvahiEntryGroup *group; + char * name; +} avahi; + +static void create_services(AvahiClient *c); +static AvahiClient * client_new(void); + +static void client_free() +{ + /* This also frees the entry group, if any. */ + if (avahi.client) { + avahi_client_free(avahi.client); + avahi.client = NULL; + avahi.group = NULL; + } +} + +static void shutdown_avahi() +{ + /* Stop the avahi client, if it's running. */ + if (avahi.poll) + avahi_threaded_poll_stop(avahi.poll); + + /* Clean up the avahi objects. The order is significant. */ + client_free(); + if (avahi.poll) { + avahi_threaded_poll_free(avahi.poll); + avahi.poll = NULL; + } + if (avahi.name) { + IIO_INFO("Avahi: Removing service '%s'\n", avahi.name); + avahi_free(avahi.name); + avahi.name = NULL; + } +} + +static void __avahi_group_cb(AvahiEntryGroup *group, + AvahiEntryGroupState state, void *d) +{ + /* Called whenever the entry group state changes */ + if (!group) { + IIO_ERROR("__avahi_group_cb with no valid group\n"); + return; + } + + avahi.group = group; + + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED : + IIO_INFO("Avahi: Service '%s' successfully established.\n", + avahi.name); + break; + case AVAHI_ENTRY_GROUP_COLLISION : { + char *n; + /* A service name collision, pick a new name */ + n = avahi_alternative_service_name(avahi.name); + avahi_free(avahi.name); + avahi.name = n; + IIO_INFO("Avahi: Group Service name collision, renaming service to '%s'\n", + avahi.name); + create_services(avahi_entry_group_get_client(group)); + break; + } + case AVAHI_ENTRY_GROUP_FAILURE : + IIO_ERROR("Entry group failure: %s\n", + avahi_strerror(avahi_client_errno( + avahi_entry_group_get_client(group)))); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + /* This is normal, + * since we commit things in the create_services() + */ + IIO_DEBUG("Avahi: Group uncommitted\n"); + break; + case AVAHI_ENTRY_GROUP_REGISTERING: + IIO_DEBUG("Avahi: Group registering\n"); + break; + } +} + +static void __avahi_client_cb(AvahiClient *client, + AvahiClientState state, void *d) +{ + if (!client) { + IIO_ERROR("__avahi_client_cb with no valid client\n"); + return; + } + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + /* Same as AVAHI_SERVER_RUNNING */ + IIO_DEBUG("Avahi: create services\n"); + /* The server has startup successfully, so create our services */ + create_services(client); + break; + case AVAHI_CLIENT_FAILURE: + if (avahi_client_errno(client) != AVAHI_ERR_DISCONNECTED) { + IIO_ERROR("Avahi: Client failure: %s\n", + avahi_strerror(avahi_client_errno(client))); + break; + } + IIO_INFO("Avahi: server disconnected\n"); + avahi_client_free(client); + avahi.group = NULL; + avahi.client = client_new(); + break; + case AVAHI_CLIENT_S_COLLISION: + /* Same as AVAHI_SERVER_COLLISION */ + /* When the server is back in AVAHI_SERVER_RUNNING state + * we will register them again with the new host name. */ + IIO_DEBUG("Avahi: Client collision\n"); + /* Let's drop our registered services.*/ + if (avahi.group) + avahi_entry_group_reset(avahi.group); + break; + case AVAHI_CLIENT_S_REGISTERING: + /* Same as AVAHI_SERVER_REGISTERING */ + IIO_DEBUG("Avahi: Client group reset\n"); + if (avahi.group) + avahi_entry_group_reset(avahi.group); + break; + case AVAHI_CLIENT_CONNECTING: + IIO_DEBUG("Avahi: Client Connecting\n"); + break; + } + + /* NOTE: group is freed by avahi_client_free */ +} + +static AvahiClient * client_new(void) +{ + int ret; + AvahiClient * client; + + client = avahi_client_new(avahi_threaded_poll_get(avahi.poll), + AVAHI_CLIENT_NO_FAIL, __avahi_client_cb, NULL, &ret); + + /* No Daemon is handled by the avahi_start thread */ + if (!client && ret != AVAHI_ERR_NO_DAEMON) { + IIO_ERROR("Avahi: failure creating client: %s (%d)\n", + avahi_strerror(ret), ret); + } + + return client; +} + + +static void create_services(AvahiClient *c) +{ + int ret; + + if (!c) { + IIO_ERROR("create_services called with no valid client\n"); + goto fail; + } + + if (!avahi.group) { + avahi.group = avahi_entry_group_new(c, __avahi_group_cb, NULL); + if (!avahi.group) { + IIO_ERROR("avahi_entry_group_new() failed: %s\n", + avahi_strerror(avahi_client_errno(c))); + goto fail; + } + } + + if (!avahi_entry_group_is_empty(avahi.group)) { + IIO_DEBUG("Avahi group not empty\n"); + return; + } + + ret = avahi_entry_group_add_service(avahi.group, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + 0, avahi.name, "_iio._tcp", NULL, NULL, IIOD_PORT, NULL); + if (ret < 0) { + if (ret == AVAHI_ERR_COLLISION) { + char *n; + n = avahi_alternative_service_name(avahi.name); + avahi_free(avahi.name); + avahi.name = n; + IIO_DEBUG("Service name collision, renaming service to '%s'\n", + avahi.name); + avahi_entry_group_reset(avahi.group); + create_services(c); + return; + } + IIO_ERROR("Failed to add _iio._tcp service: %s\n", avahi_strerror(ret)); + goto fail; + } + + ret = avahi_entry_group_commit(avahi.group); + if (ret < 0) { + IIO_ERROR("Failed to commit entry group: %s\n", avahi_strerror(ret)); + goto fail; + } + + IIO_INFO("Avahi: Registered '%s' to ZeroConf server %s\n", + avahi.name, avahi_client_get_version_string(c)); + + return; + +fail: + avahi_entry_group_reset(avahi.group); +} + +#define IIOD_ON "iiod on " + +static void start_avahi_thd(struct thread_pool *pool, void *d) +{ + + struct pollfd pfd[2]; + int ret = ENOMEM; + char label[AVAHI_LABEL_MAX]; + char host[AVAHI_LABEL_MAX - sizeof(IIOD_ON)]; + struct timespec ts; + ts.tv_nsec = 0; + ts.tv_sec = 1; + + pfd[1].fd = thread_pool_get_poll_fd(pool); + pfd[1].events = POLLIN; + pfd[1].revents = 0; + + while(true) { + if (pfd[1].revents & POLLIN) /* STOP event */ + break; + + ret = gethostname(host, sizeof(host)); + IIO_ERROR("host %s\n", host); + if (ret || !strncmp(host, "none", sizeof("none") - 1)) + goto again; + + iio_snprintf(label, sizeof(label), "%s%s", IIOD_ON, host); + + if (!avahi.name) + avahi.name = avahi_strdup(label); + if (!avahi.name) + break; + + if (!avahi.poll) + avahi.poll = avahi_threaded_poll_new(); + if (!avahi.poll) { + goto again; + } + + if (!avahi.client) + avahi.client = client_new(); + if (avahi.client) + break; +again: + IIO_INFO("Avahi didn't start, try again later\n"); + nanosleep(&ts, NULL); + ts.tv_sec++; + /* If it hasn't started in 10 times over 60 seconds, + * it is not going to, so stop + */ + if (ts.tv_sec >= 11) + break; + } + + if (avahi.client && avahi.poll) { + avahi_threaded_poll_start(avahi.poll); + IIO_INFO("Avahi: Started.\n"); + } else { + shutdown_avahi(); + IIO_INFO("Avahi: Failed to start.\n"); + } +} + +void start_avahi(struct thread_pool *pool) +{ + int ret; + char err_str[1024]; + + IIO_INFO("Attempting to start Avahi\n"); + + avahi.poll = NULL; + avahi.client = NULL; + avahi.group = NULL; + avahi.name = NULL; + + /* In case dbus, or avahi deamon are not started, we create a thread + * that tries a few times to attach, if it can't within the first + * minute, it gives up. + */ + ret = thread_pool_add_thread(pool, start_avahi_thd, NULL, "avahi_thd"); + if (ret) { + iio_strerror(ret, err_str, sizeof(err_str)); + IIO_ERROR("Failed to create new Avahi thread: %s\n", + err_str); + } +} + +void stop_avahi(void) +{ + shutdown_avahi(); + IIO_INFO("Avahi: Stopped\n"); +} diff --git a/iiod/dns-sd.h b/iiod/dns-sd.h new file mode 100644 index 000000000..c00c38c5d --- /dev/null +++ b/iiod/dns-sd.h @@ -0,0 +1,17 @@ +/* SPDX-license-identifier: LGPL-v2-or-later */ +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2021 Analog Devices, Inc. + * Author: Paul Cercueil + */ + +#ifndef __IIOD_DNS_SD_H +#define __IIOD_DNS_SD_H + +struct thread_pool; + +void start_avahi(struct thread_pool *pool); +void stop_avahi(void); + +#endif /* __IIOD_DNS_SD_H */ diff --git a/iiod/iiod.c b/iiod/iiod.c index 0f0adaef9..ea6cd59d6 100644 --- a/iiod/iiod.c +++ b/iiod/iiod.c @@ -19,6 +19,7 @@ #include "../debug.h" #include "../iio.h" #include "../iio-config.h" +#include "dns-sd.h" #include "ops.h" #include "thread-pool.h" @@ -38,20 +39,8 @@ #include #include -#ifdef HAVE_AVAHI -#include -#include -#include -#include -#include -#include -#include -#endif - #define MY_NAME "iiod" -#define IIOD_PORT 30431 - struct client_data { int fd; bool debug; @@ -109,317 +98,6 @@ static const char *options_descriptions[] = { "Specify the number of USB pipes (ep couples) to use", }; -#ifdef HAVE_AVAHI - -/*** - * Parts of the avahi code are borrowed from the client-publish-service.c - * https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html - * which is an example in the avahi library. Copyright Lennart Poettering - * released under the LGPL 2.1 or (at your option) any later version. - */ - -/* Global Data */ - -static struct avahi_data { - AvahiThreadedPoll *poll; - AvahiClient *client; - AvahiEntryGroup *group; - char * name; -} avahi; - -static void create_services(AvahiClient *c); -static AvahiClient * client_new(void); - -static void client_free() -{ - /* This also frees the entry group, if any. */ - if (avahi.client) { - avahi_client_free(avahi.client); - avahi.client = NULL; - avahi.group = NULL; - } -} - -static void shutdown_avahi() -{ - /* Stop the avahi client, if it's running. */ - if (avahi.poll) - avahi_threaded_poll_stop(avahi.poll); - - /* Clean up the avahi objects. The order is significant. */ - client_free(); - if (avahi.poll) { - avahi_threaded_poll_free(avahi.poll); - avahi.poll = NULL; - } - if (avahi.name) { - IIO_INFO("Avahi: Removing service '%s'\n", avahi.name); - avahi_free(avahi.name); - avahi.name = NULL; - } -} - -static void __avahi_group_cb(AvahiEntryGroup *group, - AvahiEntryGroupState state, void *d) -{ - /* Called whenever the entry group state changes */ - if (!group) { - IIO_ERROR("__avahi_group_cb with no valid group\n"); - return; - } - - avahi.group = group; - - switch (state) { - case AVAHI_ENTRY_GROUP_ESTABLISHED : - IIO_INFO("Avahi: Service '%s' successfully established.\n", - avahi.name); - break; - case AVAHI_ENTRY_GROUP_COLLISION : { - char *n; - /* A service name collision, pick a new name */ - n = avahi_alternative_service_name(avahi.name); - avahi_free(avahi.name); - avahi.name = n; - IIO_INFO("Avahi: Group Service name collision, renaming service to '%s'\n", - avahi.name); - create_services(avahi_entry_group_get_client(group)); - break; - } - case AVAHI_ENTRY_GROUP_FAILURE : - IIO_ERROR("Entry group failure: %s\n", - avahi_strerror(avahi_client_errno( - avahi_entry_group_get_client(group)))); - break; - case AVAHI_ENTRY_GROUP_UNCOMMITED: - /* This is normal, - * since we commit things in the create_services() - */ - IIO_DEBUG("Avahi: Group uncommitted\n"); - break; - case AVAHI_ENTRY_GROUP_REGISTERING: - IIO_DEBUG("Avahi: Group registering\n"); - break; - } -} - -static void __avahi_client_cb(AvahiClient *client, - AvahiClientState state, void *d) -{ - if (!client) { - IIO_ERROR("__avahi_client_cb with no valid client\n"); - return; - } - - switch (state) { - case AVAHI_CLIENT_S_RUNNING: - /* Same as AVAHI_SERVER_RUNNING */ - IIO_DEBUG("Avahi: create services\n"); - /* The server has startup successfully, so create our services */ - create_services(client); - break; - case AVAHI_CLIENT_FAILURE: - if (avahi_client_errno(client) != AVAHI_ERR_DISCONNECTED) { - IIO_ERROR("Avahi: Client failure: %s\n", - avahi_strerror(avahi_client_errno(client))); - break; - } - IIO_INFO("Avahi: server disconnected\n"); - avahi_client_free(client); - avahi.group = NULL; - avahi.client = client_new(); - break; - case AVAHI_CLIENT_S_COLLISION: - /* Same as AVAHI_SERVER_COLLISION */ - /* When the server is back in AVAHI_SERVER_RUNNING state - * we will register them again with the new host name. */ - IIO_DEBUG("Avahi: Client collision\n"); - /* Let's drop our registered services.*/ - if (avahi.group) - avahi_entry_group_reset(avahi.group); - break; - case AVAHI_CLIENT_S_REGISTERING: - /* Same as AVAHI_SERVER_REGISTERING */ - IIO_DEBUG("Avahi: Client group reset\n"); - if (avahi.group) - avahi_entry_group_reset(avahi.group); - break; - case AVAHI_CLIENT_CONNECTING: - IIO_DEBUG("Avahi: Client Connecting\n"); - break; - } - - /* NOTE: group is freed by avahi_client_free */ -} - -static AvahiClient * client_new(void) -{ - int ret; - AvahiClient * client; - - client = avahi_client_new(avahi_threaded_poll_get(avahi.poll), - AVAHI_CLIENT_NO_FAIL, __avahi_client_cb, NULL, &ret); - - /* No Daemon is handled by the avahi_start thread */ - if (!client && ret != AVAHI_ERR_NO_DAEMON) { - IIO_ERROR("Avahi: failure creating client: %s (%d)\n", - avahi_strerror(ret), ret); - } - - return client; -} - - -static void create_services(AvahiClient *c) -{ - int ret; - - if (!c) { - IIO_ERROR("create_services called with no valid client\n"); - goto fail; - } - - if (!avahi.group) { - avahi.group = avahi_entry_group_new(c, __avahi_group_cb, NULL); - if (!avahi.group) { - IIO_ERROR("avahi_entry_group_new() failed: %s\n", - avahi_strerror(avahi_client_errno(c))); - goto fail; - } - } - - if (!avahi_entry_group_is_empty(avahi.group)) { - IIO_DEBUG("Avahi group not empty\n"); - return; - } - - ret = avahi_entry_group_add_service(avahi.group, - AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - 0, avahi.name, "_iio._tcp", NULL, NULL, IIOD_PORT, NULL); - if (ret < 0) { - if (ret == AVAHI_ERR_COLLISION) { - char *n; - n = avahi_alternative_service_name(avahi.name); - avahi_free(avahi.name); - avahi.name = n; - IIO_DEBUG("Service name collision, renaming service to '%s'\n", - avahi.name); - avahi_entry_group_reset(avahi.group); - create_services(c); - return; - } - IIO_ERROR("Failed to add _iio._tcp service: %s\n", avahi_strerror(ret)); - goto fail; - } - - ret = avahi_entry_group_commit(avahi.group); - if (ret < 0) { - IIO_ERROR("Failed to commit entry group: %s\n", avahi_strerror(ret)); - goto fail; - } - - IIO_INFO("Avahi: Registered '%s' to ZeroConf server %s\n", - avahi.name, avahi_client_get_version_string(c)); - - return; - -fail: - avahi_entry_group_reset(avahi.group); -} - -#define IIOD_ON "iiod on " - -static void start_avahi_thd(struct thread_pool *pool, void *d) -{ - - struct pollfd pfd[2]; - int ret = ENOMEM; - char label[AVAHI_LABEL_MAX]; - char host[AVAHI_LABEL_MAX - sizeof(IIOD_ON)]; - struct timespec ts; - ts.tv_nsec = 0; - ts.tv_sec = 1; - - pfd[1].fd = thread_pool_get_poll_fd(main_thread_pool); - pfd[1].events = POLLIN; - pfd[1].revents = 0; - - while(true) { - if (pfd[1].revents & POLLIN) /* STOP event */ - break; - - ret = gethostname(host, sizeof(host)); - IIO_ERROR("host %s\n", host); - if (ret || !strncmp(host, "none", sizeof("none") - 1)) - goto again; - - iio_snprintf(label, sizeof(label), "%s%s", IIOD_ON, host); - - if (!avahi.name) - avahi.name = avahi_strdup(label); - if (!avahi.name) - break; - - if (!avahi.poll) - avahi.poll = avahi_threaded_poll_new(); - if (!avahi.poll) { - goto again; - } - - if (!avahi.client) - avahi.client = client_new(); - if (avahi.client) - break; -again: - IIO_INFO("Avahi didn't start, try again later\n"); - nanosleep(&ts, NULL); - ts.tv_sec++; - /* If it hasn't started in 10 times over 60 seconds, - * it is not going to, so stop - */ - if (ts.tv_sec >= 11) - break; - } - - if (avahi.client && avahi.poll) { - avahi_threaded_poll_start(avahi.poll); - IIO_INFO("Avahi: Started.\n"); - } else { - shutdown_avahi(); - IIO_INFO("Avahi: Failed to start.\n"); - } -} - -static void start_avahi(void) -{ - int ret; - char err_str[1024]; - - IIO_INFO("Attempting to start Avahi\n"); - - avahi.poll = NULL; - avahi.client = NULL; - avahi.group = NULL; - avahi.name = NULL; - - /* In case dbus, or avahi deamon are not started, we create a thread - * that tries a few times to attach, if it can't within the first - * minute, it gives up. - */ - ret = thread_pool_add_thread(main_thread_pool, start_avahi_thd, NULL, "avahi_thd"); - if (ret) { - iio_strerror(ret, err_str, sizeof(err_str)); - IIO_ERROR("Failed to create new Avahi thread: %s\n", - err_str); - } -} -static void stop_avahi(void) -{ - shutdown_avahi(); - IIO_INFO("Avahi: Stopped\n"); -} -#endif /* HAVE_AVAHI */ - static void usage(void) { @@ -543,7 +221,7 @@ static int main_server(struct iio_context *ctx, bool debug) } #ifdef HAVE_AVAHI - start_avahi(); + start_avahi(main_thread_pool); #endif pfd[0].fd = fd; diff --git a/iiod/ops.h b/iiod/ops.h index 107e2caa7..86bd8321a 100644 --- a/iiod/ops.h +++ b/iiod/ops.h @@ -35,6 +35,8 @@ #include #endif +#define IIOD_PORT 30431 + #ifndef __bswap_constant_16 #define __bswap_constant_16(x) \ ((unsigned short int) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)))