Browse Source

import v0.1

Signed-off-by: John Crispin <blogic@openwrt.org>
John Crispin 10 years ago
commit
788316ef66
19 changed files with 2101 additions and 0 deletions
  1. 7 0
      .gitignore
  2. 22 0
      CMakeLists.txt
  3. 5 0
      TODO
  4. 84 0
      announce.c
  5. 21 0
      announce.h
  6. 394 0
      cache.c
  7. 61 0
      cache.h
  8. 331 0
      dns.c
  9. 81 0
      dns.h
  10. 3 0
      json/http.json
  11. 3 0
      json/ssh.json
  12. 254 0
      main.c
  13. 72 0
      service-types
  14. 325 0
      service.c
  15. 26 0
      service.h
  16. 196 0
      ubus.c
  17. 19 0
      ubus.h
  18. 163 0
      util.c
  19. 34 0
      util.h

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+mdns
+.*
+Makefile
+CMakeCache.txt
+CMakeFiles
+*.cmake
+install_manifest.txt

+ 22 - 0
CMakeLists.txt

@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 2.6)
+
+PROJECT(mdns C)
+ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations)
+
+SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
+
+SET(SOURCES main.c dns.c announce.c cache.c service.c util.c ubus.c)
+
+SET(LIBS ubox ubus resolv blobmsg_json)
+
+IF(DEBUG)
+  ADD_DEFINITIONS(-DDEBUG -g3)
+ENDIF()
+
+ADD_EXECUTABLE(mdns ${SOURCES})
+
+TARGET_LINK_LIBRARIES(mdns ${LIBS})
+
+INSTALL(TARGETS mdns
+	RUNTIME DESTINATION sbin
+)

+ 5 - 0
TODO

@@ -0,0 +1,5 @@
+* timeouts and delays as described in rfc
+* service.c needs a cleanup/shutdown path
+* bridge devices dont works yet
+* automagic route setting
+* ipv6

+ 84 - 0
announce.c

@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <sys/types.h>
+
+#include <stdio.h>
+
+#include <libubox/uloop.h>
+
+#include "cache.h"
+#include "dns.h"
+#include "util.h"
+#include "service.h"
+#include "announce.h"
+
+#define TTL_TIMEOUT	75
+
+enum {
+	STATE_PROBE1 = 0,
+	STATE_PROBE2,
+	STATE_PROBE3,
+	STATE_PROBE_WAIT,
+	STATE_PROBE_END,
+	STATE_ANNOUNCE,
+};
+
+static struct uloop_timeout announce;
+struct uloop_fd *announce_fd;
+static int announce_state;
+int announce_ttl = 75 * 60;
+
+static void
+announce_timer(struct uloop_timeout *timeout)
+{
+	char host[256];
+
+	snprintf(host, sizeof(host), "%s.local", hostname);
+
+	switch (announce_state) {
+		case STATE_PROBE1:
+		case STATE_PROBE2:
+		case STATE_PROBE3:
+			dns_send_question(announce_fd, host, TYPE_ANY);
+			uloop_timeout_set(timeout, 250);
+			announce_state++;
+			break;
+
+		case STATE_PROBE_WAIT:
+			uloop_timeout_set(timeout, 500);
+			announce_state++;
+			break;
+
+		case STATE_PROBE_END:
+			if (cache_host_is_known(host)) {
+				fprintf(stderr, "the host %s already exists. stopping announce service\n", host);
+				return;
+			}
+			announce_state++;
+
+		case STATE_ANNOUNCE:
+			service_announce(announce_fd);
+			uloop_timeout_set(timeout, announce_ttl * 800);
+			break;
+	}
+}
+
+void
+announce_init(struct uloop_fd *u)
+{
+	announce_state = STATE_PROBE1;
+	announce.cb = announce_timer;
+	announce_fd = u;
+	uloop_timeout_set(&announce, 100);
+}

+ 21 - 0
announce.h

@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef _ANNOUNCE_H__
+#define _ANNOUNCE_H__
+
+extern int announce_ttl;
+extern struct uloop_fd *announce_fd;
+extern void announce_init(struct uloop_fd *u);
+
+#endif

+ 394 - 0
cache.c

