NETDB: Add support for caching DNS name resultion mappings

This commit is contained in:
Gregory Nutt 2015-07-13 10:41:32 -06:00
parent fcaac468b1
commit 95424d9ef3
5 changed files with 527 additions and 5 deletions

View File

@ -10714,4 +10714,10 @@
* include/nuttx/net/ and libc/netdb: Implement the low-level network DNS
packet protocol to request and receive IPv6 address mappings
(2015-07-12).
* libc/netdb: Add a default value for DNS server IP address. Make
sure that the IP address has been initialized before permitting DNS
queries (2015-07-13).
* libc/netdb: Add support for a DNS host name resulution cache. This
can save a lot of DNS name server lookups (but might also have the
negative consequence of using stale IP address mappings (2015-07-13).

View File

@ -548,10 +548,42 @@ config NETDB_DNSCLIENT_ENTRIES
int "Number of DNS resolver entries"
default 0 if DEFAULT_SMALL
default 8 if !DEFAULT_SMALL
range 0 255
---help---
Number of cached DNS resolver entries. Default: 8. Zero disables
all cached name resolutions.
Disabling the DNS cache means that each access call to
gethostbyname() will result in a new DNS network query. If
CONFIG_NETDB_DNSCLIENT_ENTRIES is non-zero, then entries will be
cached and if the name mapping can be found in that cache, the
network query can be avoid. Of course, this is only useful if you
query the same name often and if the IP address of the name is
stable. If the IP address can change, then cachin DNS address
might have undesirable side-effects (see help for
CONFIG_NETDB_DNSCLIENT_LIFESEC).
config NETDB_DNSCLIENT_NAMESIZE
int "Max size of a cached hostname"
default 32
---help---
The size of a hostname string in the DNS resolver cache is fixed.
This setting provides the maximum size of a hostname. Names longer
than this will be aliased! Default: 32
config NETDB_DNSCLIENT_LIFESEC
int "Life of a DNS cache entry (seconds)"
default 3600
---help---
Cached entries in the name resolution cache older than this will not
be used. Default: 1 hour. Zero means that entries will not expire.
Small values of CONFIG_NETDB_DNSCLIENT_LIFESEC may result in more
network DNS queries; larger values can make a host unreachable for
the entire duration of the timeout value. This might happen, for
example, if the remote host was assigned a different IP address by
a DHCP server.
config NETDB_DNSCLIENT_MAXRESPONSE
int "Max response size"
default 96

View File

@ -66,6 +66,14 @@
# define CONFIG_NETDB_DNSCLIENT_MAXRESPONSE 96
#endif
#ifndef CONFIG_NETDB_DNSCLIENT_NAMESIZE
# define CONFIG_NETDB_DNSCLIENT_NAMESIZE 32
#endif
#ifndef CONFIG_NETDB_DNSCLIENT_LIFESEC
# define CONFIG_NETDB_DNSCLIENT_LIFESEC 3600
#endif
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
@ -104,6 +112,15 @@ int dns_bind(void);
* Using the DNS resolver socket (sd), look up the the 'hostname', and
* return its IP address in 'ipaddr'
*
* Input Parameters:
* sd - The socket descriptor previously initialized by dsn_bind().
* hostname - The hostname string to be resolved.
* addr - The location to return the IP address associated with the
* hostname
* addrlen - On entry, the size of the buffer backing up the 'addr'
* pointer. On return, this location will hold the actual size of
* the returned address.
*
* Returned Value:
* Returns zero (OK) if the query was successful.
*
@ -112,6 +129,33 @@ int dns_bind(void);
int dns_query(int sd, FAR const char *hostname, FAR struct sockaddr *addr,
FAR socklen_t *addrlen);
/****************************************************************************
* Name: dns_find_answer
*
* Description:
* Check if we already have the resolved hostname address in the cache.
*
* Input Parameters:
* hostname - The hostname string to be resolved.
* addr - The location to return the IP address associated with the
* hostname
* addrlen - On entry, the size of the buffer backing up the 'addr'
* pointer. On return, this location will hold the actual size of
* the returned address.
*
* Returned Value:
* If the host name was successfully found in the DNS name resolution
* cache, zero (OK) will be returned. Otherwise, some negated errno
* value will be returned, typically -ENOENT meaning that the hostname
* was not found in the cache.
*
****************************************************************************/
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
int dns_find_answer(FAR const char *hostname, FAR struct sockaddr *addr,
FAR socklen_t *addrlen);
#endif
#undef EXTERN
#if defined(__cplusplus)
}

View File

