Implementation of basic MDNS support

This commit is contained in:
Christian Huitema 2016-12-22 15:51:47 -08:00
parent e01c85ef2f
commit f1b8b25afa
6 changed files with 443 additions and 6 deletions

View File

@ -1682,12 +1682,12 @@ getdns_context_set_namespaces(getdns_context *context,
for (i = 0; i < namespace_count; i++) {
if (namespaces[i] == GETDNS_NAMESPACE_NETBIOS ||
namespaces[i] == GETDNS_NAMESPACE_MDNS ||
namespaces[i] == GETDNS_NAMESPACE_NIS)
r = GETDNS_RETURN_NOT_IMPLEMENTED;
else if (namespaces[i] != GETDNS_NAMESPACE_DNS &&
namespaces[i] != GETDNS_NAMESPACE_LOCALNAMES)
namespaces[i] != GETDNS_NAMESPACE_LOCALNAMES &&
namespaces[i] != GETDNS_NAMESPACE_MDNS)
return GETDNS_RETURN_CONTEXT_UPDATE_FAIL;
}
GETDNS_FREE(context->my_mf, context->namespaces);

View File

@ -46,17 +46,33 @@
#define STUB_DEBUG_WRITE "------- WRITE: "
#define STUB_DEBUG_CLEANUP "--- CLEANUP: "
#ifdef GETDNS_ON_WINDOWS
#define DEBUG_ON(...) do { \
struct timeval tv; \
struct tm tm; \
char buf[10]; \
time_t tsec; \
\
gettimeofday(&tv, NULL); \
tsec = (time_t) tv.tv_sec; \
gmtime_s(&tm, (const time_t *) &tsec); \
strftime(buf, 10, "%H:%M:%S", &tm); \
fprintf(stderr, "[%s.%.6d] ", buf, (int)tv.tv_usec); \
fprintf(stderr, __VA_ARGS__); \
} while (0)
#else
#define DEBUG_ON(...) do { \
struct timeval tv; \
struct tm tm; \
char buf[10]; \
\
gettimeofday(&tv, NULL); \
gmtime_r(&tv.tv_sec, &tm); \
gmtime_r(&tm, &tv.tv_sec); \
strftime(buf, 10, "%H:%M:%S", &tm); \
fprintf(stderr, "[%s.%.6d] ", buf, (int)tv.tv_usec); \
fprintf(stderr, __VA_ARGS__); \
} while (0)
#endif
#define DEBUG_NL(...) do { \
struct timeval tv; \
@ -102,5 +118,17 @@
#define DEBUG_SERVER(...) DEBUG_OFF(__VA_ARGS__)
#endif
#define MDNS_DEBUG_ENTRY "-> MDNS ENTRY: "
#define MDNS_DEBUG_READ "-- MDNS READ: "
#define MDNS_DEBUG_WRITE "-- MDNS WRITE: "
#define MDNS_DEBUG_CLEANUP "-- MDNS CLEANUP:"
#if defined(MDNS_DEBUG) && MDNS_DEBUG
#include <time.h>
#define DEBUG_MDNS(...) DEBUG_ON(__VA_ARGS__)
#else
#define DEBUG_MDNS(...) DEBUG_OFF(__VA_ARGS__)
#endif
#endif
/* debug.h */

View File

