osd-contiki/core/net/resolv.c
Robert Quattlebaum f145c17039 core/net/resolv: IPv6 and mDNS ("Bonjour") support. Major refactor.
This patch updates the DNS resolver to support IPv6 and introduces an
improved API for looking up DNS entries. This patch also adds optional
support for mDNS lookups and responses to the DNS resolver.

Here is a quick summary of the changes:

 * Added support for IPv6 lookups.
 * DNS queries now honor record expiration.
 * Added support for mDNS, compatible with "Bonjour".
 * Implemented a new lookup api, `resolv_lookup2()`, which provides
   more information about the state of the record(error, expired,
   looking-up, etc.).

About mDNS/Bonjour Support
--------------------------

This patch adds basic support for mDNS/Bonjour, which allows you to
refer to the name of a device instead of its IP address. This is
incredibly convenient for IPv6 addresses because they tend to be very
long and difficult to remember. It is especially important for
link-local IPv6 addresses, since not all programs support the '%'
notation for indicating a network interface (required on systems with
more than one network interface to disambiguate).

In other words, instead of typing in this:

 * `http://[fe80::58dc:d7ed:a644:628f%en1]/`

You can type this instead:

 * `http://contiki.local/`

Huge improvement, no?

The convenience extends beyond that: this mechanism can be used for
nodes to talk to each other based on their human-readable names instead
of their IPv6 addresses. So instead of a switch on
`aaaa::58dc:d7ed:a644:628f` triggering an actuator on
`aaaa::ed26:19c1:4bd2:f95b`, `light-switch.local` can trigger the
actuator on `living-room-lights.local`.

What you need to do to be able to look up `.local` names on your
workstation depends on a few factors:

 * Your machine needs to be able to send and receive multicast packets
   to and from the LoWPAN. You can do this easily with the Jackdaw
   firmware on an RZUSBStick. If you have a border router, you will need
   it to bridge the mDNS multicast packets across the border.

 * If you are using a Mac, you win. All Apple devices support mDNS
   lookups.

 * If you are using Windows, you can install Apple's Bonjour for Windows
   package. (This may be already installed on your machine if you have
   installed iTunes) After you install this you can easily do `.local`
   lookups.

 * If you are using a Unix machine, you can install Avahi.

The default hostname is set to `contiki.local.`. You can change the
hostname programmatically by calling `resolv_set_hostname()`. You can
change the default hostname by changing `CONTIKI_CONF_DEFAULT_HOSTNAME`.

You may disable mDNS support by setting `RESOLV_CONF_SUPPORTS_MDNS` to
`0`.

---------------------------------

core/net/resolv: `resolv_lookup2()` -> `resolv_lookup()`

Note that this patch should fix several `resolv_lookup()` bugs
that already existed. There were many cases where `resolv_lookup()`
was being called and the IP address ignored, but later code
assumed that the IP address had been fetched... ANYWAY, those
should be fixed now.

---------------------------------

examples/udp-ipv6: Updated client to use MDNS to lookup the server.

Also updated the Cooja regression test simulation.
2013-03-10 11:40:08 -07:00

1516 lines
40 KiB
C