@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <time.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <asm/byteorder.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <time.h>
+
+#include <libubox/usock.h>
+#include <libubox/uloop.h>
+#include <libubox/avl-cmp.h>
+#include <libubox/blobmsg_json.h>
+#include <libubus.h>
+
+#include "cache.h"
+#include "util.h"
+#include "dns.h"
+
+static struct uloop_timeout cache_gc;
+struct avl_tree records, entries, types, hosts;
+static struct blob_buf b;
+
+static void
+cache_record_free(struct cache_record *r, int rem)
+{
+	DBG(2, "%s %s\n", dns_type_string(r->type), r->record);
+	if (rem)
+		avl_delete(&records, &r->avl);
+	if (r->record)
+		free(r->record);
+	if (r->rdata)
+		free(r->rdata);
+	if (r->txt)
+		free(r->txt);
+	free(r);
+}
+
+static void
+cache_entry_free(struct cache_entry *s)
+{
+	DBG(2, "%s\n", s->entry);
+	avl_delete(&entries, &s->avl);
+	if (s->host)
+		free(s->host);
+	if (s->entry)
+		free(s->entry);
+	free(s);
+}
+
+static int
+cache_is_expired(time_t t, uint32_t ttl)
+{
+	if (time(NULL) - t >= ttl)
+		return 1;
+
+	return 0;
+}
+
+static void
+cache_gc_timer(struct uloop_timeout *timeout)
+{
+	struct cache_record *r, *p;
+	struct cache_entry *s, *t;
+
+	avl_for_each_element_safe(&records, r, avl, p)
+		if (cache_is_expired(r->time, r->ttl))
+			cache_record_free(r, 1);
+
+	avl_for_each_element_safe(&entries, s, avl, t) {
+		if (!s->host)
+			continue;
+		if (cache_is_expired(s->time, s->ttl))
+			cache_entry_free(s);
+	}
+
+	uloop_timeout_set(timeout, 10000);
+}
+
+static void
+cache_load_services(void)
+{
+	struct blob_attr *cur;
+        int rem;
+
+        blob_buf_init(&b, 0);
+
+	if (!blobmsg_add_json_from_file(&b, "/lib/mdns/service-types"))
+		return;
+
+	blob_for_each_attr(cur, b.head, rem) {
+		struct cache_type *t = malloc(sizeof(struct cache_type));
+
+		if (!t)
+			continue;
+		t->avl.key = t->key = strdup(blobmsg_name(cur));
+		t->val = strdup(blobmsg_get_string(cur));
+		avl_insert(&types, &t->avl);
+	}
+}
+
+char*
+cache_lookup_name(const char *key)
+{
+	struct cache_type *t;
+
+	t = avl_find_element(&types, key, t, avl);
+	if (!t)
+		return NULL;
+
+	return t->val;
+}
+
+int
+cache_init(void)
+{
+	avl_init(&entries, avl_strcmp, true, NULL);
+	avl_init(&types, avl_strcmp, false, NULL);
+	avl_init(&records, avl_strcmp, true, NULL);
+
+	cache_gc.cb = cache_gc_timer;
+	uloop_timeout_set(&cache_gc, 10000);
+	cache_load_services();
+
+	return 0;
+}
+
+void cache_cleanup(void)
+{
+	struct cache_record *r, *p;
+	struct cache_entry *s, *t;
+
+	avl_for_each_element_safe(&records, r, avl, p)
+		cache_record_free(r, 1);
+
+	avl_for_each_element_safe(&entries, s, avl, t)
+		cache_entry_free(s);
+}
+
+void
+cache_scan(void)
+{
+	struct cache_entry *s;
+
+	avl_for_each_element(&entries, s, avl)
+		dns_send_question(&listener, s->entry, TYPE_PTR);
+}
+
+static struct cache_entry*
+cache_find_entry(char *entry)
+{
+	struct cache_entry *s;
+
+	avl_for_each_element(&entries, s, avl)
+		if (!strcmp(s->entry, entry))
+			return s;
+	return NULL;
+}
+
+static struct cache_entry*
+cache_entry(struct uloop_fd *u, char *entry, int hlen, int ttl)
+{
+	struct cache_entry *s;
+	char *type;
+
+	s = cache_find_entry(entry);
+	if (s)
+		return s;
+
+	s = malloc(sizeof(struct cache_entry));
+	memset(s, 0, sizeof(struct cache_entry));
+	s->avl.key = s->entry = strdup(entry);
+	s->time = time(NULL);
+	s->ttl = ttl;
+
+	if (hlen)
+		s->host = strndup(s->entry, hlen);
+	type = strstr(s->entry, "._");
+	if (type)
+		type++;
+	if (type)
+		s->avl.key = type;
+	avl_insert(&entries, &s->avl);
+
+	if (!hlen)
+		dns_send_question(u, entry, TYPE_PTR);
+
+	return s;
+}
+
+static struct cache_record*
+cache_record_find(char *record, int type, int port, int rdlength, uint8_t *rdata)
+{
+	struct cache_record *l = avl_find_element(&records, record, l, avl);
+
+	if (!l)
+		return NULL;
+
+	while (l && !avl_is_last(&records, &l->avl) && !strcmp(l->record, record)) {
+		struct cache_record *r = l;
+
+		l = avl_next_element(l, avl);
+		if (r->type != type)
+			continue;
+
+		if (r->type == TYPE_TXT || (r->type == TYPE_SRV))
+			return r;
+
+		if (r->port != port)
+			continue;
+
+		if (r->rdlength != rdlength)
+			continue;
+
+		if (!!r->rdata != !!rdata)
+			continue;
+
+		if (!r->rdata || !rdata || memcmp(r->rdata, rdata, rdlength))
+			continue;
+
+		return r;
+	}
+
+	return NULL;
+}
+
+int
+cache_host_is_known(char *record)
+{
+	struct cache_record *l = avl_find_element(&records, record, l, avl);
+
+	if (!l)
+		return 0;
+
+	while (l && !avl_is_last(&records, &l->avl) && !strcmp(l->record, record)) {
+		struct cache_record *r = l;
+
+		l = avl_next_element(l, avl);
+		if ((r->type != TYPE_A) && (r->type != TYPE_AAAA))
+			continue;
+		return 1;
+	}
+
+	return 0;
+}
+
+void
+cache_answer(struct uloop_fd *u, uint8_t *base, int blen, char *name, struct dns_answer *a, uint8_t *rdata)
+{
+	struct dns_srv_data *dsd = (struct dns_srv_data *) rdata;
+	struct cache_record *r;
+	int port = 0, dlen = 0, tlen = 0, nlen, rdlength;
+	char *p = NULL;
+
+	if (!(a->class & CLASS_IN))
+		return;
+
+	nlen = strlen(name);
+
+	switch (a->type) {
+	case TYPE_PTR:
+		if (a->rdlength < 2)
+			return;
+
+		if (dn_expand(base, base + blen, rdata, rdata_buffer, MAX_DATA_LEN) < 0) {
+			perror("process_answer/dn_expand");
+			return;
+		}
+
+		DBG(1, "A -> %s %s %s\n", dns_type_string(a->type), name, rdata_buffer);
+
+		rdlength = strlen(rdata_buffer);
+
+		if (!strcmp(C_DNS_SD, name)) {
+			cache_entry(u, rdata_buffer, 0, a->ttl);
+			return;
+		}
+
+		if ((rdlength < nlen) && (rdlength - nlen - 1 > 0))
+			return;
+
+		cache_entry(u, rdata_buffer, rdlength - nlen - 1, a->ttl);
+		return;
+
+	case TYPE_SRV:
+		if (a->rdlength < 8)
+			return;
+
+		port = be16_to_cpu(dsd->port);
+		break;
+
+	case TYPE_TXT:
+		rdlength = a->rdlength;
+		if (rdlength <= 2)
+			return;
+
+		memcpy(rdata_buffer, &rdata[1], rdlength);
+		rdata_buffer[rdlength] = rdata_buffer[rdlength + 1] = '\0';
+		tlen = rdlength + 1;
+		p = &rdata_buffer[*rdata];
+
+		do {
+			uint8_t v = *p;
+
+			*p = '\0';
+			if (v)
+				p += v + 1;
+		} while (*p);
+		break;
+
+	case TYPE_A:
+		cache_entry(u, name, strlen(name), a->ttl);
+		if (a->rdlength != 4)
+			return;
+		dlen = 4;
+		break;
+
+	case TYPE_AAAA:
+		cache_entry(u, name, strlen(name), a->ttl);
+		if (a->rdlength != 16)
+			return;
+		dlen = 16;
+		break;
+
+	default:
+		return;
+	}
+
+	r = cache_record_find(name, a->type, port, dlen, rdata);
+	if (r) {
+		if (!a->ttl) {
+			cache_record_free(r, 1);
+			DBG(1, "D -> %s %s ttl:%d\n", dns_type_string(r->type), r->record, r->ttl);
+		} else {
+			r->ttl = a->ttl;
+			DBG(1, "A -> %s %s ttl:%d\n", dns_type_string(r->type), r->record, r->ttl);
+		}
+		return;
+	}
+
+	if (!a->ttl)
+		return;
+
+	r = malloc(sizeof(struct cache_record));
+	memset(r, 0, sizeof(struct cache_record));
+	r->avl.key = r->record = strdup(name);
+	r->type = a->type;
+	r->ttl = a->ttl;
+	r->port = port;
+	r->rdlength = dlen;
+	r->time = time(NULL);
+
+	if (tlen) {
+		r->txt = malloc(tlen);
+		if (r->txt)
+			memcpy(r->txt, rdata_buffer, tlen);
+	}
+
+	if (dlen) {
+		r->rdata = malloc(dlen);
+		if (!r->rdata) {
+			cache_record_free(r, 0);
+			return;
+		}
+		memcpy(r->rdata, rdata, dlen);
+	}
+
+	if (avl_insert(&records, &r->avl))
+		cache_record_free(r, 0);
+	else
+		DBG(1, "A -> %s %s ttl:%d\n", dns_type_string(r->type), r->record, r->ttl);
+}

