2016-12-22 17:51:47 -06:00
|
|
|
/*
|
|
|
|
* 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"
|
|
|
|
|
2016-12-22 22:30:14 -06:00
|
|
|
#ifdef HAVE_MDNS_SUPPORT
|
|
|
|
|
2016-12-22 17:51:47 -06:00
|
|
|
#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
|
|
|
|
|
2017-02-06 22:23:35 -06:00
|
|
|
static uint8_t mdns_mcast_ipv6[] = {
|
|
|
|
0xFF, 0x02, 0, 0, 0, 0, 0, 0,
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0xFB
|
|
|
|
};
|
2016-12-22 17:51:47 -06:00
|
|
|
|
|
|
|
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',
|
2017-02-06 22:23:35 -06:00
|
|
|
3, 'i', 'p', '6',
|
2016-12-22 17:51:47 -06:00
|
|
|
4, 'a', 'r', 'p', 'a', 0 };
|
|
|
|
static uint8_t mdns_suffix_9_e_f_ip6_arpa[] = {
|
|
|
|
1, '9', 1, 'e', 1, 'f',
|
2017-02-06 22:23:35 -06:00
|
|
|
3, 'i', 'p', '6',
|
2016-12-22 17:51:47 -06:00
|
|
|
4, 'a', 'r', 'p', 'a', 0 };
|
|
|
|
static uint8_t mdns_suffix_a_e_f_ip6_arpa[] = {
|
|
|
|
1, 'a', 1, 'e', 1, 'f',
|
2017-02-06 22:23:35 -06:00
|
|
|
3, 'i', 'p', '6',
|
2016-12-22 17:51:47 -06:00
|
|
|
4, 'a', 'r', 'p', 'a', 0 };
|
|
|
|
static uint8_t mdns_suffix_b_e_f_ip6_arpa[] = {
|
|
|
|
1, 'b', 1, 'e', 1, 'f',
|
2017-02-06 22:23:35 -06:00
|
|
|
3, 'i', 'p', '6',
|
2016-12-22 17:51:47 -06:00
|
|
|
4, 'a', 'r', 'p', 'a', 0 };
|
|
|
|
|
2017-01-20 21:44:05 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare function for the netreq_by_query_id,
|
|
|
|
* used in the red-black tree of all netreq by continuous query.
|
|
|
|
*/
|
|
|
|
static int mdns_cmp_netreq_by_query_id(const void * id1, const void * id2)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (id1 != id2)
|
|
|
|
{
|
|
|
|
ret = (((intptr_t)id1) < ((intptr_t)id2)) ? -1 : 1;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare function for the getdns_mdns_known_record type,
|
|
|
|
* used in the red-black tree of known records per query.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int mdns_cmp_known_records(const void * nkr1, const void * nkr2)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
getdns_mdns_known_record * kr1 = (getdns_mdns_known_record *)nkr1;
|
|
|
|
getdns_mdns_known_record * kr2 = (getdns_mdns_known_record *)nkr2;
|
|
|
|
|
2017-02-06 22:23:35 -06:00
|
|
|
if (kr1->request_class != kr2->request_class)
|
2017-01-20 21:44:05 -06:00
|
|
|
{
|
2017-02-06 22:23:35 -06:00
|
|
|
ret = (kr1->request_class < kr2->request_class) ? -1 : 1;
|
2017-01-20 21:44:05 -06:00
|
|
|
}
|
2017-02-06 22:23:35 -06:00
|
|
|
else if (kr1->request_type != kr2->request_type)
|
|
|
|
{
|
|
|
|
ret = (kr1->request_type < kr2->request_type) ? -1 : 1;
|
|
|
|
}
|
|
|
|
else if (kr1->name_len != kr2->name_len)
|
|
|
|
{
|
|
|
|
ret = (kr1->name_len < kr2->name_len) ? -1 : 1;
|
|
|
|
}
|
|
|
|
else if (kr1->record_len != kr2->record_len)
|
2017-01-20 21:44:05 -06:00
|
|
|
{
|
2017-02-06 22:23:35 -06:00
|
|
|
ret = (kr1->record_len < kr2->record_len) ? -1 : 1;
|
|
|
|
}
|
|
|
|
else if ((ret = memcmp((void*)kr1->name, (void*)kr2->name, kr2->name_len)) == 0)
|
|
|
|
{
|
|
|
|
ret = memcmp((const void*)kr1->record_data, (const void*)kr2->record_data, kr1->record_len);
|
2017-01-20 21:44:05 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare function for the mdns_continuous_query_by_name_rrtype,
|
|
|
|
* used in the red-black tree of all ongoing queries.
|
|
|
|
*/
|
|
|
|
static int mdns_cmp_continuous_queries_by_name_rrtype(const void * nqnr1, const void * nqnr2)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
getdns_mdns_continuous_query * qnr1 = (getdns_mdns_continuous_query *)nqnr1;
|
|
|
|
getdns_mdns_continuous_query * qnr2 = (getdns_mdns_continuous_query *)nqnr2;
|
|
|
|
|
|
|
|
if (qnr1->request_class != qnr2->request_class)
|
|
|
|
{
|
|
|
|
ret = (qnr1->request_class < qnr2->request_class) ? -1 : 1;
|
|
|
|
}
|
|
|
|
else if (qnr1->request_type != qnr2->request_type)
|
|
|
|
{
|
|
|
|
ret = (qnr1->request_type < qnr2->request_type) ? -1 : 1;
|
|
|
|
}
|
|
|
|
else if (qnr1->name_len != qnr2->name_len)
|
|
|
|
{
|
|
|
|
ret = (qnr1->name_len < qnr2->name_len) ? -1 : 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = memcmp((void*)qnr1->name, (void*)qnr2->name, qnr1->name_len);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-02-06 22:23:35 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Create the two required multicast sockets
|
|
|
|
*/
|
|
|
|
static int mdns_open_ipv4_multicast()
|
|
|
|
{
|
|
|
|
getdns_return_t ret = 0;
|
|
|
|
SOCKET fd4 = -1;
|
|
|
|
SOCKADDR_IN ipv4_dest;
|
|
|
|
SOCKADDR_IN ipv4_port;
|
|
|
|
uint8_t so_reuse_bool = 1;
|
|
|
|
uint8_t ttl = 255;
|
|
|
|
IP_MREQ mreq4;
|
|
|
|
|
|
|
|
memset(&ipv4_dest, 0, sizeof(ipv4_dest));
|
|
|
|
memset(&ipv4_port, 0, sizeof(ipv4_dest));
|
|
|
|
ipv4_dest.sin_family = AF_INET;
|
|
|
|
ipv4_dest.sin_port = htons(MDNS_MCAST_PORT);
|
|
|
|
ipv4_dest.sin_addr.S_un.S_addr = htonl(MDNS_MCAST_IPV4_LONG);
|
|
|
|
ipv4_port.sin_family = AF_INET;
|
|
|
|
ipv4_port.sin_port = htons(MDNS_MCAST_PORT);
|
|
|
|
|
|
|
|
|
|
|
|
fd4 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
|
|
|
|
|
|
if (fd4 != -1)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* No need to test the output of the so_reuse call,
|
|
|
|
* since the only result that matters is that of bind.
|
|
|
|
*/
|
|
|
|
(void)setsockopt(fd4, SOL_SOCKET, SO_REUSEADDR
|
|
|
|
, (const char*)&so_reuse_bool, (int) sizeof(BOOL));
|
|
|
|
|
|
|
|
if (bind(fd4, (SOCKADDR*)&ipv4_port, sizeof(ipv4_port)) != 0)
|
|
|
|
{
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mreq4.imr_multiaddr = ipv4_dest.sin_addr;
|
|
|
|
mreq4.imr_interface = ipv4_port.sin_addr;
|
|
|
|
|
|
|
|
if (setsockopt(fd4, IPPROTO_IP, IP_ADD_MEMBERSHIP
|
|
|
|
, (const char*)&mreq4, (int) sizeof(mreq4)) != 0)
|
|
|
|
{
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
else if (setsockopt(fd4, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) != 0)
|
|
|
|
{
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret != 0 && fd4 != -1)
|
|
|
|
{
|
|
|
|
#ifdef USE_WINSOCK
|
|
|
|
closesocket(fd4);
|
|
|
|
#else
|
|
|
|
close(fd4);
|
|
|
|
#endif
|
|
|
|
fd4 = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fd4;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdns_open_ipv6_multicast()
|
|
|
|
{
|
|
|
|
getdns_return_t ret = 0;
|
|
|
|
SOCKET fd6 = -1;
|
|
|
|
SOCKADDR_IN6 ipv6_dest;
|
|
|
|
SOCKADDR_IN6 ipv6_port;
|
|
|
|
uint8_t so_reuse_bool = 1;
|
|
|
|
uint8_t ttl = 255;
|
|
|
|
IPV6_MREQ mreq6;
|
|
|
|
|
|
|
|
memset(&ipv6_dest, 0, sizeof(ipv6_dest));
|
|
|
|
memset(&ipv6_port, 0, sizeof(ipv6_dest));
|
|
|
|
ipv6_dest.sin6_family = AF_INET6;
|
|
|
|
ipv6_dest.sin6_port = htons(MDNS_MCAST_PORT);
|
|
|
|
ipv6_port.sin6_family = AF_INET6;
|
|
|
|
ipv6_port.sin6_port = htons(MDNS_MCAST_PORT);
|
|
|
|
memcpy(&ipv6_dest.sin6_addr
|
|
|
|
, mdns_mcast_ipv6, sizeof(mdns_mcast_ipv6));
|
|
|
|
|
|
|
|
|
|
|
|
fd6 = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
|
|
|
|
|
|
if (fd6 != -1)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* No need to test the output of the so_reuse call,
|
|
|
|
* since the only result that matters is that of bind.
|
|
|
|
*/
|
|
|
|
(void)setsockopt(fd6, SOL_SOCKET, SO_REUSEADDR
|
|
|
|
, (const char*)&so_reuse_bool, (int) sizeof(BOOL));
|
|
|
|
|
|
|
|
if (bind(fd6, (SOCKADDR*)&ipv6_port, sizeof(ipv6_port)) != 0)
|
|
|
|
{
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memcpy(&mreq6.ipv6mr_multiaddr
|
|
|
|
, &ipv6_dest.sin6_addr, sizeof(mreq6.ipv6mr_multiaddr));
|
|
|
|
memcpy(&mreq6.ipv6mr_interface
|
|
|
|
, &ipv6_port.sin6_addr, sizeof(mreq6.ipv6mr_interface));
|
|
|
|
|
|
|
|
if (setsockopt(fd6, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP
|
|
|
|
, (const char*)&mreq6, (int) sizeof(mreq6)) != 0)
|
|
|
|
{
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
else if (setsockopt(fd6, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) != 0)
|
|
|
|
{
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret != 0 && fd6 != -1)
|
|
|
|
{
|
|
|
|
#ifdef USE_WINSOCK
|
|
|
|
closesocket(fd6);
|
|
|
|
#else
|
|
|
|
close(fd6);
|
|
|
|
#endif
|
|
|
|
fd6 = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fd6;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Delayed opening of the MDNS sockets, and launch of the MDNS listeners
|
|
|
|
*/
|
|
|
|
static getdns_return_t mdns_delayed_network_init(struct getdns_context *context)
|
|
|
|
{
|
|
|
|
getdns_return_t ret = 0;
|
|
|
|
|
|
|
|
if (context->mdns_extended_support == 2)
|
|
|
|
{
|
|
|
|
context->mdns_fdv4 = mdns_open_ipv4_multicast();
|
|
|
|
context->mdns_fdv6 = mdns_open_ipv6_multicast();
|
|
|
|
|
|
|
|
if (context->mdns_fdv4 == -1 || context->mdns_fdv6 == -1)
|
|
|
|
{
|
|
|
|
if (context->mdns_fdv4 != -1)
|
|
|
|
#ifdef USE_WINSOCK
|
|
|
|
closesocket(context->mdns_fdv4);
|
|
|
|
#else
|
|
|
|
close(context->mdns_fdv4);
|
|
|
|
#endif
|
|
|
|
ret = GETDNS_RETURN_GENERIC_ERROR;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* TODO: launch the receive loops */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-01-20 21:44:05 -06:00
|
|
|
/*
|
|
|
|
* Initialize a continuous query from netreq
|
|
|
|
*/
|
2017-02-06 22:23:35 -06:00
|
|
|
static getdns_return_t mdns_initialize_continuous_request(getdns_network_req *netreq)
|
2017-01-20 21:44:05 -06:00
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
getdns_mdns_continuous_query temp_query, *continuous_query, *inserted_query;
|
|
|
|
getdns_dns_req *dnsreq = netreq->owner;
|
2017-02-06 22:23:35 -06:00
|
|
|
struct getdns_context *context = dnsreq->context;
|
2017-01-20 21:44:05 -06:00
|
|
|
/*
|
|
|
|
* Fill the target request, but only initialize name and request_type
|
|
|
|
*/
|
|
|
|
temp_query.request_class = dnsreq->request_class;
|
|
|
|
temp_query.request_type = netreq->request_type;
|
|
|
|
temp_query.name_len = dnsreq->name_len;
|
|
|
|
/* TODO: check that dnsreq is in canonical form */
|
|
|
|
memcpy(temp_query.name, dnsreq->name, dnsreq->name_len);
|
|
|
|
/*
|
|
|
|
* Check whether the continuous query is already in the RB tree.
|
|
|
|
* if there is not, create one.
|
|
|
|
* TODO: should lock the context object when doing that.
|
|
|
|
*/
|
|
|
|
continuous_query = (getdns_mdns_continuous_query *)
|
2017-02-06 22:23:35 -06:00
|
|
|
_getdns_rbtree_search(&context->mdns_continuous_queries_by_name_rrtype, &temp_query);
|
2017-01-20 21:44:05 -06:00
|
|
|
if (continuous_query == NULL)
|
|
|
|
{
|
|
|
|
continuous_query = (getdns_mdns_continuous_query *)
|
2017-02-06 22:23:35 -06:00
|
|
|
GETDNS_MALLOC(context->mf, getdns_mdns_continuous_query);
|
2017-01-20 21:44:05 -06:00
|
|
|
if (continuous_query != NULL)
|
|
|
|
{
|
2017-02-06 22:23:35 -06:00
|
|
|
continuous_query->node.parent = NULL;
|
|
|
|
continuous_query->node.left = NULL;
|
|
|
|
continuous_query->node.right = NULL;
|
|
|
|
continuous_query->node.key = (void*)continuous_query;
|
2017-01-20 21:44:05 -06:00
|
|
|
continuous_query->request_class = temp_query.request_class;
|
|
|
|
continuous_query->request_type = temp_query.request_type;
|
|
|
|
continuous_query->name_len = temp_query.name_len;
|
|
|
|
memcpy(continuous_query->name, temp_query.name, temp_query.name_len);
|
2017-02-06 22:23:35 -06:00
|
|
|
continuous_query->netreq_first = NULL;
|
2017-01-20 21:44:05 -06:00
|
|
|
/* Add the new continuous query to the context */
|
2017-02-06 22:23:35 -06:00
|
|
|
inserted_query = (getdns_mdns_continuous_query *)
|
|
|
|
_getdns_rbtree_insert(&context->mdns_continuous_queries_by_name_rrtype,
|
|
|
|
&continuous_query->node);
|
2017-01-20 21:44:05 -06:00
|
|
|
if (inserted_query == NULL)
|
|
|
|
{
|
|
|
|
/* Weird. This can only happen in a race condition */
|
2017-02-06 22:23:35 -06:00
|
|
|
GETDNS_FREE(context->mf, &continuous_query);
|
2017-01-20 21:44:05 -06:00
|
|
|
ret = GETDNS_RETURN_GENERIC_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = GETDNS_RETURN_MEMORY_ERROR;
|
|
|
|
}
|
|
|
|
}
|
2017-02-06 22:23:35 -06:00
|
|
|
/* insert netreq into query list */
|
|
|
|
netreq->mdns_netreq_next = continuous_query->netreq_first;
|
|
|
|
continuous_query->netreq_first = netreq;
|
|
|
|
|
2017-01-20 21:44:05 -06:00
|
|
|
/* to do: queue message request to socket */
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-02-06 22:23:35 -06:00
|
|
|
/*
|
|
|
|
* Initialize the MDNS part of the context structure.
|
|
|
|
*/
|
|
|
|
void _getdns_mdns_context_init(struct getdns_context *context)
|
|
|
|
{
|
|
|
|
|
|
|
|
context->mdns_extended_support = 2; /* 0 = no support, 1 = supported, 2 = initialization needed */
|
|
|
|
context->mdns_fdv4 = -1; /* invalid socket, i.e. not initialized */
|
|
|
|
context->mdns_fdv6 = -1; /* invalid socket, i.e. not initialized */
|
|
|
|
_getdns_rbtree_init(&context->mdns_continuous_queries_by_name_rrtype
|
|
|
|
, mdns_cmp_continuous_queries_by_name_rrtype);
|
|
|
|
_getdns_rbtree_init(&context->mdns_known_records_by_value
|
|
|
|
, mdns_cmp_known_records);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Delete all the data allocated for MDNS in a context
|
|
|
|
*/
|
|
|
|
void _getdns_mdns_context_destroy(struct getdns_context *context)
|
|
|
|
{
|
|
|
|
/* Close the sockets */
|
|
|
|
|
|
|
|
/* Clear all the continuous queries */
|
|
|
|
|
|
|
|
/* Clear all the cached records */
|
|
|
|
}
|
|
|
|
|
2016-12-22 17:51:47 -06:00
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2016-12-22 22:30:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* HAVE_MDNS_SUPPORT */
|