--- /dev/null
+mdns
+.*
+Makefile
+CMakeCache.txt
+CMakeFiles
+*.cmake
+install_manifest.txt
--- /dev/null
+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
+)
--- /dev/null
+* timeouts and delays as described in rfc
+* service.c needs a cleanup/shutdown path
+* bridge devices dont works yet
+* automagic route setting
+* ipv6
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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 = ∈
+ 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);
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+{
+ "_http._tcp.local": { "port": 80, "txt": [ "foo", "bar"] },
+}
--- /dev/null
+{
+ "_ssh._tcp.local": { "port": 22, "txt": [ "a=foo", "d=bar"] },
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+{
+ "_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",
+}
--- /dev/null
+/*
+ * 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)
+{
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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