+ 61 - 0
cache.h

@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef _CACHE_H__
+#define _CACHE_H__
+
+#include <libubox/avl.h>
+#include <libubox/list.h>
+
+#include "dns.h"
+
+struct cache_type {
+        struct avl_node avl;
+
+	char *key;
+	char *val;
+};
+
+struct cache_entry {
+        struct avl_node avl;
+
+	char *entry;
+	char *host;
+	uint32_t ttl;
+	time_t time;
+};
+
+struct cache_record {
+        struct avl_node avl;
+
+	char *record;
+	uint16_t type;
+	uint32_t ttl;
+	int port;
+	char *txt;
+	uint8_t *rdata;
+	uint16_t rdlength;
+	time_t time;
+};
+
+extern struct avl_tree records, entries, types;
+
+extern int cache_init(void);
+extern void cache_scan(void);
+extern void cache_cleanup(void);
+extern void cache_answer(struct uloop_fd *u, uint8_t *base, int blen,
+		char *name, struct dns_answer *a, uint8_t *rdata);
+extern int cache_host_is_known(char *record);
+extern char* cache_lookup_name(const char *key);
+
+#endif

+ 331 - 0
dns.c

@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <time.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <asm/byteorder.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libubox/uloop.h>
+#include <libubox/usock.h>
+
+#include "announce.h"
+#include "util.h"
+#include "dns.h"
+
+char *rdata_buffer;
+static char *name_buffer;
+
+const char*
+dns_type_string(uint16_t type)
+{
+	switch (type) {
+	case TYPE_A:
+		return "A";
+
+	case TYPE_AAAA:
+		return "AAAA";
+
+	case TYPE_PTR:
+		return "PTR";
+
+	case TYPE_TXT:
+		return "TXT";
+
+	case TYPE_SRV:
+		return "SRV";
+
+	case TYPE_ANY:
+		return "ANY";
+	}
+
+	return "N/A";
+}
+
+void
+dns_send_question(struct uloop_fd *u, char *question, int type)
+{
+	unsigned char buffer[MAX_NAME_LEN];
+	struct dns_header h = { 0 };
+	struct dns_question q = { 0 };
+	struct msghdr m = { 0 };
+	struct iovec iov[3] = { {0}, {0}, {0} };
+	struct sockaddr_in a = { 0 };
+	int len;
+
+	a.sin_family = AF_INET;
+	a.sin_addr.s_addr = inet_addr(MCAST_ADDR);
+	a.sin_port = htons(MCAST_PORT);
+
+	h.questions = __cpu_to_be16(1);
+	q.type = __cpu_to_be16(type);
+	q.class = __cpu_to_be16(1);
+
+	len = dn_comp(question, buffer, MAX_NAME_LEN, NULL, NULL);
+	if (len < 1)
+		return;
+
+	m.msg_name = &a;
+	m.msg_namelen = sizeof(struct sockaddr_in);
+	m.msg_iov = iov;
+	m.msg_iovlen = 3;
+
+	iov[0].iov_base = &h;
+	iov[0].iov_len = sizeof(struct dns_header);
+
+	iov[1].iov_base = buffer;
+	iov[1].iov_len = len;
+
+	iov[2].iov_base = &q;
+	iov[2].iov_len = sizeof(struct dns_question);
+
+	if (sendmsg(u->fd, &m, 0) < 0)
+		fprintf(stderr, "failed to send question\n");
+	else
+		DBG(1, "Q <- %s %s\n", dns_type_string(type), question);
+}
+
+
+struct dns_reply {
+	int type;
+	struct dns_answer a;
+	uint16_t rdlength;
+	uint8_t *rdata;
+	char *buffer;
+};
+
+#define MAX_ANSWER	8
+static struct dns_reply dns_reply[1 + (MAX_ANSWER * 3)];
+static int dns_answer_cnt;
+
+void
+dns_init_answer(void)
+{
+	dns_answer_cnt = 0;
+}
+
+void
+dns_add_answer(int type, uint8_t *rdata, uint16_t rdlength)
+{
+	struct dns_reply *a = &dns_reply[dns_answer_cnt];
+	if (dns_answer_cnt == MAX_ANSWER)
+		return;
+	a->rdata = memdup(rdata, rdlength);
+	a->type = type;
+	a->rdlength = rdlength;
+	dns_answer_cnt++;
+}
+
+void
+dns_send_answer(struct uloop_fd *u, char *answer)
+{
+	uint8_t buffer[256];
+	struct dns_header h = { 0 };
+	struct msghdr m = { 0 };
+	struct iovec *iov;
+	struct sockaddr_in in = { 0 };
+	int len, i;
+
+	if (!dns_answer_cnt)
+		return;
+
+	in.sin_family = AF_INET;
+	in.sin_addr.s_addr = inet_addr(MCAST_ADDR);
+	in.sin_port = htons(MCAST_PORT);
+
+	h.answers = __cpu_to_be16(dns_answer_cnt);
+	h.flags = __cpu_to_be16(0x8400);
+
+	iov = malloc(sizeof(struct iovec) * ((dns_answer_cnt * 3) + 1));
+	if (!iov)
+		return;
+
+	m.msg_name = &in;
+	m.msg_namelen = sizeof(struct sockaddr_in);
+	m.msg_iov = iov;
+	m.msg_iovlen = (dns_answer_cnt * 3) + 1;
+
+	iov[0].iov_base = &h;
+	iov[0].iov_len = sizeof(struct dns_header);
+
+	for (i = 0; i < dns_answer_cnt; i++) {
+		struct dns_answer *a = &dns_reply[i].a;
+		int id = (i * 3) + 1;
+
+		memset(a, 0, sizeof(*a));
+		a->type = __cpu_to_be16(dns_reply[i].type);
+		a->class = __cpu_to_be16(1);
+		a->ttl = __cpu_to_be32(announce_ttl);
+		a->rdlength = __cpu_to_be16(dns_reply[i].rdlength);
+
+		len = dn_comp(answer, buffer, sizeof(buffer), NULL, NULL);
+		if (len < 1)
+			return;
+
+		dns_reply[i].buffer = iov[id].iov_base = memdup(buffer, len);
+		iov[id].iov_len = len;
+
+		iov[id + 1].iov_base = a;
+		iov[id + 1].iov_len = sizeof(struct dns_answer);
+
+		iov[id + 2].iov_base = dns_reply[i].rdata;
+		iov[id + 2].iov_len = dns_reply[i].rdlength;
+
+		DBG(1, "A <- %s %s\n", dns_type_string(dns_reply[i].type), answer);
+	}
+
+	if (sendmsg(u->fd, &m, 0) < 0)
+		fprintf(stderr, "failed to send question\n");
+
+	for (i = 0; i < dns_answer_cnt; i++) {
+		free(dns_reply[i].buffer);
+		free(dns_reply[i].rdata);
+	}
+	dns_answer_cnt = 0;
+}
+
+static int
+scan_name(uint8_t *buffer, int len)
+{
+	int offset = 0;
+
+	while (len && (*buffer != '\0')) {
+		int l = *buffer;
+
+		if (IS_COMPRESSED(l))
+			return offset + 2;
+
+		len -= l + 1;
+		offset += l + 1;
+		buffer += l + 1;
+	}
+
+	if (!len || !offset || (*buffer != '\0'))
+		return -1;
+
+	return offset + 1;
+}
+
+struct dns_header*
+dns_consume_header(uint8_t **data, int *len)
+{
+	struct dns_header *h = (struct dns_header *) *data;
+	uint16_t *swap = (uint16_t *) h;
+	int endianess = 6;
+
+	if (*len < sizeof(struct dns_header))
+		return NULL;
+
+	while (endianess--) {
+		*swap = __be16_to_cpu(*swap);
+		swap++;
+	}
+
+	*len -= sizeof(struct dns_header);
+	*data += sizeof(struct dns_header);
+
+	return h;
+}
+
+struct dns_question*
+dns_consume_question(uint8_t **data, int *len)
+{
+	struct dns_question *q = (struct dns_question *) *data;
+	uint16_t *swap = (uint16_t *) q;
+	int endianess = 2;
+
+	if (*len < sizeof(struct dns_question))
+		return NULL;
+
+	while (endianess--) {
+		*swap = __be16_to_cpu(*swap);
+		swap++;
+	}
+
+	*len -= sizeof(struct dns_question);
+	*data += sizeof(struct dns_question);
+
+	return q;
+}
+
+struct dns_answer*
+dns_consume_answer(uint8_t **data, int *len)
+{
+	struct dns_answer *a = (struct dns_answer *) *data;
+
+	if (*len < sizeof(struct dns_answer))
+		return NULL;
+
+	a->type = __be16_to_cpu(a->type);
+	a->class = __be16_to_cpu(a->class);
+	a->ttl = __be32_to_cpu(a->ttl);
+	a->rdlength = __be16_to_cpu(a->rdlength);
+
+	*len -= sizeof(struct dns_answer);
+	*data += sizeof(struct dns_answer);
+
+	return a;
+}
+
+char*
+dns_consume_name(uint8_t *base, int blen, uint8_t **data, int *len)
+{
+	int nlen = scan_name(*data, *len);
+
+	if (nlen < 1)
+		return NULL;
+
+	if (dn_expand(base, base + blen, *data, name_buffer, MAX_NAME_LEN) < 0) {
+		perror("dns_consume_name/dn_expand");
+		return NULL;
+	}
+
+	*len -= nlen;
+	*data += nlen;
+
+	return name_buffer;
+}
+
+int
+dns_init(void)
+{
+	name_buffer = malloc(MAX_NAME_LEN + 1);
+	rdata_buffer = malloc(MAX_DATA_LEN + 1);
+
+	if (!name_buffer || !rdata_buffer)
+		return -1;
+
+	memset(name_buffer, 0, MAX_NAME_LEN + 1);
+	memset(rdata_buffer, 0, MAX_NAME_LEN + 1);
+
+	return 0;
+}
+
+void
+dns_cleanup(void)
+{
+	free(name_buffer);
+	free(rdata_buffer);
+}