/**
* \addtogroup uip
* @{
*/
/**
* \defgroup uipdns uIP hostname resolver functions
* @{
*
* The uIP DNS resolver functions are used to lookup a hostname and
* map it to a numerical IP address. It maintains a list of resolved
* hostnames that can be queried with the resolv_lookup()
* function. New hostnames can be resolved using the resolv_query()
* function.
*
* The event resolv_event_found is posted when a hostname has been
* resolved. It is up to the receiving process to determine if the
* correct hostname has been found by calling the resolv_lookup()
* function with the hostname.
*/
/**
* \file
* DNS host name to IP address resolver.
* \author Adam Dunkels <adam@dunkels.com>
* \author Robert Quattlebaum <darco@deepdarc.com>
*
* This file implements a DNS host name to IP address resolver,
* as well as an MDNS responder and resolver.
*/
/*
* Copyright (c) 2002-2003, Adam Dunkels.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This file is part of the uIP TCP/IP stack.
*
*
*/
#include "net/tcpip.h"
#include "net/resolv.h"
#include "net/uip-udp-packet.h"
#include "lib/random.h"
#ifndef DEBUG
#define DEBUG defined(CONTIKI_TARGET_COOJA)
#endif
#if UIP_UDP
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>
#ifndef NULL
#define NULL (void *)0
#endif /* NULL */
#if !defined(__SDCC) && defined(SDCC_REVISION)
#define __SDCC 1
#endif
#if VERBOSE_DEBUG
#define DEBUG_PRINTF(...) printf(__VA_ARGS__)
#else
#define DEBUG_PRINTF(...) do { } while(0)
#endif
#if DEBUG || VERBOSE_DEBUG
#define PRINTF(...) printf(__VA_ARGS__)
#else
#define PRINTF(...) do { } while(0)
#endif
#ifdef __SDCC
static int
strncasecmp(const char *s1, const char *s2, size_t n)
{
/* TODO: Add case support! */
return strncmp(s1, s2, n);
}
static int
strcasecmp(const char *s1, const char *s2)
{
/* TODO: Add case support! */
return strcmp(s1, s2);
}
#endif
#define UIP_UDP_BUF ((struct uip_udpip_hdr *)&uip_buf[UIP_LLH_LEN])
/* If RESOLV_CONF_SUPPORTS_MDNS is set, then queries
* for domain names in the local TLD will use mDNS as
* described by draft-cheshire-dnsext-multicastdns.
*/
#ifndef RESOLV_CONF_SUPPORTS_MDNS
#define RESOLV_CONF_SUPPORTS_MDNS (1)
#endif
#ifndef RESOLV_CONF_MDNS_INCLUDE_GLOBAL_V6_ADDRS
#define RESOLV_CONF_MDNS_INCLUDE_GLOBAL_V6_ADDRS (0)
#endif
/** The maximum number of retries when asking for a name. */
#ifndef RESOLV_CONF_MAX_RETRIES
#define RESOLV_CONF_MAX_RETRIES (4)
#endif
#ifndef RESOLV_CONF_MAX_MDNS_RETRIES
#define RESOLV_CONF_MAX_MDNS_RETRIES (3)
#endif
#ifndef RESOLV_CONF_MAX_DOMAIN_NAME_SIZE
#define RESOLV_CONF_MAX_DOMAIN_NAME_SIZE (32)
#endif
#if !defined(CONTIKI_TARGET_NAME) && defined(BOARD)
#define stringy2(x) #x
#define stringy(x) stringy2(x)
#define CONTIKI_TARGET_NAME stringy(BOARD)
#endif
#ifndef CONTIKI_CONF_DEFAULT_HOSTNAME
#ifdef CONTIKI_TARGET_NAME
#define CONTIKI_CONF_DEFAULT_HOSTNAME "contiki-"CONTIKI_TARGET_NAME
#else
#define CONTIKI_CONF_DEFAULT_HOSTNAME "contiki"
#endif
#endif
#define DNS_TYPE_A (1)
#define DNS_TYPE_CNAME (5)
#define DNS_TYPE_PTR (12)
#define DNS_TYPE_MX (15)
#define DNS_TYPE_TXT (16)
#define DNS_TYPE_AAAA (28)
#define DNS_TYPE_SRV (33)
#define DNS_TYPE_ANY (255)
#define DNS_TYPE_NSEC (47)
#define DNS_CLASS_IN (1)
#define DNS_CLASS_ANY (255)
#ifndef DNS_PORT
#define DNS_PORT (53)
#endif
#ifndef MDNS_PORT
#define MDNS_PORT (5353)
#endif
#ifndef MDNS_RESPONDER_PORT
#define MDNS_RESPONDER_PORT (5354)
#endif
/** \internal The DNS message header. */
struct dns_hdr {
uint16_t id;
uint8_t flags1, flags2;
#define DNS_FLAG1_RESPONSE 0x80
#define DNS_FLAG1_OPCODE_STATUS 0x10
#define DNS_FLAG1_OPCODE_INVERSE 0x08
#define DNS_FLAG1_OPCODE_STANDARD 0x00
#define DNS_FLAG1_AUTHORATIVE 0x04
#define DNS_FLAG1_TRUNC 0x02
#define DNS_FLAG1_RD 0x01
#define DNS_FLAG2_RA 0x80
#define DNS_FLAG2_ERR_MASK 0x0f
#define DNS_FLAG2_ERR_NONE 0x00
#define DNS_FLAG2_ERR_NAME 0x03
uint16_t numquestions;
uint16_t numanswers;
uint16_t numauthrr;
uint16_t numextrarr;
};
#define RESOLV_ENCODE_INDEX(i) (uip_htons(i+1))
#define RESOLV_DECODE_INDEX(i) (unsigned char)(uip_ntohs(i-1))
/** These default values for the DNS server are Google's public DNS:
* <https://developers.google.com/speed/public-dns/docs/using>
*/
#if UIP_CONF_IPV6
static uip_ipaddr_t resolv_default_dns_server = {
.u8 = {
0x20, 0x01, 0x48, 0x60,
0x48, 0x60, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x88, 0x88,
}
};
#else
static uip_ipaddr_t resolv_default_dns_server = {.u8 = {8, 8, 8, 8} };
#endif
/** \internal The DNS answer message structure. */
struct dns_answer {
/* DNS answer record starts with either a domain name or a pointer
* to a name already present somewhere in the packet. */
uint16_t type;
uint16_t class;
uint16_t ttl[2];
uint16_t len;
#if UIP_CONF_IPV6
uint8_t ipaddr[16];
#else
uint8_t ipaddr[4];
#endif
};
struct namemap {
#define STATE_UNUSED 0
#define STATE_ERROR 1
#define STATE_NEW 2
#define STATE_ASKING 3
#define STATE_DONE 4
uint8_t state;
uint8_t tmr;
uint8_t retries;
uint8_t seqno;
unsigned long expiration;
uint8_t err;
#if RESOLV_CONF_SUPPORTS_MDNS
uint8_t is_mdns:1, is_probe:1;
#endif
uip_ipaddr_t ipaddr;
char name[RESOLV_CONF_MAX_DOMAIN_NAME_SIZE + 1];
};
#ifndef UIP_CONF_RESOLV_ENTRIES
#define RESOLV_ENTRIES 4
#else /* UIP_CONF_RESOLV_ENTRIES */
#define RESOLV_ENTRIES UIP_CONF_RESOLV_ENTRIES
#endif /* UIP_CONF_RESOLV_ENTRIES */
static struct namemap names[RESOLV_ENTRIES];
static uint8_t seqno;
static struct uip_udp_conn *resolv_conn = NULL;
static struct etimer retry;
process_event_t resolv_event_found;
PROCESS(resolv_process, "DNS resolver");
static void resolv_found(char *name, uip_ipaddr_t * ipaddr);
enum {
EVENT_NEW_SERVER = 0
};
/*---------------------------------------------------------------------------*/
#if RESOLV_CONF_SUPPORTS_MDNS
/** \internal The DNS question message structure. */
struct dns_question {
uint16_t type;
uint16_t class;
};
static char resolv_hostname[RESOLV_CONF_MAX_DOMAIN_NAME_SIZE + 1];
enum {
MDNS_STATE_WAIT_BEFORE_PROBE,
MDNS_STATE_PROBING,
MDNS_STATE_READY,
};
static uint8_t mdns_state;
#if UIP_CONF_IPV6
#include "net/uip-ds6.h"
static const uip_ipaddr_t resolv_mdns_addr = {
.u8 = {
0xff, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xfb,
}
};
#else /* UIP_CONF_IPV6 */
static const uip_ipaddr_t resolv_mdns_addr = {
.u8 = {224, 0, 0, 251}
};
#endif /* !UIP_CONF_IPV6 */
static bool mdns_needs_host_announce;
PROCESS(mdns_probe_process, "mDNS probe");
#endif /* RESOLV_CONF_SUPPORTS_MDNS */
/*---------------------------------------------------------------------------*/
/** \internal
* \brief Decodes a DNS name from the DNS format into the given string.
* \return True upon success, False if the size of the name would be too large.
*
* \note `dest` must point to a buffer with at least
* `RESOLV_CONF_MAX_DOMAIN_NAME_SIZE+1` bytes large.
*/
static bool
decode_name(const unsigned char *query, char *dest,
const unsigned char *packet)
{
int len = RESOLV_CONF_MAX_DOMAIN_NAME_SIZE;
unsigned char n = *query++;
//DEBUG_PRINTF("resolver: decoding name: \"");
while(len && n) {
if(n & 0xc0) {
const uint16_t offset = query[0] + ((n & ~0xC0) << 8);
//DEBUG_PRINTF("<skip-to-%d>",offset);
query = packet + offset;
n = *query++;
}
if(!n)
break;
for(; n; n--) {
//DEBUG_PRINTF("%c",*query);
*dest++ = *query++;
if(!--len) {
*dest = 0;
return false;
}
}
n = *query++;
if(n) {
//DEBUG_PRINTF(".");
*dest++ = '.';
len--;
}
}
//DEBUG_PRINTF("\"\n");
*dest = 0;
return len != 0;
}
/*---------------------------------------------------------------------------*/
/** \internal
*/
static bool
dns_name_isequal(const unsigned char *queryptr, const char *name,
const unsigned char *packet)
{
unsigned char n = *queryptr++;
if(*name == 0)
return false;
while(n) {
if(n & 0xc0) {
queryptr = packet + queryptr[0] + ((n & ~0xC0) << 8);
n = *queryptr++;
}
for(; n; n--) {
if(!*name) {
return false;
}
if(tolower(*name++) != tolower(*queryptr++)) {
return false;
}
}
n = *queryptr++;
if((n != 0) && (*name++ != '.')) {
return false;
}
}
if(*name == '.')
name++;
return name[0] == 0;
}
/*---------------------------------------------------------------------------*/
/** \internal
*/
static unsigned char *
skip_name(unsigned char *query)
{
unsigned char n;
DEBUG_PRINTF("resolver: skip name: ");
do {
n = *query;
if(n & 0xc0) {
DEBUG_PRINTF("<skip-to-%d>", query[0] + ((n & ~0xC0) << 8));
query++;
break;
}
query++;
while(n > 0) {
DEBUG_PRINTF("%c", *query);
++query;
--n;
};
DEBUG_PRINTF(".");
} while(*query != 0);
DEBUG_PRINTF("\n");
return query + 1;
}
/*---------------------------------------------------------------------------*/
/** \internal
*/
static unsigned char *
encode_name(unsigned char *query, const char *nameptr)
{
char *nptr;
--nameptr;
/* Convert hostname into suitable query format. */
do {
uint8_t n = 0;
++nameptr;
nptr = (char *)query;
++query;
for(n = 0; *nameptr != '.' && *nameptr != 0; ++nameptr) {
*query = *nameptr;
++query;
++n;
}
*nptr = n;
} while(*nameptr != 0);
/* End the the name. */
*query++ = 0;
return query;
}
#if RESOLV_CONF_SUPPORTS_MDNS
/*---------------------------------------------------------------------------*/
/** \internal
*/
static void
mdns_announce_requested(void)
{
mdns_needs_host_announce = true;
}
/*---------------------------------------------------------------------------*/
/** \internal
*/
static void
start_name_collision_check(clock_time_t after)
{
process_exit(&mdns_probe_process);
process_start(&mdns_probe_process, (void *)&after);
}
/*---------------------------------------------------------------------------*/
/** \internal
*/
static unsigned char *
mdns_write_announce_records(unsigned char *queryptr, uint8_t * count)
{
struct dns_answer *ans;
#if UIP_CONF_IPV6
uint8_t i;
for(i = 0; i < UIP_DS6_ADDR_NB; i++) {
if(uip_ds6_if.addr_list[i].isused
#if !RESOLV_CONF_MDNS_INCLUDE_GLOBAL_V6_ADDRS
&& uip_is_addr_link_local(&uip_ds6_if.addr_list[i].ipaddr)
#endif
) {
if(!*count) {
queryptr = encode_name(queryptr, resolv_hostname);
} else {
*queryptr++ = 0xc0;
*queryptr++ = sizeof(struct dns_hdr);
}
ans = (struct dns_answer *)queryptr;
*queryptr++ = (uint8_t) ((DNS_TYPE_AAAA) >> 8);
*queryptr++ = (uint8_t) ((DNS_TYPE_AAAA));
*queryptr++ = (uint8_t) ((DNS_CLASS_IN | 0x8000) >> 8);
*queryptr++ = (uint8_t) ((DNS_CLASS_IN | 0x8000));
*queryptr++ = 0;
*queryptr++ = 0;
*queryptr++ = 0;
*queryptr++ = 120;
*queryptr++ = 0;
*queryptr++ = sizeof(uip_ipaddr_t);
uip_ipaddr_copy((uip_ipaddr_t *) queryptr,
&uip_ds6_if.addr_list[i].ipaddr);
queryptr += sizeof(uip_ipaddr_t);
(*count)++;
}
}
#else
queryptr = encode_name(queryptr, resolv_hostname);
ans = (struct dns_answer *)queryptr;
ans->type = UIP_HTONS(DNS_TYPE_A);
ans->class = UIP_HTONS(DNS_CLASS_IN | 0x8000);
ans->ttl[0] = 0;
ans->ttl[1] = UIP_HTONS(120);
ans->len = UIP_HTONS(sizeof(uip_ipaddr_t));
uip_gethostaddr((uip_ipaddr_t *) ans->ipaddr);
queryptr = (unsigned char *)ans + sizeof(*ans);
(*count)++;
#endif
return queryptr;
}
/*---------------------------------------------------------------------------*/
/** \internal
* Called when we need to announce ourselves
*/
static size_t
mdns_prep_host_announce_packet(void)
{
unsigned char *queryptr;
struct dns_answer *ans;
struct dns_hdr *hdr;
uint8_t total_answers = 0;
hdr = (struct dns_hdr *)uip_appdata;
hdr->flags1 |= DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE;
hdr->numquestions = UIP_HTONS(0);
hdr->numauthrr = 0;
hdr->numextrarr = 0;
queryptr = (unsigned char *)hdr + sizeof(*hdr);
queryptr = mdns_write_announce_records(queryptr, &total_answers);
/* We now need to add an NSEC record to indicate
* that this is all there is.
*/
if(!total_answers) {
queryptr = encode_name(queryptr, resolv_hostname);
} else {
*queryptr++ = 0xc0;
*queryptr++ = sizeof(*hdr);
}
ans = (struct dns_answer *)queryptr;
ans->type = UIP_HTONS(DNS_TYPE_NSEC);
ans->class = UIP_HTONS(DNS_CLASS_IN | 0x8000);
ans->ttl[0] = 0;
ans->ttl[1] = UIP_HTONS(120);
ans->len = UIP_HTONS(8);
queryptr += 10;
*queryptr++ = 0xc0;
*queryptr++ = sizeof(*hdr);
*queryptr++ = 0x00;
*queryptr++ = 0x04;
#if UIP_CONF_IPV6
*queryptr++ = 0x00;
*queryptr++ = 0x00;
*queryptr++ = 0x00;
*queryptr++ = 0x08;
#else
*queryptr++ = 0x40;
*queryptr++ = 0x00;
*queryptr++ = 0x00;
*queryptr++ = 0x00;
#endif
hdr->numanswers = uip_htons(total_answers);
hdr->numextrarr = UIP_HTONS(1);
return (queryptr - (unsigned char *)uip_appdata);
}
#endif // #if RESOLV_CONF_SUPPORTS_MDNS
/*---------------------------------------------------------------------------*/
/** \internal
* Runs through the list of names to see if there are any that have
* not yet been queried and, if so, sends out a query.
*/
static void
check_entries(void)
{
volatile uint8_t i;
uint8_t *query;
register struct dns_hdr *hdr;
register struct namemap *namemapptr;
for(i = 0; i < RESOLV_ENTRIES; ++i) {
namemapptr = &names[i];
if(namemapptr->state == STATE_NEW || namemapptr->state == STATE_ASKING) {
etimer_set(&retry, CLOCK_SECOND / 4);
if(namemapptr->state == STATE_ASKING) {
if(--namemapptr->tmr == 0) {
#if RESOLV_CONF_SUPPORTS_MDNS
if(++namemapptr->retries ==
(namemapptr->is_mdns ? RESOLV_CONF_MAX_MDNS_RETRIES :
RESOLV_CONF_MAX_RETRIES))
#else
if(++namemapptr->retries == RESOLV_CONF_MAX_RETRIES)
#endif
{
/* STATE_ERROR basically means "not found". */
namemapptr->state = STATE_ERROR;
/* Keep the "not found" error valid for 30 seconds */
namemapptr->expiration = clock_seconds() + 30;
resolv_found(namemapptr->name, NULL);
continue;
}
namemapptr->tmr = namemapptr->retries * namemapptr->retries * 3;
#if RESOLV_CONF_SUPPORTS_MDNS
if(namemapptr->is_probe) {
/* Probing retries are much more aggressive, 250ms */
namemapptr->tmr = 2;
}
#endif
} else {
/* Its timer has not run out, so we move on to next
* entry.
*/
continue;
}
} else {
namemapptr->state = STATE_ASKING;
namemapptr->tmr = 1;
namemapptr->retries = 0;
}
hdr = (struct dns_hdr *)uip_appdata;
memset(hdr, 0, sizeof(struct dns_hdr));
hdr->id = RESOLV_ENCODE_INDEX(i);
#if RESOLV_CONF_SUPPORTS_MDNS
if(!namemapptr->is_mdns || namemapptr->is_probe) {
hdr->flags1 = DNS_FLAG1_RD;
}
if(namemapptr->is_mdns) {
hdr->id = 0;
}
#else
hdr->flags1 = DNS_FLAG1_RD;
#endif
hdr->numquestions = UIP_HTONS(1);
query = uip_appdata + sizeof(*hdr);
query = encode_name(query, namemapptr->name);
#if RESOLV_CONF_SUPPORTS_MDNS
if(namemapptr->is_probe) {
*query++ = (uint8_t) ((DNS_TYPE_ANY) >> 8);
*query++ = (uint8_t) ((DNS_TYPE_ANY));
} else
#endif
{
#if UIP_CONF_IPV6
*query++ = (uint8_t) ((DNS_TYPE_AAAA) >> 8);
*query++ = (uint8_t) ((DNS_TYPE_AAAA));
#else
*query++ = (uint8_t) ((DNS_TYPE_A) >> 8);
*query++ = (uint8_t) ((DNS_TYPE_A));
#endif
}
*query++ = (uint8_t) ((DNS_CLASS_IN) >> 8);
*query++ = (uint8_t) ((DNS_CLASS_IN));
#if RESOLV_CONF_SUPPORTS_MDNS
if(namemapptr->is_mdns) {
#if RESOLV_CONF_SUPPORTS_MDNS
if(namemapptr->is_probe) {
/* This is our conflict detection request.
* In order to be in compliance with the MDNS
* spec, we need to add the records we are proposing
* to the rrauth section.
*/
uint8_t count = 0;
query = mdns_write_announce_records(query, &count);
hdr->numauthrr = UIP_HTONS(count);
}
#endif
uip_udp_packet_sendto(resolv_conn,
uip_appdata,
(query - (uint8_t *) uip_appdata),
&resolv_mdns_addr, UIP_HTONS(MDNS_PORT)
);
PRINTF("resolver: (i=%d) Sent MDNS request for \"%s\".\n", i,
namemapptr->name);
} else {
uip_udp_packet_sendto(resolv_conn,
uip_appdata,
(query - (uint8_t *) uip_appdata),
&resolv_default_dns_server, UIP_HTONS(DNS_PORT)
);
PRINTF("resolver: (i=%d) Sent DNS request for \"%s\".\n", i,
namemapptr->name);
}
#else
uip_udp_packet_sendto(resolv_conn,
uip_appdata,
(query - (uint8_t *) uip_appdata),
&resolv_default_dns_server, UIP_HTONS(DNS_PORT)
);
PRINTF("resolver: (i=%d) Sent DNS request for \"%s\".\n", i,
namemapptr->name);
#endif
break;
}
}
}
/*---------------------------------------------------------------------------*/
/** \internal
* Called when new UDP data arrives.
*/
static void
newdata(void)
{
struct dns_answer *ans;
struct dns_hdr const *hdr = (struct dns_hdr *)uip_appdata;
unsigned char *queryptr = (unsigned char *)hdr + sizeof(*hdr);
register struct namemap *namemapptr;
static uint8_t nquestions, nanswers, nauthrr = 0;
static int8_t i;
/* We only care about the question(s) and the answers. The authrr
* and the extrarr are simply discarded.
*/
nquestions = (uint8_t) uip_ntohs(hdr->numquestions);
nanswers = (uint8_t) uip_ntohs(hdr->numanswers);
DEBUG_PRINTF
("resolver: flags1=0x%02X flags2=0x%02X nquestions=%d, nanswers=%d, nauthrr=%d, nextrarr=%d\n",
hdr->flags1, hdr->flags2, (uint8_t) nquestions, (uint8_t) nanswers,
(uint8_t) uip_ntohs(hdr->numauthrr),
(uint8_t) uip_ntohs(hdr->numextrarr));
/** QUESTION HANDLING SECTION ************************************************/
if(((hdr->flags1 & ~1) == 0) && (hdr->flags2 == 0)) {
/* This is an DNS request! */
#if RESOLV_CONF_SUPPORTS_MDNS
/* Skip requests with no questions. */
if(!nquestions) {
return;
}
queryptr = (unsigned char *)hdr + sizeof(*hdr);
i = 0;
for(; nquestions > 0;
queryptr = skip_name(queryptr) + sizeof(struct dns_question),
nquestions--) {
struct dns_question *question =
(struct dns_question *)skip_name(queryptr);
#if __ARM__
static struct dns_question aligned;
memcpy(&aligned, question, sizeof(aligned));
question = &aligned;
#endif
DEBUG_PRINTF("resolver: Question %d: type=%d class=%d\n", ++i,
uip_htons(question->type), uip_htons(question->class));
if(((uip_ntohs(question->class) & 0x7FFF) != DNS_CLASS_IN)
|| ((question->type != UIP_HTONS(DNS_TYPE_ANY))
&& (question->type != UIP_HTONS(DNS_TYPE_AAAA))
&& (question->type != UIP_HTONS(DNS_TYPE_A))
)) {
/* Skip unrecognised records. */
continue;
}
if(!dns_name_isequal(queryptr, resolv_hostname, uip_appdata)) {
continue;
}
PRINTF("resolver: THIS IS A REQUEST FOR US!!!\n");
if(mdns_state == MDNS_STATE_READY) {
/* We only send immediately if this isn't an MDNS request.
* Otherwise, we schedule ourselves to send later.
*/
if(UIP_UDP_BUF->srcport == UIP_HTONS(MDNS_PORT)) {
mdns_announce_requested();
} else {
uip_udp_packet_sendto(resolv_conn,
uip_appdata,
mdns_prep_host_announce_packet(),
&UIP_UDP_BUF->srcipaddr,
UIP_UDP_BUF->srcport);
}
return;
} else {
PRINTF("resolver: But we are still probing. Waiting...\n");
/* We are still probing. We need to do the mDNS
* probe race condition check here and make sure
* we don't need to delay probing for a second.
*/
nauthrr = (uint8_t) uip_ntohs(hdr->numauthrr);
/* For now, we will always restart the collision check if
* there are *any* authority records present.
* In the future we should follow the spec more closely,
* but this should eventually converge to something reasonable.
*/
if(nauthrr) {
start_name_collision_check(CLOCK_SECOND * 1.5);
}
}
}
#endif
}
/** ANSWER HANDLING SECTION **************************************************/
if(!nanswers) {
DEBUG_PRINTF("resolver: Skipping request/response with no answers.\n");
/* We demand answers! */
return;
}
#if RESOLV_CONF_SUPPORTS_MDNS
if(UIP_UDP_BUF->srcport == UIP_HTONS(MDNS_PORT)
&& hdr->id == 0) {
/* OK, this was from MDNS. Things get a little weird here,
* because we can't use the `id` field. We will look up the
* appropriate request in a later step. */
i = -1;
namemapptr = NULL;
} else
#endif // RESOLV_CONF_SUPPORTS_MDNS
{
/* The ID in the DNS header should be our entry into the name table. */
i = RESOLV_DECODE_INDEX(hdr->id);
namemapptr = &names[i];
if(i >= RESOLV_ENTRIES || i < 0 || namemapptr->state != STATE_ASKING) {
PRINTF("resolver: Bad ID (%04X) on incoming DNS response\n",
uip_ntohs(hdr->id));
return;
}
PRINTF("resolver: Incoming response for \"%s\".\n", namemapptr->name);
namemapptr->state = STATE_ERROR; /* We'll change this to DONE when we find the record. */
namemapptr->err = hdr->flags2 & DNS_FLAG2_ERR_MASK;
/* If we remain in the error state, keep it cached for 30 seconds. */
namemapptr->expiration = clock_seconds() + 30;
/* Check for error. If so, call callback to inform. */
if(namemapptr->err != 0) {
namemapptr->state = STATE_ERROR;
resolv_found(namemapptr->name, NULL);
return;
}
}
/* Discard all remaining questions */
for(; nquestions > 0; queryptr += 4, nquestions--) {
if(namemapptr
&& 0 != dns_name_isequal(queryptr, namemapptr->name, uip_appdata)) {
DEBUG_PRINTF("resolver: Question name doesn't look familiar...!\n");
return;
}
queryptr = skip_name(queryptr);
}
/* Answer parsing loop */
while(nanswers > 0) {
ans = (struct dns_answer *)skip_name(queryptr);
#if __ARM__
static struct dns_answer aligned;
memcpy(&aligned, ans, sizeof(aligned));
ans = &aligned;
#endif
#if VERBOSE_DEBUG
static char debug_name[40];
decode_name(queryptr, debug_name, uip_appdata);
DEBUG_PRINTF("resolver: \"%s\", type %d, class %d, ttl %d, length %d\n",
debug_name, uip_ntohs(ans->type),
uip_ntohs(ans->class) & 0x7FFF,
(int)((uint32_t) uip_ntohs(ans->ttl[0]) << 16) | (uint32_t)
uip_ntohs(ans->ttl[1]), uip_ntohs(ans->len)
);
#endif
/* Check the class and length of the answer to make sure
* it matches what we are expecting
*/
if(((uip_ntohs(ans->class) & 0x7FFF) != DNS_CLASS_IN)
|| (ans->len != UIP_HTONS(sizeof(uip_ipaddr_t)))
) {
goto skip_to_next_answer;
}
#if UIP_CONF_IPV6
if(ans->type != UIP_HTONS(DNS_TYPE_AAAA)) {
goto skip_to_next_answer;
}
#else // UIP_CONF_IPV6
if(ans->type != UIP_HTONS(DNS_TYPE_A)) {
goto skip_to_next_answer;
}
#endif
#if RESOLV_CONF_SUPPORTS_MDNS
if(UIP_UDP_BUF->srcport == UIP_HTONS(MDNS_PORT)
&& hdr->id == 0) {
int8_t available_i = RESOLV_ENTRIES;
DEBUG_PRINTF("resolver: MDNS query.\n");
/* For MDNS, we need to actually look up the name we
* are looking for.
*/
for(i = 0; i < RESOLV_ENTRIES; ++i) {
namemapptr = &names[i];
if(dns_name_isequal(queryptr, namemapptr->name, uip_appdata)) {
break;
}
if((namemapptr->state == STATE_UNUSED)
|| (namemapptr->state == STATE_DONE
&& clock_seconds() > namemapptr->expiration)
) {
available_i = i;
}
}
if(i == RESOLV_ENTRIES) {
DEBUG_PRINTF("resolver: Unsolicited MDNS response.\n");
i = available_i;
namemapptr = &names[i];
if(!decode_name(queryptr, namemapptr->name, uip_appdata)) {
DEBUG_PRINTF("resolver: MDNS name too big to cache.\n");
namemapptr = NULL;
goto skip_to_next_answer;
}
}
if(i == RESOLV_ENTRIES) {
DEBUG_PRINTF
("resolver: Not enough room to keep track of unsolicited MDNS answer.\n");
if(dns_name_isequal(queryptr, resolv_hostname, uip_appdata)) {
/* Oh snap, they say they are us! We had better report them... */
resolv_found(resolv_hostname, (uip_ipaddr_t *) ans->ipaddr);
}
namemapptr = NULL;
goto skip_to_next_answer;
}
namemapptr = &names[i];
} else
#endif // #if RESOLV_CONF_SUPPORTS_MDNS
{
/* This will force us to stop even if there are more answers. */
nanswers = 1;
}
if(namemapptr
&& !dns_name_isequal(queryptr, namemapptr->name, uip_appdata)) {
DEBUG_PRINTF("resolver: Answer name doesn't match question...!\n");
goto skip_to_next_answer;
}
DEBUG_PRINTF("resolver: Answer for \"%s\" is usable.\n",
namemapptr->name);
namemapptr->state = STATE_DONE;
namemapptr->expiration = ans->ttl[1] + (ans->ttl[0] << 8);
namemapptr->expiration += clock_seconds();
uip_ipaddr_copy(&namemapptr->ipaddr, (uip_ipaddr_t *) ans->ipaddr);
resolv_found(namemapptr->name, &namemapptr->ipaddr);
skip_to_next_answer:
queryptr =
(unsigned char *)skip_name(queryptr) + 10 + uip_htons(ans->len);
--nanswers;
}
}
#if RESOLV_CONF_SUPPORTS_MDNS
/*---------------------------------------------------------------------------*/
/**
* \brief Changes the local hostname advertised by MDNS.
* \param hostname The new hostname to advertise.
*/
void
resolv_set_hostname(const char *hostname)
{
strncpy(resolv_hostname, hostname, RESOLV_CONF_MAX_DOMAIN_NAME_SIZE);
/* Add the .local suffix if it isn't already there */
if(strlen(resolv_hostname) < 7
|| strcasecmp(resolv_hostname + strlen(resolv_hostname) - 6,
".local") != 0) {
strncat(resolv_hostname, ".local", RESOLV_CONF_MAX_DOMAIN_NAME_SIZE);
}
PRINTF("resolver: hostname changed to \"%s\"\n", resolv_hostname);
start_name_collision_check(0);
}
/*---------------------------------------------------------------------------*/
/**
* \brief Returns the local hostname being advertised via MDNS.
* \return C-string containing the local hostname.
*/
const char *
resolv_get_hostname(void)
{
return resolv_hostname;
}
/*---------------------------------------------------------------------------*/
/** \internal
* Process for probing for name conflicts.
*/
PROCESS_THREAD(mdns_probe_process, ev, data)
{
static struct etimer delay;
PROCESS_BEGIN();
mdns_state = MDNS_STATE_WAIT_BEFORE_PROBE;
PRINTF("mdns-probe: Process (re)started.\n");
/* Wait extra time if specified in data */
if(NULL != data) {
PRINTF("mdns-probe: Probing will begin in %ld clocks.\n",
(long)*(clock_time_t *) data);
etimer_set(&delay, *(clock_time_t *) data);
PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER);
}
/* We need to wait a random (0-250ms) period of time before
* probing to be in compliance with the MDNS spec. */
etimer_set(&delay, CLOCK_SECOND * (random_rand() & 0xFF) / 1024);
PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER);
/* Begin searching for our name. */
mdns_state = MDNS_STATE_PROBING;
resolv_query(resolv_hostname);
do {
PROCESS_WAIT_EVENT_UNTIL(ev == resolv_event_found);
} while(strcasecmp(resolv_hostname, data) != 0);
mdns_state = MDNS_STATE_READY;
mdns_announce_requested();
PRINTF("mdns-probe: Finished probing.\n");
PROCESS_END();
}
#endif // RESOLV_CONF_SUPPORTS_MDNS
/*---------------------------------------------------------------------------*/
/** \internal
* The main UDP function.
*/
PROCESS_THREAD(resolv_process, ev, data)
{
PROCESS_BEGIN();
memset(names, 0, sizeof(names));
resolv_event_found = process_alloc_event();
PRINTF("resolver: Process started.\n");
resolv_conn = udp_new(NULL, 0, NULL);
resolv_conn->rport = 0;
#if RESOLV_CONF_SUPPORTS_MDNS
PRINTF("resolver: Supports MDNS name resolution.\n");
#endif
#if RESOLV_CONF_SUPPORTS_MDNS
PRINTF("resolver: Supports MDNS responder.\n");
uip_udp_bind(resolv_conn, UIP_HTONS(MDNS_PORT));
#if UIP_CONF_IPV6
uip_ds6_maddr_add(&resolv_mdns_addr);
#endif
resolv_set_hostname(CONTIKI_CONF_DEFAULT_HOSTNAME);
#endif /* RESOLV_CONF_SUPPORTS_MDNS */
while(1) {
PROCESS_WAIT_EVENT();
if(ev == PROCESS_EVENT_TIMER) {
tcpip_poll_udp(resolv_conn);
} else if(ev == tcpip_event) {
if(uip_udp_conn == resolv_conn) {
if(uip_newdata()) {
newdata();
}
if(uip_poll()) {
#if RESOLV_CONF_SUPPORTS_MDNS
if(mdns_needs_host_announce) {
size_t len;
PRINTF("resolver: Announcing that we are \"%s\".\n",
resolv_hostname);
memset(uip_appdata, 0, sizeof(struct dns_hdr));
len = mdns_prep_host_announce_packet();
uip_udp_packet_sendto(resolv_conn,
uip_appdata,
len, &resolv_mdns_addr, UIP_HTONS(MDNS_PORT)
);
mdns_needs_host_announce = 0;
/* Poll again in case this fired
* at the same time the event timer did.
*/
tcpip_poll_udp(resolv_conn);
} else
#endif
{
check_entries();
}
}
}
}
#if RESOLV_CONF_SUPPORTS_MDNS
if(mdns_needs_host_announce) {
tcpip_poll_udp(resolv_conn);
}
#endif
}
PROCESS_END();
}
/* For removing trailing dots in resolv_query() and resolve_lookup2(). */
static char dns_name_without_dots[RESOLV_CONF_MAX_DOMAIN_NAME_SIZE + 1];
/*---------------------------------------------------------------------------*/
/**
* Queues a name so that a question for the name will be sent out.
*
* \param name The hostname that is to be queried.
*/
void
resolv_query(const char *name)
{
static uint8_t i;
static uint8_t lseq, lseqi;
register struct namemap *nameptr = 0;
lseq = lseqi = 0;
{ /* Remove trailing dots, if present. */
size_t len = strlen(name);
if(name[len - 1] == '.') {
strncpy(dns_name_without_dots, name, sizeof(dns_name_without_dots));
while(len && (dns_name_without_dots[len - 1] == '.')) {
dns_name_without_dots[--len] = 0;
}
name = dns_name_without_dots;
}
}
for(i = 0; i < RESOLV_ENTRIES; ++i) {
nameptr = &names[i];
if(0 == strcasecmp(nameptr->name, name)) {
break;
}
if((nameptr->state == STATE_UNUSED)
|| (nameptr->state == STATE_DONE
&& clock_seconds() > nameptr->expiration)
) {
lseqi = i;
lseq = 255;
} else if(seqno - nameptr->seqno > lseq) {
lseq = seqno - nameptr->seqno;
lseqi = i;
}
}
if(i == RESOLV_ENTRIES) {
i = lseqi;
nameptr = &names[i];
}
PRINTF("resolver: Starting query for \"%s\".\n", name);
memset(nameptr, 0, sizeof(*nameptr));
strncpy(nameptr->name, name, sizeof(nameptr->name));
nameptr->state = STATE_NEW;
nameptr->seqno = seqno;
++seqno;
#if RESOLV_CONF_SUPPORTS_MDNS
{
size_t name_len = strlen(name);
static const char local_suffix[] = "local";
if((name_len > (sizeof(local_suffix) - 1))
&& (0 ==
strcasecmp(name + name_len - (sizeof(local_suffix) - 1),
local_suffix))
) {
PRINTF("resolver: Using MDNS to look up \"%s\".\n", name);
nameptr->is_mdns = 1;
} else {
nameptr->is_mdns = 0;
}
}
nameptr->is_probe = (mdns_state == MDNS_STATE_PROBING)
&& (0 == strcmp(nameptr->name, resolv_hostname));
#endif
/* Force check_entires() to run on our process. */
process_post(&resolv_process, PROCESS_EVENT_TIMER, 0);
}
/*---------------------------------------------------------------------------*/
/**
* Look up a hostname in the array of known hostnames.
*
* \note This function only looks in the internal array of known
* hostnames, it does not send out a query for the hostname if none
* was found. The function resolv_query() can be used to send a query
* for a hostname.
*
*/
resolv_status_t
resolv_lookup(const char *name, uip_ipaddr_t ** ipaddr)
{
resolv_status_t ret = RESOLV_STATUS_UNCACHED;
static uint8_t i;
struct namemap *nameptr;
{ /* Remove trailing dots, if present. */
size_t len = strlen(name);
if(name[len - 1] == '.') {
strncpy(dns_name_without_dots, name, sizeof(dns_name_without_dots) - 1);
name = dns_name_without_dots;
while(len && (dns_name_without_dots[len - 1] == '.')) {
dns_name_without_dots[--len] = 0;
}
}
}
#if UIP_CONF_LOOPBACK_INTERFACE
if(strcmp(name, "localhost")) {
#if UIP_CONF_IPV6
static uip_ipaddr_t loopback = {
.u8 = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
}
};
#else
static uip_ipaddr_t loopback = {
.u8 = {127, 0, 0, 1}
};
#endif
if(ipaddr) {
*ipaddr = &loopback;
}
ret = RESOLV_STATUS_CACHED;
}
#endif
/* Walk through the list to see if the name is in there. */
for(i = 0; i < RESOLV_ENTRIES; ++i) {
nameptr = &names[i];
if(strcasecmp(name, nameptr->name) == 0) {
switch (nameptr->state) {
case STATE_DONE:
if(clock_seconds() > nameptr->expiration) {
ret = RESOLV_STATUS_EXPIRED;
} else {
ret = RESOLV_STATUS_CACHED;
}
break;
case STATE_NEW:
case STATE_ASKING:
ret = RESOLV_STATUS_RESOLVING;
break;
case STATE_ERROR: /* Almost certainly a not-found error from server */
if(clock_seconds() >= nameptr->expiration) {
ret = RESOLV_STATUS_NOT_FOUND;
}
break;
}
if(ipaddr) {
*ipaddr = &nameptr->ipaddr;
}
/* Break out of for loop. */
break;
}
}
#if VERBOSE_DEBUG
switch (ret) {
case RESOLV_STATUS_CACHED:{
PRINTF("resolver: Found \"%s\" in cache.\n", name);
const uip_ipaddr_t *addr = *ipaddr;
DEBUG_PRINTF
("resolver: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x \n",
((uint8_t *) addr)[0], ((uint8_t *) addr)[1], ((uint8_t *) addr)[2],
((uint8_t *) addr)[3], ((uint8_t *) addr)[4], ((uint8_t *) addr)[5],
((uint8_t *) addr)[6], ((uint8_t *) addr)[7], ((uint8_t *) addr)[8],
((uint8_t *) addr)[9], ((uint8_t *) addr)[10],
((uint8_t *) addr)[11], ((uint8_t *) addr)[12],
((uint8_t *) addr)[13], ((uint8_t *) addr)[14],
((uint8_t *) addr)[15]);
break;
}
default:
DEBUG_PRINTF("resolver: \"%s\" is NOT cached.\n", name);
break;
}
#endif
return ret;
}
/*---------------------------------------------------------------------------*/
/**
* Obtain the currently configured DNS server.
*
* \return A pointer to a 4-byte representation of the IP address of
* the currently configured DNS server or NULL if no DNS server has
* been configured.
*/
uip_ipaddr_t *
resolv_getserver(void)
{
return &resolv_default_dns_server;
}
/*---------------------------------------------------------------------------*/
/**
* Configure a DNS server.
*
* \param dnsserver A pointer to a 4-byte representation of the IP
* address of the DNS server to be configured.
*/
void
resolv_conf(const uip_ipaddr_t * dnsserver)
{
uip_ipaddr_copy(&resolv_default_dns_server, dnsserver);
process_post(&resolv_process, EVENT_NEW_SERVER, &resolv_default_dns_server);
}
/*---------------------------------------------------------------------------*/
/** \internal
* Callback function which is called when a hostname is found.
*
*/
static void
resolv_found(char *name, uip_ipaddr_t * ipaddr)
{
#if RESOLV_CONF_SUPPORTS_MDNS
if(strncasecmp(resolv_hostname, name, strlen(resolv_hostname)) == 0
&& ipaddr
#if UIP_CONF_IPV6
&& !uip_ds6_is_my_addr(ipaddr)
#else
&& uip_ipaddr_cmp(&uip_hostaddr, ipaddr) != 0
#endif
) {
uint8_t i;
if(mdns_state == MDNS_STATE_PROBING) {
/* We found this new name while probing.
* We must now rename ourselves.
*/
PRINTF("resolver: Name collision detected for \"%s\".\n", name);
/* Remove the ".local" suffix. */
resolv_hostname[strlen(resolv_hostname) - 6] = 0;
/* Append the last three hex parts of the link-level address. */
for(i = 0; i < 3; i++) {
uint8_t val = uip_lladdr.addr[(UIP_LLADDR_LEN - 3) + i];
char append_str[4] = "-XX";
append_str[2] = (((val & 0xF) > 9) ? 'a' : '0') + (val & 0xF);
val >>= 4;
append_str[1] = (((val & 0xF) > 9) ? 'a' : '0') + (val & 0xF);
strncat(resolv_hostname, append_str,
sizeof(resolv_hostname) - strlen(resolv_hostname));
}
/* Re-add the .local suffix */
strncat(resolv_hostname, ".local", RESOLV_CONF_MAX_DOMAIN_NAME_SIZE);
start_name_collision_check(CLOCK_SECOND * 5);
} else if(mdns_state == MDNS_STATE_READY) {
/* We found a collision after we had already asserted
* that we owned this name. We need to immediately
* and explicitly begin probing.
*/
PRINTF("resolver: Possible name collision, probing...\n");
start_name_collision_check(0);
}
} else
#endif
#if VERBOSE_DEBUG
if(ipaddr) {
PRINTF("resolver: Found address for \"%s\".\n", name);
PRINTF
("resolver: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x \n",
((uint8_t *) ipaddr)[0], ((uint8_t *) ipaddr)[1],
((uint8_t *) ipaddr)[2], ((uint8_t *) ipaddr)[3],
((uint8_t *) ipaddr)[4], ((uint8_t *) ipaddr)[5],
((uint8_t *) ipaddr)[6], ((uint8_t *) ipaddr)[7],
((uint8_t *) ipaddr)[8], ((uint8_t *) ipaddr)[9],
((uint8_t *) ipaddr)[10], ((uint8_t *) ipaddr)[11],
((uint8_t *) ipaddr)[12], ((uint8_t *) ipaddr)[13],
((uint8_t *) ipaddr)[14], ((uint8_t *) ipaddr)[15]);
} else {
PRINTF("resolver: Unable to retrieve address for \"%s\".\n", name);
}
#endif
process_post(PROCESS_BROADCAST, resolv_event_found, name);
}
/*---------------------------------------------------------------------------*/
#endif /* UIP_UDP */
/** @} */
/** @} */