@ -52,6 +52,8 @@
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <time.h>
#include <errno.h>
#include <debug.h>
#include <assert.h>
@ -76,26 +78,56 @@
#define SEND_BUFFER_SIZE 64
#define RECV_BUFFER_SIZE CONFIG_NETDB_DNSCLIENT_MAXRESPONSE
/* Use clock monotonic, if possible */
#ifdef CONFIG_CLOCK_MONOTONIC
# define DNS_CLOCK CLOCK_MONOTONIC
#else
# define DNS_CLOCK CLOCK_REALTIME
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* This describes either an IPv4 or IPv6 address. It is essentially a named
* alternative to sockaddr_storage.
*/
union dns_server_u
{
struct sockaddr addr;
struct sockaddr addr; /* Common address representation */
#ifdef CONFIG_NET_IPv4
struct sockaddr_in ipv4;
struct sockaddr_in ipv4; /* IPv4 address */
#endif
#ifdef CONFIG_NET_IPv6
struct sockaddr_in6 ipv6;
struct sockaddr_in6 ipv6; /* IPv6 address */
#endif
};
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
/* This described one entry in the cache of resolved hostnames */
struct dns_cache_s
{
#if CONFIG_NETDB_DNSCLIENT_LIFESEC > 0
time_t ctime; /* Creation time */
#endif
char name[CONFIG_NETDB_DNSCLIENT_NAMESIZE];
union dns_server_u addr; /* Resolved address */
};
#endif
/****************************************************************************
* Private Data
****************************************************************************/
static sem_t g_dns_sem; /* Protects g_seqno and DNS cache */
static bool g_dns_initialized; /* DNS data structures initialized */
static bool g_dns_address; /* We have the address of the DNS server */
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
static uint8_t g_dns_head; /* Head of the circular, DNS resolver cache */
static uint8_t g_dns_tail; /* Tail of the circular, DNS resolver cache */
#endif
static uint8_t g_seqno; /* Sequence number of the next request */
/* The DNS server address */
@ -118,10 +150,51 @@ static const uint16_t g_ipv6_hostaddr[8] =
};
#endif
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
/* This is the DNS resolver cache */
static struct dns_cache_s g_dns_cache[CONFIG_NETDB_DNSCLIENT_ENTRIES];
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: dns_semtake
*
* Description:
* Take the DNS semaphore, ignoring errors do to the receipt of signals.
*
****************************************************************************/
static void dns_semtake(void)
{
int errcode = 0;
int ret;
do
{
ret = sem_wait(&g_dns_sem);
if (ret < 0)
{
errcode = get_errno();
DEBUGASSERT(errcode == EINTR);
}
}
while (ret < 0 && errcode == EINTR);
}
/****************************************************************************
* Name: dns_semgive
*
* Description:
* Release the DNS semaphore
*
****************************************************************************/
#define dns_semgive() sem_post(&g_dns_sem)
/****************************************************************************
* Name: dns_initialize
*
@ -132,6 +205,14 @@ static const uint16_t g_ipv6_hostaddr[8] =
static bool dns_initialize(void)
{
/* Have DNS data structures been initialized? */
if (!g_dns_initialized)
{
sem_init(&g_dns_sem, 0, 1);
g_dns_initialized = true;
}
/* Has the DNS server IP address been assigned? */
if (!g_dns_address)
@ -180,6 +261,83 @@ static bool dns_initialize(void)
return true;
}
/****************************************************************************
* Name: dns_save_answer
*
* Description:
* Same the last resolved hostname in the DNS cache
*
* Input Parameters:
* hostname - The hostname string to be cached.
* addr - The IP address associated with the hostname
* addrlen - The size of the of the IP address.
*
* Returned Value:
* None
*
****************************************************************************/
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
static void dns_save_answer(FAR const char *hostname,
FAR const struct sockaddr *addr,
socklen_t addrlen)
{
FAR struct dns_cache_s *entry;
#if CONFIG_NETDB_DNSCLIENT_LIFESEC > 0
struct timespec now;
#endif
int next;
int ndx;
/* Get exclusive access to the DNS cache */
dns_semtake();
/* Get the index to the new head of the list */
ndx = g_dns_head;
next = ndx + 1;
if (next >= CONFIG_NETDB_DNSCLIENT_ENTRIES)
{
next = 0;
}
/* If the next head pointer would match the tail index, then increment
* the tail index, discarding the oldest mapping in the cache.
*/
if (next == g_dns_tail)
{
int tmp = g_dns_tail + 1;
if (tmp >= CONFIG_NETDB_DNSCLIENT_ENTRIES)
{
tmp = 0;
}
g_dns_tail = tmp;
}
/* Save the answer in the cache */
entry = &g_dns_cache[ndx];
#if CONFIG_NETDB_DNSCLIENT_LIFESEC > 0
/* Get the current time, using CLOCK_MONOTONIC if possible */
(void)clock_settime(DNS_CLOCK, &now);
entry->ctime = (time_t)now.tv_sec;
#endif
strncpy(entry->name, hostname, CONFIG_NETDB_DNSCLIENT_NAMESIZE);
memcpy(&entry->addr.addr, addr, addrlen);
/* Save the updated head index */
g_dns_head = next;
dns_semgive();
}
#endif
/****************************************************************************
* Name: dns_parse_name
*
@ -223,13 +381,21 @@ static int dns_send_query(int sd, FAR const char *name,
FAR uint8_t *dest;
FAR uint8_t *nptr;
FAR const char *src;
uint8_t seqno = g_seqno++; /* REVISIT: Not thread safe */
uint8_t buffer[SEND_BUFFER_SIZE];
uint8_t seqno;
socklen_t addrlen;
int errcode;
int ret;
int n;
/* Increment the sequence number */
dns_semtake();
seqno = g_seqno++;
dns_semgive();
/* Initialize the request header */
hdr = (FAR struct dns_header_s *)buffer;
memset(hdr, 0, sizeof(struct dns_header_s));
hdr->id = htons(seqno);
@ -556,6 +722,15 @@ int dns_bind(void)
* Using the DNS resolver socket (sd), look up the the 'hostname', and
* return its IP address in 'ipaddr'
*
* Input Parameters:
* sd - The socket descriptor previously initialized by dsn_bind().
* hostname - The hostname string to be resolved.
* addr - The location to return the IP address associated with the
* hostname
* addrlen - On entry, the size of the buffer backing up the 'addr'
* pointer. On return, this location will hold the actual size of
* the returned address.
*
* Returned Value:
* Returns zero (OK) if the query was successful.
*
@ -600,6 +775,11 @@ int dns_query(int sd, FAR const char *hostname, FAR struct sockaddr *addr,
{
/* IPv4 response received successfully */
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
/* Save the answer in the DNS cache */
dns_save_answer(hostname, addr, *addrlen);
#endif
return OK;
}
@ -657,6 +837,11 @@ int dns_query(int sd, FAR const char *hostname, FAR struct sockaddr *addr,
{
/* IPv6 response received successfully */
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
/* Save the answer in the DNS cache */
dns_save_answer(hostname, addr, *addrlen);
#endif
return OK;
}
@ -808,3 +993,140 @@ int dns_getserver(FAR struct sockaddr *addr, FAR socklen_t *addrlen)
*addrlen = copylen;
return OK;
}
/****************************************************************************
* Name: dns_find_answer
*
* Description:
* Check if we already have the resolved hostname address in the cache.
*
* Input Parameters:
* hostname - The hostname string to be resolved.
* addr - The location to return the IP address associated with the
* hostname
* addrlen - On entry, the size of the buffer backing up the 'addr'
* pointer. On return, this location will hold the actual size of
* the returned address.
*
* Returned Value:
* If the host name was successfully found in the DNS name resolution
* cache, zero (OK) will be returned. Otherwise, some negated errno
* value will be returned, typically -ENOENT meaning that the hostname
* was not found in the cache.
*
****************************************************************************/
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
int dns_find_answer(FAR const char *hostname, FAR struct sockaddr *addr,
FAR socklen_t *addrlen)
{
FAR struct dns_cache_s *entry;
#if CONFIG_NETDB_DNSCLIENT_LIFESEC > 0
struct timespec now;
uint32_t elapsed;
int ret;
#endif
int next;
int ndx;
/* Get exclusive access to the DNS cache */
dns_semtake();
#if CONFIG_NETDB_DNSCLIENT_LIFESEC > 0
/* Get the current time, using CLOCK_MONOTONIC if possible */
ret = clock_settime(DNS_CLOCK, &now);
#endif
/* REVISIT: This is not thread safe */
for (ndx = g_dns_tail; ndx != g_dns_head; ndx = next)
{
entry = &g_dns_cache[ndx];
/* Advance the index for the next time through the loop, handling
* wrapping to the beginning of the circular buffer.
*/
next = ndx + 1;
if (next >= CONFIG_NETDB_DNSCLIENT_ENTRIES)
{
next = 0;
}
#if CONFIG_NETDB_DNSCLIENT_LIFESEC > 0
/* Check if this entry has expired
* REVISIT: Does not this calculation assume that the sizeof(time_t)
* is equal to the sizeof(uint32_t)?
*/
elapsed = (uint32_t)now.tv_sec - (uint32_t)entry->ctime;
if (ret >= 0 && elapsed > CONFIG_NETDB_DNSCLIENT_LIFESEC)
{
/* This entry has expired. Increment the tail index to exclude
* this entry on future traversals.
*/
g_dns_tail = next;
}
else
#endif
{
/* The entry has not expired, check for a name match. Notice that
* because the names are truncated to CONFIG_NETDB_DNSCLIENT_NAMESIZE,
* this has the possibility of aliasing two names and returning
* the wrong entry from the cache.
*/
if (strncmp(hostname, entry->name, CONFIG_NETDB_DNSCLIENT_NAMESIZE) == 0)
{
socklen_t inlen;
/* We have a match. Return the resolved host address */
#ifdef CONFIG_NET_IPv4
if (entry->addr.addr.sa_family == AF_INET)
#ifdef CONFIG_NET_IPv6
#endif
{
inlen = sizeof(struct sockaddr_in);
}
#endif
#ifdef CONFIG_NET_IPv6
else
#ifdef CONFIG_NET_IPv4
#endif
{
inlen = sizeof(struct sockaddr_in6);
}
#endif
/* Make sure that the address will fit in the caller-provided
* buffer.
*/
if (*addrlen < inlen)
{
ret = -ERANGE;
goto errout_with_sem;
}
/* Return the address information */
memcpy(addr, &entry->addr.addr, inlen);
*addrlen = inlen;
dns_semgive();
return OK;
}
}
}
ret = -ENOENT;
errout_with_sem:
dns_semgive();
return ret;
}
#endif

View File

@ -194,6 +194,112 @@ static int lib_numeric_address(FAR const char *name, FAR struct hostent *host,
return 0;
}
/****************************************************************************
* Name: lib_find_answer
*
* Description:
* Check if we previously resolved this hostname and if that resolved
* address is already available in the DNS cache.
*
* Input Parameters:
* name - The name of the host to find.
* host - Caller provided location to return the host data.
* buf - Caller provided buffer to hold string data associated with the
* host data.
* buflen - The size of the caller-provided buffer
*
* Returned Value:
* Zero (0) is returned if the DNS lookup was successful.
*
****************************************************************************/
#ifdef CONFIG_NETDB_DNSCLIENT
static int lib_find_answer(FAR const char *name, FAR struct hostent *host,
FAR char *buf, size_t buflen)
{
FAR struct hostent_info_s *info;
FAR char *ptr;
socklen_t addrlen;
int addrtype;
int namelen;
int ret;
/* Verify that we have a buffer big enough to get started (it still may not
* be big enough).
*/
if (buflen <= sizeof(struct hostent_info_s))
{
return -ERANGE;
}
/* Initialize buffers */
info = (FAR struct hostent_info_s *)buf;
ptr = info->hi_data;
buflen -= (sizeof(struct hostent_info_s) - 1);
memset(host, 0, sizeof(struct hostent));
memset(info, 0, sizeof(struct hostent_info_s));
/* Try to get the host address using the DNS name server */
addrlen = buflen;
ret = dns_find_answer(name, (FAR struct sockaddr *)ptr, &addrlen);
if (ret < 0)
{
/* No, nothing found in the cache */
return ret;
}
/* Get the address type; verify the address size. */
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
if (((FAR struct sockaddr_in *)ptr)->sin_family == AF_INET)
#endif
{
DEBUGASSERT(addrlen == sizeof(struct sockaddr_in));
addrlen = sizeof(struct sockaddr_in);
addrtype = AF_INET;
}
#endif
#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
else
#endif
{
DEBUGASSERT(addrlen == sizeof(struct sockaddr_in6));
addrlen = sizeof(struct sockaddr_in6);
addrtype = AF_INET6;
}
#endif
/* Yes.. Return the address that we obtained from the DNS cache. */
info->hi_addrlist[0] = ptr;
host->h_addr_list = info->hi_addrlist;
host->h_addrtype = addrtype;
host->h_length = addrlen;
ptr += addrlen;
buflen -= addrlen;
/* And copy the host name */
namelen = strlen(name);
if (addrlen + namelen + 1 > buflen)
{
return -ERANGE;
}
strncpy(ptr, name, buflen);
return OK;
}
#endif /* CONFIG_NETDB_DNSCLIENT */
/****************************************************************************
* Name: lib_dns_query
*
@ -524,6 +630,18 @@ int gethostbyname_r(FAR const char *name, FAR struct hostent *host,
/* REVISIT: Not implemented */
#ifdef CONFIG_NETDB_DNSCLIENT
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
/* Check if we already have this hostname mapping cached */
ret = lib_find_answer(name, host, buf, buflen);
if (ret >= 0)
{
/* Found the address mapping in the cache */
return OK;
}
#endif
/* Try to get the host address using the DNS name server */
ret = lib_dns_lookup(name, host, buf, buflen);