import v0.1
authorJohn Crispin <blogic@openwrt.org>
Thu, 16 Jan 2014 02:46:46 +0000 (02:46 +0000)
committerJohn Crispin <blogic@openwrt.org>
Mon, 17 Mar 2014 10:25:32 +0000 (10:25 +0000)
Signed-off-by: John Crispin <blogic@openwrt.org>
19 files changed:
.gitignore [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
TODO [new file with mode: 0644]
announce.c [new file with mode: 0644]
announce.h [new file with mode: 0644]
cache.c [new file with mode: 0644]
cache.h [new file with mode: 0644]
dns.c [new file with mode: 0644]
dns.h [new file with mode: 0644]
json/http.json [new file with mode: 0644]
json/ssh.json [new file with mode: 0644]
main.c [new file with mode: 0644]
service-types [new file with mode: 0644]
service.c [new file with mode: 0644]
service.h [new file with mode: 0644]
ubus.c [new file with mode: 0644]
ubus.h [new file with mode: 0644]
util.c [new file with mode: 0644]
util.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..957380e
--- /dev/null
@@ -0,0 +1,7 @@
+mdns
+.*
+Makefile
+CMakeCache.txt
+CMakeFiles
+*.cmake
+install_manifest.txt
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9807fe9
--- /dev/null
@@ -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
+)
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..908cf8e
--- /dev/null
+++ b/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
diff --git a/announce.c b/announce.c
new file mode 100644 (file)
index 0000000..a8a9540
--- /dev/null
@@ -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);
+}
diff --git a/announce.h b/announce.h
new file mode 100644 (file)
index 0000000..50ba631
--- /dev/null
@@ -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
diff --git a/cache.c b/cache.c
new file mode 100644 (file)
index 0000000..b244dae
--- /dev/null
+++ b/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);
+}
diff --git a/cache.h b/cache.h
new file mode 100644 (file)
index 0000000..03c35f6
--- /dev/null
+++ b/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
diff --git a/dns.c b/dns.c
new file mode 100644 (file)
index 0000000..1d8a830
--- /dev/null
+++ b/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);
+}
diff --git a/dns.h b/dns.h
new file mode 100644 (file)
index 0000000..b29e297
--- /dev/null
+++ b/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
diff --git a/json/http.json b/json/http.json
new file mode 100644 (file)
index 0000000..f7e607c
--- /dev/null
@@ -0,0 +1,3 @@
+{
+       "_http._tcp.local": { "port": 80, "txt": [ "foo", "bar"] },
+}
diff --git a/json/ssh.json b/json/ssh.json
new file mode 100644 (file)
index 0000000..c61423f
--- /dev/null
@@ -0,0 +1,3 @@
+{
+       "_ssh._tcp.local": { "port": 22, "txt": [ "a=foo", "d=bar"] },
+}
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..a6c99b5
--- /dev/null
+++ b/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;
+}
diff --git a/service-types b/service-types
new file mode 100644 (file)
index 0000000..fd738f6
--- /dev/null
@@ -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",
+}
diff --git a/service.c b/service.c
new file mode 100644 (file)
index 0000000..354099b
--- /dev/null
+++ b/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)
+{
+}
diff --git a/service.h b/service.h
new file mode 100644 (file)
index 0000000..a8937a7
--- /dev/null
+++ b/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
diff --git a/ubus.c b/ubus.c
new file mode 100644 (file)
index 0000000..c474bb5
--- /dev/null
+++ b/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);
+}
diff --git a/ubus.h b/ubus.h
new file mode 100644 (file)
index 0000000..428d166
--- /dev/null
+++ b/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
diff --git a/util.c b/util.c
new file mode 100644 (file)
index 0000000..a4a117b
--- /dev/null
+++ b/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;
+}
+
diff --git a/util.h b/util.h
new file mode 100644 (file)
index 0000000..6c73904
--- /dev/null
+++ b/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