summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Sieber <mail@strfry.org>2017-09-20 19:05:16 +0200
committerAlfred E. Heggestad <alfred.heggestad@gmail.com>2017-09-20 19:05:16 +0200
commit27ebca8548c3fbb8809d85a25262200f79d82448 (patch)
treeb402a0b630c4cc066df9209935f8e4a4bb18fce9
parent4ba1cfe60b62fb96d4de213cea3992846becf543 (diff)
Local Discovery through Avahi/ZeroConf module (#293)
* Avahi Service Announce and Discovery * Replace all instances of error() with warning() to work around #295 * avahi: Fix IPv4/v6 support, and a few nitpicks * avahi: Advertise non-standard ports
-rw-r--r--mk/modules.mk4
-rw-r--r--modules/avahi/avahi.c361
-rw-r--r--modules/avahi/module.mk12
3 files changed, 377 insertions, 0 deletions
diff --git a/mk/modules.mk b/mk/modules.mk
index c5e02f0..20ff07a 100644
--- a/mk/modules.mk
+++ b/mk/modules.mk
@@ -81,6 +81,7 @@ USE_AVFORMAT := $(shell [ -f $(SYSROOT)/include/libavformat/avformat.h ] || \
[ -f $(SYSROOT)/local/include/libavformat/avformat.h ] || \
[ -f $(SYSROOT)/include/$(MACHINE)/libavformat/avformat.h ] || \
[ -f $(SYSROOT_ALT)/include/libavformat/avformat.h ] && echo "yes")
+USE_AVAHI := $(shell pkg-config --exists avahi-client && echo "yes")
USE_BV32 := $(shell [ -f $(SYSROOT)/include/bv32/bv32.h ] || \
[ -f $(SYSROOT)/local/include/bv32/bv32.h ] && echo "yes")
USE_CAIRO := $(shell [ -f $(SYSROOT)/include/cairo/cairo.h ] || \
@@ -300,6 +301,9 @@ MODULES += avcodec
ifneq ($(USE_AVFORMAT),)
MODULES += avformat
endif
+ifneq ($(USE_AVAHI),)
+MODULES += avahi
+endif
endif
ifneq ($(USE_BV32),)
MODULES += bv32
diff --git a/modules/avahi/avahi.c b/modules/avahi/avahi.c
new file mode 100644
index 0000000..9bf4a8c
--- /dev/null
+++ b/modules/avahi/avahi.c
@@ -0,0 +1,361 @@
+/**
+ * @file avahi.c Avahi Zeroconf Module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * Copyright (C) 2017 Jonathan Sieber
+ */
+
+/**
+ * @defgroup avahi avahi
+ *
+ * This module implements DNS Service Discovery via Avahi Client API
+ * It does 2 things:
+ * 1) Announce _sipuri._udp resource for the main UA (under the local IP)
+ * 2) Fills contact list with discovered hosts
+ *
+ * NOTE: This module is experimental.
+ *
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <avahi-common/simple-watch.h>
+#include <avahi-client/lookup.h>
+#include <avahi-client/publish.h>
+#include <avahi-common/error.h>
+
+/* for if_nametoindex */
+#include <net/if.h>
+
+/* gethostname, getaddrinfo */
+#define __USE_XOPEN2K
+#include <unistd.h>
+#include <netdb.h>
+
+
+struct avahi_st {
+ AvahiSimplePoll* poll;
+ AvahiClient* client;
+ AvahiEntryGroup* group;
+ AvahiServiceBrowser* browser;
+ struct ua* local_ua;
+ struct tmr poll_timer;
+};
+
+static struct avahi_st* avahi = NULL;
+
+static void group_callback(AvahiEntryGroup* group,
+ AvahiEntryGroupState state, void* userdata)
+{
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ info ("avahi: Service Registration completed\n");
+ break;
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ warning("avahi: Service Registration failed\n");
+ /* TODO: Think of smart way to handle collision? */
+ break;
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ /* Do nothing */
+ break;
+ }
+}
+
+static void create_services(AvahiClient* client)
+{
+ int err;
+ char buf[128] = "";
+ char hostname[128] = "";
+
+ int if_idx = AVAHI_IF_UNSPEC;
+ int af = AVAHI_PROTO_INET;
+
+ struct sa laddr;
+
+ /* Build announced sipuri as username@hostname */
+ strncpy(hostname, avahi_client_get_host_name_fqdn(client),
+ sizeof (hostname));
+ re_snprintf(buf, sizeof(buf), "<sip:%s@%s>;regint=0",
+ sys_username(),
+ hostname);
+
+ info("avahi: Creating local UA %s\n", buf);
+ err = ua_alloc(&avahi->local_ua, buf);
+
+ if (err) {
+ warning("avahi: Could not create UA %s: %m\n", buf, err);
+ return;
+ }
+
+ re_snprintf(buf, sizeof(buf), "sip:%s@%s",
+ sys_username(),
+ hostname);
+
+ debug("avahi: Announcing URI: %s\n", buf);
+
+ /* Get interface number of baresip interface */
+ if (str_isset(conf_config()->net.ifname)) {
+ if_idx = if_nametoindex(conf_config()->net.ifname);
+ }
+
+ if (net_af(baresip_network()) == AF_INET6) {
+ af = AVAHI_PROTO_INET6;
+ }
+
+ err |= sip_transp_laddr(uag_sip(), &laddr, SIP_TRANSP_UDP, 0);
+ if (err) {
+ warning("avahi: Can not find local SIP address\n");
+ }
+
+ /* TODO: Query enabled transports and register these */
+ avahi->group = avahi_entry_group_new(client, group_callback, NULL);
+ err = avahi_entry_group_add_service(avahi->group,
+ if_idx, af, 0,
+ buf, "_sipuri._udp",
+ NULL, NULL,
+ ntohs(laddr.u.in.sin_port), NULL);
+ err |= avahi_entry_group_commit(avahi->group);
+
+ if (err) {
+ warning("avahi: Error in registering service");
+ }
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state,
+ AVAHI_GCC_UNUSED void * userdata)
+{
+ switch (state) {
+ case AVAHI_CLIENT_S_RUNNING:
+ info("avahi: Avahi Daemon running\n", state);
+ break;
+ default:
+ warning("avahi: unknown client_callback: %d\n", state);
+ break;
+ }
+}
+
+static void add_contact(const char* uri,
+ const AvahiAddress *address, uint16_t port)
+{
+ int err;
+ struct pl addr;
+ char buf[128];
+ struct contact *c;
+ struct sa sa;
+ struct sip_addr sipaddr;
+
+ /* Parse SIPURI to get username and stuff... */
+ pl_set_str(&addr, uri);
+ if (sip_addr_decode(&sipaddr, &addr)) {
+ warning("avahi: could not decode sipuri %s\n", uri);
+ return;
+ }
+
+ if (address->proto == AVAHI_PROTO_INET6) {;
+ sa_set_in6(&sa, address->data.ipv6.address, port);
+ }
+ else {
+ sa_set_in(&sa, htonl(address->data.ipv4.address), port);
+ }
+
+ re_snprintf(buf, sizeof(buf),
+ "\"%r@%r\" <sip:%r@%J>;presence=p2p",
+ &sipaddr.uri.user, &sipaddr.uri.host,
+ &sipaddr.uri.user, &sa);
+ pl_set_str(&addr, buf);
+
+ err = contact_add(baresip_contacts(), &c, &addr);
+ if (err) {
+ warning("Could not add contact %s: %m\n", buf, err);
+ }
+}
+
+static void remove_contact_by_dname(const char* dname)
+{
+ const struct list *lst;
+ struct le *le;
+
+ /* remove sip: scheme for comparison */
+ if (0 != re_regex(dname, str_len(dname), "^sip:")) {
+ dname += 4;
+ }
+
+ lst = contact_list(baresip_contacts());
+
+ for (le = list_head(lst); le; le = le->next) {
+ struct contact *c = le->data;
+ const struct sip_addr* addr = contact_addr(c);
+
+ if (pl_strcmp(&addr->dname, dname) == 0) {
+ contact_remove(baresip_contacts(), c);
+ return;
+ }
+ }
+
+ warning("avahi: Could not remove contact %s\n", dname);
+}
+
+static void resolve_callback(
+ AvahiServiceResolver *r,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiResolverEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ const char *hostname,
+ const AvahiAddress *address,
+ uint16_t port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ void *userdata)
+{
+ info("avahi: resolve %s %s %s %s\n", name, type, domain, hostname);
+
+ if (event == AVAHI_RESOLVER_FOUND) {
+ if (protocol != address->proto) {
+ warning("avahi: Resolved address type ambiguous\n");
+ }
+
+ /* TODO: Process TXT field */
+ if (!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN)) {
+ add_contact(name, address, port);
+ }
+ }
+ else {
+ warning("avahi: Resolver Error on %s: %s\n", name,
+ avahi_strerror(avahi_client_errno(avahi->client)));
+ }
+}
+
+
+static void browse_callback(
+ AvahiServiceBrowser *b,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
+ void* userdata)
+{
+
+ int proto = AVAHI_PROTO_INET;
+
+ switch (event) {
+ case AVAHI_BROWSER_NEW:
+ debug("avahi: browse_callback if=%d proto=%d %s\n",
+ interface, protocol, name);
+ if (net_af(baresip_network()) == AF_INET6) {
+ proto = AVAHI_PROTO_INET6;
+ }
+
+ if (!(avahi_service_resolver_new(avahi->client,
+ interface, protocol,
+ name, type, domain,
+ proto, 0, resolve_callback,
+ avahi->client))) {
+ warning("avahi: Error resolving %s\n", name);
+ }
+ break;
+
+ case AVAHI_BROWSER_REMOVE:
+ remove_contact_by_dname(name);
+ break;
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ debug("avahi: (Browser) %s\n",
+ event == AVAHI_BROWSER_CACHE_EXHAUSTED ?
+ "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
+ break;
+ default:
+ warning("avahi: browse_callback %d %s\n", event, name);
+ break;
+ }
+}
+
+static void avahi_update(void* arg)
+{
+ avahi_simple_poll_iterate(avahi->poll, 0);
+ tmr_start(&avahi->poll_timer, 250, avahi_update, 0);
+}
+
+static void destructor(void* arg)
+{
+ struct avahi_st* a = arg;
+
+ tmr_cancel(&a->poll_timer);
+
+ mem_deref(a->local_ua);
+
+ /* Calling these destructor commands would be correct, but they
+ * spew out a lot of ugly D-Bus warning */
+ if (a->browser) {
+ avahi_service_browser_free(avahi->browser);
+ }
+
+ if (a->group) {
+ avahi_entry_group_free(avahi->group);
+ }
+
+ if (a->client) {
+ avahi_client_free(avahi->client);
+ }
+}
+
+static int module_init(void)
+{
+ int err;
+ avahi = mem_zalloc(sizeof(struct avahi_st), destructor);
+ if (!avahi) {
+ return ENOMEM;
+ }
+
+ avahi->poll = avahi_simple_poll_new();
+ avahi->client = avahi_client_new(
+ avahi_simple_poll_get(avahi->poll),
+ 0, client_callback, NULL, &err);
+
+ /* Check wether creating the client object succeeded */
+ if (!avahi->client) {
+ warning("Failed to create client: %s\n", avahi_strerror(err));
+ return err;
+ }
+
+ avahi->browser = avahi_service_browser_new(avahi->client,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_sipuri._udp", NULL,
+ 0, browse_callback, 0);
+
+ tmr_init(&avahi->poll_timer);
+ avahi_update(0);
+
+ /* Register services when UA is ready */
+ if (!avahi->group) {
+ create_services(avahi->client);
+ }
+
+ return 0;
+}
+
+static int module_close(void)
+{
+ debug("avahi: module_close\n");
+
+ avahi = mem_deref(avahi);
+
+ return 0;
+}
+
+const struct mod_export DECL_EXPORTS(avahi) = {
+ "avahi",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/avahi/module.mk b/modules/avahi/module.mk
new file mode 100644
index 0000000..0d796e1
--- /dev/null
+++ b/modules/avahi/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := avahi
+$(MOD)_SRCS += avahi.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs avahi-client)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags avahi-client)
+
+include mk/mod.mk