From 0575299a8792d936f75d6e760e58c9d9bee4015f Mon Sep 17 00:00:00 2001 From: Justin Maggard Date: Fri, 28 Apr 2017 01:18:53 -0700 Subject: [PATCH] tivo: Add Avahi support TiVo Bolt doesn't support the old TiVo Beacon discovery mechanism, so we need to support Bonjour discovery now. Use Avahi if it's available. --- Makefile.am | 8 +- avahi.c | 271 +++++++++++++++++++++++++++++++++++++++++++++++ avahi.h | 9 ++ configure.ac | 8 ++ minidlna.c | 30 ++++-- options.c | 3 +- options.h | 3 +- upnpglobalvars.c | 2 +- upnpglobalvars.h | 5 + 9 files changed, 325 insertions(+), 14 deletions(-) create mode 100644 avahi.c create mode 100644 avahi.h diff --git a/Makefile.am b/Makefile.am index 9273aa5..c0bfdc5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,7 +28,7 @@ minidlnad_SOURCES = minidlna.c upnphttp.c upnpdescgen.c upnpsoap.c \ sql.c utils.c metadata.c scanner.c inotify.c \ tivo_utils.c tivo_beacon.c tivo_commands.c \ playlist.c image_utils.c albumart.c log.c \ - containers.c tagutils/tagutils.c + containers.c avahi.c tagutils/tagutils.c #if NEED_VORBIS vorbisflag = -lvorbis @@ -38,6 +38,10 @@ vorbisflag = -lvorbis flacoggflag = -logg #endif +#if HAVE_AVAHI +avahilibs = -lavahi-client -lavahi-common +#endif + minidlnad_LDADD = \ @LIBJPEG_LIBS@ \ @LIBID3TAG_LIBS@ \ @@ -47,7 +51,7 @@ minidlnad_LDADD = \ @LIBEXIF_LIBS@ \ @LIBINTL@ \ @LIBICONV@ \ - -lFLAC $(flacoggflag) $(vorbisflag) + -lFLAC $(flacoggflag) $(vorbisflag) $(avahilibs) minidlnad_LDFLAGS = @STATIC_LDFLAGS@ diff --git a/avahi.c b/avahi.c new file mode 100644 index 0000000..965d9ed --- /dev/null +++ b/avahi.c @@ -0,0 +1,271 @@ +/* + * Author: Daniel S. Haischt + * Purpose: Avahi based Zeroconf support + * Docs: http://avahi.org/download/doxygen/ + * + */ + +#include + +#if defined(TIVO_SUPPORT) && defined(HAVE_AVAHI) + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "upnpglobalvars.h" +#include "log.h" + +static struct context { + /* Avahi stuff */ + AvahiThreadedPoll *threaded_poll; + AvahiClient *client; + AvahiEntryGroup *group; +} ctx; + +/***************************************************************** + * Private functions + *****************************************************************/ + +static void publish_reply(AvahiEntryGroup *g, + AvahiEntryGroupState state, + void *userdata); + +/* + * This function tries to register the AFP DNS + * SRV service type. + */ +static void register_stuff(void) { + char name[128+1]; + + assert(ctx.client); + + if (!ctx.group) { + if (!(ctx.group = avahi_entry_group_new(ctx.client, publish_reply, NULL))) { + DPRINTF(E_ERROR, L_SSDP, "Failed to create entry group: %s\n", + avahi_strerror(avahi_client_errno(ctx.client))); + goto fail; + } + } + + if (avahi_entry_group_is_empty(ctx.group)) { + /* Register our service */ + + DPRINTF(E_INFO, L_SSDP, "Registering '%s' with Avahi\n", friendly_name); + + snprintf(name, sizeof(name), "Music on %s", friendly_name); + if (avahi_entry_group_add_service(ctx.group, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + 0, + name, + "_tivo-music._tcp", + NULL, + NULL, + runtime_vars.port, + "protocol=http", + "path=/TiVoConnect?Command=QueryContainer&Container=1", + NULL) < 0) { + DPRINTF(E_ERROR, L_SSDP, "Failed to add service: %s\n", + avahi_strerror(avahi_client_errno(ctx.client))); + goto fail; + } + + snprintf(name, sizeof(name), "Photos on %s", friendly_name); + if (avahi_entry_group_add_service(ctx.group, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + 0, + name, + "_tivo-photos._tcp", + NULL, + NULL, + runtime_vars.port, + "protocol=http", + "path=/TiVoConnect?Command=QueryContainer&Container=3", + NULL) < 0) { + DPRINTF(E_ERROR, L_SSDP, "Failed to add service: %s\n", + avahi_strerror(avahi_client_errno(ctx.client))); + goto fail; + } + + snprintf(name, sizeof(name), "Videos on %s", friendly_name); + if (avahi_entry_group_add_service(ctx.group, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + 0, + name, + "_tivo-videos._tcp", + NULL, + NULL, + runtime_vars.port, + "platform=pc/readydlna", + "protocol=http", + "path=/TiVoConnect?Command=QueryContainer&Container=2", + NULL) < 0) { + DPRINTF(E_ERROR, L_SSDP, "Failed to add service: %s\n", + avahi_strerror(avahi_client_errno(ctx.client))); + goto fail; + } + + if (avahi_entry_group_commit(ctx.group) < 0) { + DPRINTF(E_ERROR, L_SSDP, "Failed to commit entry group: %s\n", + avahi_strerror(avahi_client_errno(ctx.client))); + goto fail; + } + + } /* if avahi_entry_group_is_empty*/ + + return; + +fail: + time(NULL); +} + +/* Called when publishing of service data completes */ +static void publish_reply(AvahiEntryGroup *g, + AvahiEntryGroupState state, + AVAHI_GCC_UNUSED void *userdata) +{ + assert(ctx.group == NULL || g == ctx.group); + + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED : + /* The entry group has been established successfully */ + DPRINTF(E_MAXDEBUG, L_SSDP, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED\n"); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + /* With multiple names there's no way to know which one collided */ + DPRINTF(E_ERROR, L_SSDP, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION: %s\n", + avahi_strerror(avahi_client_errno(ctx.client))); + avahi_threaded_poll_quit(ctx.threaded_poll); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + DPRINTF(E_ERROR, L_SSDP, "Failed to register service: %s\n", + avahi_strerror(avahi_client_errno(ctx.client))); + avahi_threaded_poll_quit(ctx.threaded_poll); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + break; + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } +} + +static void client_callback(AvahiClient *client, + AvahiClientState state, + void *userdata) +{ + ctx.client = client; + + 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 */ + if (!ctx.group) + register_stuff(); + break; + + case AVAHI_CLIENT_S_COLLISION: + if (ctx.group) + avahi_entry_group_reset(ctx.group); + break; + + case AVAHI_CLIENT_FAILURE: { + if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) { + int error; + + avahi_client_free(ctx.client); + ctx.client = NULL; + ctx.group = NULL; + + /* Reconnect to the server */ + if (!(ctx.client = avahi_client_new(avahi_threaded_poll_get(ctx.threaded_poll), + AVAHI_CLIENT_NO_FAIL, + client_callback, + &ctx, + &error))) { + + DPRINTF(E_ERROR, L_SSDP, "Failed to contact server: %s\n", + avahi_strerror(error)); + + avahi_threaded_poll_quit(ctx.threaded_poll); + } + + } else { + DPRINTF(E_ERROR, L_SSDP, "Client failure: %s\n", + avahi_strerror(avahi_client_errno(client))); + avahi_threaded_poll_quit(ctx.threaded_poll); + } + break; + } + + case AVAHI_CLIENT_S_REGISTERING: + break; + case AVAHI_CLIENT_CONNECTING: + break; + } +} + +/************************************************************************ + * Public funcions + ************************************************************************/ + +/* + * Tries to shutdown this loop impl. + * Call this function from inside this thread. + */ +void tivo_bonjour_unregister(void) +{ + DPRINTF(E_DEBUG, L_SSDP, "tivo_bonjour_unregister\n"); + + if (ctx.threaded_poll) + avahi_threaded_poll_stop(ctx.threaded_poll); + if (ctx.client) + avahi_client_free(ctx.client); + if (ctx.threaded_poll) + avahi_threaded_poll_free(ctx.threaded_poll); +} + +/* + * Tries to setup the Zeroconf thread and any + * neccessary config setting. + */ +void tivo_bonjour_register(void) +{ + int error; + + /* first of all we need to initialize our threading env */ + ctx.threaded_poll = avahi_threaded_poll_new(); + if (!ctx.threaded_poll) + return; + + /* now we need to acquire a client */ + ctx.client = avahi_client_new(avahi_threaded_poll_get(ctx.threaded_poll), + AVAHI_CLIENT_NO_FAIL, client_callback, NULL, &error); + if (!ctx.client) + { + DPRINTF(E_ERROR, L_SSDP, "Failed to create client object: %s\n", + avahi_strerror(error)); + tivo_bonjour_unregister(); + return; + } + + if (avahi_threaded_poll_start(ctx.threaded_poll) < 0) + { + DPRINTF(E_ERROR, L_SSDP, "Failed to create thread: %s\n", + avahi_strerror(avahi_client_errno(ctx.client))); + tivo_bonjour_unregister(); + } + else + DPRINTF(E_INFO, L_SSDP, "Successfully started avahi loop.\n"); +} + +#endif /* HAVE_AVAHI */ diff --git a/avahi.h b/avahi.h new file mode 100644 index 0000000..8939c60 --- /dev/null +++ b/avahi.h @@ -0,0 +1,9 @@ +#include "config.h" + +#if defined(TIVO_SUPPORT) && defined(HAVE_AVAHI) +void tivo_bonjour_register(void); +void tivo_bonjour_unregister(void); +#else +static inline void tivo_bonjour_register(void) {}; +static inline void tivo_bonjour_unregister(void) {}; +#endif diff --git a/configure.ac b/configure.ac index 79a7994..5a18ba8 100644 --- a/configure.ac +++ b/configure.ac @@ -478,6 +478,14 @@ AC_CHECK_LIB(vorbisfile, vorbis_comment_query, AM_CONDITIONAL(NEED_VORBIS, true), -logg) +AC_CHECK_LIB(avahi-client, avahi_threaded_poll_new, + [AC_CHECK_HEADERS([avahi-common/thread-watch.h], + AM_CONDITIONAL(HAVE_AVAHI, true) + AC_DEFINE(HAVE_AVAHI,1,[Have avahi]), + AM_CONDITIONAL(HAVE_AVAHI, false))], + AM_CONDITIONAL(HAVE_AVAHI, false), + -lavahi-client -lavahi-common) + ################################################################################################################ ### Header checks diff --git a/minidlna.c b/minidlna.c index 36de340..16c27b9 100644 --- a/minidlna.c +++ b/minidlna.c @@ -94,6 +94,7 @@ #include "log.h" #include "tivo_beacon.h" #include "tivo_utils.h" +#include "avahi.h" #if SQLITE_VERSION_NUMBER < 3005001 # warning "Your SQLite3 library appears to be too old! Please use 3.5.1 or newer." @@ -134,7 +135,7 @@ OpenAndConfHTTPSocket(unsigned short port) return -1; } - if (listen(s, 6) < 0) + if (listen(s, 16) < 0) { DPRINTF(E_ERROR, L_GENERAL, "listen(http): %s\n", strerror(errno)); close(s); @@ -730,6 +731,10 @@ init(int argc, char **argv) if (strtobool(ary_options[i].value)) SETFLAG(WIDE_LINKS_MASK); break; + case TIVO_DISCOVERY: + if (strcasecmp(ary_options[i].value, "beacon") == 0) + CLEARFLAG(TIVO_BONJOUR_MASK); + break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n", optionsfile); @@ -1069,14 +1074,21 @@ main(int argc, char **argv) ret = sqlite3_create_function(db, "tivorandom", 1, SQLITE_UTF8, NULL, &TiVoRandomSeedFunc, NULL, NULL); if (ret != SQLITE_OK) DPRINTF(E_ERROR, L_TIVO, "ERROR: Failed to add sqlite randomize function for TiVo!\n"); - /* open socket for sending Tivo notifications */ - sbeacon = OpenAndConfTivoBeaconSocket(); - if(sbeacon < 0) - DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify " - "messages. EXITING\n"); - tivo_bcast.sin_family = AF_INET; - tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress()); - tivo_bcast.sin_port = htons(2190); + if (GETFLAG(TIVO_BONJOUR_MASK)) + { + tivo_bonjour_register(); + } + else + { + /* open socket for sending Tivo notifications */ + sbeacon = OpenAndConfTivoBeaconSocket(); + if(sbeacon < 0) + DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify " + "messages. EXITING\n"); + tivo_bcast.sin_family = AF_INET; + tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress()); + tivo_bcast.sin_port = htons(2190); + } } #endif diff --git a/options.c b/options.c index 15e05ee..79e4ffe 100644 --- a/options.c +++ b/options.c @@ -65,7 +65,8 @@ static const struct { { FORCE_SORT_CRITERIA, "force_sort_criteria" }, { MAX_CONNECTIONS, "max_connections" }, { MERGE_MEDIA_DIRS, "merge_media_dirs" }, - { WIDE_LINKS, "wide_links" } + { WIDE_LINKS, "wide_links" }, + { TIVO_DISCOVERY, "tivo_discovery" }, }; int diff --git a/options.h b/options.h index e9ef921..cb4c3be 100644 --- a/options.h +++ b/options.h @@ -58,7 +58,8 @@ enum upnpconfigoptions { FORCE_SORT_CRITERIA, /* force sorting by a given sort criteria */ MAX_CONNECTIONS, /* maximum number of simultaneous connections */ MERGE_MEDIA_DIRS, /* don't add an extra directory level when there are multiple media dirs */ - WIDE_LINKS /* allow following symlinks outside the defined media_dirs */ + WIDE_LINKS, /* allow following symlinks outside the defined media_dirs */ + TIVO_DISCOVERY, /* TiVo discovery protocol: bonjour or beacon. Defaults to bonjour if supported */ }; /* readoptionsfile() diff --git a/upnpglobalvars.c b/upnpglobalvars.c index 6660ac8..17d4dff 100644 --- a/upnpglobalvars.c +++ b/upnpglobalvars.c @@ -58,7 +58,7 @@ time_t startup_time = 0; struct runtime_vars_s runtime_vars; -uint32_t runtime_flags = INOTIFY_MASK; +uint32_t runtime_flags = INOTIFY_MASK | TIVO_BONJOUR_MASK; const char *pidfilename = "/var/run/minidlna/minidlna.pid"; diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 6021a2a..0c9664d 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -188,6 +188,11 @@ extern uint32_t runtime_flags; #define SYSTEMD_MASK 0x0010 #define MERGE_MEDIA_DIRS_MASK 0x0020 #define WIDE_LINKS_MASK 0x0040 +#ifdef HAVE_AVAHI +#define TIVO_BONJOUR_MASK 0x0080 +#else +#define TIVO_BONJOUR_MASK 0x0000 +#endif #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) (runtime_flags & mask)