+ 81 - 0
dns.h

@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef _DNS_H__
+#define _DNS_H__
+
+#define FLAG_RESPONSE		0x8000
+#define FLAG_AUTHORATIVE	0x0400
+
+#define TYPE_A			0x0001
+#define TYPE_PTR		0x000C
+#define TYPE_TXT		0x0010
+#define TYPE_AAAA		0x001c
+#define TYPE_SRV		0x0021
+#define TYPE_ANY		0x00ff
+
+#define IS_COMPRESSED(x)	((x & 0xc0) == 0xc0)
+
+#define MCAST_ADDR		"224.0.0.251"
+#define MCAST_PORT		5353
+
+#define CLASS_FLUSH		0x8000
+#define CLASS_IN		0x0001
+
+#define MAX_NAME_LEN		8096
+#define MAX_DATA_LEN		8096
+
+#define C_DNS_SD                "_services._dns-sd._udp.local"
+
+struct dns_header {
+	uint16_t id;
+	uint16_t flags;
+	uint16_t questions;
+	uint16_t answers;
+	uint16_t authority;
+	uint16_t additional;
+};
+
+struct dns_srv_data {
+	uint16_t priority;
+	uint16_t weight;
+	uint16_t port;
+} __attribute__((packed, aligned(2)));
+
+struct dns_answer {
+	uint16_t type;
+	uint16_t class;
+	uint32_t ttl;
+	uint16_t rdlength;
+} __attribute__((packed, aligned(2)));
+
+struct dns_question {
+	uint16_t type;
+	uint16_t class;
+} __attribute__((packed, aligned(2)));
+
+extern char *rdata_buffer;
+
+extern int dns_init(void);
+extern void dns_cleanup(void);
+extern void dns_send_question(struct uloop_fd *u, char *question, int type);
+extern void dns_init_answer(void);
+extern void dns_add_answer(int type, uint8_t *rdata, uint16_t rdlength);
+extern void dns_send_answer(struct uloop_fd *u, char *answer);
+extern char* dns_consume_name(uint8_t *base, int blen, uint8_t **data, int *len);
+extern struct dns_answer* dns_consume_answer(uint8_t **data, int *len);
+extern struct dns_question* dns_consume_question(uint8_t **data, int *len);
+extern struct dns_header* dns_consume_header(uint8_t **data, int *len);
+extern const char* dns_type_string(uint16_t type);
+
+#endif

+ 3 - 0
json/http.json

@@ -0,0 +1,3 @@
+{
+	"_http._tcp.local": { "port": 80, "txt": [ "foo", "bar"] },
+}

+ 3 - 0
json/ssh.json

@@ -0,0 +1,3 @@
+{
+	"_ssh._tcp.local": { "port": 22, "txt": [ "a=foo", "d=bar"] },
+}

+ 254 - 0
main.c

