nuttx/libs/libc/netdb/lib_dnsquery.c
SPRESENSE 3c96621e1e libs:netdb: Restrict DNS query types
Corresponds to the problem of name resolution with different IP
address types in networks where only one of IPv4 or IPv6 can be
used due to physical layer reasons (e.g., LTE networks).
2023-09-16 14:43:12 +08:00

824 lines
23 KiB
C

/****************************************************************************
* libs/libc/netdb/lib_dnsquery.c
* DNS host name to IP address resolver.
*
* The DNS resolver functions are used to lookup a hostname and map it to a
* numerical IP address.
*
* Copyright (C) 2007, 2009, 2012, 2014-2018 Gregory Nutt. All rights
* reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Based heavily on portions of uIP:
*
* Author: Adam Dunkels <adam@dunkels.com>
* 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <debug.h>
#include <assert.h>
#include <arpa/inet.h>
#include <nuttx/lib/lib.h>
#include <nuttx/net/net.h>
#include <nuttx/net/ip.h>
#include <nuttx/net/dns.h>
#include "netdb/lib_dns.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Buffer sizes
*
* The SEND_BUFFER_SIZE depends the configured DNS name size:
*
* sizeof(DNS query0 = Header (12 bytes) + DNS Name (Variable) +
* Query type (2 bytes) + Query Class (2 bytes)
*
* sizeof(DNS Name) = Encoded length (1 byte) + Maximum length of name +
* NUL-terminator (1 byte)
*/
#define SEND_BUFFER_SIZE (16 + CONFIG_NETDB_DNSCLIENT_NAMESIZE + 2)
#define RECV_BUFFER_SIZE CONFIG_NETDB_DNSCLIENT_MAXRESPONSE
#define QUERY_BUFFER_SIZE MAX(SEND_BUFFER_SIZE, RECV_BUFFER_SIZE)
/****************************************************************************
* Private Types
****************************************************************************/
struct dns_query_s
{
int result; /* Explanation of the failure */
FAR const char *hostname; /* Hostname to lookup */
FAR union dns_addr_u *addr; /* Location to return host address */
FAR int *naddr; /* Number of returned addresses */
uint32_t ttl; /* Time to Live, unit:s */
};
/* Query info to check response against. */
struct dns_query_info_s
{
uint16_t id; /* Query ID */
uint16_t rectype; /* Queried record type */
uint16_t qnamelen; /* Queried hostname length */
char qname[CONFIG_NETDB_DNSCLIENT_NAMESIZE + 2]; /* Queried hostname in
* encoded format + NUL */
};
struct dns_query_data_s
{
struct dns_query_s query;
struct dns_query_info_s qinfo;
uint8_t buffer[QUERY_BUFFER_SIZE]; /* Buffer to hold request & response */
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: dns_parse_name
*
* Description:
* Walk through a compact encoded DNS name and return the end of it.
*
* Input Parameters:
*
* query - A pointer to the starting byte of the name entry in the DNS
* response.
* queryend - A pointer to the byte after the last byte of the response.
*
* Returned Value:
* Pointer to the first byte after the parsed name, or the value of
* `queryend` if the name did not fit into provided DNS response.
*
****************************************************************************/
static FAR uint8_t *dns_parse_name(FAR uint8_t *query, FAR uint8_t *queryend)
{
uint8_t n;
while (query < queryend)
{
n = *query++;
/* Check for a leading or trailing pointer */
if ((n & 0xc0) != 0)
{
/* Eat second pointer byte and terminate search */
ninfo("Compressed answer\n");
query++;
break;
}
/* Check for final label with zero-length */
if (!n)
{
break;
}
/* Eat non-empty label */
query += n;
}
if (query >= queryend)
{
/* Always return `queryend` in case of errors */
nerr("ERROR: DNS response is too short\n");
query = queryend;
}
return query;
}
/****************************************************************************
* Name: dns_alloc_id
*
* Description:
* Gets a new ID for query. Function manufactures a reasonably unique ID,
* but we accept rare collisions, since they are harmless when resolver
* clients retry failed DNS few times.
*
****************************************************************************/
static inline uint16_t dns_alloc_id(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint32_t)ts.tv_nsec + ((uint32_t)ts.tv_nsec >> 16);
}
/****************************************************************************
* Name: dns_send_query
*
* Description:
* 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 int dns_send_query(int sd, FAR const char *name,
FAR union dns_addr_u *uaddr, uint16_t rectype,
FAR struct dns_query_info_s *qinfo,
FAR uint8_t *buffer)
{
FAR struct dns_header_s *hdr;
FAR uint8_t *dest;
FAR uint8_t *nptr;
FAR char *qname;
FAR char *qptr;
FAR const char *src;
uint16_t id;
socklen_t addrlen;
int ret;
int len;
int n;
/* Get a new ID for query */
id = dns_alloc_id();
/* Initialize the request header */
hdr = (FAR struct dns_header_s *)buffer;
memset(hdr, 0, sizeof(*hdr));
hdr->id = HTONS(id);
hdr->flags1 = DNS_FLAG1_RD;
hdr->numquestions = HTONS(1);
/* Convert hostname into suitable query format.
*
* There is space for CONFIG_NETDB_DNSCLIENT_NAMESIZE
* plus one pre-pended name length and NUL-terminator
* (other pre-pended name lengths replace dots).
*/
src = name - 1;
dest = buffer + sizeof(*hdr);
qname = qinfo->qname;
len = 0;
do
{
/* Copy the name string to both query and saved info. */
src++;
nptr = dest++;
qptr = qname++;
len++;
for (n = 0;
*src != '.' && *src != '\0' &&
len <= CONFIG_NETDB_DNSCLIENT_NAMESIZE;
src++)
{
*dest++ = *(FAR uint8_t *)src;
*qname++ = *(FAR uint8_t *)src;
n++;
len++;
}
/* Pre-pend the name length */
*nptr = n;
*qptr = n;
}
while (*src != '\0' && len <= CONFIG_NETDB_DNSCLIENT_NAMESIZE);
/* Add NUL termination */
*dest++ = '\0';
*qname++ = '\0';
/* Store name length to saved info */
DEBUGASSERT(len <= CONFIG_NETDB_DNSCLIENT_NAMESIZE + 1);
DEBUGASSERT(qname - qinfo->qname == len + 1);
qinfo->qnamelen = len;
/* Add DNS record type, and DNS class */
*dest++ = (rectype >> 8); /* DNS record type (big endian) */
*dest++ = (rectype & 0xff);
*dest++ = (DNS_CLASS_IN >> 8); /* DNS record class (big endian) */
*dest++ = (DNS_CLASS_IN & 0xff);
qinfo->rectype = HTONS(rectype);
qinfo->id = hdr->id;
/* Send the request */
if (uaddr->addr.sa_family == AF_INET)
{
addrlen = sizeof(struct sockaddr_in);
}
else
{
addrlen = sizeof(struct sockaddr_in6);
}
ret = connect(sd, &uaddr->addr, addrlen);
if (ret < 0)
{
ret = -get_errno();
nerr("ERROR: connect failed: %d\n", ret);
return ret;
}
ret = send(sd, buffer, dest - buffer, 0);
if (ret < 0)
{
ret = -get_errno();
nerr("ERROR: sendto failed: %d\n", ret);
return ret;
}
return OK;
}
/****************************************************************************
* Name: dns_recv_response
*
* Description:
* Called when new UDP data arrives
*
* Returned Value:
* Returns number of valid IP address responses. Negated errno value is
* returned in all other cases.
*
****************************************************************************/
static int dns_recv_response(int sd, FAR union dns_addr_u *addr, int naddr,
FAR struct dns_query_info_s *qinfo,
FAR uint32_t *ttl, FAR uint8_t *buffer)
{
FAR uint8_t *nameptr;
FAR uint8_t *namestart;
FAR uint8_t *endofbuffer;
FAR struct dns_answer_s *ans;
FAR struct dns_header_s *hdr;
FAR struct dns_question_s *que;
uint16_t nquestions;
uint16_t nanswers;
uint16_t temp;
int naddr_read;
int ret;
if (naddr <= 0)
{
return -ERANGE;
}
/* Receive the response */
ret = recv(sd, buffer, RECV_BUFFER_SIZE, 0);
if (ret < 0)
{
ret = -get_errno();
nerr("ERROR: recv failed: %d\n", ret);
return ret;
}
if (ret < sizeof(*hdr))
{
/* DNS header can't fit in received data */
nerr("ERROR: DNS response is too short\n");
return -EILSEQ;
}
hdr = (FAR struct dns_header_s *)buffer;
endofbuffer = buffer + ret;
ninfo("ID %d\n", NTOHS(hdr->id));
ninfo("Query %d\n", hdr->flags1 & DNS_FLAG1_RESPONSE);
ninfo("Error %d\n", hdr->flags2 & DNS_FLAG2_ERR_MASK);
ninfo("Num questions %d, answers %d, authrr %d, extrarr %d\n",
NTOHS(hdr->numquestions), NTOHS(hdr->numanswers),
NTOHS(hdr->numauthrr), NTOHS(hdr->numextrarr));
/* Check for error */
if ((hdr->flags2 & DNS_FLAG2_ERR_MASK) != 0)
{
nerr("ERROR: DNS reported error: flags2=%02x\n", hdr->flags2);
return -EPROTO;
}
/* Check for matching ID. */
if (hdr->id != qinfo->id)
{
nerr("ERROR: DNS wrong response ID (expected %d, got %d)\n",
NTOHS(qinfo->id), NTOHS(hdr->id));
return -EBADMSG;
}
/* We only care about the question(s) and the answers. The authrr
* and the extrarr are simply discarded.
*/
nquestions = NTOHS(hdr->numquestions);
nanswers = NTOHS(hdr->numanswers);
/* We only ever send queries with one question. */
if (nquestions != 1)
{
nerr("ERROR: DNS wrong number of questions %d\n", nquestions);
return -EBADMSG;
}
/* Skip the name in the answer, but do check that it
* matches against the name in the question.
*/
namestart = buffer + sizeof(*hdr);
nameptr = dns_parse_name(namestart, endofbuffer);
if (nameptr == endofbuffer)
{
return -EILSEQ;
}
/* Since dns_parse_name() skips any pointer bytes,
* we cannot compare for equality here.
*/
if (nameptr - namestart < qinfo->qnamelen)
{
nerr("ERROR: DNS response name wrong length\n");
return -EBADMSG;
}
/* qname is NUL-terminated and we must include NUL to the comparison. */
if (memcmp(namestart, qinfo->qname, qinfo->qnamelen + 1) != 0)
{
nerr("ERROR: DNS response with wrong name\n");
return -EBADMSG;
}
/* Validate query type and class */
que = (FAR struct dns_question_s *)nameptr;
/* N.B. Unaligned access may occur here */
temp = HTONS(DNS_CLASS_IN);
if (memcmp(&que->type, &qinfo->rectype, sizeof(uint16_t)) != 0 ||
memcmp(&que->class, &temp, sizeof(uint16_t)) != 0)
{
nerr("ERROR: DNS response with wrong question\n");
return -EBADMSG;
}
/* Skip over question */
nameptr += sizeof(struct dns_question_s);
ret = OK;
naddr_read = 0;
for (; nanswers > 0; nanswers--)
{
/* Each answer starts with a name */
nameptr = dns_parse_name(nameptr, endofbuffer);
if (nameptr == endofbuffer)
{
ret = -EILSEQ;
nwarn("Further parse returned %d\n", ret);
break;
}
ans = (FAR struct dns_answer_s *)nameptr;
ninfo("Answer: type=%04x, class=%04x, ttl=%06x, length=%04x\n",
NTOHS(ans->type), NTOHS(ans->class),
(NTOHS(ans->ttl[0]) << 16) | NTOHS(ans->ttl[1]),
NTOHS(ans->len));
if (ttl)
{
*ttl = (NTOHS(ans->ttl[0]) << 16) | NTOHS(ans->ttl[1]);
}
/* Check for IPv4/6 address type and Internet class. Others are
* discarded.
*/
#ifdef CONFIG_NET_IPv4
if (ans->type == HTONS(DNS_RECTYPE_A) &&
ans->class == HTONS(DNS_CLASS_IN) &&
ans->len == HTONS(4) &&
nameptr + 10 + 4 <= endofbuffer)
{
FAR struct sockaddr_in *inaddr;
nameptr += 10 + 4;
ninfo("IPv4 address: %u.%u.%u.%u\n",
ip4_addr1(ans->u.ipv4.s_addr),
ip4_addr2(ans->u.ipv4.s_addr),
ip4_addr3(ans->u.ipv4.s_addr),
ip4_addr4(ans->u.ipv4.s_addr));
inaddr = &addr[naddr_read].ipv4;
inaddr->sin_family = AF_INET;
inaddr->sin_port = 0;
inaddr->sin_addr.s_addr = ans->u.ipv4.s_addr;
if (++naddr_read >= naddr)
{
ret = -ERANGE;
break;
}
}
else
#endif
#ifdef CONFIG_NET_IPv6
if (ans->type == HTONS(DNS_RECTYPE_AAAA) &&
ans->class == HTONS(DNS_CLASS_IN) &&
ans->len == HTONS(16) &&
nameptr + 10 + 16 <= endofbuffer)
{
FAR struct sockaddr_in6 *inaddr;
nameptr += 10 + 16;
ninfo("IPv6 address: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
NTOHS(ans->u.ipv6.s6_addr16[0]),
NTOHS(ans->u.ipv6.s6_addr16[1]),
NTOHS(ans->u.ipv6.s6_addr16[2]),
NTOHS(ans->u.ipv6.s6_addr16[3]),
NTOHS(ans->u.ipv6.s6_addr16[4]),
NTOHS(ans->u.ipv6.s6_addr16[5]),
NTOHS(ans->u.ipv6.s6_addr16[6]),
NTOHS(ans->u.ipv6.s6_addr16[7]));
inaddr = &addr[naddr_read].ipv6;
inaddr->sin6_family = AF_INET6;
inaddr->sin6_port = 0;
memcpy(inaddr->sin6_addr.s6_addr, ans->u.ipv6.s6_addr, 16);
if (++naddr_read >= naddr)
{
ret = -ERANGE;
break;
}
}
else
#endif
{
nameptr = nameptr + 10 + NTOHS(ans->len);
}
}
if (naddr_read == 0 && ret == OK)
{
ret = -EADDRNOTAVAIL;
}
return naddr_read > 0 ? naddr_read : ret;
}
/****************************************************************************
* Name: dns_query_error
*
* Description:
* Displays information about dns query errors
*
* Input Parameters:
* prompt - Error description.
* ret - Error code.
* uaddr - DNS name server address.
*
****************************************************************************/
static void dns_query_error(FAR const char *prompt, int ret,
FAR union dns_addr_u *uaddr)
{
char addrstr[INET6_ADDRSTRLEN];
#ifdef CONFIG_NET_IPv4
if (uaddr->addr.sa_family == AF_INET)
{
inet_ntop(AF_INET, &uaddr->ipv4.sin_addr, addrstr, INET6_ADDRSTRLEN);
}
else
#endif
#ifdef CONFIG_NET_IPv6
if (uaddr->addr.sa_family == AF_INET6)
{
inet_ntop(AF_INET6, &uaddr->ipv6.sin6_addr, addrstr, INET6_ADDRSTRLEN);
}
else
#endif
{
strlcpy(addrstr, "Unknown address", sizeof(addrstr));
}
nerr("%s: %d, server address: %s\n", prompt, ret, addrstr);
}
/****************************************************************************
* Name: dns_query_callback
*
* Description:
* Using the DNS information and this DNS server address, look up the
* hostname.
*
* Input Parameters:
* arg - Query arguments
* addr - DNS name server address
* addrlen - Length of the DNS name server address.
*
* Returned Value:
* Returns one (1) if the query was successful. Zero is returned in all
* other cases. The result field of the query structure is set to a
* negated errno value indicate the reason for the last failure (only).
*
****************************************************************************/
static int dns_query_callback(FAR void *arg, FAR struct sockaddr *addr,
FAR socklen_t addrlen)
{
FAR struct dns_query_data_s *qdata = arg;
FAR struct dns_query_s *query = &qdata->query;
int next = 0;
int retries;
int ret;
int sd;
/* Loop while receive timeout errors occur and there are remaining
* retries.
*/
for (retries = 0; retries < CONFIG_NETDB_DNSCLIENT_RETRIES; retries++)
{
#ifdef CONFIG_NET_IPv6
if (dns_is_queryfamily(AF_INET6))
{
/* Send the IPv6 query */
sd = dns_bind(addr->sa_family);
if (sd < 0)
{
query->result = sd;
return 0;
}
ret = dns_send_query(sd, query->hostname,
(FAR union dns_addr_u *)addr,
DNS_RECTYPE_AAAA, &qdata->qinfo,
qdata->buffer);
if (ret < 0)
{
dns_query_error("ERROR: IPv6 dns_send_query failed",
ret, (FAR union dns_addr_u *)addr);
query->result = ret;
}
else
{
/* Obtain the IPv6 response */
ret = dns_recv_response(sd, &query->addr[next],
*query->naddr - next, &qdata->qinfo,
&query->ttl, qdata->buffer);
if (ret >= 0)
{
next += ret;
}
else
{
dns_query_error("ERROR: IPv6 dns_recv_response failed",
ret, (FAR union dns_addr_u *)addr);
query->result = ret;
}
}
close(sd);
}
#endif
#ifdef CONFIG_NET_IPv4
if (dns_is_queryfamily(AF_INET))
{
/* Send the IPv4 query */
sd = dns_bind(addr->sa_family);
if (sd < 0)
{
query->result = sd;
return 0;
}
ret = dns_send_query(sd, query->hostname,
(FAR union dns_addr_u *)addr,
DNS_RECTYPE_A, &qdata->qinfo, qdata->buffer);
if (ret < 0)
{
dns_query_error("ERROR: IPv4 dns_send_query failed",
ret, (FAR union dns_addr_u *)addr);
query->result = ret;
}
else
{
/* Obtain the IPv4 response */
ret = dns_recv_response(sd, &query->addr[next],
*query->naddr - next, &qdata->qinfo,
&query->ttl, qdata->buffer);
if (ret >= 0)
{
next += ret;
}
else
{
dns_query_error("ERROR: IPv4 dns_recv_response failed",
ret, (FAR union dns_addr_u *)addr);
query->result = ret;
}
}
close(sd);
}
#endif /* CONFIG_NET_IPv4 */
if (next > 0)
{
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
/* Save the answer in the DNS cache */
dns_save_answer(query->hostname, query->addr, next, query->ttl);
#endif
/* Return 1 to indicate to (1) stop the traversal, and (2)
* indicate that the address was found.
*/
*query->naddr = next;
return 1;
}
else if (query->result != -EAGAIN)
{
break;
}
}
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: dns_query
*
* Description:
* Using the DNS resolver socket (sd), look up the 'hostname', and
* return its IP address in 'ipaddr'
*
* Input Parameters:
* hostname - The hostname string to be resolved.
* addr - The location to return the IP addresses associated with the
* hostname.
* naddr - On entry, the count of addresses backing up the 'addr'
* pointer. On return, this location will hold the actual count of
* the returned addresses.
*
* Returned Value:
* Returns zero (OK) if the query was successful.
*
****************************************************************************/
int dns_query(FAR const char *hostname, FAR union dns_addr_u *addr,
FAR int *naddr)
{
FAR struct dns_query_data_s *qdata = lib_malloc(sizeof(*qdata));
int ret;
if (qdata == NULL)
{
return -ENOMEM;
}
/* Set up the query info structure */
qdata->query.result = -EADDRNOTAVAIL;
qdata->query.hostname = hostname;
qdata->query.addr = addr;
qdata->query.naddr = naddr;
/* Perform the query. dns_foreach_nameserver() will return:
*
* 1 - The query was successful.
* 0 - Look up failed
* <0 - Some other failure (?, shouldn't happen)
*/
ret = dns_foreach_nameserver(dns_query_callback, qdata);
if (ret > 0)
{
/* The lookup was successful */
ret = OK;
}
else if (ret == 0)
{
ret = qdata->query.result;
}
/* Free the query data */
lib_free(qdata);
return ret;
}