Remove mdns (we will use avahi instead)
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
config BR2_PACKAGE_MDNS
|
||||
bool "mdns"
|
||||
default n
|
||||
select BR2_PACKAGE_LIBUBOX
|
||||
select BR2_PACKAGE_UBUS
|
||||
help
|
||||
mdns.
|
||||
@@ -1,21 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# UBOX TOOLS
|
||||
#
|
||||
################################################################################
|
||||
|
||||
MDNS_VERSION:= 2015.09.03
|
||||
|
||||
MDNS_SITE = $(TOPDIR)/../../src/3P/mdns/builders/cmake
|
||||
MDNS_SITE_METHOD = local
|
||||
MDNS_INSTALL_STAGING = YES
|
||||
|
||||
MDNS_DEPENDENCIES = libubox ubus
|
||||
|
||||
MDNS_CONF = SRC_DIR=$(TOPDIR)/../..
|
||||
|
||||
MDNS_CONF_ENV = $(UBOX_CONF)
|
||||
MDNS_MAKE_ENV = $(UBOX_CONF)
|
||||
MDNS_CONF_OPTS += -DMODULE_PATH=$(TOPDIR)/../../bsp/cmake-modules -DCMAKE_BUILD_TYPE=$(BUILD_TYPE)
|
||||
|
||||
$(eval $(cmake-package))
|
||||
@@ -1,22 +0,0 @@
|
||||
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 interface.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
|
||||
)
|
||||
@@ -1,3 +0,0 @@
|
||||
* timeouts and delays as described in rfc
|
||||
* automagic route setting
|
||||
* ipv6
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <libubox/uloop.h>
|
||||
|
||||
#include "cache.h"
|
||||
#include "dns.h"
|
||||
#include "util.h"
|
||||
#include "service.h"
|
||||
#include "announce.h"
|
||||
#include "interface.h"
|
||||
|
||||
#define TTL_TIMEOUT 75
|
||||
|
||||
enum {
|
||||
STATE_PROBE1 = 0,
|
||||
STATE_PROBE2,
|
||||
STATE_PROBE3,
|
||||
STATE_PROBE_WAIT,
|
||||
STATE_PROBE_END,
|
||||
STATE_ANNOUNCE,
|
||||
};
|
||||
|
||||
int announce_ttl = 75 * 60;
|
||||
|
||||
static void
|
||||
announce_timer(struct uloop_timeout *timeout)
|
||||
{
|
||||
struct interface *iface = container_of(timeout, struct interface, announce_timer);
|
||||
|
||||
switch (iface->announce_state) {
|
||||
case STATE_PROBE1:
|
||||
case STATE_PROBE2:
|
||||
case STATE_PROBE3:
|
||||
dns_send_question(iface, mdns_hostname_local, TYPE_ANY, 0);
|
||||
uloop_timeout_set(timeout, 250);
|
||||
iface->announce_state++;
|
||||
break;
|
||||
|
||||
case STATE_PROBE_WAIT:
|
||||
uloop_timeout_set(timeout, 500);
|
||||
iface->announce_state++;
|
||||
break;
|
||||
|
||||
case STATE_PROBE_END:
|
||||
if (cache_host_is_known(mdns_hostname_local)) {
|
||||
fprintf(stderr, "the host %s already exists. stopping announce service\n", mdns_hostname_local);
|
||||
return;
|
||||
}
|
||||
iface->announce_state++;
|
||||
|
||||
case STATE_ANNOUNCE:
|
||||
service_announce(iface, announce_ttl);
|
||||
uloop_timeout_set(timeout, announce_ttl * 800);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
announce_init(struct interface *iface)
|
||||
{
|
||||
iface->announce_state = STATE_PROBE1;
|
||||
iface->announce_timer.cb = announce_timer;
|
||||
uloop_timeout_set(&iface->announce_timer, 100);
|
||||
}
|
||||
|
||||
void
|
||||
announce_free(struct interface *iface)
|
||||
{
|
||||
uloop_timeout_cancel(&iface->announce_timer);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _ANNOUNCE_H__
|
||||
#define _ANNOUNCE_H__
|
||||
|
||||
struct interface;
|
||||
extern int announce_ttl;
|
||||
|
||||
void announce_init(struct interface *iface);
|
||||
void announce_free(struct interface *iface);
|
||||
|
||||
#endif
|
||||
@@ -1,26 +0,0 @@
|
||||
cmake_minimum_required (VERSION 3.0)
|
||||
|
||||
project (mdns)
|
||||
|
||||
ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations)
|
||||
|
||||
set (CMAKE_MODULE_PATH "${MODULE_PATH}")
|
||||
|
||||
set(DISABLE_TARGET_OPTIMIZATION ON)
|
||||
|
||||
include (br)
|
||||
|
||||
# mdns
|
||||
add_executable (mdns
|
||||
|
||||
$ENV{SRC_DIR}/src/3P/mdns/main.c
|
||||
$ENV{SRC_DIR}/src/3P/mdns/dns.c
|
||||
$ENV{SRC_DIR}/src/3P/mdns/announce.c
|
||||
$ENV{SRC_DIR}/src/3P/mdns/cache.c
|
||||
$ENV{SRC_DIR}/src/3P/mdns/service.c
|
||||
$ENV{SRC_DIR}/src/3P/mdns/util.c
|
||||
$ENV{SRC_DIR}/src/3P/mdns/ubus.c
|
||||
$ENV{SRC_DIR}/src/3P/mdns/interface.c
|
||||
)
|
||||
target_link_libraries (mdns ubox ubus resolv blobmsg_json)
|
||||
install (TARGETS mdns RUNTIME DESTINATION sbin)
|
||||
@@ -1,479 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <arpa/nameser.h>
|
||||
#include <resolv.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <libubox/usock.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/avl-cmp.h>
|
||||
#include <libubox/blobmsg_json.h>
|
||||
#include <libubox/kvlist.h>
|
||||
#include <ubus/libubus.h>
|
||||
|
||||
#include "cache.h"
|
||||
#include "util.h"
|
||||
#include "dns.h"
|
||||
#include "interface.h"
|
||||
|
||||
static struct uloop_timeout cache_gc;
|
||||
struct avl_tree services;
|
||||
static AVL_TREE(records, avl_strcmp, true, NULL);
|
||||
|
||||
static void
|
||||
cache_record_free(struct cache_record *r)
|
||||
{
|
||||
DBG(2, "%s %s\n", dns_type_string(r->type), r->record);
|
||||
avl_delete(&records, &r->avl);
|
||||
free(r);
|
||||
}
|
||||
|
||||
static void
|
||||
cache_service_free(struct cache_service *s)
|
||||
{
|
||||
DBG(2, "%s\n", s->entry);
|
||||
avl_delete(&services, &s->avl);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static int
|
||||
cache_is_expired(time_t t, uint32_t ttl, int frac)
|
||||
{
|
||||
if (monotonic_time() - t >= ttl * frac / 100)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
cache_gc_timer(struct uloop_timeout *timeout)
|
||||
{
|
||||
struct cache_record *r, *p;
|
||||
struct cache_service *s, *t;
|
||||
|
||||
avl_for_each_element_safe(&records, r, avl, p)
|
||||
if (cache_is_expired(r->time, r->ttl, 100))
|
||||
cache_record_free(r);
|
||||
|
||||
avl_for_each_element_safe(&services, s, avl, t) {
|
||||
if (!s->host)
|
||||
continue;
|
||||
if (!cache_is_expired(s->time, s->ttl, s->refresh))
|
||||
continue;
|
||||
if (s->refresh >= 100) {
|
||||
cache_service_free(s);
|
||||
continue;
|
||||
}
|
||||
s->refresh += 50;
|
||||
dns_send_question(s->iface, s->entry, TYPE_PTR, 1);
|
||||
}
|
||||
|
||||
uloop_timeout_set(timeout, 10000);
|
||||
}
|
||||
|
||||
int
|
||||
cache_init(void)
|
||||
{
|
||||
avl_init(&services, avl_strcmp, true, NULL);
|
||||
|
||||
cache_gc.cb = cache_gc_timer;
|
||||
uloop_timeout_set(&cache_gc, 10000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cache_cleanup(struct interface *iface)
|
||||
{
|
||||
struct cache_record *r, *p;
|
||||
struct cache_service *s, *t;
|
||||
|
||||
avl_for_each_element_safe(&services, s, avl, t)
|
||||
if (!iface || iface == s->iface)
|
||||
cache_service_free(s);
|
||||
|
||||
avl_for_each_element_safe(&records, r, avl, p)
|
||||
if (!iface || iface == r->iface)
|
||||
cache_record_free(r);
|
||||
}
|
||||
|
||||
void
|
||||
cache_scan(void)
|
||||
{
|
||||
struct interface *iface;
|
||||
struct cache_service *s;
|
||||
|
||||
vlist_for_each_element(&interfaces, iface, node)
|
||||
avl_for_each_element(&services, s, avl)
|
||||
dns_send_question(iface, s->entry, TYPE_PTR, 1);
|
||||
}
|
||||
|
||||
static struct cache_service*
|
||||
cache_service(struct interface *iface, char *entry, int hlen, int ttl)
|
||||
{
|
||||
struct cache_service *s, *t;
|
||||
char *entry_buf;
|
||||
char *host_buf;
|
||||
char *type;
|
||||
|
||||
avl_for_each_element_safe(&services, s, avl, t)
|
||||
if (!strcmp(s->entry, entry)) {
|
||||
s->refresh = 50;
|
||||
s->time = monotonic_time();
|
||||
return s;
|
||||
}
|
||||
|
||||
s = calloc_a(sizeof(*s),
|
||||
&entry_buf, strlen(entry) + 1,
|
||||
&host_buf, hlen ? hlen + 1 : 0);
|
||||
|
||||
s->avl.key = s->entry = strcpy(entry_buf, entry);
|
||||
s->time = monotonic_time();
|
||||
s->ttl = ttl;
|
||||
s->iface = iface;
|
||||
s->refresh = 50;
|
||||
|
||||
if (hlen)
|
||||
s->host = strncpy(host_buf, s->entry, hlen);
|
||||
|
||||
type = strstr(entry_buf, "._");
|
||||
if (type)
|
||||
type++;
|
||||
if (type)
|
||||
s->avl.key = type;
|
||||
avl_insert(&services, &s->avl);
|
||||
|
||||
if (!hlen)
|
||||
dns_send_question(iface, entry, TYPE_PTR, !iface->multicast);
|
||||
|
||||
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 && l->record && !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 interface *iface, uint8_t *base, int blen, char *name, struct dns_answer *a, uint8_t *rdata, int flush)
|
||||
{
|
||||
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;
|
||||
char *name_buf;
|
||||
void *rdata_ptr, *txt_ptr;
|
||||
int host_len = 0;
|
||||
static char *rdata_buffer = (char *) mdns_buf;
|
||||
time_t now = monotonic_time();
|
||||
|
||||
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 ttl:%d\n", dns_type_string(a->type), name, rdata_buffer, a->ttl);
|
||||
|
||||
rdlength = strlen(rdata_buffer);
|
||||
|
||||
if (strcmp(C_DNS_SD, name) != 0 &&
|
||||
nlen + 1 < rdlength && !strcmp(rdata_buffer + rdlength - nlen, name))
|
||||
host_len = rdlength - nlen - 1;
|
||||
|
||||
cache_service(iface, rdata_buffer, host_len, a->ttl);
|
||||
|
||||
dlen = strlen(rdata_buffer) + 1;
|
||||
rdata = (uint8_t*)rdata_buffer;
|
||||
break;
|
||||
|
||||
case TYPE_SRV:
|
||||
if (a->rdlength < 8)
|
||||
return;
|
||||
|
||||
port = be16_to_cpu(dsd->port);
|
||||
memcpy(rdata_buffer, dsd, sizeof(*dsd));
|
||||
if (dn_expand(base, base + blen, (const uint8_t*)&dsd[1],
|
||||
&rdata_buffer[sizeof(*dsd)], MAX_DATA_LEN - sizeof(*dsd)) < 0) {
|
||||
perror("process_answer/dn_expand");
|
||||
return;
|
||||
}
|
||||
dlen = sizeof(*dsd) + strlen(&rdata_buffer[sizeof(*dsd)]) + 1;
|
||||
rdata = (uint8_t*)rdata_buffer;
|
||||
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 < &rdata_buffer[rdlength])
|
||||
p += v + 1;
|
||||
} while (*p);
|
||||
break;
|
||||
|
||||
case TYPE_A:
|
||||
cache_service(iface, name, strlen(name), a->ttl);
|
||||
if (a->rdlength != 4)
|
||||
return;
|
||||
dlen = 4;
|
||||
break;
|
||||
|
||||
case TYPE_AAAA:
|
||||
cache_service(iface, 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) {
|
||||
DBG(1, "D -> %s %s ttl:%d\n", dns_type_string(r->type), r->record, r->ttl);
|
||||
r->time = now + 1 - r->ttl;
|
||||
} else {
|
||||
r->ttl = a->ttl;
|
||||
r->time = now;
|
||||
DBG(1, "A -> %s %s ttl:%d\n", dns_type_string(r->type), r->record, r->ttl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!a->ttl)
|
||||
return;
|
||||
|
||||
r = calloc_a(sizeof(*r),
|
||||
&name_buf, strlen(name) + 1,
|
||||
&txt_ptr, tlen,
|
||||
&rdata_ptr, dlen);
|
||||
|
||||
r->avl.key = r->record = strcpy(name_buf, name);
|
||||
r->type = a->type;
|
||||
r->ttl = a->ttl;
|
||||
r->port = port;
|
||||
r->rdlength = dlen;
|
||||
r->time = now;
|
||||
r->iface = iface;
|
||||
|
||||
if (tlen)
|
||||
r->txt = memcpy(txt_ptr, rdata_buffer, tlen);
|
||||
|
||||
if (dlen)
|
||||
r->rdata = memcpy(rdata_ptr, rdata, dlen);
|
||||
|
||||
if (avl_insert(&records, &r->avl))
|
||||
free(r);
|
||||
else
|
||||
DBG(1, "A -> %s %s ttl:%d\n", dns_type_string(r->type), r->record, r->ttl);
|
||||
}
|
||||
|
||||
void
|
||||
cache_dump_records(struct blob_buf *buf, const char *name)
|
||||
{
|
||||
struct cache_record *r, *last, *next;
|
||||
const char *txt;
|
||||
char buffer[INET6_ADDRSTRLEN];
|
||||
|
||||
last = avl_last_element(&records, last, avl);
|
||||
for (r = avl_find_element(&records, name, r, avl); r; r = next) {
|
||||
switch (r->type) {
|
||||
case TYPE_TXT:
|
||||
if (r->txt && strlen(r->txt)) {
|
||||
txt = r->txt;
|
||||
do {
|
||||
blobmsg_add_string(buf, "txt", txt);
|
||||
txt = &txt[strlen(txt) + 1];
|
||||
} while (*txt);
|
||||
}
|
||||
break;
|
||||
|
||||
case TYPE_SRV:
|
||||
if (r->port)
|
||||
blobmsg_add_u32(buf, "port", r->port);
|
||||
break;
|
||||
|
||||
case TYPE_A:
|
||||
if ((r->rdlength == 4) && inet_ntop(AF_INET, r->rdata, buffer, INET6_ADDRSTRLEN))
|
||||
blobmsg_add_string(buf, "ipv4", buffer);
|
||||
break;
|
||||
|
||||
case TYPE_AAAA:
|
||||
if ((r->rdlength == 16) && inet_ntop(AF_INET6, r->rdata, buffer, INET6_ADDRSTRLEN))
|
||||
blobmsg_add_string(buf, "ipv6", buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
if (r == last)
|
||||
break;
|
||||
|
||||
next = avl_next_element(r, avl);
|
||||
if (strcmp(r->record, next->record) != 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cache_dump_recursive(struct blob_buf *b, const char *name, uint16_t type, struct interface *iface)
|
||||
{
|
||||
time_t now = monotonic_time();
|
||||
for (struct cache_record *r = avl_find_ge_element(&records, name, r, avl);
|
||||
r && !strcmp(r->record, name);
|
||||
r = !avl_is_last(&records, &r->avl) ? avl_next_element(r, avl) : NULL) {
|
||||
int32_t ttl = r->ttl - (now - r->time);
|
||||
if (ttl <= 0 || (iface && iface->ifindex != r->iface->ifindex) ||
|
||||
(type != TYPE_ANY && type != r->type))
|
||||
continue;
|
||||
|
||||
const char *txt;
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
void *k = blobmsg_open_table(b, NULL), *l;
|
||||
const struct dns_srv_data *dsd = (const struct dns_srv_data*)r->rdata;
|
||||
|
||||
blobmsg_add_string(b, "name", r->record);
|
||||
blobmsg_add_string(b, "type", dns_type_string(r->type));
|
||||
blobmsg_add_u32(b, "ttl", ttl);
|
||||
|
||||
switch (r->type) {
|
||||
case TYPE_TXT:
|
||||
if ((txt = r->txt) && strlen(txt)) {
|
||||
l = blobmsg_open_array(b, "data");
|
||||
do {
|
||||
blobmsg_add_string(b, NULL, txt);
|
||||
txt = &txt[strlen(txt) + 1];
|
||||
} while (*txt);
|
||||
blobmsg_close_array(b, l);
|
||||
}
|
||||
break;
|
||||
|
||||
case TYPE_SRV:
|
||||
if (r->rdlength > sizeof(*dsd)) {
|
||||
blobmsg_add_u32(b, "priority", be16_to_cpu(dsd->priority));
|
||||
blobmsg_add_u32(b, "weight", be16_to_cpu(dsd->weight));
|
||||
blobmsg_add_u32(b, "port", be16_to_cpu(dsd->port));
|
||||
blobmsg_add_string(b, "target", (const char*)&dsd[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case TYPE_PTR:
|
||||
if (r->rdlength > 0)
|
||||
blobmsg_add_string(b, "target", (const char*)r->rdata);
|
||||
break;
|
||||
|
||||
case TYPE_A:
|
||||
if ((r->rdlength == 4) && inet_ntop(AF_INET, r->rdata, buf, sizeof(buf)))
|
||||
blobmsg_add_string(b, "target", buf);
|
||||
break;
|
||||
|
||||
case TYPE_AAAA:
|
||||
if ((r->rdlength == 16) && inet_ntop(AF_INET6, r->rdata, buf, sizeof(buf)))
|
||||
blobmsg_add_string(b, "target", buf);
|
||||
break;
|
||||
}
|
||||
|
||||
blobmsg_close_table(b, k);
|
||||
|
||||
|
||||
if (r->type == TYPE_PTR) {
|
||||
cache_dump_recursive(b, (const char*)r->rdata, TYPE_SRV, iface);
|
||||
cache_dump_recursive(b, (const char*)r->rdata, TYPE_TXT, iface);
|
||||
}
|
||||
|
||||
if (r->type == TYPE_SRV) {
|
||||
cache_dump_recursive(b, (const char*)&dsd[1], TYPE_A, iface);
|
||||
cache_dump_recursive(b, (const char*)&dsd[1], TYPE_AAAA, iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _CACHE_H__
|
||||
#define _CACHE_H__
|
||||
|
||||
#include <libubox/avl.h>
|
||||
#include <libubox/list.h>
|
||||
#include <libubox/blob.h>
|
||||
|
||||
#include "dns.h"
|
||||
#include "interface.h"
|
||||
|
||||
struct cache_service {
|
||||
struct avl_node avl;
|
||||
|
||||
const char *entry;
|
||||
const char *host;
|
||||
uint32_t ttl;
|
||||
time_t time;
|
||||
struct interface *iface;
|
||||
int refresh;
|
||||
};
|
||||
|
||||
struct cache_record {
|
||||
struct avl_node avl;
|
||||
|
||||
const char *record;
|
||||
uint16_t type;
|
||||
uint32_t ttl;
|
||||
int port;
|
||||
const char *txt;
|
||||
const uint8_t *rdata;
|
||||
uint16_t rdlength;
|
||||
time_t time;
|
||||
struct interface *iface;
|
||||
};
|
||||
|
||||
extern struct avl_tree services;
|
||||
|
||||
int cache_init(void);
|
||||
void cache_scan(void);
|
||||
void cache_cleanup(struct interface *iface);
|
||||
void cache_answer(struct interface *iface, uint8_t *base, int blen,
|
||||
char *name, struct dns_answer *a, uint8_t *rdata, int flush);
|
||||
int cache_host_is_known(char *record);
|
||||
char *cache_lookup_name(const char *key);
|
||||
void cache_dump_records(struct blob_buf *buf, const char *name);
|
||||
void cache_dump_recursive(struct blob_buf *b, const char *name, uint16_t type, struct interface *iface);
|
||||
|
||||
#endif
|
||||
@@ -1,404 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <arpa/nameser.h>
|
||||
#include <resolv.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/usock.h>
|
||||
#include <libubox/utils.h>
|
||||
|
||||
#include "announce.h"
|
||||
#include "util.h"
|
||||
#include "dns.h"
|
||||
#include "cache.h"
|
||||
#include "service.h"
|
||||
#include "interface.h"
|
||||
|
||||
static char name_buffer[MAX_NAME_LEN + 1];
|
||||
static char dns_buffer[MAX_NAME_LEN];
|
||||
static struct blob_buf ans_buf;
|
||||
|
||||
const char*
|
||||
dns_type_string(uint16_t type)
|
||||
{
|
||||
static const struct {
|
||||
uint16_t type;
|
||||
char str[5];
|
||||
} type_str[] = {
|
||||
{ TYPE_A, "A" },
|
||||
{ TYPE_AAAA, "AAAA" },
|
||||
{ TYPE_PTR, "PTR" },
|
||||
{ TYPE_TXT, "TXT" },
|
||||
{ TYPE_SRV, "SRV" },
|
||||
{ TYPE_ANY, "ANY" },
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(type_str); i++) {
|
||||
if (type == type_str[i].type)
|
||||
return type_str[i].str;
|
||||
}
|
||||
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
void
|
||||
dns_send_question(struct interface *iface, const char *question, int type, int unicast)
|
||||
{
|
||||
static struct dns_header h;
|
||||
static struct dns_question q;
|
||||
static struct iovec iov[] = {
|
||||
{
|
||||
.iov_base = &h,
|
||||
.iov_len = sizeof(h),
|
||||
},
|
||||
{
|
||||
.iov_base = dns_buffer,
|
||||
},
|
||||
{
|
||||
.iov_base = &q,
|
||||
.iov_len = sizeof(q),
|
||||
}
|
||||
};
|
||||
int len;
|
||||
|
||||
h.questions = cpu_to_be16(1);
|
||||
q.class = cpu_to_be16(((unicast) ? (CLASS_UNICAST) : (0)) | 1);
|
||||
q.type = cpu_to_be16(type);
|
||||
|
||||
len = dn_comp(question, (void *) dns_buffer, sizeof(dns_buffer), NULL, NULL);
|
||||
if (len < 1)
|
||||
return;
|
||||
|
||||
iov[1].iov_len = len;
|
||||
|
||||
DBG(1, "Q <- %s %s\n", dns_type_string(type), question);
|
||||
if (interface_send_packet(iface, iov, ARRAY_SIZE(iov)) < 0)
|
||||
perror("failed to send question :");
|
||||
}
|
||||
|
||||
|
||||
struct dns_reply {
|
||||
int type;
|
||||
struct dns_answer a;
|
||||
uint16_t rdlength;
|
||||
uint8_t *rdata;
|
||||
char *buffer;
|
||||
};
|
||||
|
||||
static int dns_answer_cnt;
|
||||
|
||||
void
|
||||
dns_init_answer(void)
|
||||
{
|
||||
dns_answer_cnt = 0;
|
||||
blob_buf_init(&ans_buf, 0);
|
||||
}
|
||||
|
||||
void
|
||||
dns_add_answer(int type, const uint8_t *rdata, uint16_t rdlength, int ttl)
|
||||
{
|
||||
struct blob_attr *attr;
|
||||
struct dns_answer *a;
|
||||
|
||||
attr = blob_new(&ans_buf, 0, sizeof(*a) + rdlength);
|
||||
a = blob_data(attr);
|
||||
a->type = cpu_to_be16(type);
|
||||
a->class = cpu_to_be16(1);
|
||||
a->ttl = cpu_to_be32(ttl);
|
||||
a->rdlength = cpu_to_be16(rdlength);
|
||||
memcpy(a + 1, rdata, rdlength);
|
||||
|
||||
dns_answer_cnt++;
|
||||
}
|
||||
|
||||
void
|
||||
dns_send_answer(struct interface *iface, const char *answer)
|
||||
{
|
||||
uint8_t buffer[256];
|
||||
struct blob_attr *attr;
|
||||
struct dns_header h = { 0 };
|
||||
struct iovec *iov;
|
||||
int answer_len, rem;
|
||||
int n_iov = 0;
|
||||
|
||||
if (!dns_answer_cnt)
|
||||
return;
|
||||
|
||||
h.answers = cpu_to_be16(dns_answer_cnt);
|
||||
h.flags = cpu_to_be16(0x8400);
|
||||
|
||||
iov = alloca(sizeof(struct iovec) * ((dns_answer_cnt * 2) + 1));
|
||||
|
||||
iov[n_iov].iov_base = &h;
|
||||
iov[n_iov].iov_len = sizeof(struct dns_header);
|
||||
n_iov++;
|
||||
|
||||
answer_len = dn_comp(answer, buffer, sizeof(buffer), NULL, NULL);
|
||||
if (answer_len < 1)
|
||||
return;
|
||||
|
||||
blob_for_each_attr(attr, ans_buf.head, rem) {
|
||||
struct dns_answer *a = blob_data(attr);
|
||||
|
||||
iov[n_iov].iov_base = buffer;
|
||||
iov[n_iov].iov_len = answer_len;
|
||||
n_iov++;
|
||||
|
||||
iov[n_iov].iov_base = blob_data(attr);
|
||||
iov[n_iov].iov_len = blob_len(attr);
|
||||
n_iov++;
|
||||
|
||||
DBG(1, "A <- %s %s\n", dns_type_string(be16_to_cpu(a->type)), answer);
|
||||
}
|
||||
|
||||
if (interface_send_packet(iface, iov, n_iov) < 0)
|
||||
fprintf(stderr, "failed to send question\n");
|
||||
}
|
||||
|
||||
static int
|
||||
scan_name(const 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static char *
|
||||
dns_consume_name(const 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;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_answer(struct interface *iface, 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;
|
||||
}
|
||||
|
||||
if ((a->class & ~CLASS_FLUSH) != CLASS_IN)
|
||||
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(iface, buffer, len, name, a, rdata, a->class & CLASS_FLUSH);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
parse_question(struct interface *iface, char *name, struct dns_question *q)
|
||||
{
|
||||
char *host;
|
||||
|
||||
if ((q->class & CLASS_UNICAST) && iface->multicast)
|
||||
iface = iface->peer;
|
||||
|
||||
DBG(1, "Q -> %s %s\n", dns_type_string(q->type), name);
|
||||
|
||||
switch (q->type) {
|
||||
case TYPE_ANY:
|
||||
if (!strcmp(name, mdns_hostname_local))
|
||||
service_reply(iface, NULL, announce_ttl);
|
||||
break;
|
||||
|
||||
case TYPE_PTR:
|
||||
service_announce_services(iface, name, announce_ttl);
|
||||
service_reply(iface, name, announce_ttl);
|
||||
break;
|
||||
|
||||
case TYPE_AAAA:
|
||||
case TYPE_A:
|
||||
host = strstr(name, ".local");
|
||||
if (host)
|
||||
*host = '\0';
|
||||
if (!strcmp(mdns_hostname, name))
|
||||
service_reply_a(iface, announce_ttl);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
dns_handle_packet(struct interface *iface, struct sockaddr *s, uint16_t port, uint8_t *buffer, int len)
|
||||
{
|
||||
struct dns_header *h;
|
||||
uint8_t *b = buffer;
|
||||
int rlen = len;
|
||||
|
||||
h = dns_consume_header(&b, &rlen);
|
||||
if (!h) {
|
||||
fprintf(stderr, "dropping: bad header\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (h->questions && !iface->multicast && port != 5353)
|
||||
// silently drop unicast questions that dont originate from port 5353
|
||||
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(iface, name, q);
|
||||
}
|
||||
|
||||
if (!(h->flags & FLAG_RESPONSE))
|
||||
return;
|
||||
|
||||
while (h->answers-- > 0)
|
||||
if (parse_answer(iface, buffer, len, &b, &rlen, 1))
|
||||
return;
|
||||
|
||||
while (h->authority-- > 0)
|
||||
if (parse_answer(iface, buffer, len, &b, &rlen, 1))
|
||||
return;
|
||||
|
||||
while (h->additional-- > 0)
|
||||
if (parse_answer(iface, buffer, len, &b, &rlen, 1))
|
||||
return;
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _DNS_H__
|
||||
#define _DNS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <arpa/inet.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_ADDR6 "ff02::fb"
|
||||
#define MCAST_PORT 5353
|
||||
|
||||
#define CLASS_FLUSH 0x8000
|
||||
#define CLASS_UNICAST 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)));
|
||||
|
||||
struct interface;
|
||||
extern int cfg_proto;
|
||||
extern int cfg_no_subnet;
|
||||
|
||||
void dns_send_question(struct interface *iface, const char *question, int type, int unicast);
|
||||
void dns_init_answer(void);
|
||||
void dns_add_answer(int type, const uint8_t *rdata, uint16_t rdlength, int ttl);
|
||||
void dns_send_answer(struct interface *iface, const char *answer);
|
||||
const char* dns_type_string(uint16_t type);
|
||||
void dns_handle_packet(struct interface *iface, struct sockaddr *s, uint16_t port, uint8_t *buf, int len);
|
||||
|
||||
#endif
|
||||
@@ -1,639 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
* Copyright (C) 2014 Felix Fietkau <nbd@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <ifaddrs.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <libubox/usock.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/avl-cmp.h>
|
||||
#include <libubox/utils.h>
|
||||
#include "interface.h"
|
||||
#include "util.h"
|
||||
#include "dns.h"
|
||||
#include "announce.h"
|
||||
#include "service.h"
|
||||
|
||||
static int
|
||||
interface_send_packet4(struct interface *iface, struct iovec *iov, int iov_len)
|
||||
{
|
||||
static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in_pktinfo)) / sizeof(size_t)) + 1];
|
||||
static struct sockaddr_in a;
|
||||
static struct msghdr m = {
|
||||
.msg_name = (struct sockaddr *) &a,
|
||||
.msg_namelen = sizeof(a),
|
||||
.msg_control = cmsg_data,
|
||||
.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)),
|
||||
};
|
||||
struct in_pktinfo *pkti;
|
||||
struct cmsghdr *cmsg;
|
||||
int fd = iface->fd.fd;
|
||||
|
||||
a.sin_family = AF_INET;
|
||||
a.sin_port = htons(MCAST_PORT);
|
||||
m.msg_iov = iov;
|
||||
m.msg_iovlen = iov_len;
|
||||
|
||||
memset(cmsg_data, 0, sizeof(cmsg_data));
|
||||
cmsg = CMSG_FIRSTHDR(&m);
|
||||
cmsg->cmsg_len = m.msg_controllen;
|
||||
cmsg->cmsg_level = IPPROTO_IP;
|
||||
cmsg->cmsg_type = IP_PKTINFO;
|
||||
|
||||
pkti = (struct in_pktinfo*) CMSG_DATA(cmsg);
|
||||
pkti->ipi_ifindex = iface->ifindex;
|
||||
|
||||
a.sin_addr.s_addr = inet_addr(MCAST_ADDR);
|
||||
|
||||
return sendmsg(fd, &m, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
interface_send_packet6(struct interface *iface, struct iovec *iov, int iov_len)
|
||||
{
|
||||
static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in6_pktinfo)) / sizeof(size_t)) + 1];
|
||||
static struct sockaddr_in6 a;
|
||||
static struct msghdr m = {
|
||||
.msg_name = (struct sockaddr *) &a,
|
||||
.msg_namelen = sizeof(a),
|
||||
.msg_control = cmsg_data,
|
||||
.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)),
|
||||
};
|
||||
struct in6_pktinfo *pkti;
|
||||
struct cmsghdr *cmsg;
|
||||
int fd = iface->fd.fd;
|
||||
|
||||
a.sin6_family = AF_INET6;
|
||||
a.sin6_port = htons(MCAST_PORT);
|
||||
m.msg_iov = iov;
|
||||
m.msg_iovlen = iov_len;
|
||||
|
||||
memset(cmsg_data, 0, sizeof(cmsg_data));
|
||||
cmsg = CMSG_FIRSTHDR(&m);
|
||||
cmsg->cmsg_len = m.msg_controllen;
|
||||
cmsg->cmsg_level = IPPROTO_IPV6;
|
||||
cmsg->cmsg_type = IPV6_PKTINFO;
|
||||
|
||||
pkti = (struct in6_pktinfo*) CMSG_DATA(cmsg);
|
||||
pkti->ipi6_ifindex = iface->ifindex;
|
||||
|
||||
inet_pton(AF_INET6, MCAST_ADDR6, &a.sin6_addr);
|
||||
|
||||
return sendmsg(fd, &m, 0);
|
||||
}
|
||||
|
||||
int
|
||||
interface_send_packet(struct interface *iface, struct iovec *iov, int iov_len)
|
||||
{
|
||||
if (debug > 1) {
|
||||
fprintf(stderr, "TX ipv%d: %s\n", iface->v6 * 2 + 4, iface->name);
|
||||
fprintf(stderr, " multicast: %d\n", iface->multicast);
|
||||
}
|
||||
|
||||
if (iface->v6)
|
||||
return interface_send_packet6(iface, iov, iov_len);
|
||||
|
||||
return interface_send_packet4(iface, iov, iov_len);
|
||||
}
|
||||
|
||||
static void interface_close(struct interface *iface)
|
||||
{
|
||||
if (iface->fd.fd < 0)
|
||||
return;
|
||||
|
||||
announce_free(iface);
|
||||
uloop_fd_delete(&iface->fd);
|
||||
close(iface->fd.fd);
|
||||
iface->fd.fd = -1;
|
||||
}
|
||||
|
||||
static void interface_free(struct interface *iface)
|
||||
{
|
||||
interface_close(iface);
|
||||
free(iface);
|
||||
}
|
||||
|
||||
static int
|
||||
interface_valid_src(void *ip1, void *mask, void *ip2, int len)
|
||||
{
|
||||
uint8_t *i1 = ip1;
|
||||
uint8_t *i2 = ip2;
|
||||
uint8_t *m = mask;
|
||||
int i;
|
||||
|
||||
if (cfg_no_subnet)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < len; i++, i1++, i2++, m++) {
|
||||
if ((*i1 & *m) != (*i2 & *m))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
read_socket4(struct uloop_fd *u, unsigned int events)
|
||||
{
|
||||
struct interface *iface = container_of(u, struct interface, fd);
|
||||
static uint8_t buffer[8 * 1024];
|
||||
struct iovec iov[1];
|
||||
char cmsg[CMSG_SPACE(sizeof(struct in_pktinfo)) + CMSG_SPACE(sizeof(int)) + 1];
|
||||
struct cmsghdr *cmsgptr;
|
||||
struct msghdr msg;
|
||||
socklen_t len;
|
||||
struct sockaddr_in from;
|
||||
int flags = 0, ifindex = -1;
|
||||
uint8_t ttl = 0;
|
||||
struct in_pktinfo *inp = NULL;
|
||||
|
||||
if (u->eof) {
|
||||
interface_close(iface);
|
||||
uloop_timeout_set(&iface->reconnect, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
iov[0].iov_base = buffer;
|
||||
iov[0].iov_len = sizeof(buffer);
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_name = (struct sockaddr *) &from;
|
||||
msg.msg_namelen = sizeof(struct sockaddr_in);
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = &cmsg;
|
||||
msg.msg_controllen = sizeof(cmsg);
|
||||
|
||||
len = recvmsg(u->fd, &msg, flags);
|
||||
if (len == -1) {
|
||||
perror("read failed");
|
||||
return;
|
||||
}
|
||||
for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
|
||||
void *c = CMSG_DATA(cmsgptr);
|
||||
|
||||
switch (cmsgptr->cmsg_type) {
|
||||
case IP_PKTINFO:
|
||||
inp = ((struct in_pktinfo *) c);
|
||||
break;
|
||||
|
||||
case IP_TTL:
|
||||
ttl = (uint8_t) *((int *) c);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "unknown cmsg %x\n", cmsgptr->cmsg_type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ttl != 255)
|
||||
return;
|
||||
|
||||
if (debug > 1) {
|
||||
char buf[256];
|
||||
|
||||
fprintf(stderr, "RX ipv4: %s\n", iface->name);
|
||||
fprintf(stderr, " multicast: %d\n", iface->multicast);
|
||||
inet_ntop(AF_INET, &from.sin_addr, buf, 256);
|
||||
fprintf(stderr, " src %s:%d\n", buf, from.sin_port);
|
||||
inet_ntop(AF_INET, &inp->ipi_spec_dst, buf, 256);
|
||||
fprintf(stderr, " dst %s\n", buf);
|
||||
inet_ntop(AF_INET, &inp->ipi_addr, buf, 256);
|
||||
fprintf(stderr, " real %s\n", buf);
|
||||
}
|
||||
|
||||
if (inp->ipi_ifindex != iface->ifindex)
|
||||
fprintf(stderr, "invalid iface index %d != %d\n", ifindex, iface->ifindex);
|
||||
else if (!interface_valid_src((void *) &iface->v4_addr, (void *) &iface->v4_netmask, (void *) &from.sin_addr, 4))
|
||||
dns_handle_packet(iface, (struct sockaddr *) &from, from.sin_port, buffer, len);
|
||||
}
|
||||
|
||||
static void
|
||||
read_socket6(struct uloop_fd *u, unsigned int events)
|
||||
{
|
||||
struct interface *iface = container_of(u, struct interface, fd);
|
||||
static uint8_t buffer[8 * 1024];
|
||||
struct iovec iov[1];
|
||||
char cmsg6[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)) + 1];
|
||||
struct cmsghdr *cmsgptr;
|
||||
struct msghdr msg;
|
||||
socklen_t len;
|
||||
struct sockaddr_in6 from;
|
||||
int flags = 0, ifindex = -1;
|
||||
int ttl = 0;
|
||||
struct in6_pktinfo *inp = NULL;
|
||||
|
||||
if (u->eof) {
|
||||
interface_close(iface);
|
||||
uloop_timeout_set(&iface->reconnect, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
iov[0].iov_base = buffer;
|
||||
iov[0].iov_len = sizeof(buffer);
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_name = (struct sockaddr *) &from;
|
||||
msg.msg_namelen = sizeof(struct sockaddr_in6);
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = &cmsg6;
|
||||
msg.msg_controllen = sizeof(cmsg6);
|
||||
|
||||
len = recvmsg(u->fd, &msg, flags);
|
||||
if (len == -1) {
|
||||
perror("read failed");
|
||||
return;
|
||||
}
|
||||
for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
|
||||
void *c = CMSG_DATA(cmsgptr);
|
||||
|
||||
switch (cmsgptr->cmsg_type) {
|
||||
case IPV6_PKTINFO:
|
||||
inp = ((struct in6_pktinfo *) c);
|
||||
break;
|
||||
|
||||
case IPV6_HOPLIMIT:
|
||||
ttl = (uint8_t) *((int *) c);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "unknown cmsg %x\n", cmsgptr->cmsg_type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ttl != 255)
|
||||
return;
|
||||
|
||||
if (debug > 1) {
|
||||
char buf[256];
|
||||
|
||||
fprintf(stderr, "RX ipv6: %s\n", iface->name);
|
||||
fprintf(stderr, " multicast: %d\n", iface->multicast);
|
||||
inet_ntop(AF_INET6, &from.sin6_addr, buf, 256);
|
||||
fprintf(stderr, " src %s:%d\n", buf, from.sin6_port);
|
||||
inet_ntop(AF_INET6, &inp->ipi6_addr, buf, 256);
|
||||
fprintf(stderr, " dst %s\n", buf);
|
||||
}
|
||||
|
||||
if (inp->ipi6_ifindex != iface->ifindex)
|
||||
fprintf(stderr, "invalid iface index %d != %d\n", ifindex, iface->ifindex);
|
||||
else if (!interface_valid_src((void *) &iface->v4_addr, (void *) &iface->v4_netmask, (void *) &from.sin6_addr, 16))
|
||||
dns_handle_packet(iface, (struct sockaddr *) &from, from.sin6_port, buffer, len);
|
||||
}
|
||||
|
||||
static int
|
||||
interface_mcast_setup4(struct interface *iface)
|
||||
{
|
||||
struct ip_mreqn mreq;
|
||||
uint8_t ttl = 255;
|
||||
int no = 0;
|
||||
struct sockaddr_in sa = { 0 };
|
||||
int fd = iface->fd.fd;
|
||||
|
||||
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 = iface->v4_addr.s_addr;
|
||||
mreq.imr_multiaddr = sa.sin_addr;
|
||||
mreq.imr_ifindex = iface->ifindex;
|
||||
|
||||
if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0)
|
||||
fprintf(stderr, "ioctl failed: IP_MULTICAST_TTL\n");
|
||||
|
||||
/* Some network drivers have issues with dropping membership of
|
||||
* mcast groups when the iface is down, but don't allow rejoining
|
||||
* when it comes back up. This is an ugly workaround
|
||||
* -- this was copied from avahi --
|
||||
*/
|
||||
setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
|
||||
|
||||
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_MULTICAST_LOOP, &no, sizeof(no)) < 0)
|
||||
fprintf(stderr, "ioctl failed: IP_MULTICAST_LOOP\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
interface_socket_setup6(struct interface *iface)
|
||||
{
|
||||
struct ipv6_mreq mreq;
|
||||
int ttl = 255;
|
||||
int no = 0;
|
||||
struct sockaddr_in6 sa = { 0 };
|
||||
int fd = iface->fd.fd;
|
||||
|
||||
sa.sin6_family = AF_INET6;
|
||||
sa.sin6_port = htons(MCAST_PORT);
|
||||
inet_pton(AF_INET6, MCAST_ADDR6, &sa.sin6_addr);
|
||||
|
||||
memset(&mreq, 0, sizeof(mreq));
|
||||
mreq.ipv6mr_multiaddr = sa.sin6_addr;
|
||||
mreq.ipv6mr_interface = iface->ifindex;
|
||||
|
||||
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) < 0)
|
||||
fprintf(stderr, "ioctl failed: IPV6_MULTICAST_HOPS\n");
|
||||
|
||||
setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq, sizeof(mreq));
|
||||
if (setsockopt(fd, IPPROTO_IPV6, IPV6_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_IPV6, IPV6_MULTICAST_LOOP, &no, sizeof(no)) < 0)
|
||||
fprintf(stderr, "ioctl failed: IPV6_MULTICAST_LOOP\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
reconnect_socket4(struct uloop_timeout *timeout)
|
||||
{
|
||||
struct interface *iface = container_of(timeout, struct interface, reconnect);
|
||||
int yes = 1;
|
||||
|
||||
iface->fd.fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK | USOCK_IPV4ONLY,
|
||||
(iface->multicast) ? (iface->mcast_addr) : (iface->v4_addrs), "5353");
|
||||
if (iface->fd.fd < 0) {
|
||||
fprintf(stderr, "failed to add listener %s: %s\n", iface->mcast_addr, strerror(errno));
|
||||
goto retry;
|
||||
}
|
||||
|
||||
if (setsockopt(iface->fd.fd, SOL_SOCKET, SO_BINDTODEVICE, iface->name, strlen(iface->name) < 0))
|
||||
fprintf(stderr, "ioctl failed: SO_BINDTODEVICE\n");
|
||||
|
||||
if (setsockopt(iface->fd.fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
|
||||
fprintf(stderr, "ioctl failed: SO_REUSEADDR\n");
|
||||
|
||||
if (setsockopt(iface->fd.fd, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) < 0)
|
||||
fprintf(stderr, "ioctl failed: IP_RECVTTL\n");
|
||||
|
||||
if (setsockopt(iface->fd.fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0)
|
||||
fprintf(stderr, "ioctl failed: IP_PKTINFO\n");
|
||||
|
||||
if (iface->multicast && interface_mcast_setup4(iface)) {
|
||||
iface->fd.fd = -1;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
uloop_fd_add(&iface->fd, ULOOP_READ);
|
||||
if (iface->multicast) {
|
||||
dns_send_question(iface, "_services._dns-sd._udp.local", TYPE_PTR, 1);
|
||||
announce_init(iface);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
retry:
|
||||
uloop_timeout_set(timeout, 1000);
|
||||
}
|
||||
|
||||
static void
|
||||
reconnect_socket6(struct uloop_timeout *timeout)
|
||||
{
|
||||
struct interface *iface = container_of(timeout, struct interface, reconnect);
|
||||
char mcast_addr[128];
|
||||
int ttl = 255;
|
||||
int yes = 1;
|
||||
|
||||
snprintf(mcast_addr, sizeof(mcast_addr), "%s%%%s", (iface->multicast) ? (iface->mcast_addr) : (iface->v6_addrs), iface->name);
|
||||
iface->fd.fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK | USOCK_IPV6ONLY, mcast_addr, "5353");
|
||||
if (iface->fd.fd < 0) {
|
||||
fprintf(stderr, "failed to add listener %s: %s\n", mcast_addr, strerror(errno));
|
||||
goto retry;
|
||||
}
|
||||
|
||||
if (setsockopt(iface->fd.fd, SOL_SOCKET, SO_BINDTODEVICE, iface->name, strlen(iface->name) < 0))
|
||||
fprintf(stderr, "ioctl failed: SO_BINDTODEVICE\n");
|
||||
|
||||
if (setsockopt(iface->fd.fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)) < 0)
|
||||
fprintf(stderr, "ioctl failed: IPV6_UNICAST_HOPS\n");
|
||||
|
||||
if (setsockopt(iface->fd.fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &yes, sizeof(yes)) < 0)
|
||||
fprintf(stderr, "ioctl failed: IPV6_RECVPKTINFO\n");
|
||||
|
||||
if (setsockopt(iface->fd.fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &yes, sizeof(yes)) < 0)
|
||||
fprintf(stderr, "ioctl failed: IPV6_RECVHOPLIMIT\n");
|
||||
|
||||
if (setsockopt(iface->fd.fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
|
||||
fprintf(stderr, "ioctl failed: SO_REUSEADDR\n");
|
||||
|
||||
if (iface->multicast && interface_socket_setup6(iface)) {
|
||||
iface->fd.fd = -1;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
uloop_fd_add(&iface->fd, ULOOP_READ);
|
||||
|
||||
if (iface->multicast) {
|
||||
dns_send_question(iface, "_services._dns-sd._udp.local", TYPE_PTR, 1);
|
||||
announce_init(iface);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
retry:
|
||||
uloop_timeout_set(timeout, 1000);
|
||||
}
|
||||
|
||||
|
||||
static void interface_start(struct interface *iface)
|
||||
{
|
||||
if (iface->v6) {
|
||||
iface->fd.cb = read_socket6;
|
||||
iface->reconnect.cb = reconnect_socket6;
|
||||
} else {
|
||||
iface->fd.cb = read_socket4;
|
||||
iface->reconnect.cb = reconnect_socket4;
|
||||
}
|
||||
uloop_timeout_set(&iface->reconnect, 100);
|
||||
}
|
||||
|
||||
static void
|
||||
iface_update_cb(struct vlist_tree *tree, struct vlist_node *node_new,
|
||||
struct vlist_node *node_old)
|
||||
{
|
||||
struct interface *iface;
|
||||
|
||||
if (node_old) {
|
||||
iface = container_of(node_old, struct interface, node);
|
||||
interface_free(iface);
|
||||
}
|
||||
|
||||
if (node_new) {
|
||||
iface = container_of(node_new, struct interface, node);
|
||||
interface_start(iface);
|
||||
}
|
||||
}
|
||||
|
||||
static struct interface* _interface_add(const char *name, int multicast, int v6)
|
||||
{
|
||||
struct interface *iface;
|
||||
char *name_buf;
|
||||
char *id_buf;
|
||||
|
||||
iface = calloc_a(sizeof(*iface),
|
||||
&name_buf, strlen(name) + 1,
|
||||
&id_buf, strlen(name) + 5);
|
||||
|
||||
sprintf(id_buf, "%d_%d_%s", multicast, v6, name);
|
||||
iface->name = strcpy(name_buf, name);
|
||||
iface->id = id_buf;
|
||||
iface->ifindex = if_nametoindex(name);
|
||||
iface->fd.fd = -1;
|
||||
iface->multicast = multicast;
|
||||
iface->v6 = v6;
|
||||
if (v6)
|
||||
iface->mcast_addr = MCAST_ADDR6;
|
||||
else
|
||||
iface->mcast_addr = MCAST_ADDR;
|
||||
|
||||
if (iface->ifindex <= 0)
|
||||
goto error;
|
||||
|
||||
vlist_add(&interfaces, &iface->node, iface->id);
|
||||
return iface;
|
||||
|
||||
error:
|
||||
free(iface);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int interface_add(const char *name)
|
||||
{
|
||||
struct interface *v4 = NULL, *v6 = NULL, *unicast;
|
||||
struct ifaddrs *ifap, *ifa;
|
||||
|
||||
getifaddrs(&ifap);
|
||||
|
||||
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
||||
if (strcmp(ifa->ifa_name, name))
|
||||
continue;
|
||||
if (ifa->ifa_addr->sa_family == AF_INET && !v4) {
|
||||
struct sockaddr_in *sa;
|
||||
|
||||
if (cfg_proto && (cfg_proto != 4))
|
||||
continue;
|
||||
|
||||
unicast = _interface_add(name, 0, 0);
|
||||
if (!unicast)
|
||||
continue;
|
||||
v4 = _interface_add(name, 1, 0);
|
||||
if (!v4)
|
||||
continue;
|
||||
|
||||
sa = (struct sockaddr_in *) ifa->ifa_addr;
|
||||
memcpy(&v4->v4_addr, &sa->sin_addr, sizeof(v4->v4_addr));
|
||||
memcpy(&unicast->v4_addr, &sa->sin_addr, sizeof(unicast->v4_addr));
|
||||
|
||||
inet_ntop(AF_INET, &sa->sin_addr, v4->v4_addrs, sizeof(v4->v4_addrs));
|
||||
inet_ntop(AF_INET, &sa->sin_addr, unicast->v4_addrs, sizeof(unicast->v4_addrs));
|
||||
|
||||
sa = (struct sockaddr_in *) ifa->ifa_netmask;
|
||||
memcpy(&unicast->v4_netmask, &sa->sin_addr, sizeof(unicast->v4_netmask));
|
||||
memcpy(&v4->v4_netmask, &sa->sin_addr, sizeof(v4->v4_netmask));
|
||||
|
||||
v4->peer = unicast;
|
||||
unicast->peer = v4;
|
||||
}
|
||||
|
||||
if (ifa->ifa_addr->sa_family == AF_INET6 && !v6) {
|
||||
uint8_t ll_prefix[] = {0xfe, 0x80 };
|
||||
struct sockaddr_in6 *sa6;
|
||||
|
||||
if (cfg_proto && (cfg_proto != 6))
|
||||
continue;
|
||||
|
||||
sa6 = (struct sockaddr_in6 *) ifa->ifa_addr;
|
||||
if (memcmp(&sa6->sin6_addr, &ll_prefix, 2))
|
||||
continue;
|
||||
|
||||
unicast = _interface_add(name, 0, 1);
|
||||
if (!unicast)
|
||||
continue;
|
||||
v6 = _interface_add(name, 1, 1);
|
||||
if (!v6)
|
||||
continue;
|
||||
|
||||
memcpy(&v6->v6_addr, &sa6->sin6_addr, sizeof(v6->v6_addr));
|
||||
memcpy(&unicast->v6_addr, &sa6->sin6_addr, sizeof(unicast->v6_addr));
|
||||
|
||||
inet_ntop(AF_INET6, &sa6->sin6_addr, v6->v6_addrs, sizeof(v6->v6_addrs));
|
||||
inet_ntop(AF_INET6, &sa6->sin6_addr, unicast->v6_addrs, sizeof(unicast->v6_addrs));
|
||||
|
||||
sa6 = (struct sockaddr_in6 *) ifa->ifa_netmask;
|
||||
memcpy(&v6->v6_netmask, &sa6->sin6_addr, sizeof(v6->v6_netmask));
|
||||
memcpy(&unicast->v6_netmask, &sa6->sin6_addr, sizeof(unicast->v6_netmask));
|
||||
|
||||
v6->peer = unicast;
|
||||
unicast->peer = v6;
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifap);
|
||||
|
||||
return !v4 && !v6;
|
||||
}
|
||||
|
||||
void interface_shutdown(void)
|
||||
{
|
||||
struct interface *iface;
|
||||
|
||||
vlist_for_each_element(&interfaces, iface, node)
|
||||
if (iface->fd.fd > 0 && iface->multicast) {
|
||||
service_announce(iface, 0);
|
||||
service_reply_a(iface, 0);
|
||||
}
|
||||
vlist_for_each_element(&interfaces, iface, node)
|
||||
interface_close(iface);
|
||||
}
|
||||
|
||||
struct interface*
|
||||
interface_get(const char *name, int v6, int multicast)
|
||||
{
|
||||
char id_buf[32];
|
||||
snprintf(id_buf, sizeof(id_buf), "%d_%d_%s", multicast, v6, name);
|
||||
struct interface *iface = vlist_find(&interfaces, id_buf, iface, node);
|
||||
return iface;
|
||||
}
|
||||
|
||||
VLIST_TREE(interfaces, avl_strcmp, iface_update_cb, false, false);
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
* Copyright (C) 2014 Felix Fietkau <nbd@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef __MDNS_INTERFACE_H
|
||||
#define __MDNS_INTERFACE_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/vlist.h>
|
||||
|
||||
extern struct vlist_tree interfaces;
|
||||
|
||||
struct interface {
|
||||
struct vlist_node node;
|
||||
struct interface *peer;
|
||||
|
||||
const char *name;
|
||||
char *id;
|
||||
struct uloop_fd fd;
|
||||
struct uloop_timeout reconnect;
|
||||
|
||||
int v6;
|
||||
int multicast;
|
||||
int ifindex;
|
||||
|
||||
struct in_addr v4_addr;
|
||||
struct in_addr v4_netmask;
|
||||
struct in6_addr v6_addr;
|
||||
struct in6_addr v6_netmask;
|
||||
char v4_addrs[16];
|
||||
char v6_addrs[64];
|
||||
|
||||
struct uloop_timeout announce_timer;
|
||||
int announce_state;
|
||||
|
||||
char *mcast_addr;
|
||||
};
|
||||
|
||||
int interface_add(const char *name);
|
||||
void interface_shutdown(void);
|
||||
int interface_send_packet(struct interface *iface, struct iovec *iov, int iov_len);
|
||||
struct interface* interface_get(const char *name, int v6, int multicast);
|
||||
|
||||
#endif
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"http_80": { "service": "_http._tcp.local", "port": 80, "txt": [ "foo=bar"] },
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"ssh_22": { "service": "_ssh._tcp.local", "port": 22, "txt": [ "daemon=dropbear" ] }
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <resolv.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/nameser.h>
|
||||
|
||||
#include <ubus/libubus.h>
|
||||
#include <libubox/uloop.h>
|
||||
|
||||
#include "dns.h"
|
||||
#include "ubus.h"
|
||||
#include "util.h"
|
||||
#include "cache.h"
|
||||
#include "service.h"
|
||||
#include "announce.h"
|
||||
#include "interface.h"
|
||||
|
||||
int cfg_proto = 0;
|
||||
int cfg_no_subnet = 0;
|
||||
|
||||
static void
|
||||
signal_shutdown(int signal)
|
||||
{
|
||||
uloop_end();
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int ch, ttl;
|
||||
|
||||
uloop_init();
|
||||
|
||||
while ((ch = getopt(argc, argv, "t:i:d46n")) != -1) {
|
||||
switch (ch) {
|
||||
case 't':
|
||||
ttl = atoi(optarg);
|
||||
if (ttl > 0)
|
||||
announce_ttl = ttl;
|
||||
else
|
||||
fprintf(stderr, "invalid ttl\n");
|
||||
break;
|
||||
case 'd':
|
||||
debug++;
|
||||
break;
|
||||
case 'i':
|
||||
interface_add(optarg);
|
||||
break;
|
||||
case '4':
|
||||
cfg_proto = 4;
|
||||
break;
|
||||
case '6':
|
||||
cfg_proto = 6;
|
||||
break;
|
||||
case 'n':
|
||||
cfg_no_subnet = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
signal(SIGTERM, signal_shutdown);
|
||||
signal(SIGKILL, signal_shutdown);
|
||||
|
||||
if (cache_init())
|
||||
return -1;
|
||||
|
||||
ubus_startup();
|
||||
|
||||
service_init(0);
|
||||
|
||||
uloop_run();
|
||||
uloop_done();
|
||||
|
||||
interface_shutdown();
|
||||
cache_cleanup(NULL);
|
||||
service_cleanup();
|
||||
vlist_flush(&interfaces);
|
||||
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,390 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <arpa/nameser.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <ifaddrs.h>
|
||||
#include <resolv.h>
|
||||
#include <glob.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <ubus/libubus.h>
|
||||
#include <libubox/vlist.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/avl-cmp.h>
|
||||
#include <libubox/blobmsg_json.h>
|
||||
|
||||
#include "ubus.h"
|
||||
#include "dns.h"
|
||||
#include "service.h"
|
||||
#include "util.h"
|
||||
#include "interface.h"
|
||||
#include "announce.h"
|
||||
|
||||
enum {
|
||||
SERVICE_SERVICE,
|
||||
SERVICE_PORT,
|
||||
SERVICE_TXT,
|
||||
__SERVICE_MAX,
|
||||
};
|
||||
|
||||
struct service {
|
||||
struct vlist_node node;
|
||||
|
||||
time_t t;
|
||||
|
||||
const char *id;
|
||||
const char *service;
|
||||
const uint8_t *txt;
|
||||
int txt_len;
|
||||
int port;
|
||||
int active;
|
||||
};
|
||||
|
||||
static const struct blobmsg_policy service_policy[__SERVICE_MAX] = {
|
||||
[SERVICE_SERVICE] = { .name = "service", .type = BLOBMSG_TYPE_STRING },
|
||||
[SERVICE_PORT] = { .name = "port", .type = BLOBMSG_TYPE_INT32 },
|
||||
[SERVICE_TXT] = { .name = "txt", .type = BLOBMSG_TYPE_ARRAY },
|
||||
};
|
||||
|
||||
static void
|
||||
service_update(struct vlist_tree *tree, struct vlist_node *node_new,
|
||||
struct vlist_node *node_old);
|
||||
|
||||
static struct blob_buf b;
|
||||
static VLIST_TREE(services, avl_strcmp, service_update, false, false);
|
||||
static char *sdudp = "_services._dns-sd._udp.local";
|
||||
static char *sdtcp = "_services._dns-sd._tcp.local";
|
||||
static int service_init_announce;
|
||||
|
||||
static const char *
|
||||
service_name(const char *domain)
|
||||
{
|
||||
static char buffer[256];
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%s.%s", mdns_hostname, domain);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void
|
||||
service_add_ptr(const char *host, int ttl)
|
||||
{
|
||||
int len = dn_comp(host, mdns_buf, sizeof(mdns_buf), NULL, NULL);
|
||||
|
||||
if (len < 1)
|
||||
return;
|
||||
|
||||
dns_add_answer(TYPE_PTR, mdns_buf, len, ttl);
|
||||
}
|
||||
|
||||
static void
|
||||
service_add_srv(struct service *s, int ttl)
|
||||
{
|
||||
struct dns_srv_data *sd = (struct dns_srv_data *) mdns_buf;
|
||||
int len = sizeof(*sd);
|
||||
|
||||
len += dn_comp(mdns_hostname_local, mdns_buf + len, sizeof(mdns_buf) - len, NULL, NULL);
|
||||
if (len <= sizeof(*sd))
|
||||
return;
|
||||
|
||||
sd->port = cpu_to_be16(s->port);
|
||||
dns_add_answer(TYPE_SRV, mdns_buf, len, ttl);
|
||||
}
|
||||
|
||||
#define TOUT_LOOKUP 60
|
||||
|
||||
static int
|
||||
service_timeout(struct service *s)
|
||||
{
|
||||
time_t t = monotonic_time();
|
||||
|
||||
if (t - s->t <= TOUT_LOOKUP)
|
||||
return 0;
|
||||
|
||||
s->t = t;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
service_reply_a(struct interface *iface, int ttl)
|
||||
{
|
||||
struct ifaddrs *ifap, *ifa;
|
||||
struct sockaddr_in *sa;
|
||||
struct sockaddr_in6 *sa6;
|
||||
|
||||
getifaddrs(&ifap);
|
||||
|
||||
dns_init_answer();
|
||||
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
||||
if (strcmp(ifa->ifa_name, iface->name))
|
||||
continue;
|
||||
if (ifa->ifa_addr->sa_family == AF_INET) {
|
||||
sa = (struct sockaddr_in *) ifa->ifa_addr;
|
||||
dns_add_answer(TYPE_A, (uint8_t *) &sa->sin_addr, 4, ttl);
|
||||
}
|
||||
if (ifa->ifa_addr->sa_family == AF_INET6) {
|
||||
uint8_t ll_prefix[] = {0xfe, 0x80 };
|
||||
sa6 = (struct sockaddr_in6 *) ifa->ifa_addr;
|
||||
if (!memcmp(&sa6->sin6_addr, &ll_prefix, 2))
|
||||
dns_add_answer(TYPE_AAAA, (uint8_t *) &sa6->sin6_addr, 16, ttl);
|
||||
}
|
||||
}
|
||||
dns_send_answer(iface, mdns_hostname_local);
|
||||
|
||||
freeifaddrs(ifap);
|
||||
}
|
||||
|
||||
static void
|
||||
service_reply_single(struct interface *iface, struct service *s, const char *match, int ttl, int force)
|
||||
{
|
||||
const char *host = service_name(s->service);
|
||||
char *service = strstr(host, "._");
|
||||
|
||||
if (!force && (!s->active || !service || !service_timeout(s)))
|
||||
return;
|
||||
|
||||
service++;
|
||||
|
||||
if (match && strcmp(match, s->service))
|
||||
return;
|
||||
|
||||
dns_init_answer();
|
||||
service_add_ptr(service_name(s->service), ttl);
|
||||
dns_send_answer(iface, service);
|
||||
|
||||
dns_init_answer();
|
||||
service_add_srv(s, ttl);
|
||||
if (s->txt && s->txt_len)
|
||||
dns_add_answer(TYPE_TXT, (uint8_t *) s->txt, s->txt_len, ttl);
|
||||
dns_send_answer(iface, host);
|
||||
}
|
||||
|
||||
void
|
||||
service_reply(struct interface *iface, const char *match, int ttl)
|
||||
{
|
||||
struct service *s;
|
||||
|
||||
vlist_for_each_element(&services, s, node)
|
||||
service_reply_single(iface, s, match, ttl, 0);
|
||||
|
||||
if (match)
|
||||
return;
|
||||
|
||||
if (ttl)
|
||||
service_reply_a(iface, ttl);
|
||||
}
|
||||
|
||||
void
|
||||
service_announce_services(struct interface *iface, const char *service, int ttl)
|
||||
{
|
||||
struct service *s;
|
||||
int tcp = 1;
|
||||
|
||||
if (!strcmp(service, sdudp))
|
||||
tcp = 0;
|
||||
else if (strcmp(service, sdtcp))
|
||||
return;
|
||||
|
||||
vlist_for_each_element(&services, s, node) {
|
||||
if (!strstr(s->service, "._tcp") && tcp)
|
||||
continue;
|
||||
if (!strstr(s->service, "._udp") && !tcp)
|
||||
continue;
|
||||
s->t = 0;
|
||||
if (ttl) {
|
||||
dns_init_answer();
|
||||
service_add_ptr(s->service, ttl);
|
||||
if (tcp)
|
||||
dns_send_answer(iface, sdtcp);
|
||||
else
|
||||
dns_send_answer(iface, sdudp);
|
||||
}
|
||||
service_reply(iface, s->service, ttl);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
service_announce(struct interface *iface, int ttl)
|
||||
{
|
||||
service_announce_services(iface, sdudp, ttl);
|
||||
service_announce_services(iface, sdtcp, ttl);
|
||||
}
|
||||
|
||||
static void
|
||||
service_update(struct vlist_tree *tree, struct vlist_node *node_new,
|
||||
struct vlist_node *node_old)
|
||||
{
|
||||
struct interface *iface;
|
||||
struct service *s;
|
||||
|
||||
if (!node_old) {
|
||||
s = container_of(node_new, struct service, node);
|
||||
if (service_init_announce)
|
||||
vlist_for_each_element(&interfaces, iface, node) {
|
||||
s->t = 0;
|
||||
service_reply_single(iface, s, NULL, announce_ttl, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
s = container_of(node_old, struct service, node);
|
||||
if (!node_new && service_init_announce)
|
||||
vlist_for_each_element(&interfaces, iface, node)
|
||||
service_reply_single(iface, s, NULL, 0, 1);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void
|
||||
service_load_blob(struct blob_attr *b)
|
||||
{
|
||||
struct blob_attr *txt, *_tb[__SERVICE_MAX];
|
||||
struct service *s;
|
||||
char *d_service, *d_id;
|
||||
uint8_t *d_txt;
|
||||
int rem2;
|
||||
int txt_len = 0;
|
||||
|
||||
blobmsg_parse(service_policy, ARRAY_SIZE(service_policy),
|
||||
_tb, blobmsg_data(b), blobmsg_data_len(b));
|
||||
if (!_tb[SERVICE_PORT] || !_tb[SERVICE_SERVICE])
|
||||
return;
|
||||
|
||||
if (_tb[SERVICE_SERVICE])
|
||||
blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2)
|
||||
txt_len += 1 + strlen(blobmsg_get_string(txt));
|
||||
|
||||
s = calloc_a(sizeof(*s),
|
||||
&d_id, strlen(blobmsg_name(b)) + 1,
|
||||
&d_service, strlen(blobmsg_get_string(_tb[SERVICE_SERVICE])) + 1,
|
||||
&d_txt, txt_len);
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
s->port = blobmsg_get_u32(_tb[SERVICE_PORT]);
|
||||
s->id = strcpy(d_id, blobmsg_name(b));
|
||||
s->service = strcpy(d_service, blobmsg_get_string(_tb[SERVICE_SERVICE]));
|
||||
s->active = 1;
|
||||
s->t = 0;
|
||||
s->txt_len = txt_len;
|
||||
s->txt = d_txt;
|
||||
|
||||
if (_tb[SERVICE_SERVICE])
|
||||
blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2) {
|
||||
int len = strlen(blobmsg_get_string(txt));
|
||||
if (!len)
|
||||
return;
|
||||
if (len > 0xff)
|
||||
len = 0xff;
|
||||
*d_txt = len;
|
||||
d_txt++;
|
||||
memcpy(d_txt, blobmsg_get_string(txt), len);
|
||||
d_txt += len;
|
||||
}
|
||||
|
||||
vlist_add(&services, &s->node, s->id);
|
||||
}
|
||||
|
||||
static void
|
||||
service_load(char *path)
|
||||
{
|
||||
struct blob_attr *cur;
|
||||
glob_t gl;
|
||||
int i, rem;
|
||||
|
||||
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]))
|
||||
blob_for_each_attr(cur, b.head, rem)
|
||||
service_load_blob(cur);
|
||||
}
|
||||
globfree(&gl);
|
||||
}
|
||||
|
||||
static void
|
||||
service_init_cb(struct ubus_request *req, int type, struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *cur;
|
||||
int rem;
|
||||
|
||||
get_hostname();
|
||||
|
||||
vlist_update(&services);
|
||||
service_load("/tmp/run/mdns/*");
|
||||
|
||||
blob_for_each_attr(cur, msg, rem) {
|
||||
struct blob_attr *cur2;
|
||||
int rem2;
|
||||
|
||||
blobmsg_for_each_attr(cur2, cur, rem2) {
|
||||
struct blob_attr *cur3;
|
||||
int rem3;
|
||||
|
||||
if (strcmp(blobmsg_name(cur2), "instances"))
|
||||
continue;
|
||||
|
||||
blobmsg_for_each_attr(cur3, cur2, rem3) {
|
||||
struct blob_attr *cur4;
|
||||
int rem4;
|
||||
int running = 0;
|
||||
|
||||
blobmsg_for_each_attr(cur4, cur3, rem4) {
|
||||
const char *name = blobmsg_name(cur4);
|
||||
|
||||
if (!strcmp(name, "running")) {
|
||||
running = blobmsg_get_bool(cur4);
|
||||
} else if (running && !strcmp(name, "data")) {
|
||||
struct blob_attr *cur5;
|
||||
int rem5;
|
||||
|
||||
blobmsg_for_each_attr(cur5, cur4, rem5) {
|
||||
struct blob_attr *cur6;
|
||||
int rem6;
|
||||
|
||||
if (strcmp(blobmsg_name(cur5), "mdns"))
|
||||
continue;
|
||||
|
||||
blobmsg_for_each_attr(cur6, cur5, rem6)
|
||||
service_load_blob(cur6);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vlist_flush(&services);
|
||||
}
|
||||
|
||||
void
|
||||
service_init(int announce)
|
||||
{
|
||||
get_hostname();
|
||||
|
||||
service_init_announce = announce;
|
||||
ubus_service_list(service_init_cb);
|
||||
}
|
||||
|
||||
void
|
||||
service_cleanup(void)
|
||||
{
|
||||
vlist_flush(&services);
|
||||
blob_buf_free(&b);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _SERVICE_H__
|
||||
#define _SERVICE_H__
|
||||
|
||||
extern void service_init(int announce);
|
||||
extern void service_cleanup(void);
|
||||
extern void service_announce(struct interface *iface, int ttl);
|
||||
extern void service_announce_services(struct interface *iface, const char *service, int ttl);
|
||||
extern void service_reply(struct interface *iface, const char *match, int ttl);
|
||||
extern void service_reply_a(struct interface *iface, int ttl);
|
||||
|
||||
#endif
|
||||
@@ -1,275 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <ubus/libubus.h>
|
||||
#include <libubox/vlist.h>
|
||||
#include <libubox/uloop.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "ubus.h"
|
||||
#include "cache.h"
|
||||
#include "service.h"
|
||||
#include "interface.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)
|
||||
{
|
||||
service_init(1);
|
||||
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 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_service *s, *q;
|
||||
char *buffer = (char *) mdns_buf;
|
||||
void *c1 = NULL, *c2;
|
||||
|
||||
blob_buf_init(&b, 0);
|
||||
avl_for_each_element(&services, s, avl) {
|
||||
char *local;
|
||||
if (*((char *) s->avl.key) != '_')
|
||||
continue;
|
||||
snprintf(buffer, MAX_NAME_LEN, "%s", (const char *) s->avl.key);
|
||||
local = strstr(buffer, ".local");
|
||||
if (local)
|
||||
*local = '\0';
|
||||
if (!strcmp(buffer, "_tcp") || !strcmp(buffer, "_udp"))
|
||||
continue;
|
||||
|
||||
if (!c1) {
|
||||
c1 = blobmsg_open_table(&b, buffer);
|
||||
}
|
||||
snprintf(buffer, MAX_NAME_LEN, "%s", (const char *) s->entry);
|
||||
local = strstr(buffer, "._");
|
||||
if (local)
|
||||
*local = '\0';
|
||||
c2 = blobmsg_open_table(&b, buffer);
|
||||
strncat(buffer, ".local", MAX_NAME_LEN);
|
||||
cache_dump_records(&b, buffer);
|
||||
cache_dump_records(&b, s->entry);
|
||||
blobmsg_close_table(&b, c2);
|
||||
q = avl_next_element(s, avl);
|
||||
if (!q || avl_is_last(&services, &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_service *s;
|
||||
char *buffer = (char *) mdns_buf;
|
||||
void *c;
|
||||
|
||||
blob_buf_init(&b, 0);
|
||||
avl_for_each_element(&services, s, avl) {
|
||||
char *local;
|
||||
if (*((char *) s->avl.key) == '_')
|
||||
continue;
|
||||
snprintf(buffer, MAX_NAME_LEN, "%s", (const char *) s->entry);
|
||||
local = strstr(buffer, "._");
|
||||
if (local)
|
||||
*local = '\0';
|
||||
c = blobmsg_open_table(&b, buffer);
|
||||
strncat(buffer, ".local", MAX_NAME_LEN);
|
||||
cache_dump_records(&b, buffer);
|
||||
cache_dump_records(&b, s->entry);
|
||||
blobmsg_close_table(&b, c);
|
||||
}
|
||||
ubus_send_reply(ctx, req, b.head);
|
||||
|
||||
return UBUS_STATUS_OK;
|
||||
}
|
||||
|
||||
enum {
|
||||
CFG_INTERFACES,
|
||||
CFG_KEEP,
|
||||
CFG_MAX
|
||||
};
|
||||
|
||||
static const struct blobmsg_policy config_policy[] = {
|
||||
[CFG_INTERFACES] = { "interfaces", BLOBMSG_TYPE_ARRAY },
|
||||
[CFG_KEEP] = { "keep", BLOBMSG_TYPE_BOOL },
|
||||
};
|
||||
|
||||
static int
|
||||
mdns_set_config(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *data[CFG_MAX], *cur;
|
||||
int rem, keep = false;
|
||||
|
||||
blobmsg_parse(config_policy, CFG_MAX, data, blob_data(msg), blob_len(msg));
|
||||
if (!data[CFG_INTERFACES])
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
if (!blobmsg_check_attr_list(data[CFG_INTERFACES], BLOBMSG_TYPE_STRING))
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
keep = data[CFG_KEEP] && blobmsg_get_bool(data[CFG_KEEP]);
|
||||
if (!keep) {
|
||||
vlist_update(&interfaces);
|
||||
ubus_notify(ctx, obj, "set_config", NULL, 1000);
|
||||
}
|
||||
|
||||
blobmsg_for_each_attr(cur, data[CFG_INTERFACES], rem)
|
||||
interface_add(blobmsg_data(cur));
|
||||
|
||||
if (!keep)
|
||||
vlist_flush(&interfaces);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum query_attr {
|
||||
QUERY_QUESTION,
|
||||
QUERY_IFACE,
|
||||
QUERY_TYPE,
|
||||
QUERY_MAX
|
||||
};
|
||||
|
||||
static const struct blobmsg_policy query_policy[QUERY_MAX] = {
|
||||
[QUERY_QUESTION]= { "question", BLOBMSG_TYPE_STRING },
|
||||
[QUERY_IFACE] = { "interface", BLOBMSG_TYPE_STRING },
|
||||
[QUERY_TYPE] = { "type", BLOBMSG_TYPE_INT32 },
|
||||
};
|
||||
|
||||
static int
|
||||
mdns_query(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
struct ubus_request_data *req, const char *method,
|
||||
struct blob_attr *msg)
|
||||
{
|
||||
struct blob_attr *tb[QUERY_MAX], *c;
|
||||
const char *question = "_services._dns-sd._udp.local";
|
||||
const char *ifname;
|
||||
int type = TYPE_ANY;
|
||||
|
||||
blobmsg_parse(query_policy, QUERY_MAX, tb, blob_data(msg), blob_len(msg));
|
||||
|
||||
if (!(c = tb[QUERY_IFACE]))
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
|
||||
ifname = blobmsg_get_string(c);
|
||||
|
||||
if ((c = tb[QUERY_QUESTION]))
|
||||
question = blobmsg_get_string(c);
|
||||
|
||||
if ((c = tb[QUERY_TYPE]))
|
||||
type = blobmsg_get_u32(c);
|
||||
|
||||
struct interface *iface_v4 = interface_get(ifname, 0, 1);
|
||||
struct interface *iface_v6 = interface_get(ifname, 1, 1);
|
||||
|
||||
if (!iface_v4 && !iface_v6)
|
||||
return UBUS_STATUS_NOT_FOUND;
|
||||
|
||||
if (!strcmp(method, "query")) {
|
||||
if (iface_v4)
|
||||
dns_send_question(iface_v4, question, type, 0);
|
||||
|
||||
if (iface_v6)
|
||||
dns_send_question(iface_v6, question, type, 0);
|
||||
|
||||
return UBUS_STATUS_OK;
|
||||
} else if (!strcmp(method, "fetch")) {
|
||||
blob_buf_init(&b, 0);
|
||||
void *k = blobmsg_open_array(&b, "records");
|
||||
cache_dump_recursive(&b, question, type, iface_v4 ? iface_v4 : iface_v6);
|
||||
blobmsg_close_array(&b, k);
|
||||
ubus_send_reply(ctx, req, b.head);
|
||||
return UBUS_STATUS_OK;
|
||||
} else {
|
||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const struct ubus_method mdns_methods[] = {
|
||||
UBUS_METHOD("set_config", mdns_set_config, config_policy),
|
||||
UBUS_METHOD("query", mdns_query, query_policy),
|
||||
UBUS_METHOD("fetch", mdns_query, query_policy),
|
||||
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);
|
||||
}
|
||||
|
||||
int ubus_service_list(ubus_data_handler_t cb)
|
||||
{
|
||||
uint32_t id;
|
||||
int ret;
|
||||
|
||||
blob_buf_init(&b, 0);
|
||||
ret = ubus_lookup_id(&conn.ctx, "service", &id);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ubus_invoke(&conn.ctx, id, "list", b.head, cb, NULL, 5 * 1000);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _UBUS_H__
|
||||
#define _UBUS_H__
|
||||
|
||||
extern void ubus_startup(void);
|
||||
extern int ubus_service_list(ubus_data_handler_t cb);
|
||||
|
||||
#endif
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/utils.h>
|
||||
|
||||
#include "dns.h"
|
||||
#include "util.h"
|
||||
|
||||
uint8_t mdns_buf[MDNS_BUF_LEN];
|
||||
int debug = 0;
|
||||
|
||||
char mdns_hostname[HOSTNAME_LEN];
|
||||
char mdns_hostname_local[HOSTNAME_LEN + 6];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void get_hostname(void)
|
||||
{
|
||||
struct utsname utsname;
|
||||
|
||||
mdns_hostname[0] = 0;
|
||||
mdns_hostname_local[0] = 0;
|
||||
|
||||
if (uname(&utsname) < 0)
|
||||
return;
|
||||
|
||||
snprintf(mdns_hostname, sizeof(mdns_hostname), "%s", utsname.nodename);
|
||||
snprintf(mdns_hostname_local, sizeof(mdns_hostname_local), "%s.local", utsname.nodename);
|
||||
}
|
||||
|
||||
time_t monotonic_time(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return ts.tv_sec;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _UTIL_H__
|
||||
#define _UTIL_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#define DBG(level, fmt, ...) do { \
|
||||
if (debug >= level) \
|
||||
fprintf(stderr, "mdnsd: %s (%d): " fmt, __func__, __LINE__, ## __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define MDNS_BUF_LEN (8 * 1024)
|
||||
#define HOSTNAME_LEN 256
|
||||
|
||||
extern int debug;
|
||||
extern uint8_t mdns_buf[MDNS_BUF_LEN];
|
||||
extern char mdns_hostname[HOSTNAME_LEN];
|
||||
extern char mdns_hostname_local[HOSTNAME_LEN + 6];
|
||||
|
||||
extern void get_hostname(void);
|
||||
extern uint32_t rand_time_delta(uint32_t t);
|
||||
extern time_t monotonic_time(void);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user