@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <time.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <resolv.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <asm/byteorder.h>
+
+#include <libubus.h>
+#include <libubox/usock.h>
+#include <libubox/uloop.h>
+#include <libubox/avl-cmp.h>
+
+#include "dns.h"
+#include "ubus.h"
+#include "util.h"
+#include "cache.h"
+#include "service.h"
+#include "announce.h"
+
+static struct uloop_timeout reconnect;
+char *iface_name = "eth0";
+const char *iface_ip;
+
+static int
+parse_answer(struct uloop_fd *u, uint8_t *buffer, int len, uint8_t **b, int *rlen, int cache)
+{
+	char *name = dns_consume_name(buffer, len, b, rlen);
+	struct dns_answer *a;
+	uint8_t *rdata;
+
+	if (!name) {
+		fprintf(stderr, "dropping: bad question\n");
+		return -1;
+	}
+
+	a = dns_consume_answer(b, rlen);
+	if (!a) {
+		fprintf(stderr, "dropping: bad question\n");
+		return -1;
+	}
+
+	rdata = *b;
+	if (a->rdlength > *rlen) {
+		fprintf(stderr, "dropping: bad question\n");
+		return -1;
+	}
+
+	*rlen -= a->rdlength;
+	*b += a->rdlength;
+
+	if (cache)
+		cache_answer(u, buffer, len, name, a, rdata);
+
+	return 0;
+}
+
+static void
+parse_question(struct uloop_fd *u, char *name, struct dns_question *q)
+{
+	char *host;
+
+	DBG(1, "Q -> %s %s\n", dns_type_string(q->type), name);
+
+	switch (q->type) {
+	case TYPE_ANY:
+		host = service_name("local");
+		if (!strcmp(name, host))
+			service_reply(u, NULL);
+		break;
+
+	case TYPE_PTR:
+		service_announce_services(u, name);
+		service_reply(u, name);
+		break;
+
+	case TYPE_AAAA:
+	case TYPE_A:
+		host = strstr(name, ".local");
+		if (host)
+			*host = '\0';
+		if (!strcmp(hostname, name))
+			service_reply_a(u, q->type);
+		break;
+	};
+}
+
+static void
+read_socket(struct uloop_fd *u, unsigned int events)
+{
+	uint8_t buffer[8 * 1024];
+	uint8_t *b = buffer;
+	struct dns_header *h;
+	int len, rlen;
+
+	if (u->eof) {
+		uloop_fd_delete(u);
+		close(u->fd);
+		u->fd = -1;
+		uloop_timeout_set(&reconnect, 1000);
+		return;
+	}
+
+	rlen = len = read(u->fd, buffer, sizeof(buffer));
+	if (len < 1) {
+		fprintf(stderr, "read failed: %s\n", strerror(errno));
+		return;
+	}
+
+	h = dns_consume_header(&b, &rlen);
+	if (!h) {
+		fprintf(stderr, "dropping: bad header\n");
+		return;
+	}
+
+	while (h->questions-- > 0) {
+		char *name = dns_consume_name(buffer, len, &b, &rlen);
+		struct dns_question *q;
+
+		if (!name) {
+			fprintf(stderr, "dropping: bad name\n");
+			return;
+		}
+
+		q = dns_consume_question(&b, &rlen);
+		if (!q) {
+			fprintf(stderr, "dropping: bad question\n");
+			return;
+		}
+
+		if (!(h->flags & FLAG_RESPONSE))
+			parse_question(announce_fd, name, q);
+	}
+
+	if (!(h->flags & FLAG_RESPONSE))
+		return;
+
+	while (h->answers-- > 0)
+		parse_answer(u, buffer, len, &b, &rlen, 1);
+
+	while (h->authority-- > 0)
+		parse_answer(u, buffer, len, &b, &rlen, 0);
+
+	while (h->additional-- > 0)
+		parse_answer(u, buffer, len, &b, &rlen, 1);
+}
+
+static void
+reconnect_socket(struct uloop_timeout *timeout)
+{
+
+	if (iface_ip)
+		listener.fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK, MCAST_ADDR, "5353");
+
+	if (!iface_ip || listener.fd < 0) {
+		fprintf(stderr, "failed to add listener: %s\n", strerror(errno));
+		uloop_timeout_set(&reconnect, 1000);
+	} else {
+		if (socket_setup(listener.fd, iface_ip)) {
+			uloop_timeout_set(&reconnect, 1000);
+			listener.fd = -1;
+			return;
+		}
+
+		uloop_fd_add(&listener, ULOOP_READ);
+		sleep(5);
+		dns_send_question(&listener, "_services._dns-sd._tcp.local", TYPE_PTR);
+		dns_send_question(&listener, "_services._dns-sd._udp.local", TYPE_PTR);
+		announce_init(&listener);
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	int ch, ttl;
+
+	while ((ch = getopt(argc, argv, "h:t:i:d")) != -1) {
+		switch (ch) {
+		case 'h':
+			hostname = optarg;
+			break;
+		case 't':
+			ttl = atoi(optarg);
+			if (ttl > 0)
+				announce_ttl = ttl;
+			else
+				fprintf(stderr, "invalid ttl\n");
+			break;
+		case 'd':
+			debug++;
+			break;
+		case 'i':
+			iface_name = optarg;
+			break;
+		}
+	}
+
+	if (!iface_name)
+		return -1;
+
+	iface_ip = get_iface_ipv4(iface_name);
+
+	if (!iface_ip) {
+		fprintf(stderr, "failed to read ip for %s\n", iface_name);
+		return -1;
+	}
+	fprintf(stderr, "interface %s has ip %s\n", iface_name, iface_ip);
+	signal_setup();
+
+	if (dns_init())
+		return -1;
+
+	if (cache_init())
+		return -1;
+
+	service_init();
+
+	listener.cb = read_socket;
+	reconnect.cb = reconnect_socket;
+
+	uloop_init();
+	uloop_timeout_set(&reconnect, 100);
+	ubus_startup();
+	uloop_run();
+	uloop_done();
+
+	dns_cleanup();
+	cache_cleanup();
+	service_cleanup();
+
+	return 0;
+}

+ 72 - 0
service-types

@@ -0,0 +1,72 @@
+{
+	"_workstation._tcp": "Workstation",
+	"_http._tcp": "Web Site",
+	"_https._tcp": "Secure Web Site",
+	"_rss._tcp": "Web Syndication RSS",
+	"_domain._udp": "DNS Server",
+	"_ntp._udp": "NTP Time Server",
+	"_smb._tcp": "Microsoft Windows Network",
+	"_airport._tcp": "Apple AirPort",
+	"_ftp._tcp": "FTP File Transfer",
+	"_tftp._udp": "TFTP Trivial File Transfer",
+	"_webdav._tcp": "WebDAV File Share",
+	"_webdavs._tcp": "Secure WebDAV File Share",
+	"_afpovertcp._tcp": "Apple File Sharing",
+	"_nfs._tcp": "Network File System",
+	"_sftp-ssh._tcp": "SFTP File Transfer",
+	"_apt._tcp": "APT Package Repository",
+	"_odisk._tcp": "DVD or CD Sharing",
+	"_adisk._tcp": "Apple TimeMachine",
+	"_ssh._tcp": "SSH Remote Terminal",
+	"_rfb._tcp": "VNC Remote Access",
+	"_telnet._tcp": "Telnet Remote Terminal",
+	"_timbuktu._tcp": "Timbuktu Remote Desktop Control",
+	"_net-assistant._udp": "Apple Net Assistant",
+	"_udisks-ssh._tcp": "Remote Disk Management",
+	"_imap._tcp": "IMAP Mail Access",
+	"_pop3._tcp": "POP3 Mail Access",
+	"_printer._tcp": "UNIX Printer",
+	"_pdl-datastream._tcp": "PDL Printer",
+	"_ipp._tcp": "Internet Printer",
+	"_daap._tcp": "iTunes Audio Access",
+	"_dacp._tcp": "iTunes Remote Control",
+	"_realplayfavs._tcp": "RealPlayer Shared Favorites",
+	"_raop._tcp": "AirTunes Remote Audio",
+	"_rtsp._tcp": "RTSP Realtime Streaming Server",
+	"_rtp._udp": "RTP Realtime Streaming Server",
+	"_dpap._tcp": "Digital Photo Sharing",
+	"_pulse-server._tcp": "PulseAudio Sound Server",
+	"_pulse-sink._tcp": "PulseAudio Sound Sink",
+	"_pulse-source._tcp": "PulseAudio Sound Source",
+	"_mpd._tcp": "Music Player Daemon",
+	"_remote-jukebox._tcp": "Remote Jukebox",
+	"_touch-able._tcp": "iPod Touch Music Library",
+	"_vlc-http._tcp": "VLC Streaming",
+	"_presence._tcp": "iChat Presence",
+	"_sip._udp": "SIP Telephony",
+	"_h323._tcp": "H.323 Telephony",
+	"_presence_olpc._tcp": "OLPC Presence",
+	"_iax._udp": "Asterisk Exchange",
+	"_skype._tcp": "Skype VoIP",
+	"_see._tcp": "SubEthaEdit Collaborative Text Editor",
+	"_lobby._tcp": "Gobby Collaborative Editor Session",
+	"_mumble._tcp": "Mumble Server",
+	"_postgresql._tcp": "PostgreSQL Server",
+	"_svn._tcp": "Subversion Revision Control",
+	"_distcc._tcp": "Distributed Compiler",
+	"_bzr._tcp": "Bazaar",
+	"_MacOSXDupSuppress._tcp": "MacOS X Duplicate Machine Suppression",
+	"_ksysguard._tcp": "KDE System Guard",
+	"_omni-bookmark._tcp": "OmniWeb Bookmark Sharing",
+	"_acrobatSRV._tcp": "Adobe Acrobat",
+	"_adobe-vc._tcp": "Adobe Version Cue",
+	"_home-sharing._tcp": "Apple Home Sharing",
+	"_pgpkey-hkp._tcp": "GnuPG/PGP HKP Key Server",
+	"_ldap._tcp": "LDAP Directory Server",
+	"_tp._tcp": "Thousand Parsec Server",
+	"_tps._tcp": "Thousand Parsec Server (Secure)",
+	"_tp-http._tcp": "Thousand Parsec Server (HTTP Tunnel)",
+	"_tp-https._tcp": "Thousand Parsec Server (Secure HTTP Tunnel)",
+	"_shifter._tcp": "Window Shifter",
+	"_libvirt._tcp": "Virtual Machine Manager",
+}