@ -52,6 +52,7 @@
#include "dnssec.h"
#include "stub.h"
#include "dict.h"
#include "mdns.h"
/* cancel, cleanup and send timeout to callback */
static void
@ -476,6 +477,26 @@ getdns_general_ns(getdns_context *context, getdns_eventloop *loop,
( req, localnames_response);
break;
}
} else if (context->namespaces[i] == GETDNS_NAMESPACE_MDNS) {
/* Check whether the name belongs in the MDNS space */
if (!(r = _getdns_mdns_namespace_check(req)))
{
// Submit the query to the MDNS transport.
for (netreq_p = req->netreqs
; !r && (netreq = *netreq_p)
; netreq_p++) {
if ((r = _getdns_submit_mdns_request(netreq))) {
if (r == DNS_REQ_FINISHED) {
if (return_netreq_p)
*return_netreq_p = NULL;
return GETDNS_RETURN_GOOD;
}
netreq->state = NET_REQ_FINISHED;
}
}
/* Stop processing more namespaces, since there was a match */
break;
}
} else if (context->namespaces[i] == GETDNS_NAMESPACE_DNS) {
/* TODO: We will get a good return code here even if

355
src/mdns.c Normal file
View File

@ -0,0 +1,355 @@
/*
* Functions for MDNS resolving.
*/
/*
* Copyright (c) 2016 Christian Huitema <huitema@huitema.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include "debug.h"
#include "context.h"
#include "general.h"
#include "gldns/pkthdr.h"
#include "util-internal.h"
#include "mdns.h"
#ifdef USE_WINSOCK
typedef u_short sa_family_t;
#define _getdns_EWOULDBLOCK (WSAGetLastError() == WSATRY_AGAIN ||\
WSAGetLastError() == WSAEWOULDBLOCK)
#define _getdns_EINPROGRESS (WSAGetLastError() == WSAEINPROGRESS)
#else
#define _getdns_EWOULDBLOCK (errno == EAGAIN || errno == EWOULDBLOCK)
#define _getdns_EINPROGRESS (errno == EINPROGRESS)
#endif
uint64_t _getdns_get_time_as_uintt64();
/*
* Constants defined in RFC 6762
*/
#define MDNS_MCAST_IPV4_LONG 0xE00000FB /* 224.0.0.251 */
#define MDNS_MCAST_PORT 5353
static uint8_t mdns_mcast_ipv6[] = {
0xFF, 0x02, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0xFB };
static uint8_t mdns_suffix_dot_local[] = { 5, 'l', 'o', 'c', 'a', 'l', 0 };
static uint8_t mdns_suffix_254_169_in_addr_arpa[] = {
3, '2', '5', '4',
3, '1', '6', '9',
7, 'i', 'n', '-', 'a', 'd', 'd', 'r',
4, 'a', 'r', 'p', 'a', 0 };
static uint8_t mdns_suffix_8_e_f_ip6_arpa[] = {
1, '8', 1, 'e', 1, 'f',
7, 'i', 'p', 'v', '6',
4, 'a', 'r', 'p', 'a', 0 };
static uint8_t mdns_suffix_9_e_f_ip6_arpa[] = {
1, '9', 1, 'e', 1, 'f',
7, 'i', 'p', 'v', '6',
4, 'a', 'r', 'p', 'a', 0 };
static uint8_t mdns_suffix_a_e_f_ip6_arpa[] = {
1, 'a', 1, 'e', 1, 'f',
7, 'i', 'p', 'v', '6',
4, 'a', 'r', 'p', 'a', 0 };
static uint8_t mdns_suffix_b_e_f_ip6_arpa[] = {
1, 'b', 1, 'e', 1, 'f',
7, 'i', 'p', 'v', '6',
4, 'a', 'r', 'p', 'a', 0 };
/* TODO: actualy delete what is required.. */
static void
mdns_cleanup(getdns_network_req *netreq)
{
DEBUG_MDNS("%s %-35s: MSG: %p\n",
MDNS_DEBUG_CLEANUP, __FUNCTION__, netreq);
getdns_dns_req *dnsreq = netreq->owner;
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
GETDNS_NULL_FREE(dnsreq->context->mf, netreq->tcp.read_buf);
}
void
_getdns_cancel_mdns_request(getdns_network_req *netreq)
{
mdns_cleanup(netreq);
if (netreq->fd >= 0) {
#ifdef USE_WINSOCK
closesocket(netreq->fd);
#else
close(netreq->fd);
#endif
}
}
static void
mdns_timeout_cb(void *userarg)
{
getdns_network_req *netreq = (getdns_network_req *)userarg;
DEBUG_MDNS("%s %-35s: MSG: %p\n",
MDNS_DEBUG_CLEANUP, __FUNCTION__, netreq);
/* TODO: do we need a retry logic here? */
/* Check the required cleanup */
mdns_cleanup(netreq);
if (netreq->fd >= 0)
#ifdef USE_WINSOCK
closesocket(netreq->fd);
#else
close(netreq->fd);
#endif
netreq->state = NET_REQ_TIMED_OUT;
if (netreq->owner->user_callback) {
netreq->debug_end_time = _getdns_get_time_as_uintt64();
(void)_getdns_context_request_timed_out(netreq->owner);
}
else
_getdns_check_dns_req_complete(netreq->owner);
}
/**************************/
/* UDP callback functions */
/**************************/
static void
mdns_udp_read_cb(void *userarg)
{
getdns_network_req *netreq = (getdns_network_req *)userarg;
getdns_dns_req *dnsreq = netreq->owner;
ssize_t read;
DEBUG_MDNS("%s %-35s: MSG: %p \n", MDNS_DEBUG_READ,
__FUNCTION__, netreq);
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
read = recvfrom(netreq->fd, (void *)netreq->response,
netreq->max_udp_payload_size + 1, /* If read == max_udp_payload_size
* then all is good. If read ==
* max_udp_payload_size + 1, then
* we receive more then requested!
* i.e. overflow
*/
0, NULL, NULL);
if (read == -1 && _getdns_EWOULDBLOCK)
return;
if (read < GLDNS_HEADER_SIZE)
return; /* Not DNS */
if (GLDNS_ID_WIRE(netreq->response) != netreq->query_id)
return; /* Cache poisoning attempt ;) */
// TODO: check whether EDNS server cookies are required for MDNS
// TODO: check that the source address originates from the local network.
// TODO: check TTL = 255
#ifdef USE_WINSOCK
closesocket(netreq->fd);
#else
close(netreq->fd);
#endif
/*
* TODO: how to handle an MDNS response with TC bit set?
* Ignore it for now, as we do not support any kind of TCP fallback
* for basic MDNS.
*/
netreq->response_len = read;
netreq->debug_end_time = _getdns_get_time_as_uintt64();
netreq->state = NET_REQ_FINISHED;
_getdns_check_dns_req_complete(dnsreq);
}
static void
mdns_udp_write_cb(void *userarg)
{
getdns_network_req *netreq = (getdns_network_req *)userarg;
getdns_dns_req *dnsreq = netreq->owner;
size_t pkt_len = netreq->response - netreq->query;
struct sockaddr_in mdns_mcast_v4;
int ttl = 255;
int r;
DEBUG_MDNS("%s %-35s: MSG: %p \n", MDNS_DEBUG_WRITE,
__FUNCTION__, netreq);
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
netreq->debug_start_time = _getdns_get_time_as_uintt64();
netreq->debug_udp = 1;
netreq->query_id = (uint16_t) arc4random();
GLDNS_ID_SET(netreq->query, netreq->query_id);
/* do we need to handle options valid in the MDNS context? */
/* Probably no need for TSIG in MDNS */
/* Always use multicast address */
mdns_mcast_v4.sin_family = AF_INET;
mdns_mcast_v4.sin_port = htons(MDNS_MCAST_PORT);
mdns_mcast_v4.sin_addr.s_addr = htonl(MDNS_MCAST_IPV4_LONG);
/* Set TTL=255 for compliance with RFC 6762 */
r = setsockopt(netreq->fd, IPPROTO_IP, IP_TTL, (const char *)&ttl, sizeof(ttl));
if (r != 0 ||
(ssize_t)pkt_len != sendto(
netreq->fd, (const void *)netreq->query, pkt_len, 0,
(struct sockaddr *)&mdns_mcast_v4,
sizeof(mdns_mcast_v4))) {
#ifdef USE_WINSOCK
closesocket(netreq->fd);
#else
close(netreq->fd);
#endif
return;
}
GETDNS_SCHEDULE_EVENT(
dnsreq->loop, netreq->fd, dnsreq->context->timeout,
getdns_eventloop_event_init(&netreq->event, netreq,
mdns_udp_read_cb, NULL, mdns_timeout_cb));
}
/*
* MDNS Request Submission
*/
getdns_return_t
_getdns_submit_mdns_request(getdns_network_req *netreq)
{
DEBUG_MDNS("%s %-35s: MSG: %p TYPE: %d\n", MDNS_DEBUG_ENTRY, __FUNCTION__,
netreq, netreq->request_type);
int fd = -1;
getdns_dns_req *dnsreq = netreq->owner;
/* Open the UDP socket required for the request */
if ((fd = socket(
AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
return -1;
/* TODO: do we need getdns_sock_nonblock(fd); */
/* Schedule the MDNS request */
netreq->fd = fd;
GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event);
GETDNS_SCHEDULE_EVENT(
dnsreq->loop, netreq->fd, dnsreq->context->timeout,
getdns_eventloop_event_init(&netreq->event, netreq,
NULL, mdns_udp_write_cb, mdns_timeout_cb));
return GETDNS_RETURN_GOOD;
}
/*
* MDNS name space management
*/
static int
mdns_suffix_compare(register const uint8_t *d1, register const uint8_t *d2)
{
int ret = 0;
uint8_t *d1_head = (uint8_t *) d1;
uint8_t *d1_current;
uint8_t *d2_current;
int is_matching = 0;
int part_length;
int i;
uint8_t c;
/* Skip the first name part, since we want at least one label before the suffix */
if (*d1_head != 0)
d1_head += *d1_head + 1;
while (*d1_head != 0)
{
/* check whether we have a match at this point */
d1_current = d1_head;
d2_current = (uint8_t *) d2;
is_matching = 0;
/* compare length and value of all successive labels */
while (*d1_current == *d2_current)
{
part_length = *d1_current;
if (part_length == 0)
{
/* We have reached the top label, there is a match */
ret = 1;
break;
}
/* The label's lengths are matching, check the content */
is_matching = 1;
d1_current++;
d2_current++;
for (i = 0; i < part_length; i++)
{
c = d1_current[i];
if (isupper(c))
c = tolower(c);
if (c != d2_current[i])
{
is_matching = 0;
break;
}
}
/* move the pointers to the next label */
if (is_matching)
{
d1_current += part_length;
d2_current += part_length;
}
}
/* if no match found yet, move to the next label of d1 */
if (is_matching)
break;
else
d1_head += *d1_head + 1;
}
return ret;
}
getdns_return_t
_getdns_mdns_namespace_check(
getdns_dns_req *dnsreq)
{
getdns_return_t ret = GETDNS_RETURN_GENERIC_ERROR;
/* Checking the prefixes defined in RFC 6762 */
if (mdns_suffix_compare(dnsreq->name, mdns_suffix_dot_local) ||
mdns_suffix_compare(dnsreq->name, mdns_suffix_254_169_in_addr_arpa) ||
mdns_suffix_compare(dnsreq->name, mdns_suffix_8_e_f_ip6_arpa) ||
mdns_suffix_compare(dnsreq->name, mdns_suffix_9_e_f_ip6_arpa) ||
mdns_suffix_compare(dnsreq->name, mdns_suffix_a_e_f_ip6_arpa) ||
mdns_suffix_compare(dnsreq->name, mdns_suffix_b_e_f_ip6_arpa))
ret = GETDNS_RETURN_GOOD;
return ret;
}

33
src/mdns.h Normal file
View File

@ -0,0 +1,33 @@
/*
* Functions for MDNS resolving.
*/
/*
* Copyright (c) 2016 Christian Huitema <huitema@huitema.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MDNS_H
#define MDNS_H
#include "getdns/getdns.h"
#include "types-internal.h"
getdns_return_t
_getdns_submit_mdns_request(getdns_network_req *netreq);
getdns_return_t
getdns_mdns_namespace_check(getdns_dns_req *dnsreq);
#endif /* MDNS_H */

View File

@ -91,7 +91,7 @@ static int upstream_connect(getdns_upstream *upstream,
static int fallback_on_write(getdns_network_req *netreq);
static void stub_timeout_cb(void *userarg);
static uint64_t _getdns_get_time_as_uintt64();
uint64_t _getdns_get_time_as_uintt64();
/*****************************/
/* General utility functions */
/*****************************/
@ -475,7 +475,7 @@ stub_cleanup(getdns_network_req *netreq)
GETDNS_NULL_FREE(dnsreq->context->mf, netreq->tcp.read_buf);
/* Nothing globally scheduled? Then nothing queued */
if (!(upstream = netreq->upstream)->event.ev)
if (!netreq->upstream || !(upstream = netreq->upstream)->event.ev)
return;
/* Delete from upstream->netreq_by_query_id (if present) */
@ -1278,7 +1278,7 @@ stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp,
return STUB_TCP_ERROR;
}
static uint64_t
uint64_t
_getdns_get_time_as_uintt64() {
struct timeval tv;