From: John Crispin Date: Thu, 16 Jan 2014 02:46:46 +0000 (+0000) Subject: import v0.1 X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fmdnsd.git;a=commitdiff_plain;h=788316ef66136e6a68d881e5b24ccf5a98fa4550 import v0.1 Signed-off-by: John Crispin --- 788316ef66136e6a68d881e5b24ccf5a98fa4550 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