+ 325 - 0
service.c

@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <resolv.h>
+#include <glob.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <uci.h>
+#include <uci_blob.h>
+
+#include <libubox/avl.h>
+#include <libubox/uloop.h>
+#include <libubox/avl-cmp.h>
+#include <libubox/blobmsg_json.h>
+
+#include "dns.h"
+#include "service.h"
+#include "util.h"
+
+enum {
+	SERVICE_PORT,
+	SERVICE_TXT,
+	__SERVICE_MAX,
+};
+
+struct service {
+        struct avl_node avl;
+
+	time_t t;
+
+	char *service;
+	char *daemon;
+	char *txt;
+	int txt_len;
+	int port;
+	int active;
+};
+
+static const struct blobmsg_policy service_policy[__SERVICE_MAX] = {
+	[SERVICE_PORT] = { .name = "port", .type = BLOBMSG_TYPE_INT32 },
+	[SERVICE_TXT] = { .name = "txt", .type = BLOBMSG_TYPE_ARRAY },
+};
+
+static const struct uci_blob_param_list service_attr_list = {
+	.n_params = __SERVICE_MAX,
+	.params = service_policy,
+};
+
+static struct blob_buf b;
+static struct avl_tree services;
+char *hostname = NULL;
+static char *sdudp =  "_services._dns-sd._udp.local";
+static char *sdtcp =  "_services._dns-sd._tcp.local";
+
+char*
+service_name(char *domain)
+{
+	static char buffer[256];
+
+	snprintf(buffer, sizeof(buffer), "%s.%s", hostname, domain);
+
+	return buffer;
+}
+
+static void
+service_send_ptr(struct uloop_fd *u, struct service *s)
+{
+	unsigned char buffer[MAX_NAME_LEN];
+	char *host = service_name(s->service);
+	int len = dn_comp(host, buffer, MAX_NAME_LEN, NULL, NULL);
+
+	if (len < 1)
+		return;
+
+	dns_add_answer(TYPE_PTR, buffer, len);
+}
+
+static void
+service_send_ptr_c(struct uloop_fd *u, char *host)
+{
+	unsigned char buffer[MAX_NAME_LEN];
+	int len = dn_comp(host, buffer, MAX_NAME_LEN, NULL, NULL);
+
+	if (len < 1)
+		return;
+
+	dns_add_answer(TYPE_PTR, buffer, len);
+}
+
+static void
+service_send_a(struct uloop_fd *u)
+{
+	unsigned char buffer[MAX_NAME_LEN];
+	char *host = service_name("local");
+	int len = dn_comp(host, buffer, MAX_NAME_LEN, NULL, NULL);
+	struct in_addr in;
+
+	if (!inet_aton(iface_ip, &in)) {
+		fprintf(stderr, "%s is not valid\n", iface_ip);
+		return;
+	}
+
+	if (len < 1)
+		return;
+
+	dns_add_answer(TYPE_A, (uint8_t *) &in.s_addr, 4);
+}
+
+static void
+service_send_srv(struct uloop_fd *u, struct service *s)
+{
+	unsigned char buffer[MAX_NAME_LEN];
+	struct dns_srv_data *sd;
+	char *host = service_name("local");
+	int len = dn_comp(host, buffer, MAX_NAME_LEN, NULL, NULL);
+
+	if (len < 1)
+		return;
+
+	sd = malloc(len + sizeof(struct dns_srv_data));
+	if (!sd)
+		return;
+
+	memset(sd, 0, sizeof(struct dns_srv_data));
+	sd->port = cpu_to_be16(s->port);
+	memcpy(&sd[1], buffer, len);
+	host = service_name(s->service);
+	dns_add_answer(TYPE_SRV, (uint8_t *) sd, len + sizeof(struct dns_srv_data));
+	free(sd);
+}
+
+#define TOUT_LOOKUP	60
+
+static int
+service_timeout(struct service *s)
+{
+	time_t t = time(NULL);
+
+	if (t - s->t <= TOUT_LOOKUP)
+		return 0;
+
+	s->t = t;
+
+	return 1;
+}
+
+void
+service_reply_a(struct uloop_fd *u, int type)
+{
+	if (type != TYPE_A)
+		return;
+
+	dns_init_answer();
+	service_send_a(u);
+	dns_send_answer(u, service_name("local"));
+}
+
+void
+service_reply(struct uloop_fd *u, char *match)
+{
+	struct service *s;
+
+	avl_for_each_element(&services, s, avl) {
+		char *host = service_name(s->service);
+		char *service = strstr(host, "._");
+
+		if (!s->active || !service || !service_timeout(s))
+			continue;
+
+		service++;
+
+		if (match && strcmp(match, s->service))
+			continue;
+
+		dns_init_answer();
+		service_send_ptr(u, s);
+		dns_send_answer(u, service);
+
+		dns_init_answer();
+		service_send_srv(u, s);
+		if (s->txt && s->txt_len)
+			dns_add_answer(TYPE_TXT, (uint8_t *) s->txt, s->txt_len);
+		dns_send_answer(u, host);
+	}
+
+	if (match)
+		return;
+
+	dns_init_answer();
+	service_send_a(u);
+	dns_send_answer(u, service_name("local"));
+}
+
+void
+service_announce_services(struct uloop_fd *u, char *service)
+{
+	struct service *s;
+	int tcp = 1;
+
+	if (!strcmp(service, sdudp))
+		tcp = 0;
+	else if (strcmp(service, sdtcp))
+		return;
+
+	avl_for_each_element(&services, s, avl) {
+		if (!strstr(s->service, "._tcp") && tcp)
+			continue;
+		if (!strstr(s->service, "._udp") && !tcp)
+			continue;
+		s->t = 0;
+		dns_init_answer();
+		service_send_ptr_c(u, s->service);
+		if (tcp)
+			dns_send_answer(u, sdtcp);
+		else
+			dns_send_answer(u, sdudp);
+		service_reply(u, s->service);
+	}
+}
+
+void
+service_announce(struct uloop_fd *u)
+{
+	service_announce_services(u, sdudp);
+	service_announce_services(u, sdtcp);
+}
+
+static void
+service_load(char *path)
+{
+	struct blob_attr *txt, *cur, *_tb[__SERVICE_MAX];
+	int rem, i;
+	glob_t gl;
+
+	if (glob(path, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
+		return;
+
+	for (i = 0; i < gl.gl_pathc; i++) {
+	        blob_buf_init(&b, 0);
+
+		if (!blobmsg_add_json_from_file(&b, gl.gl_pathv[i]))
+			continue;
+		blob_for_each_attr(cur, b.head, rem) {
+			struct service *s;
+			char *d_service, *d_txt, *d_daemon;
+			int rem2;
+
+			blobmsg_parse(service_policy, ARRAY_SIZE(service_policy),
+				_tb, blobmsg_data(cur), blobmsg_data_len(cur));
+			if (!_tb[SERVICE_PORT] || !_tb[SERVICE_TXT])
+				continue;
+
+			s = calloc_a(sizeof(*s),
+				&d_daemon, strlen(gl.gl_pathv[i]) + 1,
+				&d_service, strlen(blobmsg_name(cur)) + 1,
+				&d_txt, blobmsg_data_len(_tb[SERVICE_TXT]));
+			if (!s)
+				continue;
+
+			s->port = blobmsg_get_u32(_tb[SERVICE_PORT]);
+			s->service = d_service;
+			s->daemon = d_daemon;
+			strcpy(s->service, blobmsg_name(cur));
+			strcpy(s->daemon, gl.gl_pathv[i]);
+			s->avl.key = s->service;
+			s->active = 1;
+			s->t = 0;
+			avl_insert(&services, &s->avl);
+
+			blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2)
+				s->txt_len += 1 + strlen(blobmsg_get_string(txt));
+
+			if (!s->txt_len)
+				continue;
+
+			d_txt = s->txt = malloc(s->txt_len);
+			if (!s->txt)
+				continue;
+
+			blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2) {
+				int len = strlen(blobmsg_get_string(txt));
+				if (!len)
+					continue;
+				if (len > 0xff)
+					len = 0xff;
+				*d_txt = len;
+				d_txt++;
+				memcpy(d_txt, blobmsg_get_string(txt), len);
+				d_txt += len;
+			}
+		}
+	}
+}
+
+void
+service_init(void)
+{
+	if (!hostname)
+		hostname = get_hostname();
+	avl_init(&services, avl_strcmp, true, NULL);
+	service_load("/tmp/run/mdnsd/*");
+}
+
+void
+service_cleanup(void)
+{
+}

