diff --git a/Makefile b/Makefile index 178f49b6..c8afce5d 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,10 @@ ifdef CONFIG_AO SRCS += audio_ao.c endif +ifdef CONFIG_AVAHI +SRCS += avahi.c +endif + # default target all: shairport diff --git a/avahi.c b/avahi.c new file mode 100644 index 00000000..f38c4129 --- /dev/null +++ b/avahi.c @@ -0,0 +1,140 @@ +/* + * Embedded Avahi client. This file is part of Shairport. + * Copyright (c) James Laird 2013 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include "common.h" +#include "mdns.h" + +static AvahiClient *client = NULL; +static AvahiEntryGroup *group = NULL; +static AvahiThreadedPoll *tpoll = NULL; + +static char *name = NULL; + +static void egroup_callback(AvahiEntryGroup *g, + AvahiEntryGroupState state, + AVAHI_GCC_UNUSED void *userdata) { + if (state==AVAHI_ENTRY_GROUP_COLLISION) + die("service name already exists on network!"); + if (state==AVAHI_ENTRY_GROUP_FAILURE) + die("avahi entry group failure!"); +} + +static void register_service(AvahiClient *c) { + debug(1, "avahi: register_service\n"); + if (!group) + group = avahi_entry_group_new(c, egroup_callback, NULL); + if (!group) + die("avahi_entry_group_new failed"); + + if (!avahi_entry_group_is_empty(group)) + return; + + int ret; + ret = avahi_entry_group_add_service(group, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + 0, + name, + "_raop._tcp", + NULL, + NULL, + config.port, + MDNS_RECORD, + NULL); + if (ret < 0) + die("avahi_entry_group_add_service failed"); + + ret = avahi_entry_group_commit(group); + if (ret < 0) + die("avahi_entry_group_commit failed"); +} + +static void client_callback(AvahiClient *c, + AvahiClientState state, + AVAHI_GCC_UNUSED void * userdata) { + switch (state) { + case AVAHI_CLIENT_S_REGISTERING: + if (group) + avahi_entry_group_reset(group); + break; + + case AVAHI_CLIENT_S_RUNNING: + register_service(c); + break; + + case AVAHI_CLIENT_FAILURE: + case AVAHI_CLIENT_S_COLLISION: + die("avahi client failure"); + + case AVAHI_CLIENT_CONNECTING: + break; + } +} + +int avahi_register(char *srvname) { + debug(1, "avahi: avahi_register\n"); + name = strdup(srvname); + + int err; + if (!(tpoll = avahi_threaded_poll_new())) { + warn("couldn't create avahi threaded tpoll!"); + return 0; + } + if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), + 0, + client_callback, + NULL, + &err))) { + warn("couldn't create avahi client: %s!", avahi_strerror(err)); + return 0; + } + + if (avahi_threaded_poll_start(tpoll) < 0) { + warn("couldn't start avahi tpoll thread"); + return 0; + } + + return 1; +} + +void avahi_unregister(void) { + debug(1, "avahi: avahi_unregister\n"); + if (tpoll) + avahi_threaded_poll_stop(tpoll); + tpoll = NULL; + + if (name) + free(name); + name = NULL; +} + + diff --git a/avahi.h b/avahi.h new file mode 100644 index 00000000..0ad53ff0 --- /dev/null +++ b/avahi.h @@ -0,0 +1,7 @@ +#ifndef _AVAHI_H +#define _AVAHI_H + +int avahi_register(char *srvname); +void avahi_unregister(void); + +#endif // _AVAHI_H diff --git a/configure b/configure index 5ee334a6..1b046002 100755 --- a/configure +++ b/configure @@ -26,6 +26,16 @@ else echo libao not found fi +if pkg-config avahi-client 2>/dev/null; then + CFLAGS="${CFLAGS} `pkg-config --cflags avahi-client`" + LDFLAGS="${LDFLAGS} `pkg-config --libs avahi-client`" + echo "#define CONFIG_AVAHI" >> config.h + echo "CONFIG_AVAHI=yes" >> config.mk + echo avahi-client found +else + echo avahi-client not found +fi + echo "CFLAGS+=${CFLAGS}" >> config.mk echo "LDFLAGS+=${LDFLAGS}" >> config.mk diff --git a/mdns.c b/mdns.c index a593316c..66d88363 100644 --- a/mdns.c +++ b/mdns.c @@ -29,25 +29,26 @@ #include #include #include +#include "config.h" #include "common.h" +#include "mdns.h" +#ifdef CONFIG_AVAHI +#include "avahi.h" +#endif int mdns_pid = 0; void mdns_unregister(void) { +#ifdef CONFIG_AVAHI + avahi_unregister(); +#endif + if (mdns_pid) kill(mdns_pid, SIGTERM); mdns_pid = 0; } void mdns_register(void) { - if (mdns_pid) - die("attempted to register with mDNS twice"); - - // XXX todo: native avahi support, if available at compile time - - if ((mdns_pid = fork())) - return; - char *mdns_apname = malloc(strlen(config.apname) + 14); char *p = mdns_apname; int i; @@ -58,15 +59,20 @@ void mdns_register(void) { *p++ = '@'; strcpy(p, config.apname); +#ifdef CONFIG_AVAHI + if (avahi_register(mdns_apname)) + return; + warn("avahi_register failed, falling back to external programs"); +#endif + + if ((mdns_pid = fork())) + return; + char mdns_port[6]; sprintf(mdns_port, "%d", config.port); -#define RECORD "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", \ - "ss=16", "sr=44100", "vn=3", "txtvers=1", \ - config.password ? "pw=true" : "pw=false" - char *argv[] = { - NULL, mdns_apname, "_raop._tcp", mdns_port, RECORD, NULL + NULL, mdns_apname, "_raop._tcp", mdns_port, MDNS_RECORD, NULL }; argv[0] = "avahi-publish-service"; @@ -76,7 +82,7 @@ void mdns_register(void) { execvp(argv[0], argv); char *mac_argv[] = {"dns-sd", "-R", mdns_apname, "_raop._tcp", ".", - mdns_port, RECORD, NULL}; + mdns_port, MDNS_RECORD, NULL}; execvp(mac_argv[0], mac_argv); die("Could not establish mDNS advertisement!"); diff --git a/mdns.h b/mdns.h index 9827c0c3..9fc5792f 100644 --- a/mdns.h +++ b/mdns.h @@ -6,4 +6,8 @@ extern int mdns_pid; void mdns_unregister(void); void mdns_register(void); +#define MDNS_RECORD "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", \ + "ss=16", "sr=44100", "vn=3", "txtvers=1", \ + config.password ? "pw=true" : "pw=false" + #endif // _MDNS_H