From 788316ef66136e6a68d881e5b24ccf5a98fa4550 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Thu, 16 Jan 2014 02:46:46 +0000 Subject: [PATCH 1/1] import v0.1 Signed-off-by: John Crispin --- .gitignore | 7 + CMakeLists.txt | 22 ++++ TODO | 5 + announce.c | 84 ++++++++++++ announce.h | 21 +++ cache.c | 394 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cache.h | 61 +++++++++ dns.c | 331 ++++++++++++++++++++++++++++++++++++++++++++++++ dns.h | 81 ++++++++++++ json/http.json | 3 + json/ssh.json | 3 + main.c | 254 +++++++++++++++++++++++++++++++++++++ service-types | 72 +++++++++++ service.c | 325 +++++++++++++++++++++++++++++++++++++++++++++++ service.h | 26 ++++ ubus.c | 196 ++++++++++++++++++++++++++++ ubus.h | 19 +++ util.c | 163 ++++++++++++++++++++++++ util.h | 34 +++++ 19 files changed, 2101 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 TODO create mode 100644 announce.c create mode 100644 announce.h create mode 100644 cache.c create mode 100644 cache.h create mode 100644 dns.c create mode 100644 dns.h create mode 100644 json/http.json create mode 100644 json/ssh.json create mode 100644 main.c create mode 100644 service-types create mode 100644 service.c create mode 100644 service.h create mode 100644 ubus.c create mode 100644 ubus.h create mode 100644 util.c create mode 100644 util.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..957380e --- /dev/null +++ b/.gitignore @@ -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 index 0000000..9807fe9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(mdns C) +ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +SET(SOURCES main.c dns.c announce.c cache.c service.c util.c ubus.c) + +SET(LIBS ubox ubus resolv blobmsg_json) + +IF(DEBUG) + ADD_DEFINITIONS(-DDEBUG -g3) +ENDIF() + +ADD_EXECUTABLE(mdns ${SOURCES}) + +TARGET_LINK_LIBRARIES(mdns ${LIBS}) + +INSTALL(TARGETS mdns + RUNTIME DESTINATION sbin +) diff --git a/TODO b/TODO new file mode 100644 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 index 0000000..a8a9540 --- /dev/null +++ b/announce.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 + +#include + +#include + +#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 index 0000000..50ba631 --- /dev/null +++ b/announce.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 index 0000000..b244dae --- /dev/null +++ b/cache.c @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 index 0000000..03c35f6 --- /dev/null +++ b/cache.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 +#include + +#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 index 0000000..1d8a830 --- /dev/null +++ b/dns.c @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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); +} diff --git a/dns.h b/dns.h new file mode 100644 index 0000000..b29e297 --- /dev/null +++ b/dns.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 index 0000000..f7e607c --- /dev/null +++ b/json/http.json @@ -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 index 0000000..c61423f --- /dev/null +++ b/json/ssh.json @@ -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 index 0000000..a6c99b5 --- /dev/null +++ b/main.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 index 0000000..fd738f6 --- /dev/null +++ b/service-types @@ -0,0 +1,72 @@ +{ + "_workstation._tcp": "Workstation", + "_http._tcp": "Web Site", + "_https._tcp": "Secure Web Site", + "_rss._tcp": "Web Syndication RSS", + "_domain._udp": "DNS Server", + "_ntp._udp": "NTP Time Server", + "_smb._tcp": "Microsoft Windows Network", + "_airport._tcp": "Apple AirPort", + "_ftp._tcp": "FTP File Transfer", + "_tftp._udp": "TFTP Trivial File Transfer", + "_webdav._tcp": "WebDAV File Share", + "_webdavs._tcp": "Secure WebDAV File Share", + "_afpovertcp._tcp": "Apple File Sharing", + "_nfs._tcp": "Network File System", + "_sftp-ssh._tcp": "SFTP File Transfer", + "_apt._tcp": "APT Package Repository", + "_odisk._tcp": "DVD or CD Sharing", + "_adisk._tcp": "Apple TimeMachine", + "_ssh._tcp": "SSH Remote Terminal", + "_rfb._tcp": "VNC Remote Access", + "_telnet._tcp": "Telnet Remote Terminal", + "_timbuktu._tcp": "Timbuktu Remote Desktop Control", + "_net-assistant._udp": "Apple Net Assistant", + "_udisks-ssh._tcp": "Remote Disk Management", + "_imap._tcp": "IMAP Mail Access", + "_pop3._tcp": "POP3 Mail Access", + "_printer._tcp": "UNIX Printer", + "_pdl-datastream._tcp": "PDL Printer", + "_ipp._tcp": "Internet Printer", + "_daap._tcp": "iTunes Audio Access", + "_dacp._tcp": "iTunes Remote Control", + "_realplayfavs._tcp": "RealPlayer Shared Favorites", + "_raop._tcp": "AirTunes Remote Audio", + "_rtsp._tcp": "RTSP Realtime Streaming Server", + "_rtp._udp": "RTP Realtime Streaming Server", + "_dpap._tcp": "Digital Photo Sharing", + "_pulse-server._tcp": "PulseAudio Sound Server", + "_pulse-sink._tcp": "PulseAudio Sound Sink", + "_pulse-source._tcp": "PulseAudio Sound Source", + "_mpd._tcp": "Music Player Daemon", + "_remote-jukebox._tcp": "Remote Jukebox", + "_touch-able._tcp": "iPod Touch Music Library", + "_vlc-http._tcp": "VLC Streaming", + "_presence._tcp": "iChat Presence", + "_sip._udp": "SIP Telephony", + "_h323._tcp": "H.323 Telephony", + "_presence_olpc._tcp": "OLPC Presence", + "_iax._udp": "Asterisk Exchange", + "_skype._tcp": "Skype VoIP", + "_see._tcp": "SubEthaEdit Collaborative Text Editor", + "_lobby._tcp": "Gobby Collaborative Editor Session", + "_mumble._tcp": "Mumble Server", + "_postgresql._tcp": "PostgreSQL Server", + "_svn._tcp": "Subversion Revision Control", + "_distcc._tcp": "Distributed Compiler", + "_bzr._tcp": "Bazaar", + "_MacOSXDupSuppress._tcp": "MacOS X Duplicate Machine Suppression", + "_ksysguard._tcp": "KDE System Guard", + "_omni-bookmark._tcp": "OmniWeb Bookmark Sharing", + "_acrobatSRV._tcp": "Adobe Acrobat", + "_adobe-vc._tcp": "Adobe Version Cue", + "_home-sharing._tcp": "Apple Home Sharing", + "_pgpkey-hkp._tcp": "GnuPG/PGP HKP Key Server", + "_ldap._tcp": "LDAP Directory Server", + "_tp._tcp": "Thousand Parsec Server", + "_tps._tcp": "Thousand Parsec Server (Secure)", + "_tp-http._tcp": "Thousand Parsec Server (HTTP Tunnel)", + "_tp-https._tcp": "Thousand Parsec Server (Secure HTTP Tunnel)", + "_shifter._tcp": "Window Shifter", + "_libvirt._tcp": "Virtual Machine Manager", +} diff --git a/service.c b/service.c new file mode 100644 index 0000000..354099b --- /dev/null +++ b/service.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#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 index 0000000..a8937a7 --- /dev/null +++ b/service.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 index 0000000..c474bb5 --- /dev/null +++ b/ubus.c @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 +#include + +#include + +#include +#include +#include + +#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 index 0000000..428d166 --- /dev/null +++ b/ubus.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 index 0000000..a4a117b --- /dev/null +++ b/util.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 index 0000000..6c73904 --- /dev/null +++ b/util.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 John Crispin + * + * 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 -- 2.11.0