+ 26 - 0
service.h

@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef _SERVICE_H__
+#define _SERVICE_H__
+
+extern char *hostname;
+extern char* service_name(char *domain);
+extern void service_init(void);
+extern void service_cleanup(void);
+extern void service_announce(struct uloop_fd *u);
+extern void service_announce_services(struct uloop_fd *u, char *service);
+extern void service_reply(struct uloop_fd *u, char *match);
+extern void service_reply_a(struct uloop_fd *u, int type);
+
+#endif

+ 196 - 0
ubus.c

@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+
+#include <libubus.h>
+#include <libubox/avl.h>
+#include <libubox/uloop.h>
+
+#include "ubus.h"
+#include "cache.h"
+
+static struct ubus_auto_conn conn;
+static struct blob_buf b;
+
+static int
+mdns_reload(struct ubus_context *ctx, struct ubus_object *obj,
+		struct ubus_request_data *req, const char *method,
+		struct blob_attr *msg)
+{
+	return 0;
+}
+
+static int
+mdns_scan(struct ubus_context *ctx, struct ubus_object *obj,
+		struct ubus_request_data *req, const char *method,
+		struct blob_attr *msg)
+{
+	cache_scan();
+	return 0;
+}
+
+static void
+mdns_add_records(char *name)
+{
+	struct cache_record *r, *q = avl_find_element(&records, name, r, avl);
+	char *txt;
+	char buffer[MAX_NAME_LEN];
+
+	if (!q)
+		return;
+
+	do {
+		r = q;
+		switch (r->type) {
+		case TYPE_TXT:
+			if (r->txt && strlen(r->txt)) {
+				txt = r->txt;
+				do {
+					blobmsg_add_string(&b, "txt", txt);
+					txt = &txt[strlen(txt) + 1];
+				} while (*txt);
+			}
+			break;
+
+		case TYPE_SRV:
+			if (r->port)
+				blobmsg_add_u32(&b, "port", r->port);
+			break;
+
+		case TYPE_A:
+			if ((r->rdlength == 4) && inet_ntop(AF_INET, r->rdata, buffer, INET6_ADDRSTRLEN))
+				blobmsg_add_string(&b, "ipv4", buffer);
+			break;
+
+		case TYPE_AAAA:
+			if ((r->rdlength == 16) && inet_ntop(AF_INET6, r->rdata, buffer, INET6_ADDRSTRLEN))
+				blobmsg_add_string(&b, "ipv6", buffer);
+			break;
+		}
+		q = avl_next_element(r, avl);
+	} while (q && !strcmp(r->record, q->record));
+}
+
+static int
+mdns_browse(struct ubus_context *ctx, struct ubus_object *obj,
+		struct ubus_request_data *req, const char *method,
+		struct blob_attr *msg)
+{
+	struct cache_entry *s, *q;
+	char buffer[MAX_NAME_LEN];
+	void *c1 = NULL, *c2;
+
+        blob_buf_init(&b, 0);
+	avl_for_each_element(&entries, s, avl) {
+		char *local;
+		if (*((char *) s->avl.key) != '_')
+			continue;
+		snprintf(buffer, MAX_NAME_LEN, s->avl.key);
+		local = strstr(buffer, ".local");
+		if (local)
+			*local = '\0';
+		if (!strcmp(buffer, "_tcp") || !strcmp(buffer, "_udp"))
+			continue;
+
+		if (!c1) {
+			char *type = cache_lookup_name(buffer);
+			c1 = blobmsg_open_table(&b, buffer);
+			if (type)
+				blobmsg_add_string(&b, ".desc", type);
+		}
+		snprintf(buffer, MAX_NAME_LEN, s->entry);
+		local = strstr(buffer, "._");
+		if (local)
+			*local = '\0';
+		c2 = blobmsg_open_table(&b, buffer);
+		strncat(buffer, ".local", MAX_NAME_LEN);
+		mdns_add_records(buffer);
+		mdns_add_records(s->entry);
+		blobmsg_close_table(&b, c2);
+		q = avl_next_element(s, avl);
+		if (!q || avl_is_last(&entries, &s->avl) || strcmp(s->avl.key, q->avl.key)) {
+			blobmsg_close_table(&b, c1);
+			c1 = NULL;
+		}
+	}
+	ubus_send_reply(ctx, req, b.head);
+
+	return UBUS_STATUS_OK;
+}
+
+static int
+mdns_hosts(struct ubus_context *ctx, struct ubus_object *obj,
+		struct ubus_request_data *req, const char *method,
+		struct blob_attr *msg)
+{
+	struct cache_entry *s;
+	char buffer[MAX_NAME_LEN];
+	void *c;
+
+        blob_buf_init(&b, 0);
+	avl_for_each_element(&entries, s, avl) {
+		char *local;
+		if (*((char *) s->avl.key) == '_')
+			continue;
+		snprintf(buffer, MAX_NAME_LEN, s->entry);
+		local = strstr(buffer, "._");
+		if (local)
+			*local = '\0';
+		c = blobmsg_open_table(&b, buffer);
+		strncat(buffer, ".local", MAX_NAME_LEN);
+		mdns_add_records(buffer);
+		mdns_add_records(s->entry);
+		blobmsg_close_table(&b, c);
+	}
+	ubus_send_reply(ctx, req, b.head);
+
+	return UBUS_STATUS_OK;
+}
+
+static const struct ubus_method mdns_methods[] = {
+	UBUS_METHOD_NOARG("scan", mdns_scan),
+	UBUS_METHOD_NOARG("browse", mdns_browse),
+	UBUS_METHOD_NOARG("hosts", mdns_hosts),
+	UBUS_METHOD_NOARG("reload", mdns_reload),
+};
+
+static struct ubus_object_type mdns_object_type =
+	UBUS_OBJECT_TYPE("mdns", mdns_methods);
+
+static struct ubus_object mdns_object = {
+	.name = "mdns",
+	.type = &mdns_object_type,
+	.methods = mdns_methods,
+	.n_methods = ARRAY_SIZE(mdns_methods),
+};
+
+static void
+ubus_connect_handler(struct ubus_context *ctx)
+{
+	int ret;
+
+	ret = ubus_add_object(ctx, &mdns_object);
+	if (ret)
+		fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
+}
+
+void
+ubus_startup(void)
+{
+	conn.cb = ubus_connect_handler;
+	ubus_auto_connect(&conn);
+}

