diff --git a/ChangeLog b/ChangeLog index ecebfe3ed59..b794344bedd 100755 --- a/ChangeLog +++ b/ChangeLog @@ -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). - \ No newline at end of file + * 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). + diff --git a/libc/Kconfig b/libc/Kconfig index f5c6c652889..e51915d1c27 100644 --- a/libc/Kconfig +++ b/libc/Kconfig @@ -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 diff --git a/libc/netdb/lib_dns.h b/libc/netdb/lib_dns.h index 35485140ba7..dee8985f995 100644 --- a/libc/netdb/lib_dns.h +++ b/libc/netdb/lib_dns.h @@ -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) } diff --git a/libc/netdb/lib_dnsclient.c b/libc/netdb/lib_dnsclient.c index 1834da21ae4..6453f80954b 100644 --- a/libc/netdb/lib_dnsclient.c +++ b/libc/netdb/lib_dnsclient.c @@ -52,6 +52,8 @@ #include #include #include +#include +#include #include #include #include @@ -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 diff --git a/libc/netdb/lib_gethostbynamer.c b/libc/netdb/lib_gethostbynamer.c index 4d69c482e8f..e90d10b3ccb 100644 --- a/libc/netdb/lib_gethostbynamer.c +++ b/libc/netdb/lib_gethostbynamer.c @@ -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);