diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e710f5..49466ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,10 @@ elseif(APPLE) set(CMAKE_MACOSX_RPATH ON) message(STATUS "RPATH support: ${CMAKE_MACOSX_RPATH}") + # just blindly enable dns-sd + set(USE_MACOS_DNSSD ON) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_MACOS_DNSSD") + elseif (WIN32) if(NOT VS2015 AND NOT VS2017) message(FATAL_ERROR "You must use Microsoft Visual Studio 2015 or 2017 as compiler") @@ -276,6 +280,11 @@ if(LINUX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_CM108") endif() + find_package(Avahi) + if(AVAHI_CLIENT_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_AVAHI_CLIENT") + endif() + elseif (NOT WIN32 AND NOT CYGWIN) find_package(Portaudio REQUIRED) if(PORTAUDIO_FOUND) diff --git a/README.md b/README.md index 51b2901..29554f9 100644 --- a/README.md +++ b/README.md @@ -131,15 +131,17 @@ On Debian / Ubuntu / Raspbian / Raspberry Pi OS: sudo apt-get install cmake sudo apt-get install libasound2-dev sudo apt-get install libudev-dev + sudo apt-get install libavahi-client-dev Or on Red Hat / Fedora / CentOS: - sudo yum install git - sudo yum install gcc - sudo yum install gcc-c++ - sudo yum install make + sudo yum install git + sudo yum install gcc + sudo yum install gcc-c++ + sudo yum install make sudo yum install alsa-lib-devel sudo yum install libudev-devel + sudo yum install avahi-devel CentOS 6 & 7 currently have cmake 2.8 but we need 3.1 or later. First you need to enable the EPEL repository. Add a symlink if you don't already have the older version and want to type cmake rather than cmake3. diff --git a/cmake/modules/FindAvahi.cmake b/cmake/modules/FindAvahi.cmake new file mode 100644 index 0000000..4a3cdd0 --- /dev/null +++ b/cmake/modules/FindAvahi.cmake @@ -0,0 +1,19 @@ + +find_library(AVAHI_COMMON_LIBRARY NAMES avahi-common PATHS ${PC_AVAHI_CLIENT_LIBRARY_DIRS}) +if(AVAHI_COMMON_LIBRARY) + set(AVAHI_COMMON_FOUND TRUE) +endif() + +find_library(AVAHI_CLIENT_LIBRARY NAMES avahi-client PATHS ${PC_AVAHI_CLIENT_LIBRARY_DIRS}) +if(AVAHI_CLIENT_LIBRARY) + set(AVAHI_CLIENT_FOUND TRUE) +endif() + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVAHI DEFAULT_MSG AVAHI_COMMON_FOUND AVAHI_CLIENT_FOUND) + +if (AVAHI_FOUND) + set(AVAHI_INCLUDE_DIRS ${AVAHI_UI_INCLUDE_DIR}) + set(AVAHI_LIBRARIES ${AVAHI_COMMON_LIBRARY} ${AVAHI_CLIENT_LIBRARY}) +endif() + +mark_as_advanced(AVAHI_INCLUDE_DIRS AVAHI_LIBRARIES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 46d3ac7..30efec0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,6 +96,12 @@ if(LINUX) cm108.c ) endif() + if(AVAHI_CLIENT_FOUND) + list(APPEND direwolf_SOURCES + dns_sd_common.c + dns_sd_avahi.c + ) + endif() elseif(WIN32 OR CYGWIN) # windows list(APPEND direwolf_SOURCES audio_win.c @@ -111,6 +117,12 @@ if(LINUX) list(APPEND direwolf_SOURCES audio_portaudio.c ) + if(USE_MACOS_DNSSD) + list(APPEND direwolf_SOURCES + dns_sd_common.c + dns_sd_macos.c + ) + endif() endif() add_executable(direwolf @@ -127,6 +139,7 @@ target_link_libraries(direwolf ${ALSA_LIBRARIES} ${UDEV_LIBRARIES} ${PORTAUDIO_LIBRARIES} + ${AVAHI_LIBRARIES} ) if(WIN32 OR CYGWIN) diff --git a/src/config.c b/src/config.c index 8588a8c..1cebe8b 100644 --- a/src/config.c +++ b/src/config.c @@ -856,6 +856,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_misc_config->enable_kiss_pt = 0; /* -p option */ p_misc_config->kiss_copy = 0; + p_misc_config->dns_sd_enabled = 1; + /* Defaults from http://info.aprs.net/index.php?title=SmartBeaconing */ p_misc_config->sb_configured = 0; /* TRUE if SmartBeaconing is configured. */ @@ -4564,6 +4566,43 @@ void config_init (char *fname, struct audio_s *p_audio_config, } +/* + * DNSSD - Enable or disable (1/0) dns-sd, DNS Service Discovery announcements + * DNSSDNAME - Set DNS-SD service name, defaults to "Dire Wolf on " + */ + + else if (strcasecmp(t, "DNSSD") == 0) { + int n; + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing integer value for DNSSD command.\n", line); + continue; + } + n = atoi(t); + if (n == 0 || n == 1) { + p_misc_config->dns_sd_enabled = n; + } else { + p_misc_config->dns_sd_enabled = 0; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid integer value for DNSSD. Disabling dns-sd.\n", line); + } + } + + else if (strcasecmp(t, "DNSSDNAME") == 0) { + t = split(NULL, 1); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing service name for DNSSDNAME.\n", line); + continue; + } + else { + strlcpy(p_misc_config->dns_sd_name, t, sizeof(p_misc_config->dns_sd_name)); + } + } + + + /* * GPSNMEA - Device name for reading from GPS receiver. */ diff --git a/src/config.h b/src/config.h index 562d307..438380d 100644 --- a/src/config.h +++ b/src/config.h @@ -88,6 +88,9 @@ struct misc_config_s { char log_path[80]; /* Either directory or full file name depending on above. */ + int dns_sd_enabled; /* DNS Service Discovery announcement enabled. */ + char dns_sd_name[64]; /* Name announced on dns-sd; defaults to "Dire Wolf on " */ + int sb_configured; /* TRUE if SmartBeaconing is configured. */ int sb_fast_speed; /* MPH */ int sb_fast_rate; /* seconds */ diff --git a/src/direwolf.c b/src/direwolf.c index 456b16f..d97e46c 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -125,6 +125,7 @@ #include "dtime_now.h" #include "fx25.h" #include "dwsock.h" +#include "dns_sd_dw.h" //static int idx_decoded = 0; @@ -944,6 +945,11 @@ int main (int argc, char *argv[]) server_init (&audio_config, &misc_config); kissnet_init (&misc_config); +#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD) + if (misc_config.kiss_port > 0 && misc_config.dns_sd_enabled) + dns_sd_announce(&misc_config); +#endif + /* * Create a pseudo terminal and KISS TNC emulator. */ diff --git a/src/dns_sd_avahi.c b/src/dns_sd_avahi.c new file mode 100644 index 0000000..81e9f19 --- /dev/null +++ b/src/dns_sd_avahi.c @@ -0,0 +1,259 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2020 Heikki Hannikainen, OH7LZB +// +// +// 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, see . + +/*------------------------------------------------------------------ + * + * Module: dns_sd_avahi.c + * + * Purpose: Announce the KISS over TCP service using DNS-SD via Avahi + * + * Description: + * + * Most people have typed in enough IP addresses and ports by now, and + * would rather just select an available TNC that is automatically + * discovered on the local network. Even more so on a mobile device + * such an Android or iOS phone or tablet. + * + * On Linux, the announcement can be made through Avahi, the mDNS + * framework commonly deployed on Linux systems. + * + * This is largely based on the publishing example of the Avahi library. + */ + +#ifdef USE_AVAHI_CLIENT + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "dns_sd_dw.h" +#include "dns_sd_common.h" +#include "textcolor.h" + +static AvahiEntryGroup *group = NULL; +static AvahiSimplePoll *simple_poll = NULL; +static AvahiClient *client = NULL; +static char *name = NULL; +static int kiss_port = 0; + +pthread_t avahi_thread; + +static void create_services(AvahiClient *c); + +#define PRINT_PREFIX "DNS-SD: Avahi: " + +static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) +{ + assert(g == group || group == NULL); + group = g; + + /* Called whenever the entry group state changes */ + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED : + /* The entry group has been established successfully */ + text_color_set(DW_COLOR_INFO); + dw_printf(PRINT_PREFIX "Service '%s' successfully registered.\n", name); + break; + case AVAHI_ENTRY_GROUP_COLLISION: { + char *n; + /* A service name collision with a remote service + * happened. Let's pick a new name. */ + n = avahi_alternative_service_name(name); + avahi_free(name); + name = n; + text_color_set(DW_COLOR_INFO); + dw_printf(PRINT_PREFIX "Service name collision, renaming service to '%s'\n", name); + /* And recreate the services */ + create_services(avahi_entry_group_get_client(g)); + break; + } + case AVAHI_ENTRY_GROUP_FAILURE: + text_color_set(DW_COLOR_ERROR); + dw_printf(PRINT_PREFIX "Entry group failure: %s\n", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + /* Some kind of failure happened while we were registering our services */ + avahi_simple_poll_quit(simple_poll); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + ; + } +} + +static void create_services(AvahiClient *c) +{ + char *n; + int ret; + assert(c); + /* If this is the first time we're called, let's create a new + * entry group if necessary */ + if (!group) { + if (!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) { + text_color_set(DW_COLOR_ERROR); + dw_printf(PRINT_PREFIX "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_client_errno(c))); + goto fail; + } + } else { + avahi_entry_group_reset(group); + } + + /* If the group is empty (either because it was just created, or + * because it was reset previously, add our entries. */ + if (avahi_entry_group_is_empty(group)) { + text_color_set(DW_COLOR_INFO); + dw_printf(PRINT_PREFIX "Announcing KISS TCP on port %d as '%s'\n", kiss_port, name); + + /* Announce with AVAHI_PROTO_INET instead of AVAHI_PROTO_UNSPEC, since Dire Wolf currently + * only listens on IPv4. + */ + + if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, 0, name, DNS_SD_SERVICE, NULL, NULL, kiss_port, NULL)) < 0) { + if (ret == AVAHI_ERR_COLLISION) + goto collision; + text_color_set(DW_COLOR_ERROR); + dw_printf(PRINT_PREFIX "Failed to add _kiss-tnc._tcp service: %s\n", avahi_strerror(ret)); + goto fail; + } + + /* Tell the server to register the service */ + if ((ret = avahi_entry_group_commit(group)) < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf(PRINT_PREFIX "Failed to commit entry group: %s\n", avahi_strerror(ret)); + goto fail; + } + } + return; + +collision: + /* A service name collision with a local service happened. Let's + * pick a new name */ + n = avahi_alternative_service_name(name); + avahi_free(name); + name = n; + text_color_set(DW_COLOR_INFO); + dw_printf(PRINT_PREFIX "Service name collision, renaming service to '%s'\n", name); + avahi_entry_group_reset(group); + create_services(c); + return; + +fail: + avahi_simple_poll_quit(simple_poll); +} + +static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) +{ + assert(c); + /* Called whenever the client or server state changes */ + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + /* The server has startup successfully and registered its host + * name on the network, so it's time to create our services */ + create_services(c); + break; + case AVAHI_CLIENT_FAILURE: + text_color_set(DW_COLOR_ERROR); + dw_printf(PRINT_PREFIX "Client failure: %s\n", avahi_strerror(avahi_client_errno(c))); + avahi_simple_poll_quit(simple_poll); + break; + case AVAHI_CLIENT_S_COLLISION: + /* Let's drop our registered services. When the server is back + * in AVAHI_SERVER_RUNNING state we will register them + * again with the new host name. */ + case AVAHI_CLIENT_S_REGISTERING: + /* The server records are now being established. This + * might be caused by a host name change. We need to wait + * for our own records to register until the host name is + * properly esatblished. */ + if (group) + avahi_entry_group_reset(group); + break; + case AVAHI_CLIENT_CONNECTING: + ; + } +} + +static void cleanup(void) +{ + /* Cleanup things */ + if (client) + avahi_client_free(client); + + if (simple_poll) + avahi_simple_poll_free(simple_poll); + + avahi_free(name); +} + + +static void *avahi_mainloop(void *arg) +{ + /* Run the main loop */ + avahi_simple_poll_loop(simple_poll); + + cleanup(); + + return NULL; +} + +void dns_sd_announce (struct misc_config_s *mc) +{ + text_color_set(DW_COLOR_DEBUG); + kiss_port = mc->kiss_port; + + int error; + + /* Allocate main loop object */ + if (!(simple_poll = avahi_simple_poll_new())) { + text_color_set(DW_COLOR_ERROR); + dw_printf(PRINT_PREFIX "Failed to create Avahi simple poll object.\n"); + goto fail; + } + + if (mc->dns_sd_name[0]) { + name = avahi_strdup(mc->dns_sd_name); + } else { + name = dns_sd_default_service_name(); + } + + /* Allocate a new client */ + client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, client_callback, NULL, &error); + + /* Check wether creating the client object succeeded */ + if (!client) { + text_color_set(DW_COLOR_ERROR); + dw_printf(PRINT_PREFIX "Failed to create Avahi client: %s\n", avahi_strerror(error)); + goto fail; + } + + pthread_create(&avahi_thread, NULL, &avahi_mainloop, NULL); + + return; + +fail: + cleanup(); +} + +#endif // USE_AVAHI_CLIENT + diff --git a/src/dns_sd_common.c b/src/dns_sd_common.c new file mode 100644 index 0000000..65a1cbf --- /dev/null +++ b/src/dns_sd_common.c @@ -0,0 +1,65 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2020 Heikki Hannikainen, OH7LZB +// +// +// 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, see . + +/*------------------------------------------------------------------ + * + * Module: dns_sd_common.c + * + * Purpose: Announce the KISS over TCP service using DNS-SD, common functions + * + * Description: + * + * Most people have typed in enough IP addresses and ports by now, and + * would rather just select an available TNC that is automatically + * discovered on the local network. Even more so on a mobile device + * such an Android or iOS phone or tablet. + * + * This module contains common functions needed on Linux and MacOS. + */ + + +#include +#include +#include + +/* Get a default service name to publish. By default, + * "Dire Wolf on ", or just "Dire Wolf" if hostname cannot + * be obtained. + */ +char *dns_sd_default_service_name(void) +{ + char hostname[51]; + char sname[64]; + + int i = gethostname(hostname, sizeof(hostname)); + if (i == 0) { + hostname[sizeof(hostname)-1] = 0; + + // on some systems, an FQDN is returned; remove domain part + char *dot = strchr(hostname, '.'); + if (dot) + *dot = 0; + + snprintf(sname, sizeof(sname), "Dire Wolf on %s", hostname); + return strdup(sname); + } + + return strdup("Dire Wolf"); +} + diff --git a/src/dns_sd_common.h b/src/dns_sd_common.h new file mode 100644 index 0000000..f104bf8 --- /dev/null +++ b/src/dns_sd_common.h @@ -0,0 +1,7 @@ + +#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD) + +char *dns_sd_default_service_name(void); + +#endif + diff --git a/src/dns_sd_dw.h b/src/dns_sd_dw.h new file mode 100644 index 0000000..79f4b86 --- /dev/null +++ b/src/dns_sd_dw.h @@ -0,0 +1,10 @@ + +#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD) + +#include "config.h" + +#define DNS_SD_SERVICE "_kiss-tnc._tcp" + +void dns_sd_announce (struct misc_config_s *mc); + +#endif // USE_AVAHI_CLIENT diff --git a/src/dns_sd_macos.c b/src/dns_sd_macos.c new file mode 100644 index 0000000..27cca8c --- /dev/null +++ b/src/dns_sd_macos.c @@ -0,0 +1,88 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2020 Heikki Hannikainen, OH7LZB +// +// +// 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, see . + +/*------------------------------------------------------------------ + * + * Module: dns_sd_macos.c + * + * Purpose: Announce the KISS over TCP service using MacOS dns-sd + * + * Description: + * + * Most people have typed in enough IP addresses and ports by now, and + * would rather just select an available TNC that is automatically + * discovered on the local network. Even more so on a mobile device + * such an Android or iOS phone or tablet. + * + * On MacOs, the announcement can be made through dns-sd. + */ + +#ifdef USE_MACOS_DNSSD + +#include +#include +#include + +#include "dns_sd_dw.h" +#include "dns_sd_common.h" +#include "textcolor.h" + +static char *name = NULL; + +static void registerServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, + const char* name, const char* regType, const char* domain, void* context) +{ + if (errorCode == kDNSServiceErr_NoError) { + text_color_set(DW_COLOR_INFO); + dw_printf("DNS-SD: Successfully registered '%s'\n", name); + } else { + text_color_set(DW_COLOR_ERROR); + dw_printf("DNS-SD: Failed to register '%s': %d\n", name, errorCode); + } +} + +void dns_sd_announce (struct misc_config_s *mc) +{ + int kiss_port = mc->kiss_port; + + if (mc->dns_sd_name[0]) { + name = strdup(mc->dns_sd_name); + } else { + name = dns_sd_default_service_name(); + } + + uint16_t port_nw = htons(kiss_port); + + DNSServiceRef registerRef; + DNSServiceErrorType err = DNSServiceRegister( + ®isterRef, 0, 0, name, DNS_SD_SERVICE, NULL, NULL, + port_nw, 0, NULL, registerServiceCallBack, NULL); + + if (err == kDNSServiceErr_NoError) { + text_color_set(DW_COLOR_INFO); + dw_printf("DNS-SD: Announcing KISS TCP on port %d as '%s'\n", kiss_port, name); + } else { + text_color_set(DW_COLOR_ERROR); + dw_printf("DNS-SD: Failed to announce '%s': %d\n", name, err); + } +} + +#endif // USE_MACOS_DNSSD + +