+ 19 - 0
ubus.h

@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef _UBUS_H__
+#define _UBUS_H__
+
+extern void ubus_startup(void);
+
+#endif

+ 163 - 0
util.c

@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <linux/if.h>
+#include <linux/sockios.h>
+#include <arpa/inet.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#include <libubox/uloop.h>
+
+#include "dns.h"
+#include "util.h"
+
+int debug = 0;
+struct uloop_fd listener;
+
+static void
+signal_shutdown(int signal)
+{
+	uloop_end();
+}
+
+void
+signal_setup(void)
+{
+	signal(SIGPIPE, SIG_IGN);
+	signal(SIGTERM, signal_shutdown);
+	signal(SIGKILL, signal_shutdown);
+}
+
+uint32_t
+rand_time_delta(uint32_t t)
+{
+	uint32_t val;
+	int fd = open("/dev/urandom", O_RDONLY);
+
+	if (!fd)
+		return t;
+
+	if (read(fd, &val, sizeof(val)) == sizeof(val)) {
+		int range = t / 30;
+
+		srand(val);
+		val = t + (rand() % range) - (range / 2);
+	} else {
+		val = t;
+	}
+
+	close(fd);
+
+	return val;
+}
+
+const char*
+get_iface_ipv4(const char *ifname)
+{
+	static char buffer[INET_ADDRSTRLEN];
+	struct ifreq ir;
+	const char *ret;
+	int sock;
+
+	sock = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sock < 0)
+		return NULL;
+
+	memset(&ir, 0, sizeof(struct ifreq));
+
+	strncpy(ir.ifr_name, ifname, sizeof(ir.ifr_name));
+
+	if (ioctl(sock, SIOCGIFADDR, &ir) < 0)
+		return NULL;
+
+	ret = inet_ntop(AF_INET, &((struct sockaddr_in *) &ir.ifr_addr)->sin_addr, buffer, sizeof(buffer));
+	close(sock);
+
+	return ret;
+}
+
+char*
+get_hostname(void)
+{
+	static struct utsname utsname;
+
+	if (uname(&utsname) < 0)
+		return NULL;
+
+	return utsname.nodename;
+}
+
+int
+socket_setup(int fd, const char *ip)
+{
+	struct ip_mreqn mreq;
+	uint8_t ttl = 255;
+	int yes = 1;
+	int no = 0;
+	struct sockaddr_in sa;
+
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(MCAST_PORT);
+	inet_pton(AF_INET, MCAST_ADDR, &sa.sin_addr);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.imr_address.s_addr = htonl(INADDR_ANY);
+	mreq.imr_multiaddr = sa.sin_addr;
+
+	if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0)
+		fprintf(stderr, "ioctl failed: IP_MULTICAST_TTL\n");
+
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
+		fprintf(stderr, "ioctl failed: SO_REUSEADDR\n");
+
+	if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
+		fprintf(stderr, "failed to join multicast group: %s\n", strerror(errno));
+		close(fd);
+		fd = -1;
+		return -1;
+	}
+
+	if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) < 0)
+		fprintf(stderr, "ioctl failed: IP_RECVTTL\n");
+
+	if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0)
+		fprintf(stderr, "ioctl failed: IP_PKTINFO\n");
+
+	if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &no, sizeof(no)) < 0)
+		fprintf(stderr, "ioctl failed: IP_MULTICAST_LOOP\n");
+
+	return 0;
+}
+
+void*
+memdup(void *d, int l)
+{
+	void *r = malloc(l);
+	if (!r)
+		return NULL;
+	memcpy(r, d, l);
+	return r;
+}
+

+ 34 - 0
util.h

@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef _UTIL_H__
+#define _UTIL_H__
+
+#define DBG(level, fmt, ...) do { \
+	if (debug >= level) \
+		fprintf(stderr, "mdnsd: %s (%d): " fmt, __func__, __LINE__, ## __VA_ARGS__); \
+	} while (0)
+
+extern int debug;
+extern struct uloop_fd listener;
+extern const char *iface_ip;
+
+void *memdup(void *d, int l);
+
+extern void signal_setup(void);
+extern int socket_setup(int fd, const char *ip);
+extern char* get_hostname(void);
+extern const char* get_iface_ipv4(const char *ifname);
+extern uint32_t rand_time_delta(uint32_t t);
+
+#endif